Skip to content

Commit cb08b46

Browse files
committed
Make sure symbols in annotation trees are fresh before pickling
1 parent 2be2a60 commit cb08b46

File tree

5 files changed

+71
-8
lines changed

5 files changed

+71
-8
lines changed

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools
22
package dotc
33
package transform
44

5-
import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar}
5+
import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar, TreeTypeMap}
66
import scala.collection.mutable
77
import core.*
88
import dotty.tools.dotc.typer.Checking
@@ -16,7 +16,7 @@ import Symbols.*, NameOps.*
1616
import ContextFunctionResults.annotateContextResults
1717
import config.Printers.typr
1818
import config.Feature
19-
import util.SrcPos
19+
import util.{SrcPos, Stats}
2020
import reporting.*
2121
import NameKinds.WildcardParamName
2222
import cc.*
@@ -154,17 +154,39 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
154154
case _ =>
155155
case _ =>
156156

157+
/** Returns a copy of the given tree with all symbols fresh.
158+
*
159+
* Used to guarantee that no symbols are shared between trees in different
160+
* annotations.
161+
*/
162+
private def copySymbols(tree: Tree)(using Context) =
163+
Stats.trackTime("Annotations copySymbols"):
164+
val ttm =
165+
new TreeTypeMap:
166+
override def withMappedSyms(syms: List[Symbol]) =
167+
withMappedSyms(syms, mapSymbols(syms, this, true))
168+
ttm(tree)
169+
170+
/** Transforms the given annotation tree. */
157171
private def transformAnnot(annot: Tree)(using Context): Tree = {
158172
val saved = inJavaAnnot
159173
inJavaAnnot = annot.symbol.is(JavaDefined)
160174
if (inJavaAnnot) checkValidJavaAnnotation(annot)
161-
try transform(annot)
175+
try transform(copySymbols(annot))
162176
finally inJavaAnnot = saved
163177
}
164178

165179
private def transformAnnot(annot: Annotation)(using Context): Annotation =
166180
annot.derivedAnnotation(transformAnnot(annot.tree))
167181

182+
/** Transforms all annotations in the given type. */
183+
private def transformAnnots(using Context) =
184+
new TypeMap:
185+
def apply(tp: Type) = tp match
186+
case tp @ AnnotatedType(parent, annot) =>
187+
tp.derivedAnnotatedType(mapOver(parent), transformAnnot(annot))
188+
case _ => mapOver(tp)
189+
168190
private def processMemberDef(tree: Tree)(using Context): tree.type = {
169191
val sym = tree.symbol
170192
Checking.checkValidOperator(sym)
@@ -524,11 +546,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
524546
super.transform(tree)
525547
case tree: TypeTree =>
526548
val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe
527-
tree.withType:
528-
tpe match
529-
case AnnotatedType(parent, annot) =>
530-
AnnotatedType(parent, transformAnnot(annot)) // TODO: Also map annotations embedded in type?
531-
case _ => tpe
549+
tree.withType(transformAnnots(tpe))
532550
case Typed(Ident(nme.WILDCARD), _) =>
533551
withMode(Mode.Pattern)(super.transform(tree))
534552
// The added mode signals that bounds in a pattern need not

tests/pos/annot-17939.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.annotation.Annotation
2+
class myRefined[T](f: T => Boolean) extends Annotation
3+
4+
class Box[T](val x: T)
5+
class Box2(val x: Int)
6+
7+
class A(a: String @myRefined((x: Int) => Box(3).x == 3)) // crash
8+
class A2(a2: String @myRefined((x: Int) => Box2(3).x == 3)) // works

tests/pos/annot-19846.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dependentAnnotation
2+
3+
class lambdaAnnot(g: () => Int) extends annotation.StaticAnnotation
4+
5+
def f(x: Int): Int @lambdaAnnot(() => x + 1) = x
6+
7+
@main def main =
8+
val y: Int = 5
9+
val z = f(y)

tests/pos/annot-19846b.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class qualified[T](predicate: T => Boolean) extends annotation.StaticAnnotation
2+
3+
class EqualPair(val x: Int, val y: Int @qualified[Int](it => it == x))
4+
5+
@main def main =
6+
val p = EqualPair(42, 42)
7+
val y = p.y
8+
println(42)

tests/pos/annot-i20272a.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import language.experimental.captureChecking
2+
3+
trait Iterable[T] { self: Iterable[T]^ =>
4+
def map[U](f: T => U): Iterable[U]^{this, f}
5+
}
6+
7+
object Test {
8+
def assertEquals[A, B](a: A, b: B): Boolean = ???
9+
10+
def foo[T](level: Int, lines: Iterable[T]) =
11+
lines.map(x => x)
12+
13+
def bar(messages: Iterable[String]) =
14+
foo(1, messages)
15+
16+
val it: Iterable[String] = ???
17+
val msgs = bar(it)
18+
19+
assertEquals(msgs, msgs)
20+
}

0 commit comments

Comments
 (0)