Skip to content

Take implicit parameters into account for is-as-specific computations #5925

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

Merged
merged 5 commits into from
Feb 22, 2019
Merged
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
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,10 @@ object Flags {
final val InlineParam: FlagConjunction = allOf(Inline, Param)

/** An extension method */
final val ExtensionMethod = allOf(Method, Extension)
final val ExtensionMethod = allOf(Extension, Method)

/** An implied method */
final val SyntheticImpliedMethod: FlagConjunction = allOf(Synthetic, Implied, Method)

/** An enum case */
final val EnumCase: FlagConjunction = allOf(Enum, Case)
Expand Down
86 changes: 69 additions & 17 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
*
* - A1's owner derives from A2's owner.
* - A1's type is more specific than A2's type.
*
* If that tournament yields a draw, a tiebreak is applied where
* an alternative that takes more implicit parameters wins over one
* that takes fewer.
*/
def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) {

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

// # skipped implicit parameters in tp1 - # skipped implicit parameters in tp2
var implicitBalance: Int = 0

/** Widen the result type of synthetic implied methods from the implementation class to the
* type that's implemented. Example
*
* implied I[X] for T { ... }
*
* This desugars to
*
* class I[X] extends T { ... }
* implied def I[X]: I[X] = new I[X]
*
* To compare specificity we should compare with `T`, not with its implementation `I[X]`.
* No such widening is performed for implied aliases, which are not synthetic. E.g.
*
* implied J[X] for T = rhs
*
* already has the right result type `T`. Neither is widening performed for implied
* objects, since these are anyway taken to be more specific than methods
* (by condition 3a above).
*/
def widenImplied(tp: Type, alt: TermRef): Type = tp match {
case mt: MethodType if mt.isImplicitMethod =>
mt.derivedLambdaType(mt.paramNames, mt.paramInfos, widenImplied(mt.resultType, alt))
case pt: PolyType =>
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, widenImplied(pt.resultType, alt))
case _ =>
if (alt.symbol.is(SyntheticImpliedMethod))
tp.parents match {
case Nil => tp
case ps => ps.reduceLeft(AndType(_, _))
}
else tp
}

/** Drop any implicit parameter section */
def stripImplicit(tp: Type): Type = tp match {
def stripImplicit(tp: Type, weight: Int): Type = tp match {
case mt: MethodType if mt.isImplicitMethod =>
implicitBalance += mt.paramInfos.length * weight
resultTypeApprox(mt)
case pt: PolyType =>
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType, weight))
case _ =>
tp
}
Expand All @@ -1304,21 +1345,32 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol
val ownerScore = compareOwner(owner1, owner2)

val tp1 = stripImplicit(alt1.widen)
val tp2 = stripImplicit(alt2.widen)
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)

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

if (ownerScore == 1)
if (winsType1 || !winsType2) 1 else 0
else if (ownerScore == -1)
if (winsType2 || !winsType1) -1 else 0
else if (winsType1)
if (winsType2) 0 else 1
else
if (winsType2) -1 else 0
def compareWithTypes(tp1: Type, tp2: Type) = {
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)

overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
if (ownerScore == 1)
if (winsType1 || !winsType2) 1 else 0
else if (ownerScore == -1)
if (winsType2 || !winsType1) -1 else 0
else if (winsType1)
if (winsType2) 0 else 1
else
if (winsType2) -1 else 0
}

val fullType1 = widenImplied(alt1.widen, alt1)
val fullType2 = widenImplied(alt2.widen, alt2)
val strippedType1 = stripImplicit(fullType1, -1)
val strippedType2 = stripImplicit(fullType2, +1)

val result = compareWithTypes(strippedType1, strippedType2)
if (result != 0) result
else if (implicitBalance != 0) -implicitBalance.signum
else if ((strippedType1 `ne` fullType1) || (strippedType2 `ne` fullType2))
compareWithTypes(fullType1, fullType2)
else 0
}}

def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class UserDefinedErrorMessages extends ErrorMessagesTest {
|class C {
| @annotation.implicitAmbiguous("msg A=${A}")
| implicit def f[A](implicit x: String): Int = 1
| implicit def g: Int = 2
| implicit def g(implicit x: String): Int = 2
| def test: Unit = {
| implicit val s: String = "Hello"
| implicitly[Int]
Expand Down
46 changes: 42 additions & 4 deletions docs/docs/reference/changed-features/implicit-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,26 @@ affect implicits on the language level.

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

3. The treatment of ambiguity errors has changed. If an ambiguity is encountered
3. Package prefixes no longer contribute to the implicit scope of a type.
Example:

package p
implied a for A

object o {
implied b for B
type C
}

Both `a` and `b` are visible as implicits at the point of the definition
of `type C`. However, a reference to `p.o.C` outside of package `p` will
have only `b` in its implicit scope but not `a`.

4. The treatment of ambiguity errors has changed. If an ambiguity is encountered
in some recursive step of an implicit search, the ambiguity is propagated to the caller.
Example: Say you have the following definitions:

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

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

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

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

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

_Original version:_

> 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.

_Modified version:_

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, or
- the relative weights are the same and A takes more inferable parameters than B, or
- the relative weights and the number of inferable parameters are the same and
A is more specific than B if all inferable parameters in either alternative are
replaced by regular parameters.

[//]: # todo: expand with precise rules
27 changes: 27 additions & 0 deletions tests/neg/overloading-specifity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Shows that overloading resolution does not test implicits to decide
// applicability. A best alternative is picked first, and then implicits
// are searched for this one.
case class Show[T](val i: Int)
class Show1[T](i: Int) extends Show[T](i)

class Generic
object Generic {
implicit val gen: Generic = new Generic
implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2)
}

object Test extends App {
trait Context
//implied ctx for Context

object a {
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
def foo[T](implicit gen: Generic, ctx: Context): Show1[T] = new Show1[T](2)
}
object b {
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
def foo[T]: Show[T] = new Show[T](2)
}

assert(a.foo[Int].i == 2) // error: no implicit argument of type Test.Context was found for parameter ctx
}
36 changes: 36 additions & 0 deletions tests/run/implicit-specifity-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Low
object Low {
implicit val low: Low = new Low
}
class Medium extends Low
object Medium {
implicit val medium: Medium = new Medium
}
class High extends Medium
object High {
implicit val high: High = new High
}

class Foo[T](val i: Int)
object Foo {
def apply[T](implicit fooT: Foo[T]): Int = fooT.i

implicit def foo[T](implicit priority: Low): Foo[T] = new Foo[T](0)
implicit def foobar[T](implicit priority: Low): Foo[Bar[T]] = new Foo[Bar[T]](1)
implicit def foobarbaz(implicit priority: Low): Foo[Bar[Baz]] = new Foo[Bar[Baz]](2)
}
class Bar[T]
object Bar {
implicit def foobar[T](implicit priority: Medium): Foo[Bar[T]] = new Foo[Bar[T]](3)
implicit def foobarbaz(implicit priority: Medium): Foo[Bar[Baz]] = new Foo[Bar[Baz]](4)
}
class Baz
object Baz {
implicit def baz(implicit priority: High): Foo[Bar[Baz]] = new Foo[Bar[Baz]](5)
}

object Test extends App {
assert(Foo[Int] == 0)
assert(Foo[Bar[Int]] == 3)
assert(Foo[Bar[Baz]] == 5)
}
30 changes: 30 additions & 0 deletions tests/run/implicit-specifity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
case class Show[T](val i: Int)
object Show {
def apply[T](implicit st: Show[T]): Int = st.i

implied showInt for Show[Int] = new Show[Int](0)
implied fallback[T] for Show[T] = new Show[T](1)
}

class Generic
object Generic {
implied gen for Generic = new Generic
implied showGen[T] given Generic for Show[T] = new Show[T](2)
}

object Contextual {
trait Context
implied ctx for Context
implied showGen[T] given Generic for Show[T] = new Show[T](2)
implied showGen[T] given Generic, Context for Show[T] = new Show[T](3)
}

object Test extends App {
assert(Show[Int] == 0)
assert(Show[String] == 1)
assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list

{ import implied Contextual._
assert(Show[Generic] == 3)
}
}
36 changes: 36 additions & 0 deletions tests/run/implied-specifity-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Low
object Low {
implied low for Low
}
class Medium extends Low
object Medium {
implied medium for Medium
}
class High extends Medium
object High {
implied high for High
}

class Foo[T](val i: Int)
object Foo {
def apply[T] given (fooT: Foo[T]): Int = fooT.i

implied foo[T] given Low for Foo[T](0)
implied foobar[T] given Low for Foo[Bar[T]](1)
implied foobarbaz given Low for Foo[Bar[Baz]](2)
}
class Bar[T]
object Bar {
implied foobar[T] given Medium for Foo[Bar[T]](3)
implied foobarbaz given Medium for Foo[Bar[Baz]](4)
}
class Baz
object Baz {
implied baz given High for Foo[Bar[Baz]](5)
}

object Test extends App {
assert(Foo[Int] == 0)
assert(Foo[Bar[Int]] == 3)
assert(Foo[Bar[Baz]] == 5)
}
30 changes: 30 additions & 0 deletions tests/run/implied-specifity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
case class Show[T](val i: Int)
object Show {
def apply[T](implicit st: Show[T]): Int = st.i

implied showInt for Show[Int](0)
implied fallback[T] for Show[T](1)
}

class Generic
object Generic {
implied gen for Generic
implied showGen[T] given Generic for Show[T](2)
}

object Contextual {
trait Context
implied ctx for Context
implied showGen2[T] given Generic for Show[T](2)
implied showGen3[T] given Generic, Context for Show[T](3)
}

object Test extends App {
assert(Show[Int] == 0)
assert(Show[String] == 1)
assert(Show[Generic] == 2) // showGen beats fallback due to longer argument list

{ import implied Contextual._
assert(Show[Generic] == 3)
}
}
27 changes: 27 additions & 0 deletions tests/run/overloading-specifity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Shows that now implicit parameters act as a tie-breaker.
// The alternative with more implicit parameters wins.
case class Show[T](val i: Int)

class Generic
object Generic {
implicit val gen: Generic = new Generic
implicit def showGen[T](implicit gen: Generic): Show[T] = new Show[T](2)
}

object Test extends App {
trait Context
implied ctx for Context

object a {
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
def foo[T](implicit gen: Generic, ctx: Context): Show[T] = new Show[T](2)
}
object b {
def foo[T](implicit gen: Generic): Show[T] = new Show[T](1)
def foo[T]: Show[T] = new Show[T](2)
}

assert(a.foo[Int].i == 2)
assert(b.foo[Int].i == 1)

}