Skip to content

Commit 16b80fb

Browse files
authored
Resolve overloading: keep track of prefix and indices of all default getters (#16009)
Fixes #16006 Fixes #15287 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.
2 parents a6a3385 + aba158c commit 16b80fb

File tree

13 files changed

+181
-72
lines changed

13 files changed

+181
-72
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: 103 additions & 62 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` */
@@ -570,6 +589,7 @@ trait Applications extends Compatibility {
570589

571590
def tryDefault(n: Int, args1: List[Arg]): Unit = {
572591
val sym = methRef.symbol
592+
val testOnly = this.isInstanceOf[TestApplication[?]]
573593

574594
val defaultArg =
575595
if (isJavaAnnotConstr(sym)) {
@@ -585,12 +605,14 @@ trait Applications extends Compatibility {
585605
else
586606
EmptyTree
587607
}
588-
else defaultArgument(normalizedFun, n, this.isInstanceOf[TestApplication[?]])
608+
else defaultArgument(normalizedFun, n, testOnly)
589609

590610
def implicitArg = implicitArgTree(formal, appPos.span)
591611

592612
if !defaultArg.isEmpty then
593-
matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
613+
defaultArg.tpe.widen match
614+
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
615+
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
594616
else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then
595617
matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1)
596618
else
@@ -1947,9 +1969,8 @@ trait Applications extends Compatibility {
19471969
def isVarArgs = ptypes.nonEmpty && ptypes.last.isRepeatedParam
19481970
def numDefaultParams =
19491971
if alt.symbol.hasDefaultParams then
1950-
trimParamss(tp, alt.symbol.rawParamss) match
1951-
case params :: _ => params.count(_.is(HasDefault))
1952-
case _ => 0
1972+
val fn = ref(alt, needLoad = false)
1973+
ptypes.indices.count(n => !findDefaultGetter(fn, n, testOnly = true).isEmpty)
19531974
else 0
19541975
if numParams < numArgs then isVarArgs
19551976
else if numParams == numArgs then true
@@ -2098,13 +2119,22 @@ trait Applications extends Compatibility {
20982119
}
20992120
end resolveOverloaded1
21002121

2101-
/** The largest suffix of `paramss` that has the same first parameter name as `t` */
2102-
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): List[List[Symbol]] = t match
2122+
/** The largest suffix of `paramss` that has the same first parameter name as `t`,
2123+
* plus the number of term parameters in `paramss` that come before that suffix.
2124+
*/
2125+
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): (List[List[Symbol]], Int) = t match
21032126
case MethodType(Nil) => trimParamss(t.resultType, paramss)
21042127
case t: MethodOrPoly =>
21052128
val firstParamName = t.paramNames.head
2106-
paramss.dropWhile(_.head.name != firstParamName)
2107-
case _ => Nil
2129+
def recur(pss: List[List[Symbol]], skipped: Int): (List[List[Symbol]], Int) =
2130+
(pss: @unchecked) match
2131+
case (ps @ (p :: _)) :: pss1 =>
2132+
if p.name == firstParamName then (pss, skipped)
2133+
else recur(pss1, if p.name.isTermName then skipped + ps.length else skipped)
2134+
case Nil =>
2135+
(pss, skipped)
2136+
recur(paramss, 0)
2137+
case _ => (Nil, 0)
21082138

21092139
/** Resolve overloading by mapping to a different problem where each alternative's
21102140
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
@@ -2114,8 +2144,19 @@ trait Applications extends Compatibility {
21142144
val reverseMapping = alts.flatMap { alt =>
21152145
val t = f(alt)
21162146
if t.exists then
2147+
val (trimmed, skipped) = trimParamss(t, alt.symbol.rawParamss)
21172148
val mappedSym = alt.symbol.asTerm.copy(info = t)
2118-
mappedSym.rawParamss = trimParamss(t, alt.symbol.rawParamss)
2149+
mappedSym.rawParamss = trimmed
2150+
val (pre, totalSkipped) = mappedAltInfo(alt.symbol) match
2151+
case Some((pre, prevSkipped)) =>
2152+
mappedSym.removeAnnotation(defn.MappedAlternativeAnnot)
2153+
(pre, skipped + prevSkipped)
2154+
case None =>
2155+
(alt.prefix, skipped)
2156+
mappedSym.addAnnotation(
2157+
Annotation(TypeTree(
2158+
defn.MappedAlternativeAnnot.typeRef.appliedTo(
2159+
pre, ConstantType(Constant(totalSkipped))))))
21192160
Some((TermRef(NoPrefix, mappedSym), alt))
21202161
else
21212162
None

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,28 @@ object ErrorReporting {
6969
"\n(Note that variables need to be initialized to be defined)"
7070
else ""
7171

72+
/** Reveal arguments in FunProtos that are proteted by an IgnoredProto but were
73+
* revealed during type inference. This gives clearer error messages for overloading
74+
* resolution errors that need to show argument lists after the first. We do not
75+
* reveal other kinds of ignored prototypes since these might be misleading because
76+
* there might be a possible implicit conversion on the result.
77+
*/
78+
def revealDeepenedArgs(tp: Type): Type = tp match
79+
case tp @ IgnoredProto(deepTp: FunProto) if tp.wasDeepened => deepTp
80+
case _ => tp
81+
7282
def expectedTypeStr(tp: Type): String = tp match {
7383
case tp: PolyProto =>
74-
em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(tp.resultType)}"
84+
em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(revealDeepenedArgs(tp.resultType))}"
7585
case tp: FunProto =>
76-
val result = tp.resultType match {
77-
case _: WildcardType | _: IgnoredProto => ""
78-
case tp => em" and expected result type $tp"
79-
}
80-
em"arguments (${tp.typedArgs().tpes}%, %)$result"
86+
def argStr(tp: FunProto): String =
87+
val result = revealDeepenedArgs(tp.resultType) match {
88+
case restp: FunProto => argStr(restp)
89+
case _: WildcardType | _: IgnoredProto => ""
90+
case tp => em" and expected result type $tp"
91+
}
92+
em"(${tp.typedArgs().tpes}%, %)$result"
93+
s"arguments ${argStr(tp)}"
8194
case _ =>
8295
em"expected type $tp"
8396
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,18 @@ object ProtoTypes {
131131

132132
/** A class marking ignored prototypes that can be revealed by `deepenProto` */
133133
abstract case class IgnoredProto(ignored: Type) extends CachedGroundType with MatchAlways:
134+
private var myWasDeepened = false
134135
override def revealIgnored = ignored
135-
override def deepenProto(using Context): Type = ignored
136+
override def deepenProto(using Context): Type =
137+
myWasDeepened = true
138+
ignored
136139
override def deepenProtoTrans(using Context): Type = ignored.deepenProtoTrans
137140

141+
/** Did someone look inside via deepenProto? Used for error deagniostics
142+
* to give a more extensive expected type.
143+
*/
144+
def wasDeepened: Boolean = myWasDeepened
145+
138146
override def computeHash(bs: Hashable.Binders): Int = doHash(bs, ignored)
139147

140148
override def eql(that: Type): Boolean = that match
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

project/MiMaFilters.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import com.typesafe.tools.mima.core._
33

44
object MiMaFilters {
55
val Library: Seq[ProblemFilter] = Seq(
6+
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"),
67
)
78
}

tests/neg/i10901.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
| (x: T1)
1616
| (y: BugExp4Point2D.ColumnType[T2])
1717
| (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
18-
| both match arguments ((x : BugExp4Point2D.IntT.type))
18+
| both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type))
1919
-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ----------------------------------------------------------------
2020
48 | val pos4: Point2D[Int,Double] = x º 201.1 // error
2121
| ^^^
@@ -29,7 +29,7 @@
2929
| (x: BugExp4Point2D.ColumnType[T1])
3030
| (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
3131
| [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
32-
| both match arguments ((x : BugExp4Point2D.IntT.type))
32+
| both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double))
3333
-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ----------------------------------------------------------------
3434
62 | val y = "abc".foo // error
3535
| ^^^^^^^^^

tests/neg/i15000.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
| Ambiguous overload. The overloaded alternatives of method apply in object ExtensionMethodReproduction with types
2222
| (c: ExtensionMethodReproduction.C)(x: Int, y: Int): String
2323
| (c: ExtensionMethodReproduction.C)(x: Int, y: String): String
24-
| both match arguments (ExtensionMethodReproduction.c.type)
24+
| both match arguments (ExtensionMethodReproduction.c.type)((ExtensionMethodReproduction.x : Int), <error Not found: barY>)

tests/neg/i15287.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E134] Type Error: tests/neg/i15287.scala:4:19 ----------------------------------------------------------------------
2+
4 |@main def Test() = f('c')(2) // error
3+
| ^
4+
| None of the overloaded alternatives of method f with types
5+
| (x: Char)(min: Boolean, max: Int): String
6+
| (x: Char)(someParam: String): String
7+
| match arguments (('c' : Char))((2 : Int))

tests/neg/i15287.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f(x: Char)(someParam: String): String = "a"
2+
def f(x: Char)(min: Boolean, max: Int = 4): String = "b"
3+
4+
@main def Test() = f('c')(2) // error

tests/run/i15287.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
extension (x: Char)
2+
def f(someParam: String): String = "a"
3+
def f(min: Boolean, max: Int = 4): String = "b"
4+
5+
@main def Test() =
6+
f('c')(false)
7+
'c'.f(true)
8+
'c'.f("a")
9+

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)