Skip to content

Commit af5fee9

Browse files
committed
Make sure annotations are typed in expression contexts
The previous logic tried but had holes. A local context was established in `typedStats#localCtx` but that context was not found in annotations that were typechecked in the completer of the definition they decorated. We are now more through and always allocate a localDummy as new owner if the owner would otherwise we a class. This means that closures in annotation arguments will have a term as owner, and therefore will be allocated in distinct local scopes. Fixes #15054 This broke two pickling tests where the source defined a local class in an argument to an annotation of a toplevel class. Somehow a position for these argument classes could not be found when unpickling. But it does work for classes inside other classes or objects. I changed the tests to nested classes, and kept one of the originals in the pickling exclude list.
1 parent ed81385 commit af5fee9

File tree

6 files changed

+39
-16
lines changed

6 files changed

+39
-16
lines changed

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,25 +2238,23 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22382238
* since classes defined in a such arguments should not be entered into the
22392239
* enclosing class.
22402240
*/
2241-
def annotContext(mdef: untpd.Tree, sym: Symbol)(using Context): Context = {
2241+
def annotContext(mdef: untpd.Tree, sym: Symbol)(using Context): Context =
22422242
def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner
22432243
val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next()
2244-
var adjusted = outer.property(ExprOwner) match {
2244+
val adjusted = outer.property(ExprOwner) match {
22452245
case Some(exprOwner) if outer.owner.isClass => outer.exprContext(mdef, exprOwner)
22462246
case _ => outer
22472247
}
2248+
def local: FreshContext = adjusted.fresh.setOwner(newLocalDummy(sym.owner))
22482249
sym.owner.infoOrCompleter match
2249-
case completer: Namer#Completer if sym.is(Param) =>
2250-
val tparams = completer.completerTypeParams(sym)
2251-
if tparams.nonEmpty then
2252-
// Create a new local context with a dummy owner and a scope containing the
2253-
// type parameters of the enclosing method or class. Thus annotations can see
2254-
// these type parameters. See i12953.scala for a test case.
2255-
val dummyOwner = newLocalDummy(sym.owner)
2256-
adjusted = adjusted.fresh.setOwner(dummyOwner).setScope(newScopeWith(tparams*))
2250+
case completer: Namer#Completer
2251+
if sym.is(Param) && completer.completerTypeParams(sym).nonEmpty =>
2252+
// Create a new local context with a dummy owner and a scope containing the
2253+
// type parameters of the enclosing method or class. Thus annotations can see
2254+
// these type parameters. See i12953.scala for a test case.
2255+
local.setScope(newScopeWith(completer.completerTypeParams(sym)*))
22572256
case _ =>
2258-
adjusted
2259-
}
2257+
if outer.owner.isClass then local else adjusted
22602258

22612259
def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = {
22622260
// necessary to force annotation trees to be computed.

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ i12299a.scala
1919
i13871.scala
2020
i15181.scala
2121
i15922.scala
22+
java-inherited-type1
2223

2324
# Tree is huge and blows stack for printing Text
2425
i7034.scala
@@ -91,4 +92,6 @@ i4176-gadt.scala
9192
# GADT difference
9293
i13974a.scala
9394

94-
java-inherited-type1
95+
# Classes defined in annotation arguments of toplevel classes don't have positions
96+
i9314a.scala
97+

tests/pos/i15054.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.annotation.Annotation
2+
3+
class AnAnnotation(function: Int => String) extends Annotation
4+
5+
@AnAnnotation(_.toString)
6+
val a = 1
7+
@AnAnnotation(_.toString.length.toString)
8+
val b = 2
9+
10+
def test =
11+
@AnAnnotation(_.toString)
12+
val a = 1
13+
@AnAnnotation(_.toString.length.toString)
14+
val b = 2
15+
a + b

tests/pos/i9314.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
final class fooAnnot[T](member: T) extends scala.annotation.StaticAnnotation // must have type parameter
22

3-
@fooAnnot(new RecAnnotated {}) // must pass instance of anonymous subclass
4-
trait RecAnnotated
3+
object Test:
4+
@fooAnnot(new RecAnnotated {}) // must pass instance of anonymous subclass
5+
trait RecAnnotated

tests/pos/i9314a.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
final class fooAnnot[T](member: T) extends scala.annotation.StaticAnnotation // must have type parameter
2+
3+
object Test:
4+
@fooAnnot(new RecAnnotated {}) // must pass instance of anonymous subclass
5+
trait RecAnnotated

tests/pos/t7426.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
class foo(x: Any) extends annotation.StaticAnnotation
22

3-
@foo(new AnyRef { }) trait A
3+
object Test:
4+
@foo(new AnyRef { }) trait A

0 commit comments

Comments
 (0)