Skip to content

Commit df7968c

Browse files
authored
Merge pull request #12159 from dotty-staging/fix-11220
Refine GADT casts on singletons
2 parents 32e725f + 443a3b0 commit df7968c

File tree

8 files changed

+67
-3
lines changed

8 files changed

+67
-3
lines changed

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

+1
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

+1
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,7 @@ class Definitions {
920920
@tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param")
921921
@tu lazy val SetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.setter")
922922
@tu lazy val ShowAsInfixAnnot: ClassSymbol = requiredClass("scala.annotation.showAsInfix")
923+
@tu lazy val StableAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Stable")
923924
@tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface")
924925
@tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName")
925926
@tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs")

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

+1-1
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

+21-2
Original file line numberDiff line numberDiff line change
@@ -3501,12 +3501,31 @@ class Typer extends Namer
35013501
typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt")
35023502
//typr.println(TypeComparer.explained(tree.tpe <:< pt))
35033503
adaptToSubType(wtp)
3504-
case CompareResult.OKwithGADTUsed if pt.isValueType =>
3504+
case CompareResult.OKwithGADTUsed
3505+
if pt.isValueType
3506+
&& !inContext(ctx.fresh.setGadt(EmptyGadtConstraint)) {
3507+
val res = (tree.tpe.widenExpr frozen_<:< pt)
3508+
if res then
3509+
// we overshot; a cast is not needed, after all.
3510+
gadts.println(i"unnecessary GADTused for $tree: ${tree.tpe.widenExpr} vs $pt in ${ctx.source}")
3511+
res
3512+
} =>
35053513
// Insert an explicit cast, so that -Ycheck in later phases succeeds.
35063514
// I suspect, but am not 100% sure that this might affect inferred types,
35073515
// if the expected type is a supertype of the GADT bound. It would be good to come
35083516
// up with a test case for this.
3509-
tree.cast(pt)
3517+
val target =
3518+
if tree.tpe.isSingleton then
3519+
val conj = AndType(tree.tpe, pt)
3520+
if tree.tpe.isStable && !conj.isStable then
3521+
// this is needed for -Ycheck. Without the annotation Ycheck will
3522+
// skolemize the result type which will lead to different types before
3523+
// and after checking. See i11955.scala.
3524+
AnnotatedType(conj, Annotation(defn.StableAnnot))
3525+
else conj
3526+
else pt
3527+
gadts.println(i"insert GADT cast from $tree to $target")
3528+
tree.cast(target)
35103529
case _ =>
35113530
tree
35123531
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ i8182.scala
6565

6666
# local lifted value in annotation argument has different position after pickling
6767
i2797a
68+
69+
# GADT cast applied to singleton type difference
70+
i4176-gadt.scala
71+
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

+8
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

+25
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)