Skip to content

Commit 2f00820

Browse files
committed
Refine disambiguation rules
If both alternatives have same weights and same number of implicit parameters, treat them as normal (non-implicit) methods and try again.
1 parent 3d1045d commit 2f00820

File tree

4 files changed

+122
-30
lines changed

4 files changed

+122
-30
lines changed

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

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12971297
// # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2
12981298
var implicitBalance: Int = 0
12991299

1300-
/** Widen the type of synthetic implied methods from the implementation class to the
1300+
/** Widen the result type of synthetic implied methods from the implementation class to the
13011301
* type that's implemented. Example
13021302
*
13031303
* implied I[X] for T { ... }
@@ -1316,46 +1316,61 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13161316
* objects, since these are anyway taken to be more specific than methods
13171317
* (by condition 3a above).
13181318
*/
1319-
def widenImplied(tp: Type, alt: TermRef): Type =
1320-
if (alt.symbol.is(SyntheticImpliedMethod))
1321-
tp.parents match {
1322-
case Nil => tp
1323-
case ps => ps.reduceLeft(AndType(_, _))
1324-
}
1325-
else tp
1319+
def widenImplied(tp: Type, alt: TermRef): Type = tp match {
1320+
case mt: MethodType if mt.isImplicitMethod =>
1321+
mt.derivedLambdaType(mt.paramNames, mt.paramInfos, widenImplied(mt.resultType, alt))
1322+
case pt: PolyType =>
1323+
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, widenImplied(pt.resultType, alt))
1324+
case _ =>
1325+
if (alt.symbol.is(SyntheticImpliedMethod))
1326+
tp.parents match {
1327+
case Nil => tp
1328+
case ps => ps.reduceLeft(AndType(_, _))
1329+
}
1330+
else tp
1331+
}
13261332

13271333
/** Drop any implicit parameter section */
1328-
def stripImplicit(tp: Type, alt: TermRef, weight: Int): Type = tp match {
1334+
def stripImplicit(tp: Type, weight: Int): Type = tp match {
13291335
case mt: MethodType if mt.isImplicitMethod =>
13301336
implicitBalance += mt.paramInfos.length * weight
1331-
widenImplied(resultTypeApprox(mt), alt)
1337+
resultTypeApprox(mt)
13321338
case pt: PolyType =>
1333-
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, alt, weight))
1339+
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight))
13341340
case _ =>
1335-
widenImplied(tp, alt)
1341+
tp
13361342
}
13371343

13381344
val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol
13391345
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
13401346
val ownerScore = compareOwner(owner1, owner2)
13411347

1342-
val tp1 = stripImplicit(alt1.widen, alt1, -1)
1343-
val tp2 = stripImplicit(alt2.widen, alt2, +1)
1344-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1345-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1346-
1347-
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
1348+
def compareWithTypes(tp1: Type, tp2: Type) = {
1349+
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1350+
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1351+
1352+
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
1353+
if (ownerScore == 1)
1354+
if (winsType1 || !winsType2) 1 else 0
1355+
else if (ownerScore == -1)
1356+
if (winsType2 || !winsType1) -1 else 0
1357+
else if (winsType1)
1358+
if (winsType2) 0 else 1
1359+
else
1360+
if (winsType2) -1 else 0
1361+
}
13481362

1349-
def tieBreak = -implicitBalance.signum
1363+
val fullType1 = widenImplied(alt1.widen, alt1)
1364+
val fullType2 = widenImplied(alt2.widen, alt2)
1365+
val strippedType1 = stripImplicit(fullType1, -1)
1366+
val strippedType2 = stripImplicit(fullType2, +1)
13501367

1351-
if (ownerScore == 1)
1352-
if (winsType1 || !winsType2) 1 else tieBreak
1353-
else if (ownerScore == -1)
1354-
if (winsType2 || !winsType1) -1 else tieBreak
1355-
else if (winsType1)
1356-
if (winsType2) tieBreak else 1
1357-
else
1358-
if (winsType2) -1 else tieBreak
1368+
val result = compareWithTypes(strippedType1, strippedType2)
1369+
if (result != 0) result
1370+
else if (implicitBalance != 0) -implicitBalance.signum
1371+
else if ((strippedType1 `ne` fullType1) || (strippedType2 `ne` fullType2))
1372+
compareWithTypes(fullType1, fullType2)
1373+
else 0
13591374
}}
13601375

13611376
def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {

docs/docs/reference/changed-features/implicit-resolution.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ affect implicits on the language level.
9999
buzz(1) // error: ambiguous
100100

101101
7. The rule for picking a _most specific_ alternative among a set of overloaded or implicit
102-
alternatives is refined to take the number of inferable parameters into account. All else
102+
alternatives is refined to take inferable parameters into account. All else
103103
being equal, an alternative that takes more inferable parameters is taken to be more specific
104-
than an alternative that takes fewer. The following paragraph in the SLS is affected by this change:
104+
than an alternative that takes fewer. If both alternatives take the same number of
105+
inferable parameters, we try to choose between them as if they were methods with regular parameters.
106+
The following paragraph in the SLS is affected by this change:
105107

106108
_Original version:_
107109

@@ -112,6 +114,9 @@ affect implicits on the language level.
112114
An alternative A is _more specific_ than an alternative B if
113115

114116
- the relative weight of A over B is greater than the relative weight of B over A, or
115-
- the relative weights are the same and A takes more inferable parameters than B.
117+
- the relative weights are the same and A takes more inferable parameters than B, or
118+
- the relative weights and the number of inferable parameters are the same and
119+
A is more specific than B if all inferable parameters in either alternative are
120+
replaced by regular parameters.
116121

117122
[//]: # todo: expand with precise rules

tests/run/implicit-specifity-2.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class Low
2+
object Low {
3+
implicit val low: Low = new Low
4+
}
5+
class Medium extends Low
6+
object Medium {
7+
implicit val medium: Medium = new Medium
8+
}
9+
class High extends Medium
10+
object High {
11+
implicit val high: High = new High
12+
}
13+
14+
class Foo[T](val i: Int)
15+
object Foo {
16+
def apply[T](implicit fooT: Foo[T]): Int = fooT.i
17+
18+
implicit def foo[T](implicit priority: Low): Foo[T] = new Foo[T](0)
19+
implicit def foobar[T](implicit priority: Low): Foo[Bar[T]] = new Foo[Bar[T]](1)
20+
implicit def foobarbaz(implicit priority: Low): Foo[Bar[Baz]] = new Foo[Bar[Baz]](2)
21+
}
22+
class Bar[T]
23+
object Bar {
24+
implicit def foobar[T](implicit priority: Medium): Foo[Bar[T]] = new Foo[Bar[T]](3)
25+
implicit def foobarbaz(implicit priority: Medium): Foo[Bar[Baz]] = new Foo[Bar[Baz]](4)
26+
}
27+
class Baz
28+
object Baz {
29+
implicit def baz(implicit priority: High): Foo[Bar[Baz]] = new Foo[Bar[Baz]](5)
30+
}
31+
32+
object Test extends App {
33+
assert(Foo[Int] == 0)
34+
assert(Foo[Bar[Int]] == 3)
35+
assert(Foo[Bar[Baz]] == 5)
36+
}

tests/run/implied-specifity-2.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class Low
2+
object Low {
3+
implied low for Low
4+
}
5+
class Medium extends Low
6+
object Medium {
7+
implied medium for Medium
8+
}
9+
class High extends Medium
10+
object High {
11+
implied high for High
12+
}
13+
14+
class Foo[T](val i: Int)
15+
object Foo {
16+
def apply[T] given (fooT: Foo[T]): Int = fooT.i
17+
18+
implied foo[T] given Low for Foo[T](0)
19+
implied foobar[T] given Low for Foo[Bar[T]](1)
20+
implied foobarbaz given Low for Foo[Bar[Baz]](2)
21+
}
22+
class Bar[T]
23+
object Bar {
24+
implied foobar[T] given Medium for Foo[Bar[T]](3)
25+
implied foobarbaz given Medium for Foo[Bar[Baz]](4)
26+
}
27+
class Baz
28+
object Baz {
29+
implied baz given High for Foo[Bar[Baz]](5)
30+
}
31+
32+
object Test extends App {
33+
assert(Foo[Int] == 0)
34+
assert(Foo[Bar[Int]] == 3)
35+
assert(Foo[Bar[Baz]] == 5)
36+
}

0 commit comments

Comments
 (0)