Skip to content

Commit f14e289

Browse files
authored
Merge pull request #2819 from dotty-staging/workaround-2797
Fix #2797: Don't lift Java annotation arguments out of call
2 parents 8a42e27 + bc2b6c3 commit f14e289

File tree

10 files changed

+71
-29
lines changed

10 files changed

+71
-29
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+9-8
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,23 @@ object desugar {
106106
def valDef(vdef: ValDef)(implicit ctx: Context): Tree = {
107107
val ValDef(name, tpt, rhs) = vdef
108108
val mods = vdef.mods
109-
def setterNeeded =
109+
val setterNeeded =
110110
(mods is Mutable) && ctx.owner.isClass && (!(mods is PrivateLocal) || (ctx.owner is Trait))
111111
if (setterNeeded) {
112-
// todo: copy of vdef as getter needed?
113-
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos ?
112+
// TODO: copy of vdef as getter needed?
113+
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos?
114114
// right now vdef maps via expandedTree to a thicket which concerns itself.
115115
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
116116
val setterParam = makeSyntheticParameter(tpt = (new SetterParamTree).watching(vdef))
117+
// The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase)
117118
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
118119
val setter = cpy.DefDef(vdef)(
119-
name = name.setterName,
120-
tparams = Nil,
120+
name = name.setterName,
121+
tparams = Nil,
121122
vparamss = (setterParam :: Nil) :: Nil,
122-
tpt = TypeTree(defn.UnitType),
123-
rhs = setterRhs
124-
).withMods((mods | Accessor) &~ CaseAccessor) // rhs gets filled in later, when field is generated and getter has parameters
123+
tpt = TypeTree(defn.UnitType),
124+
rhs = setterRhs
125+
).withMods((mods | Accessor) &~ CaseAccessor)
125126
Thicket(vdef, setter)
126127
}
127128
else vdef

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ object SymDenotations {
277277
}
278278

279279
/** Update the annotations of this denotation */
280-
private[core] final def annotations_=(annots: List[Annotation]): Unit =
280+
final def annotations_=(annots: List[Annotation]): Unit =
281281
myAnnotations = annots
282282

283283
/** Does this denotation have an annotation matching the given class symbol? */

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ class ClassfileParser(
637637
val constr = ctx.newSymbol(
638638
owner = classRoot.symbol,
639639
name = nme.CONSTRUCTOR,
640-
flags = Flags.Synthetic,
640+
flags = Flags.Synthetic | Flags.JavaDefined,
641641
info = constrType
642642
).entered
643643
for ((attr, i) <- attrs.zipWithIndex)

compiler/src/dotty/tools/dotc/transform/Memoize.scala

+30-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import DenotTransformers._
66
import Phases.Phase
77
import Contexts.Context
88
import SymDenotations.SymDenotation
9+
import Denotations._
910
import Types._
1011
import Symbols._
1112
import SymUtils._
@@ -50,7 +51,7 @@ import Decorators._
5051
if !ddef.symbol.is(Deferred) &&
5152
!ddef.symbol.isConstructor && // constructors bodies are added later at phase Constructors
5253
ddef.rhs == EmptyTree =>
53-
errorLackImplementation(ddef)
54+
errorLackImplementation(ddef)
5455
case tdef: TypeDef
5556
if tdef.symbol.isClass && !tdef.symbol.is(Deferred) && tdef.rhs == EmptyTree =>
5657
errorLackImplementation(tdef)
@@ -76,24 +77,33 @@ import Decorators._
7677

7778
ctx.newSymbol(
7879
owner = ctx.owner,
79-
name = sym.name.asTermName.fieldName,
80+
name = sym.name.asTermName.fieldName,
8081
flags = Private | (if (sym is Stable) EmptyFlags else Mutable),
81-
info = fieldType,
82-
coord = tree.pos)
83-
.withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
84-
.enteredAfter(thisTransform)
82+
info = fieldType,
83+
coord = tree.pos
84+
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
85+
.enteredAfter(thisTransform)
8586
}
8687

87-
/** Can be used to filter annotations on getters and setters; not used yet */
88-
def keepAnnotations(denot: SymDenotation, meta: ClassSymbol) = {
89-
val cpy = sym.copySymDenotation()
90-
cpy.filterAnnotations(_.symbol.derivesFrom(meta))
91-
if (cpy.annotations ne denot.annotations) cpy.installAfter(thisTransform)
92-
}
88+
def addAnnotations(denot: Denotation): Unit =
89+
denot match {
90+
case fieldDenot: SymDenotation if sym.annotations.nonEmpty =>
91+
val cpy = fieldDenot.copySymDenotation()
92+
cpy.annotations = sym.annotations
93+
cpy.installAfter(thisTransform)
94+
case _ => ()
95+
}
96+
97+
def removeAnnotations(denot: SymDenotation): Unit =
98+
if (sym.annotations.nonEmpty) {
99+
val cpy = sym.copySymDenotation()
100+
cpy.annotations = Nil
101+
cpy.installAfter(thisTransform)
102+
}
93103

94104
lazy val field = sym.field.orElse(newField).asTerm
95105

96-
def adaptToField(tree: Tree) =
106+
def adaptToField(tree: Tree): Tree =
97107
if (tree.isEmpty) tree else tree.ensureConforms(field.info.widen)
98108

99109
val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline)
@@ -125,14 +135,18 @@ import Decorators._
125135
if (isErasableBottomField(rhsClass)) erasedBottomTree(rhsClass)
126136
else transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)
127137
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
138+
addAnnotations(fieldDef.denot)
139+
removeAnnotations(sym)
128140
Thicket(fieldDef, getterDef)
129141
} else if (sym.isSetter) {
130-
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
131-
field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
142+
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // This is intended as an assertion
143+
field.setFlag(Mutable) // Necessary for vals mixed in from Scala2 traits
132144
if (isErasableBottomField(tree.vparamss.head.head.tpt.tpe.classSymbol)) tree
133145
else {
134146
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
135-
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
147+
val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
148+
removeAnnotations(sym)
149+
setterDef
136150
}
137151
}
138152
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
395395
}
396396
}
397397

398+
/** Is `sym` a constructor of a Java-defined annotation? */
399+
def isJavaAnnotConstr(sym: Symbol) =
400+
sym.is(JavaDefined) && sym.isConstructor && sym.owner.derivesFrom(defn.AnnotationClass)
401+
398402
/** Match re-ordered arguments against formal parameters
399403
* @param n The position of the first parameter in formals in `methType`.
400404
*/
@@ -420,7 +424,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
420424
}
421425

422426
def tryDefault(n: Int, args1: List[Arg]): Unit = {
423-
liftFun()
427+
if (!isJavaAnnotConstr(methRef.symbol)) liftFun()
424428
val getter = findDefaultGetter(n + numArgs(normalizedFun))
425429
if (getter.isEmpty) missingArg(n)
426430
else {
@@ -607,7 +611,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
607611
val app1 =
608612
if (!success) app0.withType(UnspecifiedErrorType)
609613
else {
610-
if (!sameSeq(args, orderedArgs)) {
614+
if (!sameSeq(args, orderedArgs) && !isJavaAnnotConstr(methRef.symbol)) {
611615
// need to lift arguments to maintain evaluation order in the
612616
// presence of argument reorderings.
613617
liftFun()

docs/docs/reference/dropped/weak-conformance.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: Dropped: Weak Conformance
66
In some situations, Scala used a _weak conformance_ relation when
77
testing type compatibility or computing the least upper bound of a set
88
of types. The principal motivation behind weak conformance was to
9-
make as expression like this have type `List[Double]`:
9+
make an expression like this have type `List[Double]`:
1010

1111
List(1.0, math.sqrt(3.0), 0, -3.3) // : List[Double]
1212

@@ -37,6 +37,7 @@ assigning a type to a constant expression. The new rule is:
3737
- the alternatives of an if-then-else or match expression, or
3838
- the body and catch results of a try expression,
3939

40+
4041
and all expressions have primitive numeric types, but they do not
4142
all have the same type, then the following is attempted: Every
4243
constant expression `E` in `Es` is widened to the least primitive

tests/pending/pos/i2797/Fork_1.java

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Test is pending because we have no good way to test it.
2+
// We need to: Compile Fork.java, and then compile Test.scala
3+
// with Fork.class on the classpath.
4+
public @interface Fork_1 {
5+
int value() default -1;
6+
String[] jvmArgs() default { "nope" };
7+
}

tests/pending/pos/i2797/Test_2.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Test is pending because we have no good way to test it.
2+
// We need to: Compile Fork.java, and then compile Test.scala
3+
// with Fork.class on the classpath.
4+
@Fork_1(jvmArgs = Array("I'm", "hot"))
5+
class Test

tests/pos/i2797a/Fork.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Test is pending because we have no good way to test it.
2+
// We need to: Compile Fork.java, and then compile Test.scala
3+
// with Fork.class on the classpath.
4+
class Fork(value: Int = -1, jvmArgs: Array[String] = Array("nope"))
5+
extends annotation.Annotation

tests/pos/i2797a/Test.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Test is pending because we have no good way to test it.
2+
// We need to: Compile Fork.java, and then compile Test.scala
3+
// with Fork.class on the classpath.
4+
@Fork(jvmArgs = Array("I'm", "hot"))
5+
class Test

0 commit comments

Comments
 (0)