Skip to content

Commit

Permalink
Resolve overloading: keep track of prefix and indices of all default …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
odersky committed Sep 11, 2022
1 parent a503b7a commit 063579a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 60 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@ class Definitions {
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
@tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween")
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
Expand Down
158 changes: 98 additions & 60 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import config.Feature
import collection.mutable
import config.Printers.{overload, typr, unapp}
import TypeApplications._
import Annotations.Annotation

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

/** Optionally, if `sym` is a symbol created by `resolveMapped`, i.e. representing
* a mapped alternative, the original prefix of the alternative and the number of
* skipped term parameters.
*/
private def mappedAltInfo(sym: Symbol)(using Context): Option[(Type, Int)] =
for ann <- sym.getAnnotation(defn.MappedAlternativeAnnot) yield
val AppliedType(_, pre :: ConstantType(c) :: Nil) = ann.tree.tpe: @unchecked
(pre, c.intValue)

/** Find reference to default parameter getter for parameter #n in current
* parameter list, or NoType if none was found
*/
* parameter list, or EmptyTree if none was found.
* @param fn the tree referring to the function part of this call
* @param n the index of the parameter in the parameter list of the call
* @param testOnly true iff we just to find out whether a getter exists
*/
def findDefaultGetter(fn: Tree, n: Int, testOnly: Boolean)(using Context): Tree =
if fn.symbol.isTerm then
def reifyPrefix(pre: Type): Tree = pre match
case pre: SingletonType => singleton(pre, needLoad = !testOnly)
case pre if testOnly =>
// In this case it is safe to skolemize now; we will produce a stable prefix for the actual call.
ref(pre.narrow)
case _ => EmptyTree

if fn.symbol.hasDefaultParams then
val meth = fn.symbol.asTerm
val receiver: Tree = methPart(fn) match {
case Select(receiver, _) => receiver
case mr => mr.tpe.normalizedPrefix match {
case mr: TermRef => ref(mr)
case mr: ThisType => singleton(mr)
case mr =>
if testOnly then
// In this case it is safe to skolemize now; we will produce a stable prefix for the actual call.
ref(mr.narrow)
else
EmptyTree
}
}
val getterPrefix =
if (meth.is(Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name
def getterName = DefaultGetterName(getterPrefix, n + numArgs(fn))
if !meth.hasDefaultParams then
EmptyTree
else if (receiver.isEmpty) {
def findGetter(cx: Context): Tree =
if (cx eq NoContext) EmptyTree
else if (cx.scope != cx.outer.scope &&
cx.denotNamed(meth.name).hasAltWith(_.symbol == meth)) {
val denot = cx.denotNamed(getterName)
if (denot.exists) ref(TermRef(cx.owner.thisType, getterName, denot))
else findGetter(cx.outer)
}
val idx = n + numArgs(fn)
methPart(fn) match
case Select(receiver, _) =>
findDefaultGetter(meth, receiver, idx)
case mr => mappedAltInfo(meth) match
case Some((pre, skipped)) =>
findDefaultGetter(meth, reifyPrefix(pre), idx + skipped)
case None =>
findDefaultGetter(meth, reifyPrefix(mr.tpe.normalizedPrefix), idx)
else EmptyTree // structural applies don't have symbols or defaults
end findDefaultGetter

/** Find reference to default parameter getter for method `meth` numbered `idx`
* selected from given `receiver`, or EmptyTree if none was found.
* @param meth the called method (can be mapped by resolveMapped)
* @param receiver the receiver of the original method call, which determines
* where default getters are found
* @param idx the index of the searched for default getter, as encoded in its name
*/
def findDefaultGetter(meth: TermSymbol, receiver: Tree, idx: Int)(using Context): Tree =
val getterPrefix =
if (meth.is(Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name
val getterName = DefaultGetterName(getterPrefix, idx)

if receiver.isEmpty then
def findGetter(cx: Context): Tree =
if cx eq NoContext then EmptyTree
else if cx.scope != cx.outer.scope
&& cx.denotNamed(meth.name).hasAltWith(_.symbol == meth) then
val denot = cx.denotNamed(getterName)
if denot.exists then ref(TermRef(cx.owner.thisType, getterName, denot))
else findGetter(cx.outer)
findGetter(ctx)
}
else {
def selectGetter(qual: Tree): Tree = {
val getterDenot = qual.tpe.member(getterName)
if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot))
else EmptyTree
}
if (!meth.isClassConstructor)
selectGetter(receiver)
else {
// default getters for class constructors are found in the companion object
val cls = meth.owner
val companion = cls.companionModule
if (companion.isTerm) {
val prefix = receiver.tpe.baseType(cls).normalizedPrefix
if (prefix.exists) selectGetter(ref(TermRef(prefix, companion.asTerm)))
else EmptyTree
}
else findGetter(cx.outer)
findGetter(ctx)
else
def selectGetter(qual: Tree): Tree =
val getterDenot = qual.tpe.member(getterName)
if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot))
else EmptyTree
if !meth.isClassConstructor then
selectGetter(receiver)
else
// default getters for class constructors are found in the companion object
val cls = meth.owner
val companion = cls.companionModule
if companion.isTerm then
val prefix = receiver.tpe.baseType(cls).normalizedPrefix
if prefix.exists then selectGetter(ref(TermRef(prefix, companion.asTerm)))
else EmptyTree
}
}
else EmptyTree // structural applies don't have symbols or defaults
else EmptyTree
end findDefaultGetter

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

/** The largest suffix of `paramss` that has the same first parameter name as `t` */
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): List[List[Symbol]] = t match
/** The largest suffix of `paramss` that has the same first parameter name as `t`,
* plus the number of term parameters in `paramss` that come before that suffix.
*/
def trimParamss(t: Type, paramss: List[List[Symbol]])(using Context): (List[List[Symbol]], Int) = t match
case MethodType(Nil) => trimParamss(t.resultType, paramss)
case t: MethodOrPoly =>
val firstParamName = t.paramNames.head
paramss.dropWhile(_.head.name != firstParamName)
case _ => Nil
def recur(pss: List[List[Symbol]], skipped: Int): (List[List[Symbol]], Int) =
(pss: @unchecked) match
case (ps @ (p :: _)) :: pss1 =>
if p.name == firstParamName then (pss, skipped)
else recur(pss1, if p.name.isTermName then skipped + ps.length else skipped)
case Nil =>
(pss, skipped)
recur(paramss, 0)
case _ => (Nil, 0)

/** Resolve overloading by mapping to a different problem where each alternative's
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
Expand All @@ -2104,8 +2131,19 @@ trait Applications extends Compatibility {
val reverseMapping = alts.flatMap { alt =>
val t = f(alt)
if t.exists then
val (trimmed, skipped) = trimParamss(t, alt.symbol.rawParamss)
val mappedSym = alt.symbol.asTerm.copy(info = t)
mappedSym.rawParamss = trimParamss(t, alt.symbol.rawParamss)
mappedSym.rawParamss = trimmed
val (pre, totalSkipped) = mappedAltInfo(alt.symbol) match
case Some((pre, prevSkipped)) =>
mappedSym.removeAnnotation(defn.MappedAlternativeAnnot)
(pre, skipped + prevSkipped)
case None =>
(alt.prefix, skipped)
mappedSym.addAnnotation(
Annotation(TypeTree(
defn.MappedAlternativeAnnot.typeRef.appliedTo(
pre, ConstantType(Constant(totalSkipped))))))
Some((TermRef(NoPrefix, mappedSym), alt))
else
None
Expand Down
13 changes: 13 additions & 0 deletions library/src/scala/annotation/internal/MappedAlternative.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package scala.annotation
package internal

/** An annotation added by overloading resoluton to mapped symbols that
* explore deeper into the types of the opverloaded alternatives.
* Its tree is a TypeTree with two parameters which are both needed to
* fine default getters in later parameter sections.
* @param Prefix the prefix field of the original alternative TermRef
* @param SkipCount a ConstantType referring to the number of skipped term parameters
* The annotation is short-lived since mapped symbols are discarded immediately
* once an overloading resolution step terminates.
*/
final class MappedAlternative[Prefix, SkipCount] extends Annotation
1 change: 1 addition & 0 deletions tests/run/i16006.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
overriden (3, 10)
11 changes: 11 additions & 0 deletions tests/run/i16006.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class X:
def toString(maxLines: Int = 10, maxWidth: Int = 10): String = (maxLines -> maxWidth).toString

class Foo extends X:
override def toString(maxLines: Int, maxWidth: Int): String = s"overriden ($maxLines, $maxWidth)"
override def toString(): String = toString(maxLines = 3)


@main def Test = {
println(Foo().toString())
}

0 comments on commit 063579a

Please # to comment.