Skip to content

Commit c7cb901

Browse files
committed
Introduce SubTypeAnnotations
The idea is that such annotations create some proper subtype of the type they annotate. Hence, they cannot be stripped away like normal annotations are.
1 parent b359c7d commit c7cb901

File tree

8 files changed

+97
-10
lines changed

8 files changed

+97
-10
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,31 @@ object Trees {
213213

214214
override def toText(printer: Printer) = printer.toText(this)
215215

216+
def sameTree(that: Tree[_]): Boolean = {
217+
def isSame(x: Any, y: Any): Boolean =
218+
x.asInstanceOf[AnyRef].eq(y.asInstanceOf[AnyRef]) || {
219+
x match {
220+
case x: Tree[_] =>
221+
y match {
222+
case y: Tree[_] => x.sameTree(y)
223+
case _ => false
224+
}
225+
case x: List[_] =>
226+
y match {
227+
case y: List[_] => x.corresponds(y)(isSame)
228+
case _ => false
229+
}
230+
case _ =>
231+
false
232+
}
233+
}
234+
this.getClass == that.getClass && {
235+
val it1 = this.productIterator
236+
val it2 = that.productIterator
237+
it1.corresponds(it2)(isSame)
238+
}
239+
}
240+
216241
override def hashCode(): Int = uniqueId // for debugging; was: System.identityHashCode(this)
217242
override def equals(that: Any) = this eq that.asInstanceOf[AnyRef]
218243

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ object Annotations {
3434
def isEvaluated: Boolean = true
3535

3636
def ensureCompleted(implicit ctx: Context): Unit = tree
37+
38+
def sameAnnotation(that: Annotation)(implicit ctx: Context) =
39+
symbol == that.symbol && tree.sameTree(that.tree)
3740
}
3841

3942
case class ConcreteAnnotation(t: Tree) extends Annotation {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ class Definitions {
694694
def ClassfileAnnotationClass(implicit ctx: Context) = ClassfileAnnotationType.symbol.asClass
695695
lazy val StaticAnnotationType = ctx.requiredClassRef("scala.annotation.StaticAnnotation")
696696
def StaticAnnotationClass(implicit ctx: Context) = StaticAnnotationType.symbol.asClass
697+
lazy val SubTypeAnnotationType = ctx.requiredClassRef("scala.annotation.SubTypeAnnotation")
698+
def SubTypeAnnotationClass(implicit ctx: Context) = SubTypeAnnotationType.symbol.asClass
697699

698700
// Annotation classes
699701
lazy val AliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.Alias")

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
236236
compareWild
237237
case tp2: LazyRef =>
238238
!tp2.evaluating && recur(tp1, tp2.ref)
239-
case tp2: AnnotatedType =>
240-
recur(tp1, tp2.tpe) // todo: refine?
239+
case tp2: AnnotatedType if !tp2.isSubTypeAnnotated =>
240+
recur(tp1, tp2.tpe)
241241
case tp2: ThisType =>
242242
def compareThis = {
243243
val cls2 = tp2.cls
@@ -345,7 +345,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
345345
// because that would cause an assertionError. Return false instead.
346346
// See i859.scala for an example where we hit this case.
347347
!tp1.evaluating && recur(tp1.ref, tp2)
348-
case tp1: AnnotatedType =>
348+
case tp1: AnnotatedType if !tp1.isSubTypeAnnotated =>
349349
recur(tp1.tpe, tp2)
350350
case AndType(tp11, tp12) =>
351351
if (tp11.stripTypeVar eq tp12.stripTypeVar) recur(tp11, tp2)
@@ -567,6 +567,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
567567
false
568568
}
569569
compareTypeBounds
570+
case tp2: AnnotatedType if tp2.isSubTypeAnnotated =>
571+
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || defn.isBottomType(tp1)) &&
572+
recur(tp1, tp2.tpe)
570573
case ClassInfo(pre2, cls2, _, _, _) =>
571574
def compareClassInfo = tp1 match {
572575
case ClassInfo(pre1, cls1, _, _, _) =>
@@ -661,6 +664,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
661664
case _ =>
662665
}
663666
either(recur(tp11, tp2), recur(tp12, tp2))
667+
case tp1: AnnotatedType if tp1.isSubTypeAnnotated =>
668+
isNewSubType(tp1.tpe)
664669
case JavaArrayType(elem1) =>
665670
def compareJavaArray = tp2 match {
666671
case JavaArrayType(elem2) => isSubType(elem1, elem2)
@@ -700,7 +705,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
700705
}
701706
case tycon1: TypeVar =>
702707
isMatchingApply(tycon1.underlying)
703-
case tycon1: AnnotatedType =>
708+
case tycon1: AnnotatedType if !tycon1.isSubTypeAnnotated =>
704709
isMatchingApply(tycon1.underlying)
705710
case _ =>
706711
false
@@ -811,7 +816,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
811816
fourthTry
812817
}
813818
}
814-
case _: TypeVar | _: AnnotatedType =>
819+
case _: TypeVar =>
820+
recur(tp1, tp2.superType)
821+
case tycon2: AnnotatedType if !tycon2.isSubTypeAnnotated =>
815822
recur(tp1, tp2.superType)
816823
case tycon2: AppliedType =>
817824
fallback(tycon2.lowerBound)
@@ -1546,7 +1553,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
15461553
}
15471554
case tp1: TypeVar if tp1.isInstantiated =>
15481555
tp1.underlying & tp2
1549-
case tp1: AnnotatedType =>
1556+
case tp1: AnnotatedType if !tp1.isSubTypeAnnotated =>
15501557
tp1.underlying & tp2
15511558
case _ =>
15521559
NoType
@@ -1565,7 +1572,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
15651572
ExprType(rt1 | tp2.widenExpr)
15661573
case tp1: TypeVar if tp1.isInstantiated =>
15671574
tp1.underlying | tp2
1568-
case tp1: AnnotatedType =>
1575+
case tp1: AnnotatedType if !tp1.isSubTypeAnnotated =>
15691576
tp1.underlying | tp2
15701577
case _ =>
15711578
NoType

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ object Types {
283283
case _ => false
284284
}
285285

286+
/** Does this type have a supertype with an annotation satisfying given predicate `p`? */
287+
def derivesAnnotWith(p: Annotation => Boolean)(implicit ctx: Context): Boolean = this match {
288+
case tp: AnnotatedType => p(tp.annot) || tp.tpe.derivesAnnotWith(p)
289+
case tp: TypeProxy => tp.superType.derivesAnnotWith(p)
290+
case AndType(l, r) => l.derivesAnnotWith(p) || r.derivesAnnotWith(p)
291+
case OrType(l, r) => l.derivesAnnotWith(p) && r.derivesAnnotWith(p)
292+
case _ => false
293+
}
294+
286295
/** Does this type occur as a part of type `that`? */
287296
final def occursIn(that: Type)(implicit ctx: Context): Boolean =
288297
that existsPart (this == _)
@@ -3693,6 +3702,17 @@ object Types {
36933702

36943703
override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots
36953704

3705+
private[this] var isSubTypeAnnotatedKnown = false
3706+
private[this] var isSubTypeAnnotatedCache: Boolean = _
3707+
3708+
def isSubTypeAnnotated(implicit ctx: Context) = {
3709+
if (!isSubTypeAnnotatedKnown) {
3710+
isSubTypeAnnotatedCache = annot.symbol.derivesFrom(defn.SubTypeAnnotationClass)
3711+
isSubTypeAnnotatedKnown = true
3712+
}
3713+
isSubTypeAnnotatedCache
3714+
}
3715+
36963716
override def iso(that: Any, bs: BinderPairs): Boolean = that match {
36973717
case that: AnnotatedType => tpe.equals(that.tpe, bs) && (annot `eq` that.annot)
36983718
case _ => false

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,10 @@ object Checking {
485485
prefix = apply(tp.prefix),
486486
classParents =
487487
tp.parents.map { p =>
488-
apply(p).stripAnnots match {
489-
case ref: TypeRef => ref
490-
case ref: AppliedType => ref
488+
val p1 = apply(p)
489+
p1.stripAnnots match {
490+
case ref: TypeRef => p1
491+
case ref: AppliedType => p1
491492
case _ => defn.ObjectType // can happen if class files are missing
492493
}
493494
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package scala.annotation
2+
3+
trait SubTypeAnnotation extends StaticAnnotation

tests/pos/immutableAnnot.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.annotation._
2+
class Immutable extends Annotation with SubTypeAnnotation
3+
4+
object Test extends App {
5+
6+
def assertImmutable[T](n: T): T @ Immutable = ???
7+
8+
def g[S](x: S, y: S) = if (???) x else y
9+
10+
def f(n: Int @ Immutable): Int @ Immutable =
11+
if n == 0 then assertImmutable(0)
12+
else {
13+
val n1 = f(f(n))
14+
val x = g(n1, n1)
15+
val y = g(n1, assertImmutable(3))
16+
val z = g(x, y)
17+
val r =
18+
if (???) x
19+
else if (???) y
20+
else if (???) z
21+
else assertImmutable(n1 - 1)
22+
val s = g(assertImmutable(List(1, 2, 3)), assertImmutable("a"))
23+
val scheck: java.io.Serializable = s
24+
r
25+
}
26+
}

0 commit comments

Comments
 (0)