Skip to content

Commit 4581490

Browse files
authored
Merge pull request #8360 from dotty-staging/fix-array-annot
Fix handling of arrays in java annotation arguments
2 parents ee19855 + c41f1d4 commit 4581490

File tree

14 files changed

+117
-65
lines changed

14 files changed

+117
-65
lines changed

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package ast
44

55
import core._
66
import Types._, Names._, NameOps._, Flags._, util.Spans._, Contexts._, Constants._
7+
import typer.ProtoTypes
78
import SymDenotations._, Symbols._, Denotations._, StdNames._, Comments._
89
import language.higherKinds
910
import collection.mutable.ListBuffer
@@ -1505,5 +1506,58 @@ object Trees {
15051506
case tree: TypeDef => cpy.TypeDef(tree)(name = newName.asTypeName)
15061507
}
15071508
}.asInstanceOf[tree.ThisTree[T]]
1509+
1510+
/** Delegate to FunProto or FunProtoTyped depending on whether the prefix is `untpd` or `tpd`. */
1511+
protected def FunProto(args: List[Tree], resType: Type)(using Context): ProtoTypes.FunProto
1512+
1513+
/** Construct the application `$receiver.$method[$targs]($args)` using overloading resolution
1514+
* to find a matching overload of `$method` if necessary.
1515+
* This is useful when overloading resolution needs to be performed in a phase after typer.
1516+
* Note that this will not perform any kind of implicit search.
1517+
*
1518+
* @param expectedType An expected type of the application used to guide overloading resolution
1519+
*/
1520+
def applyOverloaded(
1521+
receiver: tpd.Tree, method: TermName, args: List[Tree], targs: List[Type],
1522+
expectedType: Type)(using parentCtx: Context): tpd.Tree = {
1523+
given ctx as Context = parentCtx.retractMode(Mode.ImplicitsEnabled)
1524+
1525+
val typer = ctx.typer
1526+
val proto = FunProto(args, expectedType)
1527+
val denot = receiver.tpe.member(method)
1528+
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
1529+
val selected =
1530+
if (denot.isOverloaded) {
1531+
def typeParamCount(tp: Type) = tp.widen match {
1532+
case tp: PolyType => tp.paramInfos.length
1533+
case _ => 0
1534+
}
1535+
val allAlts = denot.alternatives
1536+
.map(denot => TermRef(receiver.tpe, denot.symbol))
1537+
.filter(tr => typeParamCount(tr) == targs.length)
1538+
.filter { _.widen match {
1539+
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
1540+
case _ => true
1541+
}}
1542+
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1543+
assert(alternatives.size == 1,
1544+
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
1545+
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, %; expectedType: $expectedType." +
1546+
i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" +
1547+
i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it
1548+
alternatives.head
1549+
}
1550+
else TermRef(receiver.tpe, denot.symbol)
1551+
val fun = receiver.select(selected).appliedToTypes(targs)
1552+
1553+
val apply = untpd.Apply(fun, args)
1554+
typer.ApplyTo(apply, fun, selected, proto, expectedType)
1555+
}
1556+
1557+
1558+
def resolveConstructor(atp: Type, args: List[Tree])(implicit ctx: Context): tpd.Tree = {
1559+
val targs = atp.argTypes
1560+
applyOverloaded(tpd.New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
1561+
}
15081562
}
15091563
}

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

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33
package ast
44

55
import dotty.tools.dotc.transform.{ExplicitOuter, Erasure}
6-
import dotty.tools.dotc.typer.ProtoTypes.FunProtoTyped
6+
import typer.ProtoTypes
77
import transform.SymUtils._
88
import transform.TypeUtils._
99
import core._
@@ -1152,41 +1152,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11521152
}
11531153
}
11541154

1155-
def applyOverloaded(receiver: Tree, method: TermName, args: List[Tree], targs: List[Type],
1156-
expectedType: Type, isContextual: Boolean = false)(implicit ctx: Context): Tree = {
1157-
val typer = ctx.typer
1158-
val proto = FunProtoTyped(args, expectedType)(typer, isContextual)
1159-
val denot = receiver.tpe.member(method)
1160-
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
1161-
val selected =
1162-
if (denot.isOverloaded) {
1163-
def typeParamCount(tp: Type) = tp.widen match {
1164-
case tp: PolyType => tp.paramInfos.length
1165-
case _ => 0
1166-
}
1167-
var allAlts = denot.alternatives
1168-
.map(denot => TermRef(receiver.tpe, denot.symbol))
1169-
.filter(tr => typeParamCount(tr) == targs.length)
1170-
.filter { _.widen match {
1171-
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
1172-
case _ => true
1173-
}}
1174-
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
1175-
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1176-
assert(alternatives.size == 1,
1177-
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
1178-
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, % of types ${args.tpes.map(_.widenDealiasKeepAnnots)}%, %; expectedType: $expectedType." +
1179-
i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" +
1180-
i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it
1181-
alternatives.head
1182-
}
1183-
else TermRef(receiver.tpe, denot.symbol)
1184-
val fun = receiver.select(selected).appliedToTypes(targs)
1185-
1186-
val apply = untpd.Apply(fun, args)
1187-
typer.ApplyToTyped(apply, fun, selected, args, expectedType).result.asInstanceOf[Tree] // needed to handle varargs
1188-
}
1189-
11901155
@tailrec
11911156
def sameTypes(trees: List[tpd.Tree], trees1: List[tpd.Tree]): Boolean =
11921157
if (trees.isEmpty) trees.isEmpty
@@ -1361,11 +1326,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13611326
transformer.transform(tree)
13621327
}
13631328

1364-
def resolveConstructor(atp: Type, args:List[Tree])(implicit ctx: Context): Tree = {
1365-
val targs = atp.argTypes
1366-
tpd.applyOverloaded(New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
1367-
}
1368-
13691329
/** Convert a list of trees to a vararg-compatible tree.
13701330
* Used to make arguments for methods that accept varargs.
13711331
*/
@@ -1385,4 +1345,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13851345
ref(defn.ListModule).select(nme.apply)
13861346
.appliedToTypeTree(tpe)
13871347
.appliedToVarargs(trees, tpe)
1348+
1349+
1350+
protected def FunProto(args: List[Tree], resType: Type)(using ctx: Context) =
1351+
ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, isUsingApply = false)
13881352
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package ast
44

55
import core._
66
import Types._, Contexts._, Constants._, Names._, Flags._
7+
import dotty.tools.dotc.typer.ProtoTypes
78
import Symbols._, StdNames._, Trees._
89
import util.{Property, SourceFile, NoSource}
910
import util.Spans.Span
@@ -756,4 +757,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
756757
}
757758
acc(false, tree)
758759
}
760+
761+
protected def FunProto(args: List[Tree], resType: Type)(using ctx: Context) =
762+
ProtoTypes.FunProto(args, resType)(ctx.typer, isUsingApply = false)
759763
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ object Annotations {
142142
def deferred(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation =
143143
deferred(atp.classSymbol)(New(atp, args))
144144

145-
def deferredResolve(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation =
146-
deferred(atp.classSymbol)(resolveConstructor(atp, args))
145+
def deferredResolve(atp: Type, args: List[ast.untpd.Tree])(implicit ctx: Context): Annotation =
146+
deferred(atp.classSymbol)(ast.untpd.resolveConstructor(atp, args))
147147

148148
/** Extractor for child annotations */
149149
object Child {

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dotty.tools.tasty.{ TastyReader, TastyHeaderUnpickler }
88
import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, Decorators._
99
import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Spans._
1010
import NameKinds.DefaultGetterName
11+
import ast.{ tpd, untpd }
1112
import ast.tpd._, util._
1213
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, DataInputStream, IOException }
1314

@@ -480,18 +481,30 @@ class ClassfileParser(
480481
}
481482
// sigToType
482483

483-
def parseAnnotArg(skip: Boolean = false)(implicit ctx: Context): Option[Tree] = {
484+
def parseAnnotArg(skip: Boolean = false)(implicit ctx: Context): Option[untpd.Tree] = {
485+
486+
// If we encounter an empty array literal, we need the type of the corresponding
487+
// parameter to properly type it, but that would require forcing the annotation
488+
// early. To avoid this we store annotation arguments as untyped trees
489+
import untpd._
490+
491+
// ... but constants need to actually be typed with a ConstantType, so we
492+
// can't rely on type inference, and type them early.
493+
def lit(c: Constant): Tree = TypedSplice(ast.tpd.Literal(c))
494+
484495
val tag = in.nextByte.toChar
485496
val index = in.nextChar
497+
498+
486499
tag match {
487500
case STRING_TAG =>
488-
if (skip) None else Some(Literal(Constant(pool.getName(index).toString)))
501+
if (skip) None else Some(lit(Constant(pool.getName(index).toString)))
489502
case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG =>
490-
if (skip) None else Some(Literal(pool.getConstant(index, tag)))
503+
if (skip) None else Some(lit(pool.getConstant(index, tag)))
491504
case INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG =>
492-
if (skip) None else Some(Literal(pool.getConstant(index)))
505+
if (skip) None else Some(lit(pool.getConstant(index)))
493506
case CLASS_TAG =>
494-
if (skip) None else Some(Literal(Constant(pool.getType(index))))
507+
if (skip) None else Some(lit(Constant(pool.getType(index))))
495508
case ENUM_TAG =>
496509
val t = pool.getType(index)
497510
val n = pool.getName(in.nextChar)
@@ -500,7 +513,7 @@ class ClassfileParser(
500513
if (skip)
501514
None
502515
else if (s != NoSymbol)
503-
Some(Literal(Constant(s)))
516+
Some(lit(Constant(s)))
504517
else {
505518
ctx.warning(s"""While parsing annotations in ${in.file}, could not find $n in enum $module.\nThis is likely due to an implementation restriction: an annotation argument cannot refer to a member of the annotated class (SI-7014).""")
506519
None
@@ -517,10 +530,7 @@ class ClassfileParser(
517530
else if (skip) None
518531
else {
519532
val elems = arr.toList
520-
val elemType =
521-
if (elems.isEmpty) defn.ObjectType
522-
else ctx.typeComparer.lub(elems.tpes).widen
523-
Some(JavaSeqLiteral(elems, TypeTree(elemType)))
533+
Some(untpd.JavaSeqLiteral(elems, TypeTree()))
524534
}
525535
case ANNOTATION_TAG =>
526536
parseAnnotation(index, skip) map (_.tree)
@@ -533,12 +543,12 @@ class ClassfileParser(
533543
def parseAnnotation(attrNameIndex: Char, skip: Boolean = false)(implicit ctx: Context): Option[Annotation] = try {
534544
val attrType = pool.getType(attrNameIndex)
535545
val nargs = in.nextChar
536-
val argbuf = new ListBuffer[Tree]
546+
val argbuf = new ListBuffer[untpd.Tree]
537547
var hasError = false
538548
for (i <- 0 until nargs) {
539549
val name = pool.getName(in.nextChar)
540550
parseAnnotArg(skip) match {
541-
case Some(arg) => argbuf += NamedArg(name, arg)
551+
case Some(arg) => argbuf += untpd.NamedArg(name, arg)
542552
case None => hasError = !skip
543553
}
544554
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ class TreeChecker extends Phase with SymTransformer {
287287

288288
override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = {
289289
val tpdTree = super.typed(tree, pt)
290+
Typer.assertPositioned(tree)
290291
if (ctx.erasedTypes)
291292
// Can't be checked in earlier phases since `checkValue` is only run in
292293
// Erasure (because running it in Typer would force too much)

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -861,14 +861,9 @@ trait Applications extends Compatibility {
861861
def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree =
862862
methPart(fun1).tpe match {
863863
case funRef: TermRef =>
864-
val app =
865-
if (proto.allArgTypesAreCurrent())
866-
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs(), pt)
867-
else
868-
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(
869-
using fun1.nullableInArgContext(using argCtx(tree)))
864+
val app = ApplyTo(tree, fun1, funRef, proto, pt)
870865
convertNewGenericArray(
871-
postProcessByNameArgs(funRef, app.result).computeNullable())
866+
postProcessByNameArgs(funRef, app).computeNullable())
872867
case _ =>
873868
handleUnexpectedFunType(tree, fun1)
874869
}
@@ -992,6 +987,15 @@ trait Applications extends Compatibility {
992987
}
993988
}
994989

990+
/** Typecheck an Apply node with a typed function and possibly-typed arguments coming from `proto` */
991+
def ApplyTo(app: untpd.Apply, fun: tpd.Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using ctx: Context): tpd.Tree =
992+
val typer = ctx.typer
993+
if (proto.allArgTypesAreCurrent())
994+
typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType).result
995+
else
996+
typer.ApplyToUntyped(app, fun, methRef, proto, resultType)(
997+
using fun.nullableInArgContext(using argCtx(app))).result
998+
995999
/** Overridden in ReTyper to handle primitive operations that can be generated after erasure */
9961000
protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree =
9971001
if ctx.reporter.errorsReported then

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ object ProtoTypes {
399399
*/
400400
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isUsingApply: Boolean)(implicit ctx: Context) extends FunProto(args, resultType)(typer, isUsingApply)(ctx) {
401401
override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(implicit ctx: Context): List[tpd.Tree] = args
402+
override def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): tpd.Tree = arg.asInstanceOf[tpd.Tree]
403+
override def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = true
402404
override def withContext(ctx: Context): FunProtoTyped = this
403405
}
404406

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2295,7 +2295,8 @@ class Typer extends Namer
22952295
trace(i"typing $tree, pt = $pt", typr, show = true) {
22962296
record(s"typed $getClass")
22972297
record("typed total")
2298-
assertPositioned(tree)
2298+
if (ctx.phase.isTyper)
2299+
assertPositioned(tree)
22992300
if (tree.source != ctx.source && tree.source.exists)
23002301
typed(tree, pt, locked)(ctx.withSource(tree.source))
23012302
else
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public @interface Annot_1 {
2+
String[] values() default {};
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public class Class_1 {
2+
@Annot_1(values = { "foo", "bar" })
3+
void foo() {}
4+
5+
@Annot_1(values = {})
6+
void foo2() {}
7+
}
8+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class Test extends Class_1

0 commit comments

Comments
 (0)