Skip to content

Commit c036bfb

Browse files
committed
Handle alternatives with same signature in overloading resolution
Turn an assertion into an checked error with an error message pointing to the root of the problem.
1 parent 03a394b commit c036bfb

File tree

5 files changed

+77
-9
lines changed

5 files changed

+77
-9
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,13 +1308,13 @@ object messages {
13081308

13091309
}
13101310

1311-
class AmbiguousOverload(tree: tpd.Tree, val alternatives: List[SingleDenotation], pt: Type)(
1311+
class AmbiguousOverload(tree: tpd.Tree, val alternatives: List[SingleDenotation], pt: Type, addendum: String = "")(
13121312
implicit ctx: Context)
13131313
extends ReferenceMsg(AmbiguousOverloadID) {
13141314
private def all = if (alternatives.length == 2) "both" else "all"
13151315
def msg =
1316-
s"""|Ambiguous overload. The ${err.overloadedAltsStr(alternatives)}
1317-
|$all match ${err.expectedTypeStr(pt)}""".stripMargin
1316+
em"""|Ambiguous overload. The ${err.overloadedAltsStr(alternatives)}
1317+
|$all match ${err.expectedTypeStr(pt)}$addendum""".stripMargin
13181318
def explain =
13191319
em"""|There are ${alternatives.length} methods that could be referenced as the compiler knows too little
13201320
|about the expected type.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1364,7 +1364,6 @@ trait Applications extends Compatibility {
13641364
*/
13651365
def compare(alt1: TermRef, alt2: TermRef)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
13661366
record("compare")
1367-
assert(alt1 ne alt2)
13681367

13691368
/** Is alternative `alt1` with type `tp1` as specific as alternative
13701369
* `alt2` with type `tp2` ?

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,8 +2796,9 @@ class Typer extends Namer
27962796

27972797
def adaptOverloaded(ref: TermRef) = {
27982798
val altDenots = ref.denot.alternatives
2799-
println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")
2800-
val alts = altDenots.map(TermRef(ref.prefix, ref.name, _))
2799+
typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")
2800+
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
2801+
val alts = altDenots.map(altRef)
28012802
resolveOverloaded(alts, pt) match {
28022803
case alt :: Nil =>
28032804
readaptSimplified(tree.withType(alt))
@@ -2823,11 +2824,20 @@ class Typer extends Namer
28232824
noMatches
28242825
}
28252826
}
2826-
case alts =>
2827+
case ambiAlts =>
28272828
if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType)
28282829
else {
2829-
val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation])
2830-
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt))
2830+
val remainingDenots = altDenots.filter(denot => ambiAlts.contains(altRef(denot)))
2831+
val addendum =
2832+
if ambiAlts.toSet.size != ambiAlts.size then
2833+
// Several variants have the same signature. This can happen for structural
2834+
// type selections. See i8736.scala
2835+
"""|
2836+
|
2837+
|Note: this happens because one or more alternatives have the same erasure,
2838+
| so they cannot be distinguished by overloading resolution""".stripMargin
2839+
else ""
2840+
errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt, addendum))
28312841
}
28322842
}
28332843
}

tests/neg/i8736.check

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i8736.scala:29:29 -------------------------------------------------------------
2+
29 | def res1: String = rec.get("k") // error: type mismatch
3+
| ^^^^^^^^^^^^
4+
| Found: Any
5+
| Required: String
6+
-- [E007] Type Mismatch Error: tests/neg/i8736.scala:30:29 -------------------------------------------------------------
7+
30 | def res2: Int = rec.get("v") // error: type mismatch
8+
| ^^^^^^^^^^^^
9+
| Found: Any
10+
| Required: Int
11+
-- [E051] Reference Error: tests/neg/i8736.scala:31:26 -----------------------------------------------------------------
12+
31 | def res3: Boolean = rec.get("z") // error: ambiguous
13+
| ^^^^^^^
14+
| Ambiguous overload. The overloaded alternatives of method (k: ("k" : String)): String with types
15+
| (k: ("k" : String)): String
16+
| (k: ("v" : String)): Int
17+
| (k: ("z" : String)): Boolean
18+
| all match arguments (("z" : String))
19+
|
20+
| Note: this happens because one or more alternatives have the same erasure,
21+
| so they cannot be distinguised by overloading resolution
22+
23+
longer explanation available when compiling with `-explain`

tests/neg/i8736.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import reflect.Selectable.reflectiveSelectable
2+
object App extends App {
3+
4+
trait Rec0[K <: String] {
5+
private[App] val map: Map[String, Any]
6+
def get(k: K): Any
7+
}
8+
def Rec0(map0: Map[String, Any]) = new Rec0[String] {
9+
val map = map0
10+
def get(k: String): Any = map(k)
11+
}
12+
13+
type Rec[K <: String, V0] = Rec0[K] { def get(k: K): V0 }
14+
def field[V](s: String)(v: V): Rec[s.type, V] = Rec0(Map(s -> v)).asInstanceOf[Rec[s.type, V]]
15+
16+
implicit class RecOps[R <: Rec0[_]](has: R) {
17+
def +[K1 <: String, V1](that: Rec[K1, V1]): R with Rec[K1, V1] = Rec0(has.map ++ that.map).asInstanceOf[R with Rec[K1, V1]]
18+
}
19+
20+
def rec:
21+
Rec["k", String]
22+
with Rec["v", Int]
23+
with Rec["z", Boolean]
24+
= {
25+
field("k")("Str") +
26+
field("v")(0) +
27+
field("z")(true)
28+
}
29+
def res1: String = rec.get("k") // error: type mismatch
30+
def res2: Int = rec.get("v") // error: type mismatch
31+
def res3: Boolean = rec.get("z") // error: ambiguous
32+
33+
// def res4: Boolean = rec.get("nofield")
34+
35+
println((res1, res2, res3))
36+
}

0 commit comments

Comments
 (0)