Skip to content

Commit 019e45d

Browse files
authored
Merge pull request #5925 from dotty-staging/change-implicit-specifity
Take implicit parameters into account for is-as-specific computations
2 parents cd2bd96 + 2f00820 commit 019e45d

File tree

10 files changed

+302
-23
lines changed

10 files changed

+302
-23
lines changed

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,10 @@ object Flags {
612612
final val InlineParam: FlagConjunction = allOf(Inline, Param)
613613

614614
/** An extension method */
615-
final val ExtensionMethod = allOf(Method, Extension)
615+
final val ExtensionMethod = allOf(Extension, Method)
616+
617+
/** An implied method */
618+
final val SyntheticImpliedMethod: FlagConjunction = allOf(Synthetic, Implied, Method)
616619

617620
/** An enum case */
618621
final val EnumCase: FlagConjunction = allOf(Enum, Case)

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

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

@@ -1290,12 +1294,49 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12901294
(flip(tp1) relaxed_<:< flip(tp2)) || viewExists(tp1, tp2)
12911295
}
12921296

1297+
// # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2
1298+
var implicitBalance: Int = 0
1299+
1300+
/** Widen the result type of synthetic implied methods from the implementation class to the
1301+
* type that's implemented. Example
1302+
*
1303+
* implied I[X] for T { ... }
1304+
*
1305+
* This desugars to
1306+
*
1307+
* class I[X] extends T { ... }
1308+
* implied def I[X]: I[X] = new I[X]
1309+
*
1310+
* To compare specificity we should compare with `T`, not with its implementation `I[X]`.
1311+
* No such widening is performed for implied aliases, which are not synthetic. E.g.
1312+
*
1313+
* implied J[X] for T = rhs
1314+
*
1315+
* already has the right result type `T`. Neither is widening performed for implied
1316+
* objects, since these are anyway taken to be more specific than methods
1317+
* (by condition 3a above).
1318+
*/
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+
}
1332+
12931333
/** Drop any implicit parameter section */
1294-
def stripImplicit(tp: Type): Type = tp match {
1334+
def stripImplicit(tp: Type, weight: Int): Type = tp match {
12951335
case mt: MethodType if mt.isImplicitMethod =>
1336+
implicitBalance += mt.paramInfos.length * weight
12961337
resultTypeApprox(mt)
12971338
case pt: PolyType =>
1298-
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
1339+
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight))
12991340
case _ =>
13001341
tp
13011342
}
@@ -1304,21 +1345,32 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13041345
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
13051346
val ownerScore = compareOwner(owner1, owner2)
13061347

1307-
val tp1 = stripImplicit(alt1.widen)
1308-
val tp2 = stripImplicit(alt2.widen)
1309-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1310-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1311-
1312-
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
1313-
1314-
if (ownerScore == 1)
1315-
if (winsType1 || !winsType2) 1 else 0
1316-
else if (ownerScore == -1)
1317-
if (winsType2 || !winsType1) -1 else 0
1318-
else if (winsType1)
1319-
if (winsType2) 0 else 1
1320-
else
1321-
if (winsType2) -1 else 0
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+
}
1362+
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)
1367+
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
13221374
}}
13231375

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

compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class UserDefinedErrorMessages extends ErrorMessagesTest {
113113
|class C {
114114
| @annotation.implicitAmbiguous("msg A=${A}")
115115
| implicit def f[A](implicit x: String): Int = 1
116-
| implicit def g: Int = 2
116+
| implicit def g(implicit x: String): Int = 2
117117
| def test: Unit = {
118118
| implicit val s: String = "Hello"
119119
| implicitly[Int]

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,26 @@ affect implicits on the language level.
3535

3636
This will now resolve the `implicitly` call to `j`, because `j` is nested
3737
more deeply than `i`. Previously, this would have resulted in an
38-
ambiguity error.
38+
ambiguity error. The previous possibility of an implicit search failure
39+
due to _shadowing_ (where an implicit is hidden by a nested definition)
40+
no longer applies.
3941

40-
3. The treatment of ambiguity errors has changed. If an ambiguity is encountered
42+
3. Package prefixes no longer contribute to the implicit scope of a type.
43+
Example:
44+
45+
package p
46+
implied a for A
47+
48+
object o {
49+
implied b for B
50+
type C
51+
}
52+
53+
Both `a` and `b` are visible as implicits at the point of the definition
54+
of `type C`. However, a reference to `p.o.C` outside of package `p` will
55+
have only `b` in its implicit scope but not `a`.
56+
57+
4. The treatment of ambiguity errors has changed. If an ambiguity is encountered
4158
in some recursive step of an implicit search, the ambiguity is propagated to the caller.
4259
Example: Say you have the following definitions:
4360

@@ -65,14 +82,14 @@ affect implicits on the language level.
6582
which implements negation directly. For any query type `Q`: `Not[Q]` succeeds if and only if
6683
the implicit search for `Q` fails.
6784

68-
4. The treatment of divergence errors has also changed. A divergent implicit is
85+
5. The treatment of divergence errors has also changed. A divergent implicit is
6986
treated as a normal failure, after which alternatives are still tried. This also makes
7087
sense: Encountering a divergent implicit means that we assume that no finite
7188
solution can be found on the given path, but another path can still be tried. By contrast
7289
most (but not all) divergence errors in Scala 2 would terminate the implicit
7390
search as a whole.
7491

75-
5. Scala-2 gives a lower level of priority to implicit conversions with call-by-name
92+
6. Scala-2 gives a lower level of priority to implicit conversions with call-by-name
7693
parameters relative to implicit conversions with call-by-value parameters. Dotty
7794
drops this distinction. So the following code snippet would be ambiguous in Dotty:
7895

@@ -81,4 +98,25 @@ affect implicits on the language level.
8198
def buzz(y: A) = ???
8299
buzz(1) // error: ambiguous
83100

101+
7. The rule for picking a _most specific_ alternative among a set of overloaded or implicit
102+
alternatives is refined to take inferable parameters into account. All else
103+
being equal, an alternative that takes more inferable parameters is taken to be more specific
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:
107+
108+
_Original version:_
109+
110+
> 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.
111+
112+
_Modified version:_
113+
114+
An alternative A is _more specific_ than an alternative B if
115+
116+
- the relative weight of A over B is greater than the relative weight of B over A, or
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.
121+
84122
[//]: # 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-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/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/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+
}

tests/run/implied-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](0)
6+
implied fallback[T] for Show[T](1)
7+
}
8+
9+
class Generic
10+
object Generic {
11+
implied gen for Generic
12+
implied showGen[T] given Generic for Show[T](2)
13+
}
14+
15+
object Contextual {
16+
trait Context
17+
implied ctx for Context
18+
implied showGen2[T] given Generic for Show[T](2)
19+
implied showGen3[T] given Generic, Context for 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)