Skip to content

Resolve overloading: keep track of prefix and indices of all default getters #16009

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
165 changes: 103 additions & 62 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 @@ -570,6 +589,7 @@ trait Applications extends Compatibility {

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

val defaultArg =
if (isJavaAnnotConstr(sym)) {
Expand All @@ -585,12 +605,14 @@ trait Applications extends Compatibility {
else
EmptyTree
}
else defaultArgument(normalizedFun, n, this.isInstanceOf[TestApplication[?]])
else defaultArgument(normalizedFun, n, testOnly)

def implicitArg = implicitArgTree(formal, appPos.span)

if !defaultArg.isEmpty then
matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
defaultArg.tpe.widen match
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then
matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1)
else
Expand Down Expand Up @@ -1937,9 +1959,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 +2109,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 +2134,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
33 changes: 23 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,28 @@ object ErrorReporting {
"\n(Note that variables need to be initialized to be defined)"
else ""

/** Reveal arguments in FunProtos that are proteted by an IgnoredProto but were
* revealed during type inference. This gives clearer error messages for overloading
* resolution errors that need to show argument lists after the first. We do not
* reveal other kinds of ignored prototypes since these might be misleading because
* there might be a possible implicit conversion on the result.
*/
def revealDeepenedArgs(tp: Type): Type = tp match
case tp @ IgnoredProto(deepTp: FunProto) if tp.wasDeepened => deepTp
case _ => tp

def expectedTypeStr(tp: Type): String = tp match {
case tp: PolyProto =>
em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(tp.resultType)}"
em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(revealDeepenedArgs(tp.resultType))}"
case tp: FunProto =>
val result = tp.resultType match {
case _: WildcardType | _: IgnoredProto => ""
case tp => em" and expected result type $tp"
}
em"arguments (${tp.typedArgs().tpes}%, %)$result"
def argStr(tp: FunProto): String =
val result = revealDeepenedArgs(tp.resultType) match {
case restp: FunProto => argStr(restp)
case _: WildcardType | _: IgnoredProto => ""
case tp => em" and expected result type $tp"
}
em"(${tp.typedArgs().tpes}%, %)$result"
s"arguments ${argStr(tp)}"
case _ =>
em"expected type $tp"
}
Expand Down Expand Up @@ -125,25 +138,25 @@ object ErrorReporting {
def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = {
val normTp = normalize(tree.tpe, pt)
val normPt = normalize(pt, pt)

def contextFunctionCount(tp: Type): Int = tp.stripped match
case defn.ContextFunctionType(_, restp, _) => 1 + contextFunctionCount(restp)
case _ => 0
def strippedTpCount = contextFunctionCount(tree.tpe) - contextFunctionCount(normTp)
def strippedPtCount = contextFunctionCount(pt) - contextFunctionCount(normPt)

val (treeTp, expectedTp) =
if normTp <:< normPt || strippedTpCount != strippedPtCount
then (tree.tpe, pt)
else (normTp, normPt)
// use normalized types if that also shows an error, and both sides stripped
// the same number of context functions. Use original types otherwise.

def missingElse = tree match
case If(_, _, elsep @ Literal(Constant(()))) if elsep.span.isSynthetic =>
"\nMaybe you are missing an else part for the conditional?"
case _ => ""

errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), implicitFailure.whyNoConversion, missingElse))
}

Expand Down
10 changes: 9 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,18 @@ object ProtoTypes {

/** A class marking ignored prototypes that can be revealed by `deepenProto` */
abstract case class IgnoredProto(ignored: Type) extends CachedGroundType with MatchAlways:
private var myWasDeepened = false
override def revealIgnored = ignored
override def deepenProto(using Context): Type = ignored
override def deepenProto(using Context): Type =
myWasDeepened = true
ignored
override def deepenProtoTrans(using Context): Type = ignored.deepenProtoTrans

/** Did someone look inside via deepenProto? Used for error deagniostics
* to give a more extensive expected type.
*/
def wasDeepened: Boolean = myWasDeepened

override def computeHash(bs: Hashable.Binders): Int = doHash(bs, ignored)

override def eql(that: Type): Boolean = that match
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 project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import com.typesafe.tools.mima.core._

object MiMaFilters {
val Library: Seq[ProblemFilter] = Seq(
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"),
)
}
4 changes: 2 additions & 2 deletions tests/neg/i10901.check
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| (x: T1)
| (y: BugExp4Point2D.ColumnType[T2])
| (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| both match arguments ((x : BugExp4Point2D.IntT.type))
| both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type))
-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ----------------------------------------------------------------
48 | val pos4: Point2D[Int,Double] = x º 201.1 // error
| ^^^
Expand All @@ -29,7 +29,7 @@
| (x: BugExp4Point2D.ColumnType[T1])
| (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
| both match arguments ((x : BugExp4Point2D.IntT.type))
| both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double))
-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ----------------------------------------------------------------
62 | val y = "abc".foo // error
| ^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i15000.check
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
| Ambiguous overload. The overloaded alternatives of method apply in object ExtensionMethodReproduction with types
| (c: ExtensionMethodReproduction.C)(x: Int, y: Int): String
| (c: ExtensionMethodReproduction.C)(x: Int, y: String): String
| both match arguments (ExtensionMethodReproduction.c.type)
| both match arguments (ExtensionMethodReproduction.c.type)((ExtensionMethodReproduction.x : Int), <error Not found: barY>)
7 changes: 7 additions & 0 deletions tests/neg/i15287.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E134] Type Error: tests/neg/i15287.scala:4:19 ----------------------------------------------------------------------
4 |@main def Test() = f('c')(2) // error
| ^
| None of the overloaded alternatives of method f with types
| (x: Char)(min: Boolean, max: Int): String
| (x: Char)(someParam: String): String
| match arguments (('c' : Char))((2 : Int))
4 changes: 4 additions & 0 deletions tests/neg/i15287.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def f(x: Char)(someParam: String): String = "a"
def f(x: Char)(min: Boolean, max: Int = 4): String = "b"

@main def Test() = f('c')(2) // error
9 changes: 9 additions & 0 deletions tests/run/i15287.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension (x: Char)
def f(someParam: String): String = "a"
def f(min: Boolean, max: Int = 4): String = "b"

@main def Test() =
f('c')(false)
'c'.f(true)
'c'.f("a")

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)
Loading