From cc09bb3a86bc9d49183e8f18306b71a6636fd2a9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 12 Mar 2020 22:35:41 +0100 Subject: [PATCH 1/3] @constructorOnly: better error message This annotation used to be called @transientParam but that's no longer the case. --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/transform/Constructors.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 257de6c411f5..547d01ae3200 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -804,7 +804,7 @@ class Definitions { @tu lazy val TASTYLongSignatureAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.TASTYLongSignature") @tu lazy val TailrecAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.tailrec") @tu lazy val ThreadUnsafeAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.threadUnsafe") - @tu lazy val TransientParamAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.constructorOnly") + @tu lazy val ConstructorOnlyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.constructorOnly") @tu lazy val CompileTimeOnlyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.compileTimeOnly") @tu lazy val SwitchAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.switch") @tu lazy val ThrowsAnnot: ClassSymbol = ctx.requiredClass("scala.throws") diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index 37343dfb658a..a58d5c05b3ae 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -228,8 +228,8 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = Nil } else { - if (acc.hasAnnotation(defn.TransientParamAnnot)) - ctx.error(em"transient parameter $acc is retained as field in class ${acc.owner}", acc.sourcePos) + if (acc.hasAnnotation(defn.ConstructorOnlyAnnot)) + ctx.error(em"${acc.name} is marked `@constructorOnly` but it is retained as a field in ${acc.owner}", acc.sourcePos) val target = if (acc.is(Method)) acc.field else acc if (!target.exists) Nil // this case arises when the parameter accessor is an alias else { From 578b2bc721714bfd7d1941a9845dbcfd7e5ce151 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 12 Mar 2020 20:18:03 +0100 Subject: [PATCH 2/3] 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. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 27 +++++++++++-------- .../dotc/semanticdb/ExtractSemanticDB.scala | 16 ++++++----- .../tools/dotc/transform/Constructors.scala | 4 +-- tests/run/i8531/Named.java | 5 ++++ tests/run/i8531/Test.scala | 9 +++++++ 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 tests/run/i8531/Named.java create mode 100644 tests/run/i8531/Test.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 9608c4df1fb5..5a16329c43ba 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -433,6 +433,10 @@ object desugar { else originalTparams } else originalTparams + + // Annotations on class _type_ parameters are set on the derived parameters + // but not on the constructor parameters. The reverse is true for + // annotations on class _value_ parameters. val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false)) val constrVparamss = if (originalVparamss.isEmpty) { // ensure parameter list is non-empty @@ -444,7 +448,14 @@ object desugar { ctx.error(CaseClassMissingNonImplicitParamList(cdef), namePos) ListOfNil } - else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = false, keepDefault = true)) + else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true, keepDefault = true)) + val derivedTparams = + constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) => + derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations)) + val derivedVparamss = + constrVparamss.nestedMap(vparam => + derivedTermParam(vparam).withAnnotations(Nil)) + val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss) val (normalizedBody, enumCases, enumCompanionRef) = { @@ -480,14 +491,6 @@ object desugar { def anyRef = ref(defn.AnyRefAlias.typeRef) - // Annotations are dropped from the constructor parameters but should be - // preserved in all derived parameters. - val derivedTparams = - constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) => - derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations)) - val derivedVparamss = - constrVparamss.nestedMap(vparam => derivedTermParam(vparam)) - val arity = constrVparamss.head.length val classTycon: Tree = TypeRefTree() // watching is set at end of method @@ -779,8 +782,10 @@ object desugar { val originalVparamsIt = originalVparamss.iterator.flatten derivedVparamss match { case first :: rest => - first.map(_.withMods(originalVparamsIt.next().mods | caseAccessor)) ++ - rest.flatten.map(_.withMods(originalVparamsIt.next().mods)) + // Annotations on the class _value_ parameters are not set on the parameter accessors + def mods(vdef: ValDef) = vdef.mods.withAnnotations(Nil) + first.map(_.withMods(mods(originalVparamsIt.next()) | caseAccessor)) ++ + rest.flatten.map(_.withMods(mods(originalVparamsIt.next()))) case _ => Nil } diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 9435a5f487eb..c4c11e6b6f05 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -104,6 +104,14 @@ class ExtractSemanticDB extends Phase: || sym == defn.Any_typeCast || qualifier.exists(excludeQual) + private def traverseAnnotsOf(sym: Symbol)(using Context): Unit = + for annot <- sym.annotations do + if annot.tree.span.exists + && annot.tree.span.hasLength + annot.tree match + case tree: Typed => () // hack for inline code + case tree => traverse(tree) + override def traverse(tree: Tree)(using Context): Unit = inline def traverseCtorParamTpt(ctorSym: Symbol, tpt: Tree): Unit = @@ -115,12 +123,7 @@ class ExtractSemanticDB extends Phase: else traverse(tpt) - for annot <- tree.symbol.annotations do - if annot.tree.span.exists - && annot.tree.span.hasLength - annot.tree match - case tree: Typed => () // hack for inline code - case tree => traverse(tree) + traverseAnnotsOf(tree.symbol) tree match case tree: PackageDef => @@ -563,6 +566,7 @@ class ExtractSemanticDB extends Phase: vparams <- vparamss vparam <- vparams do + traverseAnnotsOf(vparam.symbol) if !excludeSymbol(vparam.symbol) val symkinds = getters.get(vparam.name).fold(SymbolKind.emptySet)(getter => diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index a58d5c05b3ae..5e63f93e5d10 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -228,12 +228,12 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = Nil } else { - if (acc.hasAnnotation(defn.ConstructorOnlyAnnot)) + val param = acc.subst(accessors, paramSyms) + if (param.hasAnnotation(defn.ConstructorOnlyAnnot)) ctx.error(em"${acc.name} is marked `@constructorOnly` but it is retained as a field in ${acc.owner}", acc.sourcePos) val target = if (acc.is(Method)) acc.field else acc if (!target.exists) Nil // this case arises when the parameter accessor is an alias else { - val param = acc.subst(accessors, paramSyms) val assigns = Assign(ref(target), ref(param)).withSpan(tree.span) :: Nil if (acc.name != nme.OUTER) assigns else { diff --git a/tests/run/i8531/Named.java b/tests/run/i8531/Named.java new file mode 100644 index 000000000000..bc8198081c00 --- /dev/null +++ b/tests/run/i8531/Named.java @@ -0,0 +1,5 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Named {} diff --git a/tests/run/i8531/Test.scala b/tests/run/i8531/Test.scala new file mode 100644 index 000000000000..918f6f2a41d2 --- /dev/null +++ b/tests/run/i8531/Test.scala @@ -0,0 +1,9 @@ +class Foo(@Named s: String) + +object Test { + def main(args: Array[String]): Unit = { + val ctor = classOf[Foo].getDeclaredConstructors()(0) + val annots = ctor.getParameterAnnotations()(0) + assert(annots.length == 1, annots.length) + } +} From f5e2f1b61b8078e38b67c59088dcf2ed87998fd9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 4 Apr 2020 19:11:02 +0200 Subject: [PATCH 3/3] Update shapeless after annotation change in previous commit Also added `primaryConstructor` to the reflection API for convenience. --- community-build/community-projects/shapeless | 2 +- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- .../dotc/tastyreflect/ReflectionCompilerInterface.scala | 3 +++ library/src/scala/tasty/Reflection.scala | 6 +++++- library/src/scala/tasty/reflect/CompilerInterface.scala | 5 ++++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/community-build/community-projects/shapeless b/community-build/community-projects/shapeless index df2e15a0282c..d41323cd3f86 160000 --- a/community-build/community-projects/shapeless +++ b/community-build/community-projects/shapeless @@ -1 +1 @@ -Subproject commit df2e15a0282c1de6adf21ff5a8c5fec041b9ecdd +Subproject commit d41323cd3f86d8f72ab0444cfe4efaa1a1ea0ab9 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 4046b6f0258e..4dbf869a1db7 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -387,7 +387,7 @@ object SymDenotations { final def setParamssFromDefs(tparams: List[TypeDef[?]], vparamss: List[List[ValDef[?]]])(using Context): Unit = setParamss(tparams.map(_.symbol), vparamss.map(_.map(_.symbol))) - /** A pair consistsing of type paremeter symbols and value parameter symbol lists + /** A pair consisting of type parameter symbols and value parameter symbol lists * of this method definition, or (Nil, Nil) for other symbols. * Makes use of `rawParamss` when present, or constructs fresh parameter symbols otherwise. * This method can be allocation-heavy. diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index 6098d82931ec..af47f6904ecd 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -1752,6 +1752,9 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Symbol_paramSymss(self: Symbol)(using ctx: Context): (List[Symbol], List[List[Symbol]]) = self.paramSymss + def Symbol_primaryConstructor(self: Symbol)(using Context): Symbol = + self.primaryConstructor + def Symbol_caseFields(self: Symbol)(using ctx: Context): List[Symbol] = if (!self.isClass) Nil else self.asClass.paramAccessors.collect { diff --git a/library/src/scala/tasty/Reflection.scala b/library/src/scala/tasty/Reflection.scala index 39726418f8ed..b5e83594c83b 100644 --- a/library/src/scala/tasty/Reflection.scala +++ b/library/src/scala/tasty/Reflection.scala @@ -2223,12 +2223,16 @@ class Reflection(private[scala] val internal: CompilerInterface) { self => def methods(using ctx: Context): List[Symbol] = internal.Symbol_methods(sym) - /** A pair consistsing of type paremeter symbols and value parameter symbol lists + /** A pair consisting of type parameter symbols and value parameter symbol lists * of this method definition, or (Nil, Nil) for other symbols. */ def paramSymss(using ctx: Context): (List[Symbol], List[List[Symbol]]) = internal.Symbol_paramSymss(sym) + /** The primary constructor of a class or trait, `noSymbol` if not applicable. */ + def primaryConstructor(using Context): Symbol = + internal.Symbol_primaryConstructor(sym) + /** Fields of a case class type -- only the ones declared in primary constructor */ def caseFields(using ctx: Context): List[Symbol] = internal.Symbol_caseFields(sym) diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index a97366711604..4ccf15c8866e 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -1305,11 +1305,14 @@ trait CompilerInterface { /** Get all non-private methods declared or inherited */ def Symbol_methods(self: Symbol)(using ctx: Context): List[Symbol] - /** A pair consistsing of type paremeter symbols and value parameter symbol lists + /** A pair consisting of type parameter symbols and value parameter symbol lists * of this method definition, or (Nil, Nil) for other symbols. */ def Symbol_paramSymss(self: Symbol)(using ctx: Context): (List[Symbol], List[List[Symbol]]) + /** The primary constructor of a class or trait, `noSymbol` if not applicable. */ + def Symbol_primaryConstructor(self: Symbol)(using Context): Symbol + /** Fields of a case class type -- only the ones declared in primary constructor */ def Symbol_caseFields(self: Symbol)(using ctx: Context): List[Symbol]