scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

References inside annotation are not avoided

Open mbovel opened this issue 6 months ago • 0 comments

Problem

Let's consider the following example:

class MyAnnotation(x: Int) extends scala.annotation.StaticAnnotation

def Test =
  val x =
    val y = 1
    "hello": String @MyAnnotation(y)

And fully print types of identifiers in RefinedPrinter:

diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
index ecc1250cbe..7f52a85021 100644
--- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
+++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
@@ -882,10 +882,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
 
     if (ctx.settings.XprintTypes.value && tree.hasType) {
       // add type to term nodes; replace type nodes with their types unless -Yprint-pos is also set.
-      val tp1 = tree.typeOpt match {
-        case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying
-        case tp => tp
-      }
+      val tp1 = tree.typeOpt
       val tp2 = {
         val tp = tp1.tryNormalize
         if (tp != NoType) tp else tp1

Then we see that y.type (a.k.a. (y: Int)) leaks into the outer scope:

sbt:scala3> scalac -Xprint:typer -Xprint-types -Ycheck:all tests/pos/annot-avoid.scala
...
    def Test: Unit =
      <
        {
          val x: String @MyAnnotation(<y:(y : Int)>) =
            <
              {
                val y: Int = <1:(1 : Int)>
                <<"hello":("hello" : String)> :
                  String @MyAnnotation(<y:(y : Int)>):
                  String @MyAnnotation(<y:(y : Int)>)>
              }
            :String @MyAnnotation(<y:(y : Int)>)>
          val x2: String @MyAnnotation(<y:(y : Int)>) =
            <x:(x : String @MyAnnotation(<y:(y : Int)>))>
          <():Unit>
        }
      :Unit>
...

Cause

The root cause is that escapingRefs uses NamedPartsAccumulator to collect references to local symbols, which does not traverse annotations (see TypeAccumulator.applyToAnnot).

-Ycheck:all does not detect the leaked symbols because the tree checker also doesn't recurse into annotated types argument trees.

Solution

We might override applyToAnnot in NamedPartsAccumulator to traverse the annotation tree, but I am not sure how much it would impact other parts that use NamedPartsAccumulator. Also, TypeAccumulator.applyToAnnot was introduced specifically to avoid traversing annotated types to improve performance in 691bae22c373bc72ad77cdd1968c35a445c34437.

This is an other issue showing that using trees as annotated types argument is fragile, and might require to be changed in the future.

mbovel avatar Jun 04 '25 11:06 mbovel