Skip to content

Commit 4d6b82e

Browse files
committed
Take implicit parameters into account for is-as-specific computations
Number of implicit parameters is applied as a final tie break, if two alternatives would be otherwise equally specific. I tried to roll them into type specificity first, but this fails the dotc bootstrap where we have: def name(implicit ctx: Context): Name in class Denotation val name: Name in subclass SymDenotation The `val name` wins, since it is defined in a subclass and the result types are the same. If we let the type `(implicit ctx: Context): Name` win over `Name` this would be ambiguous instead.
1 parent 4826b64 commit 4d6b82e

File tree

5 files changed

+118
-8
lines changed

5 files changed

+118
-8
lines changed

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11901190
*
11911191
* - A1's owner derives from A2's owner.
11921192
* - A1's type is more specific than A2's type.
1193+
*
1194+
* If that tournament yields a draw, a tiebreak is applied where
1195+
* an alternative that takes more implicit parameters wins over one
1196+
* that takes fewer.
11931197
*/
11941198
def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {
11951199

@@ -1289,12 +1293,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12891293
(flip(tp1) relaxed_<:< flip(tp2)) || viewExists(tp1, tp2)
12901294
}
12911295

1296+
// # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2
1297+
var implicitBalance: Int = 0
1298+
12921299
/** Drop any implicit parameter section */
1293-
def stripImplicit(tp: Type): Type = tp match {
1300+
def stripImplicit(tp: Type, weight: Int): Type = tp match {
12941301
case mt: MethodType if mt.isImplicitMethod =>
1302+
implicitBalance += mt.paramInfos.length * weight
12951303
resultTypeApprox(mt)
12961304
case pt: PolyType =>
1297-
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
1305+
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight))
12981306
case _ =>
12991307
tp
13001308
}
@@ -1303,21 +1311,23 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13031311
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
13041312
val ownerScore = compareOwner(owner1, owner2)
13051313

1306-
val tp1 = stripImplicit(alt1.widen)
1307-
val tp2 = stripImplicit(alt2.widen)
1314+
val tp1 = stripImplicit(alt1.widen, -1)
1315+
val tp2 = stripImplicit(alt2.widen, +1)
13081316
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
13091317
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
13101318

13111319
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
13121320

1321+
def tieBreak = -implicitBalance.signum
1322+
13131323
if (ownerScore == 1)
1314-
if (winsType1 || !winsType2) 1 else 0
1324+
if (winsType1 || !winsType2) 1 else tieBreak
13151325
else if (ownerScore == -1)
1316-
if (winsType2 || !winsType1) -1 else 0
1326+
if (winsType2 || !winsType1) -1 else tieBreak
13171327
else if (winsType1)
1318-
if (winsType2) 0 else 1
1328+
if (winsType2) tieBreak else 1
13191329
else
1320-
if (winsType2) -1 else 0
1330+
if (winsType2) -1 else tieBreak
13211331
}}
13221332

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

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,20 @@ affect implicits on the language level.
8181
def buzz(y: A) = ???
8282
buzz(1) // error: ambiguous
8383

84+
6. The rule for picking a _most specific_ alternative among a set of overloaded or implicit
85+
alternatives is refined to take the number of inferable parameters into account. All else
86+
being equal, an alternative that takes more inferable parameters is taken to be more specific
87+
than an alternative that takes fewer. The following paragraph in the SLS is affected by this change:
88+
89+
_Original version:_
90+
91+
> An alternative A is _more specific_ than an alternative B if the relative weight of A over B is greater than the relative weight of B over A.
92+
93+
_Modified version:_
94+
95+
An alternative A is _more specific_ than an alternative B if
96+
97+
- the relative weight of A over B is greater than the relative weight of B over A, or
98+
- the relative weights are the same and A takes more inferable parameters than B.
99+
84100
[//]: # todo: expand with precise rules

tests/neg/overloading-specifity.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Shows that overloading resolution does not test implicits to decide
2+
// applicability. A best alternative is picked first, and then implicits
3+
// are searched for this one.
4+
case class Show[T](val i: Int)
5+
class Show1[T](i: Int) extends Show[T](i)
6+
7+
class Generic
8+
object Generic {
9+
implicit val gen: Generic = new Generic
10+
implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2)
11+
}
12+
13+
object Test extends App {
14+
trait Context
15+
//implied ctx for Context
16+
17+
object a {
18+
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
19+
def foo[T](implicit gen: Generic, ctx: Context): Show1[T] = new Show1[T](2)
20+
}
21+
object b {
22+
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
23+
def foo[T]: Show[T] = new Show[T](2)
24+
}
25+
26+
assert(a.foo[Int].i == 2) // error: no implicit argument of type Test.Context was found for parameter ctx
27+
}

tests/run/implicit-specifity.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
case class Show[T](val i: Int)
2+
object Show {
3+
def apply[T](implicit st: Show[T]): Int = st.i
4+
5+
implied showInt for Show[Int] = new Show[Int](0)
6+
implied fallback[T] for Show[T] = new Show[T](1)
7+
}
8+
9+
class Generic
10+
object Generic {
11+
implied gen for Generic = new Generic
12+
implied showGen[T] given Generic for Show[T] = new Show[T](2)
13+
}
14+
15+
object Contextual {
16+
trait Context
17+
implied ctx for Context
18+
implied showGen[T] given Generic for Show[T] = new Show[T](2)
19+
implied showGen[T] given Generic, Context for Show[T] = new Show[T](3)
20+
}
21+
22+
object Test extends App {
23+
assert(Show[Int] == 0)
24+
assert(Show[String] == 1)
25+
assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list
26+
27+
{ import implied Contextual._
28+
assert(Show[Generic] == 3)
29+
}
30+
}

tests/run/overloading-specifity.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Shows that now implicit parameters act as a tie-breaker.
2+
// The alternative with more implicit parameters wins.
3+
case class Show[T](val i: Int)
4+
5+
class Generic
6+
object Generic {
7+
implicit val gen: Generic = new Generic
8+
implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2)
9+
}
10+
11+
object Test extends App {
12+
trait Context
13+
implied ctx for Context
14+
15+
object a {
16+
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
17+
def foo[T](implicit gen: Generic, ctx: Context): Show[T] = new Show[T](2)
18+
}
19+
object b {
20+
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
21+
def foo[T]: Show[T] = new Show[T](2)
22+
}
23+
24+
assert(a.foo[Int].i == 2)
25+
assert(b.foo[Int].i == 1)
26+
27+
}

0 commit comments

Comments
 (0)