Skip to content

Commit 27f6370

Browse files
committed
Refine GADT casts on singletons
- keep the singleton type in the cast target - assert cast target stability if original was stable - fix isStable test for TypeParamRefs - double check if GADT logic is really needed before inserting a cast Fixes #11220 Fixes #11955
1 parent f01c14d commit 27f6370

File tree

8 files changed

+61
-3
lines changed

8 files changed

+61
-3
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class CheckRealizable(using Context) {
118118
case tp =>
119119
def isConcrete(tp: Type): Boolean = tp.dealias match {
120120
case tp: TypeRef => tp.symbol.isClass
121+
case tp: TypeParamRef => false
121122
case tp: TypeProxy => isConcrete(tp.underlying)
122123
case tp: AndType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
123124
case tp: OrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,7 @@ class Definitions {
919919
@tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param")
920920
@tu lazy val SetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.setter")
921921
@tu lazy val ShowAsInfixAnnot: ClassSymbol = requiredClass("scala.annotation.showAsInfix")
922+
@tu lazy val StableAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Stable")
922923
@tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface")
923924
@tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName")
924925
@tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ object Types {
168168
case _: SingletonType | NoPrefix => true
169169
case tp: RefinedOrRecType => tp.parent.isStable
170170
case tp: ExprType => tp.resultType.isStable
171-
case tp: AnnotatedType => tp.parent.isStable
171+
case tp: AnnotatedType => tp.annot.symbol == defn.StableAnnot || tp.parent.isStable
172172
case tp: AndType =>
173173
// TODO: fix And type check when tp contains type parames for explicit-nulls flow-typing
174174
// see: tests/explicit-nulls/pos/flow-stable.scala.disabled

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3530,12 +3530,25 @@ class Typer extends Namer
35303530
typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt")
35313531
//typr.println(TypeComparer.explained(tree.tpe <:< pt))
35323532
adaptToSubType(wtp)
3533-
case CompareResult.OKwithGADTUsed if pt.isValueType =>
3533+
case CompareResult.OKwithGADTUsed
3534+
if pt.isValueType
3535+
&& !inContext(ctx.fresh.setGadt(EmptyGadtConstraint)) { tree.tpe.widenExpr frozen_<:< pt } =>
35343536
// Insert an explicit cast, so that -Ycheck in later phases succeeds.
35353537
// I suspect, but am not 100% sure that this might affect inferred types,
35363538
// if the expected type is a supertype of the GADT bound. It would be good to come
35373539
// up with a test case for this.
3538-
tree.cast(pt)
3540+
val target =
3541+
if tree.tpe.isSingleton then
3542+
val conj = AndType(tree.tpe, pt)
3543+
if tree.tpe.isStable && !conj.isStable then
3544+
// this is needed for -Ycheck. Without the annotation Ycheck will
3545+
// skolemize the result type which will lead to different types before
3546+
// and after checking. See i11955.scala.
3547+
AnnotatedType(conj, Annotation(defn.StableAnnot))
3548+
else conj
3549+
else pt
3550+
gadts.println(i"insert GADT cast from $tree to $target")
3551+
tree.cast(target)
35393552
case _ =>
35403553
tree
35413554
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,7 @@ i8182.scala
6464

6565
# local lifted value in annotation argument has different position after pickling
6666
i2797a
67+
68+
# GADT cast applied to singleton type difference
69+
i4176-gadt.scala
70+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package scala.annotation.internal
2+
3+
import scala.annotation.Annotation
4+
5+
/** An annotation asserting that the annotated type is stable */
6+
final class Stable() extends Annotation

tests/pos/i11220.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.annotation.tailrec
2+
class Context {
3+
type Tree
4+
}
5+
6+
final def loop3[C <: Context](): Unit =
7+
@tailrec
8+
def loop4[A <: C](c: A): c.Tree = loop4(c)

tests/pos/i11955.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Hello {
2+
3+
sealed abstract class X[+A] {
4+
type This[+A] <: X[A]
5+
def asThis: This[A]
6+
}
7+
8+
class Y[+A] extends X[A] {
9+
override type This[+AA] = Y[AA]
10+
override def asThis: This[A] = this
11+
}
12+
13+
def hackBackToSelf[F[+u] <: X[u], A](f: F[Any])(f2: f.This[A]): F[A] =
14+
f2.asInstanceOf[F[A]]
15+
16+
case class G[F[+u] <: X[u], A](wrapped: F[A]) {
17+
18+
def mapF[F2[+u] <: X[u]](f: F[A] => F2[A]): G[F2, A] =
19+
G[F2, A](f(wrapped))
20+
21+
def test_ko_1: G[F, A] = mapF(ct => hackBackToSelf(ct)(ct.asThis)) // error
22+
def test_ko_2: G[F, A] = mapF[F](ct => hackBackToSelf(ct)(ct.asThis)) // error
23+
def test_ok : G[F, A] = mapF(ct => hackBackToSelf[F, A](ct)(ct.asThis)) // ok
24+
}
25+
}

0 commit comments

Comments
 (0)