Skip to content

References inside annotation are not avoided #23315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mbovel opened this issue Jun 4, 2025 · 0 comments
Open

References inside annotation are not avoided #23315

mbovel opened this issue Jun 4, 2025 · 0 comments

Comments

@mbovel
Copy link
Member

mbovel commented Jun 4, 2025

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.

@mbovel mbovel added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label area:annotations prio:low and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant