Skip to content

Commit 3aa9b6e

Browse files
Merge pull request #7389 from dotty-staging/fix-6687
Fix #6687: handle gadt bounds in match type reduction
2 parents d10caaf + 54fe387 commit 3aa9b6e

File tree

9 files changed

+102
-88
lines changed

9 files changed

+102
-88
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
200200
val YerasedTerms: Setting[Boolean] = BooleanSetting("-Yerased-terms", "Allows the use of erased terms.")
201201
val YcheckInit: Setting[Boolean] = BooleanSetting("-Ycheck-init", "Check initialization of objects")
202202
val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation")
203+
val YunsoundMatchTypes: Setting[Boolean] = BooleanSetting("-Yunsound-match-types", "Use unsound match type reduction algorithm.")
203204

204205
/** Area-specific debug output */
205206
val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,7 +2388,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
23882388
case _ => false
23892389
}
23902390

2391-
23922391
/** Are `tp1` and `tp2` provablyDisjoint types?
23932392
*
23942393
* `true` implies that we found a proof; uncertainty defaults to `false`.
@@ -2507,17 +2506,25 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
25072506
case (_, tp2: AndType) =>
25082507
!(tp2 <:< tp1)
25092508
&& (provablyDisjoint(tp1, tp2.tp2) || provablyDisjoint(tp1, tp2.tp1))
2509+
case (tp1: NamedType, _) if gadtBounds(tp1.symbol) != null =>
2510+
provablyDisjoint(gadtBounds(tp1.symbol).hi, tp2) || provablyDisjoint(tp1.superType, tp2)
2511+
case (_, tp2: NamedType) if gadtBounds(tp2.symbol) != null =>
2512+
provablyDisjoint(tp1, gadtBounds(tp2.symbol).hi) || provablyDisjoint(tp1, tp2.superType)
25102513
case (tp1: TypeProxy, tp2: TypeProxy) =>
2511-
provablyDisjoint(tp1.underlying, tp2) || provablyDisjoint(tp1, tp2.underlying)
2514+
provablyDisjoint(matchTypeSuperType(tp1), tp2) || provablyDisjoint(tp1, matchTypeSuperType(tp2))
25122515
case (tp1: TypeProxy, _) =>
2513-
provablyDisjoint(tp1.underlying, tp2)
2516+
provablyDisjoint(matchTypeSuperType(tp1), tp2)
25142517
case (_, tp2: TypeProxy) =>
2515-
provablyDisjoint(tp1, tp2.underlying)
2518+
provablyDisjoint(tp1, matchTypeSuperType(tp2))
25162519
case _ =>
25172520
false
25182521
}
25192522
}
25202523

2524+
/** Restores the buggy match type reduction under -Yunsound-match-types. */
2525+
private def matchTypeSuperType(tp: TypeProxy): Type =
2526+
if ctx.settings.YunsoundMatchTypes.value then tp.underlying else tp.superType
2527+
25212528
protected def explainingTypeComparer = ExplainingTypeComparer(comparerContext)
25222529
protected def trackingTypeComparer = TrackingTypeComparer(comparerContext)
25232530

@@ -2757,34 +2764,6 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
27572764
case _ =>
27582765
cas
27592766
}
2760-
def widenAbstractTypes(tp: Type): Type = new TypeMap {
2761-
var seen = Set[TypeParamRef]()
2762-
def apply(tp: Type) = tp match {
2763-
case tp: TypeRef =>
2764-
tp.info match {
2765-
case info: MatchAlias =>
2766-
mapOver(tp)
2767-
// TODO: We should follow the alias in this case, but doing so
2768-
// risks infinite recursion
2769-
case TypeBounds(lo, hi) =>
2770-
if (hi frozen_<:< lo) {
2771-
val alias = apply(lo)
2772-
if (alias ne lo) alias else mapOver(tp)
2773-
}
2774-
else WildcardType
2775-
case _ =>
2776-
mapOver(tp)
2777-
}
2778-
case tp: TypeLambda =>
2779-
val saved = seen
2780-
seen ++= tp.paramRefs
2781-
try mapOver(tp)
2782-
finally seen = saved
2783-
case tp: TypeVar if !tp.isInstantiated => WildcardType
2784-
case tp: TypeParamRef if !seen.contains(tp) => WildcardType
2785-
case _ => mapOver(tp)
2786-
}
2787-
}.apply(tp)
27882767

27892768
val defn.MatchCase(pat, body) = cas1
27902769

@@ -2799,8 +2778,6 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
27992778
body
28002779
}
28012780
}
2802-
else if (isSubType(widenAbstractTypes(scrut), widenAbstractTypes(pat)))
2803-
Some(NoType)
28042781
else if (provablyDisjoint(scrut, pat))
28052782
// We found a proof that `scrut` and `pat` are incompatible.
28062783
// The search continues.

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ i7087.scala
3030
i7868.scala
3131
i7872.scala
3232
6709.scala
33+
6687.scala
3334

3435
# Opaque type
3536
i5720.scala

tests/neg/6314-6.scala

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
final class X
2+
final class Y
3+
4+
object Test3 {
5+
type Bar[A] = A match {
6+
case X => String
7+
case Y => Int
8+
}
9+
10+
trait XX {
11+
type Foo
12+
13+
val a: Bar[X & Foo] = "hello"
14+
val b: Bar[Y & Foo] = 1
15+
16+
def apply(fa: Bar[X & Foo]): Bar[Y & Foo]
17+
18+
def boom: Int = apply(a)
19+
}
20+
21+
trait YY extends XX {
22+
type Foo = X & Y
23+
24+
def apply(fa: Bar[X & Foo]): Bar[Y & Foo] = fa // error
25+
// overriding method apply in trait XX of type (fa: String): Int;
26+
// method apply of type (fa: String): String has incompatible type
27+
}
28+
(new YY {}).boom
29+
}
30+
31+
object Test4 {
32+
type Bar[A] = A match {
33+
case X => String
34+
case Y => Int
35+
}
36+
37+
trait XX {
38+
type Foo
39+
type FooAlias = Foo
40+
41+
val a: Bar[X & FooAlias] = "hello"
42+
val b: Bar[Y & FooAlias] = 1
43+
44+
def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias]
45+
46+
def boom: Int = apply(a)
47+
}
48+
49+
trait YY extends XX {
50+
type Foo = X & Y
51+
52+
def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias] = fa // error
53+
// overriding method apply in trait XX of type (fa: String): Int;
54+
// method apply of type (fa: String): String has incompatible type
55+
}
56+
(new YY {}).boom
57+
}

tests/neg/6314.scala

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -46,53 +46,3 @@ object Test2 {
4646
def right(fa: Bar[L]): Int = fa // error
4747
}
4848
}
49-
50-
51-
object Test3 {
52-
type Bar[A] = A match {
53-
case X => String
54-
case Y => Int
55-
}
56-
57-
trait XX {
58-
type Foo
59-
60-
val a: Bar[X & Foo] = "hello"
61-
val b: Bar[Y & Foo] = 1 // error
62-
63-
def apply(fa: Bar[X & Foo]): Bar[Y & Foo]
64-
65-
def boom: Int = apply(a) // error
66-
}
67-
68-
trait YY extends XX {
69-
type Foo = X & Y
70-
71-
def apply(fa: Bar[X & Foo]): Bar[Y & Foo] = fa
72-
}
73-
}
74-
75-
object Test4 {
76-
type Bar[A] = A match {
77-
case X => String
78-
case Y => Int
79-
}
80-
81-
trait XX {
82-
type Foo
83-
type FooAlias = Foo
84-
85-
val a: Bar[X & FooAlias] = "hello"
86-
val b: Bar[Y & FooAlias] = 1 // error
87-
88-
def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias]
89-
90-
def boom: Int = apply(a) // error
91-
}
92-
93-
trait YY extends XX {
94-
type Foo = X & Y
95-
96-
def apply(fa: Bar[X & FooAlias]): Bar[Y & FooAlias] = fa
97-
}
98-
}

tests/pos/10510.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
sealed trait Bool
2+
case object True extends Bool
3+
case object False extends Bool
4+
5+
sealed trait SBool[B <: Bool]
6+
case object STrue extends SBool[True.type]
7+
case object SFalse extends SBool[False.type]
8+
9+
type Not[B <: Bool] <: Bool = B match {
10+
case True.type => False.type
11+
case False.type => True.type
12+
}
13+
14+
def not[B <: Bool](b: SBool[B]): SBool[Not[B]] = b match {
15+
case STrue => SFalse
16+
case SFalse => STrue
17+
}

tests/pos/6687.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
type T[X] = X match {
2+
case String => Int
3+
case Int => String
4+
}
5+
6+
class Box[X](x: X)
7+
8+
def f[X](x: Box[X]): T[X] = x match {
9+
case x: Box[Int] => ""
10+
case x: Box[String] => 1
11+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Functor[[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]]
1+
Functor[Const[scala.collection.immutable.List[Dummy]]]
22
Functor[Const[scala.Int]]
3-
Functor[Id]
4-
Functor[[A >: scala.Nothing <: scala.Any] => scala.Option[A]]
3+
Functor[Const[Dummy]]
4+
Functor[Const[scala.Option[Dummy]]]

0 commit comments

Comments
 (0)