Skip to content

Commit 85cd1cf

Browse files
committed
New scheme for handling defaults of Java annotations
Previously, we made up default getters that were then recognized by the backend, this has at least two downsides: - Creating the default getters requires forcing the info of all the methods in the annotation class, this might lead to cycles. - This scheme leaks into Tasty, and therefore becomes part of the Tasty specification, this is inelegant at best. In the new scheme, no default getters are created, instead we special-case the typing of annotation constructors: when an argument for the annotation constructor is missing, we replace it with a typed wildcard Ident if the corresponding parameter is known to have a default value. The Tasty spec and the backend need to be aware of this usage of wildcard, but that's easier to specify and to implement than default getters.
1 parent 4ab86db commit 85cd1cf

File tree

4 files changed

+44
-49
lines changed

4 files changed

+44
-49
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

+8-9
Original file line numberDiff line numberDiff line change
@@ -255,16 +255,15 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
255255
}
256256
case t: TypeApply if (t.fun.symbol == Predef_classOf) =>
257257
av.visit(name, t.args.head.tpe.classSymbol.denot.info.toTypeKind(bcodeStore)(innerClasesStore).toASMType)
258+
case Ident(nme.WILDCARD) =>
259+
// An underscore argument indicates that we want to use the default value for this parameter, so do not emit anything
258260
case t: tpd.RefTree =>
259-
if (t.symbol.denot.owner.isAllOf(Flags.JavaEnumTrait)) {
260-
val edesc = innerClasesStore.typeDescriptor(t.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class.
261-
val evalue = t.symbol.name.mangledString // value the actual enumeration value.
262-
av.visitEnum(name, edesc, evalue)
263-
} else {
264-
// println(i"not an enum: ${t.symbol} / ${t.symbol.denot.owner} / ${t.symbol.denot.owner.isTerm} / ${t.symbol.denot.owner.flagsString}")
265-
assert(toDenot(t.symbol).name.is(DefaultGetterName),
266-
s"${toDenot(t.symbol).name.debugString}") // this should be default getter. do not emit.
267-
}
261+
assert(t.symbol.denot.owner.isAllOf(Flags.JavaEnumTrait),
262+
i"not an enum: $t / ${t.symbol} / ${t.symbol.denot.owner} / ${t.symbol.denot.owner.isTerm} / ${t.symbol.denot.owner.flagsString}")
263+
264+
val edesc = innerClasesStore.typeDescriptor(t.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class.
265+
val evalue = t.symbol.name.mangledString // value the actual enumeration value.
266+
av.visitEnum(name, edesc, evalue)
268267
case t: SeqLiteral =>
269268
val arrAnnotV: AnnotationVisitor = av.visitArray(name)
270269
for (arg <- t.elems) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) }

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

+1-17
Original file line numberDiff line numberDiff line change
@@ -635,21 +635,10 @@ class ClassfileParser(
635635
cook.apply(newType)
636636
}
637637

638-
/** Add synthetic constructor(s) and potentially also default getters which
639-
* reflects the fields of the annotation with given `classInfo`.
640-
* Annotations in Scala are assumed to get all their arguments as constructor
638+
/** Annotations in Scala are assumed to get all their arguments as constructor
641639
* parameters. For Java annotations we need to fake it by making up the constructor.
642-
* Note that default getters have type Nothing. That's OK because we need
643-
* them only to signal that the corresponding parameter is optional.
644640
*/
645641
def addAnnotationConstructor(classInfo: TempClassInfoType)(implicit ctx: Context): Unit = {
646-
def addDefaultGetter(attr: Symbol, n: Int) =
647-
ctx.newSymbol(
648-
owner = moduleRoot.symbol,
649-
name = DefaultGetterName(nme.CONSTRUCTOR, n),
650-
flags = attr.flags & Flags.AccessFlags,
651-
info = defn.NothingType).entered
652-
653642
val attrs = classInfo.decls.toList.filter(_.isTerm)
654643
val paramNames = attrs.map(_.name.asTermName)
655644
val paramTypes = attrs.map(_.info.resultType)
@@ -662,11 +651,6 @@ class ClassfileParser(
662651
flags = Flags.Synthetic | Flags.JavaDefined | Flags.Method,
663652
info = mtype
664653
).entered
665-
for ((attr, i) <- attrs.zipWithIndex)
666-
if (attr.hasAnnotation(defn.AnnotationDefaultAnnot)) {
667-
constr.setFlag(Flags.DefaultParameterized)
668-
addDefaultGetter(attr, i)
669-
}
670654
}
671655

672656
addConstr(paramTypes)

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

+3-13
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ object JavaParsers {
127127

128128
def makeSyntheticParam(count: Int, tpt: Tree): ValDef =
129129
makeParam(nme.syntheticParamName(count), tpt)
130-
def makeParam(name: TermName, tpt: Tree, defaultValue: Tree = EmptyTree): ValDef =
131-
ValDef(name, tpt, defaultValue).withMods(Modifiers(Flags.JavaDefined | Flags.Param))
130+
def makeParam(name: TermName, tpt: Tree): ValDef =
131+
ValDef(name, tpt, EmptyTree).withMods(Modifiers(Flags.JavaDefined | Flags.Param))
132132

133133
def makeConstructor(formals: List[Tree], tparams: List[TypeDef], flags: FlagSet = Flags.JavaDefined): DefDef = {
134134
val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p) }
@@ -768,17 +768,7 @@ object JavaParsers {
768768
val (statics, body) = typeBody(AT, name, List())
769769
val constructorParams = body.collect {
770770
case dd: DefDef =>
771-
val hasDefault =
772-
dd.mods.annotations.exists {
773-
case Apply(Select(New(Select(_, tpnme.AnnotationDefaultATTR)), nme.CONSTRUCTOR), Nil) =>
774-
true
775-
case _ =>
776-
false
777-
}
778-
// If the annotation has a default value we don't need to parse it, providing
779-
// any value at all is enough to typecheck usages of annotations correctly.
780-
val defaultParam = if (hasDefault) unimplementedExpr else EmptyTree
781-
makeParam(dd.name, dd.tpt, defaultParam)
771+
makeParam(dd.name, dd.tpt)
782772
}
783773
val constr = DefDef(nme.CONSTRUCTOR,
784774
List(), List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined))

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

+32-10
Original file line numberDiff line numberDiff line change
@@ -543,17 +543,39 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
543543
}
544544

545545
def tryDefault(n: Int, args1: List[Arg]): Unit = {
546-
val getter =
547-
// `methRef.symbol` doesn't exist for structural calls
548-
if (methRef.symbol.exists) findDefaultGetter(n + numArgs(normalizedFun))
549-
else EmptyTree
550-
if (getter.isEmpty) missingArg(n)
551-
else {
552-
val substParam = addTyped(
553-
treeToArg(spliceMeth(getter.withSpan(normalizedFun.span), normalizedFun)),
554-
formal)
546+
val sym = methRef.symbol
547+
548+
val defaultExpr =
549+
if (isJavaAnnotConstr(sym)) {
550+
val cinfo = sym.owner.asClass.classInfo
551+
val pname = methodType.paramNames(n)
552+
val hasDefault = cinfo.member(pname)
553+
.suchThat(d => d.is(Method) && d.hasAnnotation(defn.AnnotationDefaultAnnot)).exists
554+
555+
// Use `_` as a placeholder for the default value of an
556+
// annotation parameter, this will be recognized by the backend.
557+
if (hasDefault)
558+
tpd.Underscore(formal)
559+
else
560+
EmptyTree
561+
} else {
562+
val getter =
563+
if (sym.exists) // `sym` doesn't exist for structural calls
564+
findDefaultGetter(n + numArgs(normalizedFun))
565+
else
566+
EmptyTree
567+
568+
if (!getter.isEmpty)
569+
spliceMeth(getter.withSpan(normalizedFun.span), normalizedFun)
570+
else
571+
EmptyTree
572+
}
573+
574+
if (!defaultExpr.isEmpty) {
575+
val substParam = addTyped(treeToArg(defaultExpr), formal)
555576
matchArgs(args1, formals1.mapconserve(substParam), n + 1)
556-
}
577+
} else
578+
missingArg(n)
557579
}
558580

559581
if (formal.isRepeatedParam)

0 commit comments

Comments
 (0)