Skip to content

Commit 590b496

Browse files
Fixes and tweaks to implicit priority change scheme (#21339)
Based on #21328: We now use a left-biased scheme, as follows. From 3.7 on: A given x: X is better than a given or implicit y: Y if y can be instantiated/widened to X. An implicit x: X is better than a given or implicit y: Y if y can be instantiated to a supertype of X. Use owner score for givens as a tie breaker if after all other tests we still have an ambiguity. This is not transitive, but the PR implements a scheme to work around that. Other changes: - Drop special handling of NotGiven in prioritization. The previous logic pretended to do so, but was ineffective. - Fix healAmbiguous to compareAlternatives with disambiguate = true, to account for changes made in #21045
2 parents 40b8c7a + 0361991 commit 590b496

39 files changed

+581
-115
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ object desugar {
483483
params.map: param =>
484484
val normFlags = param.mods.flags &~ GivenOrImplicit | (mparam.mods.flags & (GivenOrImplicit))
485485
param.withMods(param.mods.withFlags(normFlags))
486-
.showing(i"ADAPTED PARAM $result ${result.mods.flags} for ${meth.name}")
486+
.showing(i"adapted param $result ${result.mods.flags} for ${meth.name}", Printers.desugar)
487487
else params
488488
(normParams ++ mparams) :: Nil
489489
case mparams :: mparamss1 =>

compiler/src/dotty/tools/dotc/printing/Formatting.scala

+7-10
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package dotty.tools
22
package dotc
33
package printing
44

5-
import scala.language.unsafeNulls
6-
75
import scala.collection.mutable
86

97
import core.*
@@ -52,7 +50,11 @@ object Formatting {
5250
object ShowAny extends Show[Any]:
5351
def show(x: Any): Shown = x
5452

55-
class ShowImplicits3:
53+
class ShowImplicits4:
54+
given [X: Show]: Show[X | Null] with
55+
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
56+
57+
class ShowImplicits3 extends ShowImplicits4:
5658
given Show[Product] = ShowAny
5759

5860
class ShowImplicits2 extends ShowImplicits3:
@@ -77,15 +79,10 @@ object Formatting {
7779
given [K: Show, V: Show]: Show[Map[K, V]] with
7880
def show(x: Map[K, V]) =
7981
CtxShow(x.map((k, v) => s"${toStr(k)} => ${toStr(v)}"))
80-
end given
8182

8283
given [H: Show, T <: Tuple: Show]: Show[H *: T] with
8384
def show(x: H *: T) =
8485
CtxShow(toStr(x.head) *: toShown(x.tail).asInstanceOf[Tuple])
85-
end given
86-
87-
given [X: Show]: Show[X | Null] with
88-
def show(x: X | Null) = if x == null then "null" else CtxShow(toStr(x.nn))
8986

9087
given Show[FlagSet] with
9188
def show(x: FlagSet) = x.flagsString
@@ -148,8 +145,8 @@ object Formatting {
148145
private def treatArg(arg: Shown, suffix: String)(using Context): (String, String) = arg.runCtxShow match {
149146
case arg: Seq[?] if suffix.indexOf('%') == 0 && suffix.indexOf('%', 1) != -1 =>
150147
val end = suffix.indexOf('%', 1)
151-
val sep = StringContext.processEscapes(suffix.substring(1, end))
152-
(arg.mkString(sep), suffix.substring(end + 1))
148+
val sep = StringContext.processEscapes(suffix.substring(1, end).nn)
149+
(arg.mkString(sep), suffix.substring(end + 1).nn)
153150
case arg: Seq[?] =>
154151
(arg.map(showArg).mkString("[", ", ", "]"), suffix)
155152
case arg =>

compiler/src/dotty/tools/dotc/reporting/messages.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2988,7 +2988,7 @@ class MissingImplicitArgument(
29882988

29892989
/** Default error message for non-nested ambiguous implicits. */
29902990
def defaultAmbiguousImplicitMsg(ambi: AmbiguousImplicits) =
2991-
s"Ambiguous given instances: ${ambi.explanation}${location("of")}"
2991+
s"Ambiguous given instances: ${ambi.explanation}${location("of")}${ambi.priorityChangeWarningNote}"
29922992

29932993
/** Default error messages for non-ambiguous implicits, or nested ambiguous
29942994
* implicits.

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

+46-29
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,17 @@ trait Applications extends Compatibility {
17621762
else if sym2.is(Module) then compareOwner(sym1, cls2)
17631763
else 0
17641764

1765+
enum CompareScheme:
1766+
case Old // Normal specificity test for overloading resolution (where `preferGeneral` is false)
1767+
// and in mode Scala3-migration when we compare with the old Scala 2 rules.
1768+
1769+
case Intermediate // Intermediate rules: better means specialize, but map all type arguments downwards
1770+
// These are enabled for 3.0-3.5, or if OldImplicitResolution
1771+
// is specified, and also for all comparisons between old-style implicits,
1772+
1773+
case New // New rules: better means generalize, givens (and extensions) always beat implicits
1774+
end CompareScheme
1775+
17651776
/** Compare two alternatives of an overloaded call or an implicit search.
17661777
*
17671778
* @param alt1, alt2 Non-overloaded references indicating the two choices
@@ -1788,6 +1799,15 @@ trait Applications extends Compatibility {
17881799
*/
17891800
def compare(alt1: TermRef, alt2: TermRef, preferGeneral: Boolean = false)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
17901801
record("resolveOverloaded.compare")
1802+
val scheme =
1803+
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
1804+
if !preferGeneral || Feature.migrateTo3 && oldResolution then
1805+
CompareScheme.Old
1806+
else if Feature.sourceVersion.isAtMost(SourceVersion.`3.5`)
1807+
|| oldResolution
1808+
|| alt1.symbol.is(Implicit) && alt2.symbol.is(Implicit)
1809+
then CompareScheme.Intermediate
1810+
else CompareScheme.New
17911811

17921812
/** Is alternative `alt1` with type `tp1` as good as alternative
17931813
* `alt2` with type `tp2` ?
@@ -1830,15 +1850,15 @@ trait Applications extends Compatibility {
18301850
isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
18311851
}
18321852
case _ => // (3)
1833-
def compareValues(tp1: Type, tp2: Type)(using Context) =
1834-
isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit), alt2.symbol.is(Implicit))
1853+
def compareValues(tp2: Type)(using Context) =
1854+
isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit))
18351855
tp2 match
18361856
case tp2: MethodType => true // (3a)
18371857
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
18381858
case tp2: PolyType => // (3b)
1839-
explore(compareValues(tp1, instantiateWithTypeVars(tp2)))
1859+
explore(compareValues(instantiateWithTypeVars(tp2)))
18401860
case _ => // 3b)
1841-
compareValues(tp1, tp2)
1861+
compareValues(tp2)
18421862
}
18431863

18441864
/** Test whether value type `tp1` is as good as value type `tp2`.
@@ -1849,7 +1869,7 @@ trait Applications extends Compatibility {
18491869
* available in 3.0-migration if mode `Mode.OldImplicitResolution` is turned on as well.
18501870
* It is used to highlight differences between Scala 2 and 3 behavior.
18511871
*
1852-
* - In Scala 3.0-3.5, the behavior is as follows: `T <:p U` iff there is an implicit conversion
1872+
* - In Scala 3.0-3.6, the behavior is as follows: `T <:p U` iff there is an implicit conversion
18531873
* from `T` to `U`, or
18541874
*
18551875
* flip(T) <: flip(U)
@@ -1864,21 +1884,20 @@ trait Applications extends Compatibility {
18641884
* of parameters are not affected. So `T <: U` would imply `Set[Cmp[U]] <:p Set[Cmp[T]]`,
18651885
* as usual, because `Set` is non-variant.
18661886
*
1867-
* - From Scala 3.6, `T <:p U` means `T <: U` or `T` convertible to `U`
1887+
* - From Scala 3.7, `T <:p U` means `T <: U` or `T` convertible to `U`
18681888
* for overloading resolution (when `preferGeneral is false), and the opposite relation
18691889
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
1870-
* (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept.
1890+
* (when `preferGeneral` is true). For old-style implicit values, the 3.5 behavior is kept.
18711891
* If one of the alternatives is an implicit and the other is a given (or an extension), the implicit loses.
18721892
*
1873-
* - In Scala 3.5 and Scala 3.6-migration, we issue a warning if the result under
1874-
* Scala 3.6 differ wrt to the old behavior up to 3.5.
1893+
* - In Scala 3.6 and Scala 3.7-migration, we issue a warning if the result under
1894+
* Scala 3.7 differs wrt to the old behavior up to 3.6.
18751895
*
18761896
* Also and only for given resolution: If a compared type refers to a given or its module class, use
18771897
* the intersection of its parent classes instead.
18781898
*/
1879-
def isAsGoodValueType(tp1: Type, tp2: Type, alt1IsImplicit: Boolean, alt2IsImplicit: Boolean)(using Context): Boolean =
1880-
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
1881-
if !preferGeneral || Feature.migrateTo3 && oldResolution then
1899+
def isAsGoodValueType(tp1: Type, tp2: Type, alt1IsImplicit: Boolean)(using Context): Boolean =
1900+
if scheme == CompareScheme.Old then
18821901
// Normal specificity test for overloading resolution (where `preferGeneral` is false)
18831902
// and in mode Scala3-migration when we compare with the old Scala 2 rules.
18841903
isCompatible(tp1, tp2)
@@ -1892,13 +1911,7 @@ trait Applications extends Compatibility {
18921911
val tp1p = prepare(tp1)
18931912
val tp2p = prepare(tp2)
18941913

1895-
if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`)
1896-
|| oldResolution
1897-
|| alt1IsImplicit && alt2IsImplicit
1898-
then
1899-
// Intermediate rules: better means specialize, but map all type arguments downwards
1900-
// These are enabled for 3.0-3.5, and for all comparisons between old-style implicits,
1901-
// and in 3.5 and 3.6-migration when we compare with previous rules.
1914+
if scheme == CompareScheme.Intermediate || alt1IsImplicit then
19021915
val flip = new TypeMap:
19031916
def apply(t: Type) = t match
19041917
case t @ AppliedType(tycon, args) =>
@@ -1909,9 +1922,7 @@ trait Applications extends Compatibility {
19091922
case _ => mapOver(t)
19101923
(flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2)
19111924
else
1912-
// New rules: better means generalize, givens (and extensions) always beat implicits
1913-
if alt1IsImplicit != alt2IsImplicit then alt2IsImplicit
1914-
else (tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
1925+
(tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
19151926
end isAsGoodValueType
19161927

19171928
/** Widen the result type of synthetic given methods from the implementation class to the
@@ -1982,13 +1993,19 @@ trait Applications extends Compatibility {
19821993
// alternatives are the same after following ExprTypes, pick one of them
19831994
// (prefer the one that is not a method, but that's arbitrary).
19841995
if alt1.widenExpr =:= alt2 then -1 else 1
1985-
else ownerScore match
1986-
case 1 => if winsType1 || !winsType2 then 1 else 0
1987-
case -1 => if winsType2 || !winsType1 then -1 else 0
1988-
case 0 =>
1989-
if winsType1 != winsType2 then if winsType1 then 1 else -1
1990-
else if alt1.symbol == alt2.symbol then comparePrefixes
1991-
else 0
1996+
else
1997+
// For new implicit resolution, take ownerscore as more significant than type resolution
1998+
// Reason: People use owner hierarchies to explicitly prioritize, we should not
1999+
// break that by changing implicit priority of types.
2000+
def drawOrOwner =
2001+
if scheme == CompareScheme.New then ownerScore else 0
2002+
ownerScore match
2003+
case 1 => if winsType1 || !winsType2 then 1 else drawOrOwner
2004+
case -1 => if winsType2 || !winsType1 then -1 else drawOrOwner
2005+
case 0 =>
2006+
if winsType1 != winsType2 then if winsType1 then 1 else -1
2007+
else if alt1.symbol == alt2.symbol then comparePrefixes
2008+
else 0
19922009
end compareWithTypes
19932010

19942011
if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1

0 commit comments

Comments
 (0)