Skip to content

References inside annotation are not avoided #23315

Open
@mbovel

Description

@mbovel

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 691bae2.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions