Skip to content

Commit dad8088

Browse files
committed
Use Skolems to infer GADT constraints
The rationale for using a Skolem here is: we want to record that there is at least one value that is both of the pattern type and the scrutinee type. All symbols are now considered valid for adding GADT constraints - the rationale is that set of constrainable symbols should be either selected on a per-(sub)pattern basis, or be the same for all matches. Previously, symbols which were only appearing variantly in a scrutinee type could be considered constrainable anyway because of an outer pattern match.
1 parent 5eae6e5 commit dad8088

File tree

7 files changed

+107
-19
lines changed

7 files changed

+107
-19
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
10361036
* - If a type proxy P is not a reference to a class, P's supertype is in G
10371037
*/
10381038
def isSubTypeOfParent(subtp: Type, tp: Type)(implicit ctx: Context): Boolean =
1039-
if (constrainPatternType(subtp, tp)) true
1039+
if (constrainPatternType(SkolemType(subtp), tp)) true
10401040
else tp match {
10411041
case tp: TypeRef if tp.symbol.isClass => isSubTypeOfParent(subtp, tp.firstParent)
10421042
case tp: TypeProxy => isSubTypeOfParent(subtp, tp.superType)

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ class Typer extends Namer
599599
def handlePattern: Tree = {
600600
val tpt1 = typedTpt
601601
if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef)
602-
constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible))
602+
constrainPatternType(SkolemType(tpt1.tpe), pt)(ctx.addMode(Mode.GADTflexible))
603603
// special case for an abstract type that comes with a class tag
604604
tryWithClassTag(ascription(tpt1, isWildcard = true), pt)
605605
}

tests/neg/int-extractor.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
object Test {
2+
object EssaInt {
3+
def unapply(i: Int): Some[Int] = Some(i)
4+
}
5+
6+
def foo1[T](t: T): T = t match {
7+
case EssaInt(_) =>
8+
0 // error
9+
}
10+
11+
case class Inv[T](t: T)
12+
13+
def bar1[T](t: T): T = Inv(t) match {
14+
case Inv(EssaInt(_)) =>
15+
0 // error
16+
}
17+
}

tests/neg/invariant-gadt.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object `invariant-gadt` {
2+
case class Invariant[T](value: T)
3+
4+
def unsound0[T](t: T): T = Invariant(t) match {
5+
case Invariant(_: Int) =>
6+
(0: Any) // error
7+
}
8+
9+
def unsound1[T](t: T): T = Invariant(t) match {
10+
case Invariant(_: Int) =>
11+
0 // error
12+
}
13+
14+
def unsound2[T](t: T): T = Invariant(t) match {
15+
case Invariant(value) => value match {
16+
case _: Int =>
17+
0 // error
18+
}
19+
}
20+
}

tests/neg/typeclass-derivation2.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ object TypeLevel {
111111
* It informs that type `T` has shape `S` and also implements runtime reflection on `T`.
112112
*/
113113
abstract class Shaped[T, S <: Shape] extends Reflected[T]
114+
115+
// substitute for erasedValue that allows precise matching
116+
final abstract class Type[-A, +B]
117+
type Subtype[t] = Type[_, t]
118+
type Supertype[t] = Type[t, _]
119+
type Exactly[t] = Type[t, t]
120+
erased def typeOf[T]: Type[T, T] = ???
114121
}
115122

116123
// An algebraic datatype
@@ -203,7 +210,7 @@ trait Show[T] {
203210
def show(x: T): String
204211
}
205212
object Show {
206-
import scala.compiletime.erasedValue
213+
import scala.compiletime.{erasedValue, error}
207214
import TypeLevel._
208215

209216
inline def tryShow[T](x: T): String = implicit match {
@@ -229,9 +236,14 @@ object Show {
229236
inline def showCases[T, Alts <: Tuple](r: Reflected[T], x: T): String =
230237
inline erasedValue[Alts] match {
231238
case _: (Shape.Case[alt, elems] *: alts1) =>
232-
x match {
233-
case x: `alt` => showCase[T, elems](r, x)
234-
case _ => showCases[T, alts1](r, x)
239+
inline typeOf[alt] match {
240+
case _: Subtype[T] =>
241+
x match {
242+
case x: `alt` => showCase[T, elems](r, x)
243+
case _ => showCases[T, alts1](r, x)
244+
}
245+
case _ =>
246+
error("invalid call to showCases: one of Alts is not a subtype of T")
235247
}
236248
case _: Unit =>
237249
throw new MatchError(x)

tests/pos/precise-pattern-type.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
object `precise-pattern-type` {
2+
class Type {
3+
def isType: Boolean = true
4+
}
5+
6+
class Tree[-T >: Null] {
7+
def tpe: T @annotation.unchecked.uncheckedVariance = ???
8+
}
9+
10+
case class Select[-T >: Null](qual: Tree[T]) extends Tree[T]
11+
12+
def test[T <: Tree[Type]](tree: T) = tree match {
13+
case Select(q) =>
14+
q.tpe.isType
15+
}
16+
}

tests/run/typeclass-derivation2.scala

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ object TypeLevel {
113113
* It informs that type `T` has shape `S` and also implements runtime reflection on `T`.
114114
*/
115115
abstract class Shaped[T, S <: Shape] extends Reflected[T]
116+
117+
// substitute for erasedValue that allows precise matching
118+
final abstract class Type[-A, +B]
119+
type Subtype[t] = Type[_, t]
120+
type Supertype[t] = Type[t, _]
121+
type Exactly[t] = Type[t, t]
122+
erased def typeOf[T]: Type[T, T] = ???
116123
}
117124

118125
// An algebraic datatype
@@ -217,7 +224,7 @@ trait Eq[T] {
217224
}
218225

219226
object Eq {
220-
import scala.compiletime.erasedValue
227+
import scala.compiletime.{erasedValue, error}
221228
import TypeLevel._
222229

223230
inline def tryEql[T](x: T, y: T) = implicit match {
@@ -239,8 +246,13 @@ object Eq {
239246
inline def eqlCases[T, Alts <: Tuple](xm: Mirror, ym: Mirror, ordinal: Int, n: Int): Boolean =
240247
inline erasedValue[Alts] match {
241248
case _: (Shape.Case[alt, elems] *: alts1) =>
242-
if (n == ordinal) eqlElems[elems](xm, ym, 0)
243-
else eqlCases[T, alts1](xm, ym, ordinal, n + 1)
249+
inline typeOf[alt] match {
250+
case _: Subtype[T] =>
251+
if (n == ordinal) eqlElems[elems](xm, ym, 0)
252+
else eqlCases[T, alts1](xm, ym, ordinal, n + 1)
253+
case _ =>
254+
error("invalid call to eqlCases: one of Alts is not a subtype of T")
255+
}
244256
case _: Unit =>
245257
false
246258
}
@@ -271,7 +283,7 @@ trait Pickler[T] {
271283
}
272284

273285
object Pickler {
274-
import scala.compiletime.{erasedValue, constValue}
286+
import scala.compiletime.{erasedValue, constValue, error}
275287
import TypeLevel._
276288

277289
def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1)
@@ -294,12 +306,17 @@ object Pickler {
294306
inline def pickleCases[T, Alts <: Tuple](r: Reflected[T], buf: mutable.ListBuffer[Int], x: T, n: Int): Unit =
295307
inline erasedValue[Alts] match {
296308
case _: (Shape.Case[alt, elems] *: alts1) =>
297-
x match {
298-
case x: `alt` =>
299-
buf += n
300-
pickleCase[T, elems](r, buf, x)
309+
inline typeOf[alt] match {
310+
case _: Subtype[T] =>
311+
x match {
312+
case x: `alt` =>
313+
buf += n
314+
pickleCase[T, elems](r, buf, x)
315+
case _ =>
316+
pickleCases[T, alts1](r, buf, x, n + 1)
317+
}
301318
case _ =>
302-
pickleCases[T, alts1](r, buf, x, n + 1)
319+
error("invalid pickleCases call: one of Alts is not a subtype of T")
303320
}
304321
case _: Unit =>
305322
}
@@ -362,7 +379,7 @@ trait Show[T] {
362379
def show(x: T): String
363380
}
364381
object Show {
365-
import scala.compiletime.erasedValue
382+
import scala.compiletime.{erasedValue, error}
366383
import TypeLevel._
367384

368385
inline def tryShow[T](x: T): String = implicit match {
@@ -388,9 +405,15 @@ object Show {
388405
inline def showCases[T, Alts <: Tuple](r: Reflected[T], x: T): String =
389406
inline erasedValue[Alts] match {
390407
case _: (Shape.Case[alt, elems] *: alts1) =>
391-
x match {
392-
case x: `alt` => showCase[T, elems](r, x)
393-
case _ => showCases[T, alts1](r, x)
408+
inline typeOf[alt] match {
409+
case _: Subtype[T] =>
410+
x match {
411+
case x: `alt` =>
412+
showCase[T, elems](r, x)
413+
case _ => showCases[T, alts1](r, x)
414+
}
415+
case _ =>
416+
error("invalid call to showCases: one of Alts is not a subtype of T")
394417
}
395418
case _: Unit =>
396419
throw new MatchError(x)

0 commit comments

Comments
 (0)