Skip to content

Commit 578b2bc

Browse files
committed
Fix #8531: Annnotations on class value parameters go to the constructor
Mimic the behavior of Scala 2: an annotation on a class value parameters (but not on a type parameter) is moved to the constructor parameter and not to any of the derived parameters. This required adapting the semanticdb extractor to go look for annotations in constructor parameters too.
1 parent cc09bb3 commit 578b2bc

File tree

5 files changed

+42
-19
lines changed

5 files changed

+42
-19
lines changed

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ object desugar {
433433
else originalTparams
434434
}
435435
else originalTparams
436+
437+
// Annotations on class _type_ parameters are set on the derived parameters
438+
// but not on the constructor parameters. The reverse is true for
439+
// annotations on class _value_ parameters.
436440
val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false))
437441
val constrVparamss =
438442
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
@@ -444,7 +448,14 @@ object desugar {
444448
ctx.error(CaseClassMissingNonImplicitParamList(cdef), namePos)
445449
ListOfNil
446450
}
447-
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = false, keepDefault = true))
451+
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true, keepDefault = true))
452+
val derivedTparams =
453+
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
454+
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
455+
val derivedVparamss =
456+
constrVparamss.nestedMap(vparam =>
457+
derivedTermParam(vparam).withAnnotations(Nil))
458+
448459
val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss)
449460

450461
val (normalizedBody, enumCases, enumCompanionRef) = {
@@ -480,14 +491,6 @@ object desugar {
480491

481492
def anyRef = ref(defn.AnyRefAlias.typeRef)
482493

483-
// Annotations are dropped from the constructor parameters but should be
484-
// preserved in all derived parameters.
485-
val derivedTparams =
486-
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
487-
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
488-
val derivedVparamss =
489-
constrVparamss.nestedMap(vparam => derivedTermParam(vparam))
490-
491494
val arity = constrVparamss.head.length
492495

493496
val classTycon: Tree = TypeRefTree() // watching is set at end of method
@@ -779,8 +782,10 @@ object desugar {
779782
val originalVparamsIt = originalVparamss.iterator.flatten
780783
derivedVparamss match {
781784
case first :: rest =>
782-
first.map(_.withMods(originalVparamsIt.next().mods | caseAccessor)) ++
783-
rest.flatten.map(_.withMods(originalVparamsIt.next().mods))
785+
// Annotations on the class _value_ parameters are not set on the parameter accessors
786+
def mods(vdef: ValDef) = vdef.mods.withAnnotations(Nil)
787+
first.map(_.withMods(mods(originalVparamsIt.next()) | caseAccessor)) ++
788+
rest.flatten.map(_.withMods(mods(originalVparamsIt.next())))
784789
case _ =>
785790
Nil
786791
}

compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ class ExtractSemanticDB extends Phase:
104104
|| sym == defn.Any_typeCast
105105
|| qualifier.exists(excludeQual)
106106

107+
private def traverseAnnotsOf(sym: Symbol)(using Context): Unit =
108+
for annot <- sym.annotations do
109+
if annot.tree.span.exists
110+
&& annot.tree.span.hasLength
111+
annot.tree match
112+
case tree: Typed => () // hack for inline code
113+
case tree => traverse(tree)
114+
107115
override def traverse(tree: Tree)(using Context): Unit =
108116

109117
inline def traverseCtorParamTpt(ctorSym: Symbol, tpt: Tree): Unit =
@@ -115,12 +123,7 @@ class ExtractSemanticDB extends Phase:
115123
else
116124
traverse(tpt)
117125

118-
for annot <- tree.symbol.annotations do
119-
if annot.tree.span.exists
120-
&& annot.tree.span.hasLength
121-
annot.tree match
122-
case tree: Typed => () // hack for inline code
123-
case tree => traverse(tree)
126+
traverseAnnotsOf(tree.symbol)
124127

125128
tree match
126129
case tree: PackageDef =>
@@ -563,6 +566,7 @@ class ExtractSemanticDB extends Phase:
563566
vparams <- vparamss
564567
vparam <- vparams
565568
do
569+
traverseAnnotsOf(vparam.symbol)
566570
if !excludeSymbol(vparam.symbol)
567571
val symkinds =
568572
getters.get(vparam.name).fold(SymbolKind.emptySet)(getter =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,12 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
228228
Nil
229229
}
230230
else {
231-
if (acc.hasAnnotation(defn.ConstructorOnlyAnnot))
231+
val param = acc.subst(accessors, paramSyms)
232+
if (param.hasAnnotation(defn.ConstructorOnlyAnnot))
232233
ctx.error(em"${acc.name} is marked `@constructorOnly` but it is retained as a field in ${acc.owner}", acc.sourcePos)
233234
val target = if (acc.is(Method)) acc.field else acc
234235
if (!target.exists) Nil // this case arises when the parameter accessor is an alias
235236
else {
236-
val param = acc.subst(accessors, paramSyms)
237237
val assigns = Assign(ref(target), ref(param)).withSpan(tree.span) :: Nil
238238
if (acc.name != nme.OUTER) assigns
239239
else {

tests/run/i8531/Named.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface Named {}

tests/run/i8531/Test.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Foo(@Named s: String)
2+
3+
object Test {
4+
def main(args: Array[String]): Unit = {
5+
val ctor = classOf[Foo].getDeclaredConstructors()(0)
6+
val annots = ctor.getParameterAnnotations()(0)
7+
assert(annots.length == 1, annots.length)
8+
}
9+
}

0 commit comments

Comments
 (0)