Skip to content

throw error on ambiguous overload with methods and functions #18338

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

Closed
wants to merge 2 commits into from
Closed
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
39 changes: 30 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1518,13 +1518,20 @@ trait Applications extends Compatibility {
isApplicableMethodRef(_, args, resultType, ArgMatch.Compatible)
}

private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean = tp match {

private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(using Context): Boolean =
def hasApply: Boolean =
followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))

tp match {
case expr: TermRef if expr.widenSingleton.isInstanceOf[ExprType] =>
hasApply
case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] =>
p(methRef)
case mt: MethodicType =>
p(mt.narrow)
case _ =>
followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
hasApply
}

/** Does `tp` have an extension method named `xname` with this-argument `argType` and
Expand Down Expand Up @@ -1661,7 +1668,7 @@ trait Applications extends Compatibility {
tp2.isVarArgsMethod
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible)
else
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible)
isApplicableTerm(tp1.paramInfos, WildcardType, ArgMatch.Compatible)(alt2)
}
case tp1: PolyType => // (2)
inContext(ctx.fresh.setExploreTyperState()) {
Expand Down Expand Up @@ -1804,8 +1811,7 @@ trait Applications extends Compatibility {
def narrowMostSpecific(alts: List[TermRef])(using Context): List[TermRef] = {
record("narrowMostSpecific")
alts match {
case Nil => alts
case _ :: Nil => alts
case Nil | _ :: Nil => alts
case alt1 :: alt2 :: Nil =>
compare(alt1, alt2) match {
case 1 => alt1 :: Nil
Expand Down Expand Up @@ -1937,6 +1943,21 @@ trait Applications extends Compatibility {
else resolve(alts)
end resolveOverloaded

def isApplicableTerm(argTypes: List[Type], resultType: Type, argMatch: ArgMatch)(using Context): TermRef => Boolean =
term => term.widen match
case _: AppliedType =>
isApplicableType(term, argTypes, resultType)
case _ =>
isApplicableMethodRef(term, argTypes, resultType, argMatch)

def isApplicableTerm(argTypes: List[Tree], resultType: Type, keepConstraint: Boolean, argMatch: ArgMatch)(using Context): TermRef => Boolean =
term => term.widen match
case _: AppliedType =>
isApplicableType(term, argTypes, resultType, keepConstraint)
case _ =>
isApplicableMethodRef(term, argTypes, resultType, keepConstraint, argMatch)


/** This private version of `resolveOverloaded` does the bulk of the work of
* overloading resolution, but does neither result adaptation nor apply insertion.
* It might be called twice from the public `resolveOverloaded` method, once with
Expand Down Expand Up @@ -1972,7 +1993,8 @@ trait Applications extends Compatibility {
}

def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] =
alts.filterConserve(isApplicableMethodRef(_, argTypes, resultType, ArgMatch.CompatibleCAP))
alts.filterConserve:
isApplicableTerm(argTypes, resultType, ArgMatch.CompatibleCAP)

/** Normalization steps before checking arguments:
*
Expand Down Expand Up @@ -2047,9 +2069,8 @@ trait Applications extends Compatibility {
alts

def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] =
alts.filterConserve(alt =>
isApplicableMethodRef(alt, args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)
)
alts.filterConserve:
isApplicableTerm(args, resultType, keepConstraint = false, ArgMatch.CompatibleCAP)

record("resolveOverloaded.FunProto", alts.length)
val alts1 = narrowBySize(alts)
Expand Down
16 changes: 8 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3684,8 +3684,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Search for an alternative that does not take parameters.
* If there is one, return it, otherwise emit an error.
*/
def tryParameterless(alts: List[TermRef])(error: => tpd.Tree): Tree =
alts.filter(_.info.isParameterless) match
def noApplyNoParams: TermRef => Boolean =
alt => alt.info.isParameterless && !alt.info.member(nme.apply).exists

def tryNoApplyParameterless(alts: List[TermRef])(error: => tpd.Tree): Tree =
alts.filter(noApplyNoParams) match
case alt :: Nil => readaptSimplified(tree.withType(alt))
case _ =>
if altDenots.exists(_.info.paramInfoss == ListOfNil) then
Expand All @@ -3712,7 +3715,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(errorNoMatch)
case _ =>
tryParameterless(alts)(errorNoMatch)
tryNoApplyParameterless(alts)(errorNoMatch)

case ambiAlts =>
// If there are ambiguous alternatives, and:
Expand All @@ -3739,11 +3742,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
tree.withType(UnspecifiedErrorType)
else
pt match
case _: FunProto =>
errorAmbiguous
case _ =>
tryParameterless(alts)(errorAmbiguous)

case _: FunProto => errorAmbiguous
case _ => tryNoApplyParameterless(alts)(errorAmbiguous)
end match
end adaptOverloaded

Expand Down
54 changes: 54 additions & 0 deletions tests/neg/i18294.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
-- [E051] Reference Error: tests/neg/i18294.scala:18:30 ----------------------------------------------------------------
18 | val test01: Int => String = f1 // error
| ^^
| Ambiguous overload. The overloaded alternatives of method f1 with types
| => Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
-- [E051] Reference Error: tests/neg/i18294.scala:19:30 ----------------------------------------------------------------
19 | val test02: Int => String = f2 // error
| ^^
| Ambiguous overload. The overloaded alternatives of value f2 with types
| Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
-- [E051] Reference Error: tests/neg/i18294.scala:22:36 ----------------------------------------------------------------
22 | val test1: Int => String = DefDef.f4 // error
| ^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of method f4 in object DefDef with types
| => Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
-- [E051] Reference Error: tests/neg/i18294.scala:23:37 ----------------------------------------------------------------
23 | def dtest1: Int => String = DefDef.f4 // error
| ^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of method f4 in object DefDef with types
| => Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
-- [E051] Reference Error: tests/neg/i18294.scala:25:36 ----------------------------------------------------------------
25 | val test2: Int => String = DefVal.f5 // error
| ^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of value f5 in object DefVal with types
| Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
-- [E051] Reference Error: tests/neg/i18294.scala:26:37 ----------------------------------------------------------------
26 | def dtest2: Int => String = DefVal.f5 // error
| ^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of value f5 in object DefVal with types
| Int => String
| (s: Int): String
| both match expected type Int => String
|
| longer explanation available when compiling with `-explain`
26 changes: 26 additions & 0 deletions tests/neg/i18294.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
def f1(s: Int): String = "a"
def f1: Int => String = _ => "b"

def f2(s: Int): String = "a"
val f2: Int => String = _ => "b"

def f3(s: Int): String = "a"
def f3(): Int => String = _ => "b"

object DefDef:
def f4(s: Int): String = "a"
def f4: Int => String = _ => "b"
object DefVal:
def f5(s: Int): String = "a"
val f5: Int => String = _ => "b"

@main def Test =
val test01: Int => String = f1 // error
val test02: Int => String = f2 // error
val test03: Int => String = f3

val test1: Int => String = DefDef.f4 // error
def dtest1: Int => String = DefDef.f4 // error

val test2: Int => String = DefVal.f5 // error
def dtest2: Int => String = DefVal.f5 // error