Skip to content

Commit 063579a

Browse files
committed
Resolve overloading: keep track of prefix and indices of all default getters
When resolving overloading using parameter lists after the first one, we used mapped symbols that forgot about the prefix of the original call and how many parameters were skipped. Consequently, overloading resolution got confused when there were default parameters in following parameter lists. We now keep track of these values in an annotation that gets added to the mapped symbols. We also use `findDefaultGetter` directly to compute the number of default parameters in `sizeFits`. The previous scheme of checking the `HasParam` flag of parameters fails for default values inherited from overriden methods.
1 parent a503b7a commit 063579a

File tree

5 files changed

+124
-60
lines changed

5 files changed

+124
-60
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,7 @@ class Definitions {
984984
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
985985
@tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween")
986986
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
987+
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")
987988
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
988989
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
989990
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")

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

Lines changed: 98 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import config.Feature
3030
import collection.mutable
3131
import config.Printers.{overload, typr, unapp}
3232
import TypeApplications._
33+
import Annotations.Annotation
3334

3435
import Constants.{Constant, IntTag}
3536
import Denotations.SingleDenotation
@@ -210,63 +211,81 @@ object Applications {
210211
def wrapDefs(defs: mutable.ListBuffer[Tree] | Null, tree: Tree)(using Context): Tree =
211212
if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree
212213

214+
/** Optionally, if `sym` is a symbol created by `resolveMapped`, i.e. representing
215+
* a mapped alternative, the original prefix of the alternative and the number of
216+
* skipped term parameters.
217+
*/
218+
private def mappedAltInfo(sym: Symbol)(using Context): Option[(Type, Int)] =
219+
for ann <- sym.getAnnotation(defn.MappedAlternativeAnnot) yield
220+
val AppliedType(_, pre :: ConstantType(c) :: Nil) = ann.tree.tpe: @unchecked
221+
(pre, c.intValue)
222+
213223
/** Find reference to default parameter getter for parameter #n in current
214-
* parameter list, or NoType if none was found
215-
*/
224+
* parameter list, or EmptyTree if none was found.
225+
* @param fn the tree referring to the function part of this call
226+
* @param n the index of the parameter in the parameter list of the call
227+
* @param testOnly true iff we just to find out whether a getter exists
228+
*/
216229
def findDefaultGetter(fn: Tree, n: Int, testOnly: Boolean)(using Context): Tree =
217-
if fn.symbol.isTerm then
230+
def reifyPrefix(pre: Type): Tree = pre match
231+
case pre: SingletonType => singleton(pre, needLoad = !testOnly)
232+
case pre if testOnly =>
233+
// In this case it is safe to skolemize now; we will produce a stable prefix for the actual call.
234+
ref(pre.narrow)
235+
case _ => EmptyTree
236+
237+
if fn.symbol.hasDefaultParams then
218238
val meth = fn.symbol.asTerm
219-
val receiver: Tree = methPart(fn) match {
220-
case Select(receiver, _) => receiver
221-
case mr => mr.tpe.normalizedPrefix match {
222-
case mr: TermRef => ref(mr)
223-
case mr: ThisType => singleton(mr)
224-
case mr =>
225-
if testOnly then
226-
// In this case it is safe to skolemize now; we will produce a stable prefix for the actual call.
227-
ref(mr.narrow)
228-
else
229-
EmptyTree
230-
}
231-
}
232-
val getterPrefix =
233-
if (meth.is(Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name
234-
def getterName = DefaultGetterName(getterPrefix, n + numArgs(fn))
235-
if !meth.hasDefaultParams then
236-
EmptyTree
237-
else if (receiver.isEmpty) {
238-
def findGetter(cx: Context): Tree =
239-
if (cx eq NoContext) EmptyTree
240-
else if (cx.scope != cx.outer.scope &&
241-
cx.denotNamed(meth.name).hasAltWith(_.symbol == meth)) {
242-
val denot = cx.denotNamed(getterName)
243-
if (denot.exists) ref(TermRef(cx.owner.thisType, getterName, denot))
244-
else findGetter(cx.outer)
245-
}
239+
val idx = n + numArgs(fn)
240+
methPart(fn) match
241+
case Select(receiver, _) =>
242+
findDefaultGetter(meth, receiver, idx)
243+
case mr => mappedAltInfo(meth) match
244+
case Some((pre, skipped)) =>
245+
findDefaultGetter(meth, reifyPrefix(pre), idx + skipped)
246+
case None =>
247+
findDefaultGetter(meth, reifyPrefix(mr.tpe.normalizedPrefix), idx)
248+
else EmptyTree // structural applies don't have symbols or defaults
249+
end findDefaultGetter
250+
251+
/** Find reference to default parameter getter for method `meth` numbered `idx`
252+
* selected from given `receiver`, or EmptyTree if none was found.
253+
* @param meth the called method (can be mapped by resolveMapped)
254+
* @param receiver the receiver of the original method call, which determines
255+
* where default getters are found
256+
* @param idx the index of the searched for default getter, as encoded in its name
257+
*/
258+
def findDefaultGetter(meth: TermSymbol, receiver: Tree, idx: Int)(using Context): Tree =
259+
val getterPrefix =
260+
if (meth.is(Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name
261+
val getterName = DefaultGetterName(getterPrefix, idx)
262+
263+
if receiver.isEmpty then
264+
def findGetter(cx: Context): Tree =
265+
if cx eq NoContext then EmptyTree
266+
else if cx.scope != cx.outer.scope
267+
&& cx.denotNamed(meth.name).hasAltWith(_.symbol == meth) then
268+
val denot = cx.denotNamed(getterName)
269+
if denot.exists then ref(TermRef(cx.owner.thisType, getterName, denot))
246270
else findGetter(cx.outer)
247-
findGetter(ctx)
248-
}
249-
else {
250-
def selectGetter(qual: Tree): Tree = {
251-
val getterDenot = qual.tpe.member(getterName)
252-
if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot))
253-
else EmptyTree
254-
}
255-
if (!meth.isClassConstructor)
256-
selectGetter(receiver)
257-
else {
258-
// default getters for class constructors are found in the companion object
259-
val cls = meth.owner
260-
val companion = cls.companionModule
261-
if (companion.isTerm) {
262-
val prefix = receiver.tpe.baseType(cls).normalizedPrefix
263-
if (prefix.exists) selectGetter(ref(TermRef(prefix, companion.asTerm)))
264-
else EmptyTree
265-
}
271+
else findGetter(cx.outer)
272+
findGetter(ctx)
273+
else
274+
def selectGetter(qual: Tree): Tree =
275+
val getterDenot = qual.tpe.member(getterName)
276+
if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot))
277+
else EmptyTree
278+
if !meth.isClassConstructor then
279+
selectGetter(receiver)
280+
else
281+
// default getters for class constructors are found in the companion object
282+
val cls = meth.owner
283+
val companion = cls.companionModule
284+
if companion.isTerm then
285+
val prefix = receiver.tpe.baseType(cls).normalizedPrefix
286+
if prefix.exists then selectGetter(ref(TermRef(prefix, companion.asTerm)))
266287
else EmptyTree
267-
}
268-
}
269-
else EmptyTree // structural applies don't have symbols or defaults
288+
else EmptyTree
270289
end findDefaultGetter
271290

272291
/** Splice new method reference `meth` into existing application `app` */
@@ -1937,9 +1956,8 @@ trait Applications extends Compatibility {
19371956
def isVarArgs = ptypes.nonEmpty && ptypes.last.isRepeatedParam
19381957
def numDefaultParams =
19391958
if alt.symbol.hasDefaultParams then
1940-
trimParamss(tp, alt.symbol.rawParamss) match
1941-
case params :: _ => params.count(_.is(HasDefault))
1942-
case _ => 0
1959+
val fn = ref(alt, needLoad = false)
1960+
ptypes.indices.count(n => !findDefaultGetter(fn, n, testOnly = true).isEmpty)
19431961
else 0
19441962
if numParams < numArgs then isVarArgs
19451963
else if numParams == numArgs then true
@@ -2088,13 +2106,22 @@ trait Applications extends Compatibility {
20882106
}
20892107
end resolveOverloaded1
20902108

2091-
/** The largest suffix of `paramss` that has the same first parameter name as `t` */
2092-
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): List[List[Symbol]] = t match
2109+
/** The largest suffix of `paramss` that has the same first parameter name as `t`,
2110+
* plus the number of term parameters in `paramss` that come before that suffix.
2111+
*/
2112+
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): (List[List[Symbol]], Int) = t match
20932113
case MethodType(Nil) => trimParamss(t.resultType, paramss)
20942114
case t: MethodOrPoly =>
20952115
val firstParamName = t.paramNames.head
2096-
paramss.dropWhile(_.head.name != firstParamName)
2097-
case _ => Nil
2116+
def recur(pss: List[List[Symbol]], skipped: Int): (List[List[Symbol]], Int) =
2117+
(pss: @unchecked) match
2118+
case (ps @ (p :: _)) :: pss1 =>
2119+
if p.name == firstParamName then (pss, skipped)
2120+
else recur(pss1, if p.name.isTermName then skipped + ps.length else skipped)
2121+
case Nil =>
2122+
(pss, skipped)
2123+
recur(paramss, 0)
2124+
case _ => (Nil, 0)
20982125

20992126
/** Resolve overloading by mapping to a different problem where each alternative's
21002127
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
@@ -2104,8 +2131,19 @@ trait Applications extends Compatibility {
21042131
val reverseMapping = alts.flatMap { alt =>
21052132
val t = f(alt)
21062133
if t.exists then
2134+
val (trimmed, skipped) = trimParamss(t, alt.symbol.rawParamss)
21072135
val mappedSym = alt.symbol.asTerm.copy(info = t)
2108-
mappedSym.rawParamss = trimParamss(t, alt.symbol.rawParamss)
2136+
mappedSym.rawParamss = trimmed
2137+
val (pre, totalSkipped) = mappedAltInfo(alt.symbol) match
2138+
case Some((pre, prevSkipped)) =>
2139+
mappedSym.removeAnnotation(defn.MappedAlternativeAnnot)
2140+
(pre, skipped + prevSkipped)
2141+
case None =>
2142+
(alt.prefix, skipped)
2143+
mappedSym.addAnnotation(
2144+
Annotation(TypeTree(
2145+
defn.MappedAlternativeAnnot.typeRef.appliedTo(
2146+
pre, ConstantType(Constant(totalSkipped))))))
21092147
Some((TermRef(NoPrefix, mappedSym), alt))
21102148
else
21112149
None
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package scala.annotation
2+
package internal
3+
4+
/** An annotation added by overloading resoluton to mapped symbols that
5+
* explore deeper into the types of the opverloaded alternatives.
6+
* Its tree is a TypeTree with two parameters which are both needed to
7+
* fine default getters in later parameter sections.
8+
* @param Prefix the prefix field of the original alternative TermRef
9+
* @param SkipCount a ConstantType referring to the number of skipped term parameters
10+
* The annotation is short-lived since mapped symbols are discarded immediately
11+
* once an overloading resolution step terminates.
12+
*/
13+
final class MappedAlternative[Prefix, SkipCount] extends Annotation

tests/run/i16006.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
overriden (3, 10)

tests/run/i16006.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class X:
2+
def toString(maxLines: Int = 10, maxWidth: Int = 10): String = (maxLines -> maxWidth).toString
3+
4+
class Foo extends X:
5+
override def toString(maxLines: Int, maxWidth: Int): String = s"overriden ($maxLines, $maxWidth)"
6+
override def toString(): String = toString(maxLines = 3)
7+
8+
9+
@main def Test = {
10+
println(Foo().toString())
11+
}

0 commit comments

Comments
 (0)