Skip to content

Commit 671a48c

Browse files
committed
Freeze GADTs more when comparing type member infos
1 parent 63344e7 commit 671a48c

File tree

10 files changed

+120
-5
lines changed

10 files changed

+120
-5
lines changed

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -1923,7 +1923,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19231923
|| symInfo.isInstanceOf[MethodType]
19241924
&& symInfo.signature.consistentParams(info2.signature)
19251925

1926-
def tp1IsSingleton: Boolean = tp1.isInstanceOf[SingletonType]
1926+
def allowGadt: Boolean =
1927+
def rec(tp: Type): Boolean = tp match
1928+
case RefinedType(parent, name1, _) => name == name1 || rec(parent)
1929+
case tp: TypeRef => tp.symbol.isClass
1930+
case _ => false
1931+
!approx.low && rec(tp1)
19271932

19281933
// A relaxed version of isSubType, which compares method types
19291934
// under the standard arrow rule which is contravarient in the parameter types,
@@ -1939,8 +1944,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19391944
matchingMethodParams(info1, info2, precise = false)
19401945
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1), symInfo1.resultType)
19411946
&& sigsOK(symInfo1, info2)
1942-
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
1943-
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
1947+
case _ => inFrozenGadtIf(!allowGadt) { isSubType(info1, info2) }
1948+
case _ => inFrozenGadtIf(!allowGadt) { isSubType(info1, info2) }
19441949

19451950
def qualifies(m: SingleDenotation): Boolean =
19461951
val info1 = m.info.widenExpr

compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,14 @@ object QuoteMatcher {
186186
if patternHole.symbol.eq(defn.QuotedRuntimePatterns_patternHole) &&
187187
tpt2.tpe.derivesFrom(defn.RepeatedParamClass) =>
188188
scrutinee match
189-
case Typed(s, tpt1) if s.tpe <:< tpt.tpe => matched(scrutinee)
189+
case Typed(s, tpt1) if s.tpe.widenSingleton <:< tpt.tpe => matched(scrutinee)
190190
case _ => notMatched
191191

192192
/* Term hole */
193193
// Match a scala.internal.Quoted.patternHole and return the scrutinee tree
194194
case TypeApply(patternHole, tpt :: Nil)
195195
if patternHole.symbol.eq(defn.QuotedRuntimePatterns_patternHole) &&
196-
scrutinee.tpe <:< tpt.tpe =>
196+
scrutinee.tpe.widenSingleton <:< tpt.tpe =>
197197
scrutinee match
198198
case ClosedPatternTerm(scrutinee) => matched(scrutinee)
199199
case _ => notMatched

tests/neg/i15485.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
enum SUB[-A, +B]:
2+
case Refl[C]() extends SUB[C, C]
3+
4+
trait Tag { type T }
5+
6+
def foo[A, B, X <: Tag { type T <: A } ](
7+
e: SUB[X, Tag { type T <: B }],
8+
x: A,
9+
): B = e match {
10+
case SUB.Refl() =>
11+
// X <: C and C <: Tag { T <: B }
12+
// X <: Tag { T <: B }
13+
// Tag { T <: A } <: Tag { T <: B }
14+
// A <: B
15+
x // error: Found: (x: A) Required: B
16+
}
17+
18+
def bad(x: Int): String =
19+
foo[Int, String, Tag { type T = Nothing }](SUB.Refl(), x) // cast Int to String
20+
21+
object Test:
22+
def main(args: Array[String]): Unit = bad(1) // was: ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String

tests/neg/i15485b.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
enum SUB[-A, +B]:
2+
case Refl[C]() extends SUB[C, C]
3+
4+
trait Tag { type T }
5+
6+
def foo[L, H, X <: Tag { type T >: L <: H }](
7+
e: SUB[X, Tag { type T = Int }],
8+
x: Int,
9+
): L = e match {
10+
case SUB.Refl() =>
11+
// X <: C and C <: Tag { T = Int }
12+
// X <: Tag { T = Int }
13+
// Tag { T >: L <: H } <: Tag { T = Int }
14+
// Int <: L and H <: Int
15+
x // error
16+
}
17+
18+
def bad(x: Int): String =
19+
foo[Nothing, Any, Tag { type T = Int }](SUB.Refl(), x) // cast Int to String!
20+
21+
object Test:
22+
def main(args: Array[String]): Unit = bad(1) // was: ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String

tests/neg/i15485c.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
enum SUB[-A, +B]:
2+
case Refl[C]() extends SUB[C, C]
3+
4+
trait Tag { type T }
5+
6+
def foo[L](t: Tag { type T >: L <: Int })(
7+
e: SUB[t.type, Tag { type T = Int }],
8+
x: Int,
9+
): L = e match {
10+
case SUB.Refl() =>
11+
// L = Nothing
12+
// C = t
13+
// t := Tag { T = Int..Int }
14+
// t <: Tag { T = Nothing..Int }
15+
// SUB[t, Tag { T = Int..Int }]
16+
// SUB[Tag { T = Nothing..Int }, Tag { T = Int..Int }]
17+
// SUB[Tag { T = L..Int }, Tag { T = Int..Int }] <:< SUB[C, C]
18+
// Tag { T = L..Int } <: C <: Tag { T = Int..Int }]
19+
// Tag { T = L..Int } <: Tag { T = Int..Int }
20+
// Int <: L
21+
x // error
22+
}
23+
24+
def bad(x: Int): String =
25+
val s: Tag { type T = Int } = new Tag { type T = Int }
26+
val t: Tag { type T >: Nothing <: Int } & s.type = s
27+
val e: SUB[t.type, Tag { type T = Int }] = SUB.Refl[t.type]()
28+
foo[Nothing](t)(e, x) // cast Int to String!
29+
30+
object Test:
31+
def main(args: Array[String]): Unit = bad(1) // was: ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Another minimisation (after tests/run-macros/i15485.fallout-monocle)
2+
// of monocle's GenIsoSpec.scala
3+
// which broke when fixing soundness in infering GADT constraints on refined types
4+
class Can[T]
5+
object Can:
6+
import scala.deriving.*, scala.quoted.*
7+
8+
inline given derived[T](using inline m: Mirror.Of[T]): Can[T] = ${ derivedExpr('m) }
9+
10+
private def derivedExpr[T](m: Expr[Mirror.Of[T]])(using Quotes, Type[T]): Expr[Can[T]] = m match
11+
case '{ $_ : Mirror.Sum { type MirroredElemTypes = mirroredElemTypes } } => '{ new Can[T] }
12+
case '{ $_ : Mirror.Product { type MirroredElemTypes = mirroredElemTypes } } => '{ new Can[T] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Test:
2+
def test =
3+
Can.derived[EmptyTuple]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.deriving.*, scala.quoted.*
2+
3+
trait Foo[T]:
4+
def foo: Int
5+
6+
// A minimisation of monocle's GenIsoSpec.scala
7+
// which broke when fixing soundness in infering GADT constraints on refined types
8+
object Foo:
9+
inline given derived[T](using inline m: Mirror.Of[T]): Foo[T] = ${ impl('m) }
10+
11+
private def impl[T](m: Expr[Mirror.Of[T]])(using qctx: Quotes, tpe: Type[T]): Expr[Foo[T]] = m match
12+
case '{ $m : Mirror.Product { type MirroredElemTypes = EmptyTuple } } => '{ FooN[T](0) }
13+
case '{ $m : Mirror.Product { type MirroredElemTypes = a *: EmptyTuple } } => '{ FooN[T](1) }
14+
case '{ $m : Mirror.Product { type MirroredElemTypes = mirroredElemTypes } } => '{ FooN[T](9) }
15+
16+
class FooN[T](val foo: Int) extends Foo[T]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
final case class Box(value: Int) derives Foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@main def Test =
2+
val foo = summon[Foo[Box]].foo
3+
assert(foo == 1, foo)

0 commit comments

Comments
 (0)