Skip to content

Commit a704678

Browse files
committed
Merge pull request scala#3781 from retronym/topic/8611
SI-8611 Avoid accidental patmat unification with refinement types
2 parents fdc3812 + 7046d73 commit a704678

File tree

8 files changed

+145
-9
lines changed

8 files changed

+145
-9
lines changed

src/compiler/scala/tools/nsc/transform/patmat/Logic.scala

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
505505
}
506506

507507

508-
import global.{ConstantType, Constant, SingletonType, Literal, Ident, singleType}
508+
import global.{ConstantType, Constant, EmptyScope, SingletonType, Literal, Ident, refinedType, singleType, TypeBounds, NoSymbol}
509509
import global.definitions._
510510

511511

@@ -538,23 +538,30 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
538538
private val trees = mutable.HashSet.empty[Tree]
539539

540540
// hashconsing trees (modulo value-equality)
541-
private[TreesAndTypesDomain] def uniqueTpForTree(t: Tree): Type =
542-
// a new type for every unstable symbol -- only stable value are uniqued
543-
// technically, an unreachable value may change between cases
544-
// thus, the failure of a case that matches on a mutable value does not exclude the next case succeeding
545-
// (and thuuuuus, the latter case must be considered reachable)
546-
if (!t.symbol.isStable) t.tpe.narrow
541+
private[TreesAndTypesDomain] def uniqueTpForTree(t: Tree): Type = {
542+
def freshExistentialSubtype(tp: Type): Type = {
543+
// SI-8611 tp.narrow is tempting, but unsuitable. See `testRefinedTypeSI8611` for an explanation.
544+
NoSymbol.freshExistential("").setInfo(TypeBounds.upper(tp)).tpe
545+
}
546+
547+
if (!t.symbol.isStable) {
548+
// Create a fresh type for each unstable value, since we can never correlate it to another value.
549+
// For example `case X => case X =>` should not complaing about the second case being unreachable,
550+
// if X is mutable.
551+
freshExistentialSubtype(t.tpe)
552+
}
547553
else trees find (a => a.correspondsStructure(t)(sameValue)) match {
548554
case Some(orig) =>
549-
debug.patmat("unique tp for tree: "+ ((orig, orig.tpe)))
555+
debug.patmat("unique tp for tree: " + ((orig, orig.tpe)))
550556
orig.tpe
551557
case _ =>
552558
// duplicate, don't mutate old tree (TODO: use a map tree -> type instead?)
553-
val treeWithNarrowedType = t.duplicate setType t.tpe.narrow
559+
val treeWithNarrowedType = t.duplicate setType freshExistentialSubtype(t.tpe)
554560
debug.patmat("uniqued: "+ ((t, t.tpe, treeWithNarrowedType.tpe)))
555561
trees += treeWithNarrowedType
556562
treeWithNarrowedType.tpe
557563
}
564+
}
558565
}
559566

560567
sealed abstract class Const {

test/files/run/t8611a.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings

test/files/run/t8611a.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
trait K
2+
trait L
3+
4+
object O {
5+
type LK = K with L
6+
val A: LK = new K with L
7+
val B: LK = new K with L
8+
}
9+
10+
object Test extends App {
11+
val scrut: O.LK = O.B
12+
scrut match {
13+
case O.A => ???
14+
case O.B => // spurious unreachable
15+
}
16+
}

test/files/run/t8611b.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings

test/files/run/t8611b.scala

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
sealed trait KrafsDescription
2+
3+
abstract class NotWorkingEnum extends Enumeration {
4+
5+
type ExtendedValue = Value with KrafsDescription
6+
7+
def Enum(inDescription: String): ExtendedValue = {
8+
new Val(nextId) with KrafsDescription {
9+
}
10+
}
11+
}
12+
13+
abstract class WorkingEnum extends Enumeration {
14+
15+
type ExtendedValue = Value
16+
17+
def Enum(inDescription: String): ExtendedValue = {
18+
new Val(nextId) {
19+
}
20+
}
21+
}
22+
23+
object NotWorkingTab extends NotWorkingEnum {
24+
val a = Enum("A")
25+
val b = Enum("B")
26+
}
27+
28+
object WorkingTab extends WorkingEnum {
29+
val a = Enum("A")
30+
val b = Enum("B")
31+
}
32+
33+
object Test extends App {
34+
testGris()
35+
testWorking()
36+
37+
def testGris() {
38+
val pipp = NotWorkingTab.b
39+
pipp match {
40+
case NotWorkingTab.a => ???
41+
case NotWorkingTab.b =>
42+
case _ => ???
43+
}
44+
}
45+
46+
def testWorking() {
47+
val stuff = WorkingTab.a
48+
stuff match {
49+
case WorkingTab.a =>
50+
case WorkingTab.b => ???
51+
case _ => ???
52+
}
53+
}
54+
}

test/files/run/t8611c.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings

test/files/run/t8611c.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
trait K
2+
trait L
3+
4+
object O {
5+
type LK = K with L
6+
}
7+
8+
object Test extends App {
9+
local
10+
11+
def local = {
12+
val A: O.LK = new K with L
13+
val B: O.LK = new K with L
14+
val scrut: O.LK = A
15+
scrut match {
16+
case B if "".isEmpty => ???
17+
case A =>
18+
case B => ???
19+
}
20+
}
21+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package scala.reflect.internal
2+
3+
import org.junit.Assert._
4+
import org.junit.Test
5+
import org.junit.runner.RunWith
6+
import org.junit.runners.JUnit4
7+
import scala.tools.nsc.symtab.SymbolTableForUnitTesting
8+
9+
@RunWith(classOf[JUnit4])
10+
class TypesTest {
11+
12+
object symbolTable extends SymbolTableForUnitTesting
13+
import symbolTable._, definitions._
14+
15+
@Test
16+
def testRefinedTypeSI8611(): Unit = {
17+
def stringNarrowed = StringTpe.narrow
18+
assert(stringNarrowed != stringNarrowed)
19+
assert(!(stringNarrowed =:= stringNarrowed))
20+
21+
def boolWithString = refinedType(BooleanTpe :: StringTpe :: Nil, NoSymbol)
22+
assert(boolWithString != boolWithString)
23+
assert(boolWithString =:= boolWithString)
24+
25+
val boolWithString1 = boolWithString
26+
val boolWithString1narrow1 = boolWithString1.narrow
27+
val boolWithString1narrow2 = boolWithString1.narrow
28+
// Two narrowings of the same refinement end up =:=. This was the root
29+
// cause of SI-8611. See `narrowUniquely` in `Logic` for the workaround.
30+
assert(boolWithString1narrow1 =:= boolWithString1narrow2)
31+
val uniquelyNarrowed1 = refinedType(boolWithString1narrow1 :: Nil, NoSymbol)
32+
val uniquelyNarrowed2 = refinedType(boolWithString1narrow2 :: Nil, NoSymbol)
33+
assert(uniquelyNarrowed1 =:= uniquelyNarrowed2)
34+
}
35+
}

0 commit comments

Comments
 (0)