Skip to content

Commit eff5496

Browse files
authored
Merge pull request #8534 from dotty-staging/constr-param-annot
Fix #8531: Annnotations on class value parameters go to the constructor
2 parents 4b27dc1 + f5e2f1b commit eff5496

File tree

11 files changed

+58
-25
lines changed

11 files changed

+58
-25
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/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ class Definitions {
805805
@tu lazy val TASTYLongSignatureAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.TASTYLongSignature")
806806
@tu lazy val TailrecAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.tailrec")
807807
@tu lazy val ThreadUnsafeAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.threadUnsafe")
808-
@tu lazy val TransientParamAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.constructorOnly")
808+
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.constructorOnly")
809809
@tu lazy val CompileTimeOnlyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.compileTimeOnly")
810810
@tu lazy val SwitchAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.switch")
811811
@tu lazy val ThrowsAnnot: ClassSymbol = ctx.requiredClass("scala.throws")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ object SymDenotations {
386386
final def setParamssFromDefs(tparams: List[TypeDef[?]], vparamss: List[List[ValDef[?]]])(using Context): Unit =
387387
setParamss(tparams.map(_.symbol), vparamss.map(_.map(_.symbol)))
388388

389-
/** A pair consistsing of type paremeter symbols and value parameter symbol lists
389+
/** A pair consisting of type parameter symbols and value parameter symbol lists
390390
* of this method definition, or (Nil, Nil) for other symbols.
391391
* Makes use of `rawParamss` when present, or constructs fresh parameter symbols otherwise.
392392
* This method can be allocation-heavy.

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/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,9 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
17521752
def Symbol_paramSymss(self: Symbol)(using ctx: Context): (List[Symbol], List[List[Symbol]]) =
17531753
self.paramSymss
17541754

1755+
def Symbol_primaryConstructor(self: Symbol)(using Context): Symbol =
1756+
self.primaryConstructor
1757+
17551758
def Symbol_caseFields(self: Symbol)(using ctx: Context): List[Symbol] =
17561759
if (!self.isClass) Nil
17571760
else self.asClass.paramAccessors.collect {

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

Lines changed: 3 additions & 3 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))
232-
ctx.error(em"transient parameter $acc is retained as field in class ${acc.owner}", acc.sourcePos)
231+
val param = acc.subst(accessors, paramSyms)
232+
if (param.hasAnnotation(defn.ConstructorOnlyAnnot))
233+
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 {

library/src/scala/tasty/Reflection.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2223,12 +2223,16 @@ class Reflection(private[scala] val internal: CompilerInterface) { self =>
22232223
def methods(using ctx: Context): List[Symbol] =
22242224
internal.Symbol_methods(sym)
22252225

2226-
/** A pair consistsing of type paremeter symbols and value parameter symbol lists
2226+
/** A pair consisting of type parameter symbols and value parameter symbol lists
22272227
* of this method definition, or (Nil, Nil) for other symbols.
22282228
*/
22292229
def paramSymss(using ctx: Context): (List[Symbol], List[List[Symbol]]) =
22302230
internal.Symbol_paramSymss(sym)
22312231

2232+
/** The primary constructor of a class or trait, `noSymbol` if not applicable. */
2233+
def primaryConstructor(using Context): Symbol =
2234+
internal.Symbol_primaryConstructor(sym)
2235+
22322236
/** Fields of a case class type -- only the ones declared in primary constructor */
22332237
def caseFields(using ctx: Context): List[Symbol] =
22342238
internal.Symbol_caseFields(sym)

library/src/scala/tasty/reflect/CompilerInterface.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1305,11 +1305,14 @@ trait CompilerInterface {
13051305
/** Get all non-private methods declared or inherited */
13061306
def Symbol_methods(self: Symbol)(using ctx: Context): List[Symbol]
13071307

1308-
/** A pair consistsing of type paremeter symbols and value parameter symbol lists
1308+
/** A pair consisting of type parameter symbols and value parameter symbol lists
13091309
* of this method definition, or (Nil, Nil) for other symbols.
13101310
*/
13111311
def Symbol_paramSymss(self: Symbol)(using ctx: Context): (List[Symbol], List[List[Symbol]])
13121312

1313+
/** The primary constructor of a class or trait, `noSymbol` if not applicable. */
1314+
def Symbol_primaryConstructor(self: Symbol)(using Context): Symbol
1315+
13131316
/** Fields of a case class type -- only the ones declared in primary constructor */
13141317
def Symbol_caseFields(self: Symbol)(using ctx: Context): List[Symbol]
13151318

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)