Skip to content

Commit 610450f

Browse files
committed
Store annotation arguments as untyped tree
We can't properly type annotation arguments which are empty array literals because we would need the corresponding parameter type which would require forcing the annotation constructor early. The previous commit worked around this by using `WildcardType` as the element type but that's a hack. Instead, we can fix the problem by always storing annotation arguments as untyped trees and using `applyOverloaded` to type them when the annotation tree is forced. This requires some refactoring to make `applyOverloaded` work both with typed and untyped arguments (the variants are now accessible using `untpd.applyOverloaded` and `tpd.applyOverloaded`). (the abstract `def FunProto` in Trees that gets implemented by forwarding in untpd/tpd sounds like a good candidate for `export`, except we can't export a class constructor)
1 parent 02912f0 commit 610450f

File tree

9 files changed

+104
-73
lines changed

9 files changed

+104
-73
lines changed

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

Lines changed: 55 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,59 @@ 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+
var 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+
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
1543+
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1544+
assert(alternatives.size == 1,
1545+
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
1546+
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, %; expectedType: $expectedType." +
1547+
i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" +
1548+
i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it
1549+
alternatives.head
1550+
}
1551+
else TermRef(receiver.tpe, denot.symbol)
1552+
val fun = receiver.select(selected).appliedToTypes(targs)
1553+
1554+
val apply = untpd.Apply(fun, args)
1555+
typer.ApplyTo(apply, fun, selected, proto, expectedType)
1556+
}
1557+
1558+
1559+
def resolveConstructor(atp: Type, args: List[Tree])(implicit ctx: Context): tpd.Tree = {
1560+
val targs = atp.argTypes
1561+
applyOverloaded(tpd.New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
1562+
}
15081563
}
15091564
}

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

Lines changed: 5 additions & 51 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,51 +1152,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11521152
}
11531153
}
11541154

1155-
/** Construct the application `$receiver.$method[$targs]($args)` using overloading resolution
1156-
* to find a matching overload of `$method` if necessary.
1157-
* This is useful when overloading resolution needs to be performed in a phase after typer.
1158-
* Note that this will not perform any kind of implicit search.
1159-
*
1160-
* @param expectedType An expected type of the application used to guide overloading resolution
1161-
* @param isContextual Is this a contextual application (i.e., one that would be written using `.using(...)`) ?
1162-
*/
1163-
def applyOverloaded(receiver: Tree, method: TermName, args: List[Tree], targs: List[Type],
1164-
expectedType: Type, isContextual: Boolean = false)(implicit parentCtx: Context): Tree = {
1165-
given ctx as Context = parentCtx.retractMode(Mode.ImplicitsEnabled)
1166-
1167-
val typer = ctx.typer
1168-
val proto = FunProtoTyped(args, expectedType)(typer, isContextual)
1169-
val denot = receiver.tpe.member(method)
1170-
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
1171-
val selected =
1172-
if (denot.isOverloaded) {
1173-
def typeParamCount(tp: Type) = tp.widen match {
1174-
case tp: PolyType => tp.paramInfos.length
1175-
case _ => 0
1176-
}
1177-
var allAlts = denot.alternatives
1178-
.map(denot => TermRef(receiver.tpe, denot.symbol))
1179-
.filter(tr => typeParamCount(tr) == targs.length)
1180-
.filter { _.widen match {
1181-
case MethodTpe(_, _, x: MethodType) => !x.isImplicitMethod
1182-
case _ => true
1183-
}}
1184-
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
1185-
val alternatives = ctx.typer.resolveOverloaded(allAlts, proto)
1186-
assert(alternatives.size == 1,
1187-
i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " +
1188-
i"$method on ${receiver.tpe.widenDealiasKeepAnnots} with targs: $targs%, %; args: $args%, % of types ${args.tpes.map(_.widenDealiasKeepAnnots)}%, %; expectedType: $expectedType." +
1189-
i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" +
1190-
i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it
1191-
alternatives.head
1192-
}
1193-
else TermRef(receiver.tpe, denot.symbol)
1194-
val fun = receiver.select(selected).appliedToTypes(targs)
1195-
1196-
val apply = untpd.Apply(fun, args)
1197-
typer.ApplyToTyped(apply, fun, selected, args, expectedType).result.asInstanceOf[Tree] // needed to handle varargs
1198-
}
1199-
12001155
@tailrec
12011156
def sameTypes(trees: List[tpd.Tree], trees1: List[tpd.Tree]): Boolean =
12021157
if (trees.isEmpty) trees.isEmpty
@@ -1371,11 +1326,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13711326
transformer.transform(tree)
13721327
}
13731328

1374-
def resolveConstructor(atp: Type, args:List[Tree])(implicit ctx: Context): Tree = {
1375-
val targs = atp.argTypes
1376-
tpd.applyOverloaded(New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
1377-
}
1378-
13791329
/** Convert a list of trees to a vararg-compatible tree.
13801330
* Used to make arguments for methods that accept varargs.
13811331
*/
@@ -1395,4 +1345,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13951345
ref(defn.ListModule).select(nme.apply)
13961346
.appliedToTypeTree(tpe)
13971347
.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)
13981352
}

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) WildcardType // No way to figure out the element type without forcing the annotation constructor
522-
else elems.head.tpe.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
@@ -860,14 +860,9 @@ trait Applications extends Compatibility {
860860
def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree =
861861
methPart(fun1).tpe match {
862862
case funRef: TermRef =>
863-
val app =
864-
if (proto.allArgTypesAreCurrent())
865-
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs(), pt)
866-
else
867-
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(
868-
using fun1.nullableInArgContext(using argCtx(tree)))
863+
val app = ApplyTo(tree, fun1, funRef, proto, pt)
869864
convertNewGenericArray(
870-
postProcessByNameArgs(funRef, app.result).computeNullable())
865+
postProcessByNameArgs(funRef, app).computeNullable())
871866
case _ =>
872867
handleUnexpectedFunType(tree, fun1)
873868
}
@@ -991,6 +986,15 @@ trait Applications extends Compatibility {
991986
}
992987
}
993988

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

0 commit comments

Comments
 (0)