Skip to content

Commit b3fac38

Browse files
authored
Merge pull request #6978 from dotty-staging/fix-annot-constructor-cycle
Improve handling of Java annotations, avoid cycles
2 parents b25d17c + 0ea949a commit b3fac38

File tree

6 files changed

+90
-92
lines changed

6 files changed

+90
-92
lines changed

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

Lines changed: 8 additions & 9 deletions
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

Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ class ClassfileParser(
166166
for (i <- 0 until in.nextChar) parseMember(method = false)
167167
for (i <- 0 until in.nextChar) parseMember(method = true)
168168
classInfo = parseAttributes(classRoot.symbol, classInfo)
169-
if (isAnnotation) addAnnotationConstructor(classInfo)
169+
if (isAnnotation)
170+
// classInfo must be a TempClassInfoType and not a TempPolyType,
171+
// because Java annotations cannot have type parameters.
172+
addAnnotationConstructor(classInfo.asInstanceOf[TempClassInfoType])
170173

171174
classRoot.registerCompanion(moduleRoot.symbol)
172175
moduleRoot.registerCompanion(classRoot.symbol)
@@ -632,67 +635,23 @@ class ClassfileParser(
632635
cook.apply(newType)
633636
}
634637

635-
/** Add synthetic constructor(s) and potentially also default getters which
636-
* reflects the fields of the annotation with given `classInfo`.
637-
* 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
638639
* parameters. For Java annotations we need to fake it by making up the constructor.
639-
* Note that default getters have type Nothing. That's OK because we need
640-
* them only to signal that the corresponding parameter is optional.
641640
*/
642-
def addAnnotationConstructor(classInfo: Type, tparams: List[TypeSymbol] = Nil)(implicit ctx: Context): Unit = {
643-
def addDefaultGetter(attr: Symbol, n: Int) =
644-
ctx.newSymbol(
645-
owner = moduleRoot.symbol,
646-
name = DefaultGetterName(nme.CONSTRUCTOR, n),
647-
flags = attr.flags & Flags.AccessFlags,
648-
info = defn.NothingType).entered
649-
650-
classInfo match {
651-
case classInfo @ TempPolyType(tparams, restpe) if tparams.isEmpty =>
652-
addAnnotationConstructor(restpe, tparams)
653-
case classInfo: TempClassInfoType =>
654-
val attrs = classInfo.decls.toList.filter(_.isTerm)
655-
val targs = tparams.map(_.typeRef)
656-
val paramNames = attrs.map(_.name.asTermName)
657-
val paramTypes = attrs.map(_.info.resultType)
658-
659-
def addConstr(ptypes: List[Type]) = {
660-
val mtype = MethodType(paramNames, ptypes, classRoot.typeRef.appliedTo(targs))
661-
val constrType = if (tparams.isEmpty) mtype else TempPolyType(tparams, mtype)
662-
val constr = ctx.newSymbol(
663-
owner = classRoot.symbol,
664-
name = nme.CONSTRUCTOR,
665-
flags = Flags.Synthetic | Flags.JavaDefined | Flags.Method,
666-
info = constrType
667-
).entered
668-
for ((attr, i) <- attrs.zipWithIndex)
669-
if (attr.hasAnnotation(defn.AnnotationDefaultAnnot)) {
670-
constr.setFlag(Flags.DefaultParameterized)
671-
addDefaultGetter(attr, i)
672-
}
673-
}
674-
675-
addConstr(paramTypes)
676-
677-
// The code below added an extra constructor to annotations where the
678-
// last parameter of the constructor is an Array[X] for some X, the
679-
// array was replaced by a vararg argument. Unfortunately this breaks
680-
// inference when doing:
681-
// @Annot(Array())
682-
// The constructor is overloaded so the expected type of `Array()` is
683-
// WildcardType, and the type parameter of the Array apply method gets
684-
// instantiated to `Nothing` instead of `X`.
685-
// I'm leaving this commented out in case we improve inference to make this work.
686-
// Note that if this is reenabled then JavaParser will also need to be modified
687-
// to add the extra constructor (this was not implemented before).
688-
/*
689-
if (paramTypes.nonEmpty)
690-
paramTypes.last match {
691-
case defn.ArrayOf(elemtp) =>
692-
addConstr(paramTypes.init :+ defn.RepeatedParamType.appliedTo(elemtp))
693-
case _ =>
694-
}
695-
*/
641+
def addAnnotationConstructor(classInfo: TempClassInfoType)(implicit ctx: Context): Unit =
642+
ctx.newSymbol(
643+
owner = classRoot.symbol,
644+
name = nme.CONSTRUCTOR,
645+
flags = Flags.Synthetic | Flags.JavaDefined | Flags.Method,
646+
info = new AnnotConstructorCompleter(classInfo)
647+
).entered
648+
649+
class AnnotConstructorCompleter(classInfo: TempClassInfoType) extends LazyType {
650+
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
651+
val attrs = classInfo.decls.toList.filter(sym => sym.isTerm && sym != denot.symbol)
652+
val paramNames = attrs.map(_.name.asTermName)
653+
val paramTypes = attrs.map(_.info.resultType)
654+
denot.info = MethodType(paramNames, paramTypes, classRoot.typeRef)
696655
}
697656
}
698657

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

Lines changed: 3 additions & 13 deletions
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

Lines changed: 32 additions & 10 deletions
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)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public @interface MyJava_1 {
2+
3+
public String value() default "MyJava";
4+
5+
public MyClassTypeA typeA();
6+
7+
public MyClassTypeB typeB() default @MyClassTypeB;
8+
9+
public enum MyClassTypeA {
10+
A, B
11+
}
12+
13+
public @interface MyClassTypeB {}
14+
}
15+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@MyJava_1("MyScala1", typeA = MyJava_1.MyClassTypeA.B)
2+
object MyScala {
3+
def a(mj: MyJava_1): Unit = {
4+
println("MyJava")
5+
}
6+
7+
@MyJava_1(typeA = MyJava_1.MyClassTypeA.A)
8+
def b(): Int = 1
9+
10+
@MyJava_1(value = "MyScala2", typeA = MyJava_1.MyClassTypeA.B)
11+
def c(): Int = 2
12+
}
13+

0 commit comments

Comments
 (0)