Skip to content

Make sure annotations are typed in expression contexts #16699

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

Merged
merged 2 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,9 @@ class TreeUnpickler(reader: TastyReader,
else
newSymbol(ctx.owner, name, flags, completer, privateWithin, coord)
}
val annots = annotFns.map(_(sym.owner))
val annotOwner =
if sym.owner.isClass then newLocalDummy(sym.owner) else sym.owner
val annots = annotFns.map(_(annotOwner))
sym.annotations = annots
if sym.isOpaqueAlias then sym.setFlag(Deferred)
val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased)
Expand Down
42 changes: 13 additions & 29 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ object Typer {
/** An attachment for GADT constraints that were inferred for a pattern. */
val InferredGadtConstraints = new Property.StickyKey[core.GadtConstraint]

/** A context property that indicates the owner of any expressions to be typed in the context
* if that owner is different from the context's owner. Typically, a context with a class
* as owner would have a local dummy as ExprOwner value.
*/
private val ExprOwner = new Property.Key[Symbol]

/** An attachment on a Select node with an `apply` field indicating that the `apply`
* was inserted by the Typer.
*/
Expand Down Expand Up @@ -2234,29 +2228,23 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** The context to be used for an annotation of `mdef`.
* This should be the context enclosing `mdef`, or if `mdef` defines a parameter
* the context enclosing the owner of `mdef`.
* Furthermore, we need to evaluate annotation arguments in an expression context,
* since classes defined in a such arguments should not be entered into the
* enclosing class.
* Furthermore, we need to make sure that annotation trees are evaluated
* with an owner that is not the enclosing class since otherwise locally
* defined symbols would be entered as class members.
*/
def annotContext(mdef: untpd.Tree, sym: Symbol)(using Context): Context = {
def annotContext(mdef: untpd.Tree, sym: Symbol)(using Context): Context =
def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner
val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next()
var adjusted = outer.property(ExprOwner) match {
case Some(exprOwner) if outer.owner.isClass => outer.exprContext(mdef, exprOwner)
case _ => outer
}
def local: FreshContext = outer.fresh.setOwner(newLocalDummy(sym.owner))
sym.owner.infoOrCompleter match
case completer: Namer#Completer if sym.is(Param) =>
val tparams = completer.completerTypeParams(sym)
if tparams.nonEmpty then
// Create a new local context with a dummy owner and a scope containing the
// type parameters of the enclosing method or class. Thus annotations can see
// these type parameters. See i12953.scala for a test case.
val dummyOwner = newLocalDummy(sym.owner)
adjusted = adjusted.fresh.setOwner(dummyOwner).setScope(newScopeWith(tparams*))
case completer: Namer#Completer
if sym.is(Param) && completer.completerTypeParams(sym).nonEmpty =>
// Create a new local context with a dummy owner and a scope containing the
// type parameters of the enclosing method or class. Thus annotations can see
// these type parameters. See i12953.scala for a test case.
local.setScope(newScopeWith(completer.completerTypeParams(sym)*))
case _ =>
adjusted
}
if outer.owner.isClass then local else outer

def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = {
// necessary to force annotation trees to be computed.
Expand Down Expand Up @@ -3101,10 +3089,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case nil =>
(buf.toList, ctx)
}
val localCtx = {
val exprOwnerOpt = if (exprOwner == ctx.owner) None else Some(exprOwner)
ctx.withProperty(ExprOwner, exprOwnerOpt)
}
def finalize(stat: Tree)(using Context): Tree = stat match {
case stat: TypeDef if stat.symbol.is(Module) =>
val enumContext = enumContexts(stat.symbol.linkedClass)
Expand All @@ -3117,7 +3101,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case _ =>
stat
}
val (stats0, finalCtx) = traverse(stats)(using localCtx)
val (stats0, finalCtx) = traverse(stats)
val stats1 = stats0.mapConserve(finalize)
if ctx.owner == exprOwner then checkNoTargetNameConflict(stats1)
(stats1, finalCtx)
Expand Down
15 changes: 15 additions & 0 deletions tests/pos/i15054.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.annotation.Annotation

class AnAnnotation(function: Int => String) extends Annotation

@AnAnnotation(_.toString)
val a = 1
@AnAnnotation(_.toString.length.toString)
val b = 2

def test =
@AnAnnotation(_.toString)
val a = 1
@AnAnnotation(_.toString.length.toString)
val b = 2
a + b