diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 6baa7f6f65ad..31982388bcc7 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index b8053dff8395..f513d2ba10d3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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) { @@ -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 } @@ -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") { diff --git a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala index 864bc98e3f26..829852d695a5 100644 --- a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala +++ b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala @@ -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] diff --git a/docs/docs/reference/changed-features/implicit-resolution.md b/docs/docs/reference/changed-features/implicit-resolution.md index 62bc17fc2735..927246f8ccc3 100644 --- a/docs/docs/reference/changed-features/implicit-resolution.md +++ b/docs/docs/reference/changed-features/implicit-resolution.md @@ -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: @@ -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: @@ -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 diff --git a/tests/neg/overloading-specifity.scala b/tests/neg/overloading-specifity.scala new file mode 100644 index 000000000000..d794313b45d4 --- /dev/null +++ b/tests/neg/overloading-specifity.scala @@ -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 +} \ No newline at end of file diff --git a/tests/run/implicit-specifity-2.scala b/tests/run/implicit-specifity-2.scala new file mode 100644 index 000000000000..c84a208aa023 --- /dev/null +++ b/tests/run/implicit-specifity-2.scala @@ -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) +} \ No newline at end of file diff --git a/tests/run/implicit-specifity.scala b/tests/run/implicit-specifity.scala new file mode 100644 index 000000000000..425359ba02b5 --- /dev/null +++ b/tests/run/implicit-specifity.scala @@ -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) + } +} diff --git a/tests/run/implied-specifity-2.scala b/tests/run/implied-specifity-2.scala new file mode 100644 index 000000000000..117141d50a50 --- /dev/null +++ b/tests/run/implied-specifity-2.scala @@ -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) +} \ No newline at end of file diff --git a/tests/run/implied-specifity.scala b/tests/run/implied-specifity.scala new file mode 100644 index 000000000000..4d0b72916183 --- /dev/null +++ b/tests/run/implied-specifity.scala @@ -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) + } +} diff --git a/tests/run/overloading-specifity.scala b/tests/run/overloading-specifity.scala new file mode 100644 index 000000000000..79746a3c1552 --- /dev/null +++ b/tests/run/overloading-specifity.scala @@ -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) + +} \ No newline at end of file