Skip to content

Commit f42b515

Browse files
committed
Freeze GADTs more when comparing type member infos
1 parent 9d574ce commit f42b515

File tree

11 files changed

+114
-6
lines changed

11 files changed

+114
-6
lines changed

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

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools
22
package dotc
33
package core
44

5-
import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._
5+
import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._, SymDenotations.*
66
import Decorators._
77
import Phases.{gettersPhase, elimByNamePhase}
88
import StdNames.nme
@@ -1888,7 +1888,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
18881888
* rebase both itself and the member info of `tp` on a freshly created skolem type.
18891889
*/
18901890
def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean =
1891-
trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) {
1891+
val mbr = tp1.member(name)
1892+
trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${mbr.info}", subtyping) {
18921893

18931894
// If the member is an abstract type and the prefix is a path, compare the member itself
18941895
// instead of its bounds. This case is needed situations like:
@@ -1931,7 +1932,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19311932
|| symInfo.isInstanceOf[MethodType]
19321933
&& symInfo.signature.consistentParams(info2.signature)
19331934

1934-
def tp1IsSingleton: Boolean = tp1.isInstanceOf[SingletonType]
1935+
def allowGadt = mbr match
1936+
case _ if tp1.isInstanceOf[SingletonType] => false
1937+
case d: UniqueRefDenotation if d.prefix == NoPrefix && d.symbol != NoSymbol => false
1938+
case _ => true
19351939

19361940
// A relaxed version of isSubType, which compares method types
19371941
// under the standard arrow rule which is contravarient in the parameter types,
@@ -1947,15 +1951,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19471951
matchingMethodParams(info1, info2, precise = false)
19481952
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1), symInfo1.resultType)
19491953
&& sigsOK(symInfo1, info2)
1950-
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
1951-
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
1954+
case _ => inFrozenGadtIf(!allowGadt) { isSubType(info1, info2) }
1955+
case _ => inFrozenGadtIf(!allowGadt) { isSubType(info1, info2) }
19521956

19531957
def qualifies(m: SingleDenotation): Boolean =
19541958
val info1 = m.info.widenExpr
19551959
isSubInfo(info1, tp2.refinedInfo.widenExpr, m.symbol.info.orElse(info1))
19561960
|| matchAbstractTypeMember(m.info)
19571961

1958-
tp1.member(name) match // inlined hasAltWith for performance
1962+
mbr match // inlined hasAltWith for performance
19591963
case mbr: SingleDenotation => qualifies(mbr)
19601964
case mbr => mbr hasAltWith qualifies
19611965
}

tests/neg/i15485.scala

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

tests/neg/i15485b.scala

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

tests/neg/i15485c.scala

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
enum SUB[-L, +R]:
2+
case Refl[C]() extends SUB[C, C]
3+
4+
trait Tag { type T }
5+
6+
def foo[B](g: Tag { type T >: B <: Int })(
7+
e: SUB[g.type, Tag { type T = Int }], i: Int,
8+
): B = e match
9+
case SUB.Refl() =>
10+
// B = Nothing
11+
// g = Tag { T = Int..Int } <: Tag { T = B..Int }
12+
// SUB[g, Tag { T = Int..Int }]
13+
// SUB[Tag { T = B..Int }, Tag { T = Int..Int }] approxLHS
14+
// Int <: B
15+
i // error: Found: (i: Int) Required: B
16+
17+
def bad(i: Int): String =
18+
val g = new Tag { type T = Int }
19+
val e: SUB[g.type, Tag { type T = Int }] = SUB.Refl[g.type]()
20+
foo[Nothing](g)(e, i) // cast Int to String!
21+
22+
object Test:
23+
def main(args: Array[String]): Unit = bad(1) // was: ClassCastException: Integer cannot be cast to String
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
inline given derived[T](using inline m: Mirror.Of[T]): Can[T] = ${ impl('m) }
8+
private def impl[T](m: Expr[Mirror.Of[T]])(using Quotes, Type[T]): Expr[Can[T]] = m match
9+
case '{ $_ : Mirror.Sum { type MirroredElemTypes = met } } => '{ new Can[T] }
10+
case '{ $_ : Mirror.Product { type MirroredElemTypes = met } } => '{ 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,7 @@
1+
object Iso:
2+
import scala.deriving.*, scala.quoted.*
3+
transparent inline def fields[S <: Product](using m: Mirror.ProductOf[S]): Int = ${ impl[S]('m) }
4+
private def impl[S <: Product](m: Expr[Mirror.ProductOf[S]])(using Quotes, Type[S]): Expr[Int] =
5+
import quotes.reflect.*
6+
m match
7+
case '{ type a <: Tuple; $m: Mirror.ProductOf[S] { type MirroredElemTypes = `a` } } => '{ 1 }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Test:
2+
def test =
3+
case object Foo
4+
val iso = Iso.fields[Foo.type]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// A minimisation of monocle's GenIsoSpec.scala
2+
// which broke when fixing soundness in infering GADT constraints on refined types
3+
trait Foo[T] { def foo: Int }
4+
object Foo:
5+
import scala.deriving.*, scala.quoted.*
6+
inline given derived[T](using inline m: Mirror.Of[T]): Foo[T] = ${ impl('m) }
7+
private def impl[T](m: Expr[Mirror.Of[T]])(using Quotes, Type[T]): Expr[Foo[T]] = m match
8+
case '{ $m : Mirror.Product { type MirroredElemTypes = EmptyTuple } } => '{ FooN[T](0) }
9+
case '{ $m : Mirror.Product { type MirroredElemTypes = a *: EmptyTuple } } => '{ FooN[T](1) }
10+
case '{ $m : Mirror.Product { type MirroredElemTypes = mirroredElemTypes } } => '{ FooN[T](9) }
11+
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)