Skip to content

Commit 69dc8c1

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 b64b8a9 commit 69dc8c1

File tree

3 files changed

+32
-24
lines changed

3 files changed

+32
-24
lines changed

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,10 @@ object desugar {
452452
else originalTparams
453453
}
454454
else originalTparams
455+
456+
// Annotations on class _type_ parameters are set on the derived parameters
457+
// but not on the constructor parameters. The reverse is true for
458+
// annotations on class _value_ parameters.
455459
val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false))
456460
val constrVparamss =
457461
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
@@ -463,7 +467,18 @@ object desugar {
463467
ctx.error(CaseClassMissingNonImplicitParamList(cdef), namePos)
464468
ListOfNil
465469
}
466-
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = false))
470+
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true))
471+
val derivedTparams = {
472+
val impliedTparamsIt = impliedTparams.iterator
473+
constrTparams.map(tparam => derivedTypeParam(tparam)
474+
.withAnnotations(impliedTparamsIt.next().mods.annotations))
475+
}
476+
val derivedVparamss = {
477+
val constrVparamsIt = constrVparamss.iterator.flatten
478+
constrVparamss.nestedMap(vparam => derivedTermParam(vparam)
479+
.withAnnotations(Nil))
480+
}
481+
467482
val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss)
468483

469484
val (normalizedBody, enumCases, enumCompanionRef) = {
@@ -499,19 +514,6 @@ object desugar {
499514

500515
def anyRef = ref(defn.AnyRefAlias.typeRef)
501516

502-
// Annotations are dropped from the constructor parameters but should be
503-
// preserved in all derived parameters.
504-
val derivedTparams = {
505-
val impliedTparamsIt = impliedTparams.iterator
506-
constrTparams.map(tparam => derivedTypeParam(tparam)
507-
.withAnnotations(impliedTparamsIt.next().mods.annotations))
508-
}
509-
val derivedVparamss = {
510-
val constrVparamsIt = constrVparamss.iterator.flatten
511-
constrVparamss.nestedMap(vparam => derivedTermParam(vparam)
512-
.withAnnotations(constrVparamsIt.next().mods.annotations))
513-
}
514-
515517
val arity = constrVparamss.head.length
516518

517519
val classTycon: Tree = TypeRefTree() // watching is set at end of method
@@ -800,8 +802,10 @@ object desugar {
800802
val originalVparamsIt = originalVparamss.iterator.flatten
801803
derivedVparamss match {
802804
case first :: rest =>
803-
first.map(_.withMods(originalVparamsIt.next().mods | caseAccessor)) ++
804-
rest.flatten.map(_.withMods(originalVparamsIt.next().mods))
805+
// Annotations on the class _value_ parameters are not set on the parameter accessors
806+
def mods(vdef: ValDef) = vdef.mods.withAnnotations(Nil)
807+
first.map(_.withMods(mods(originalVparamsIt.next()) | caseAccessor)) ++
808+
rest.flatten.map(_.withMods(mods(originalVparamsIt.next())))
805809
case _ =>
806810
Nil
807811
}

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.TransientParamAnnot))
231+
val param = acc.subst(accessors, paramSyms)
232+
if (param.hasAnnotation(defn.TransientParamAnnot))
232233
ctx.error(em"transient parameter $acc is retained as field in class ${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 {

0 commit comments

Comments
 (0)