Skip to content

Commit b644d30

Browse files
committed
Refine prioritization rules between givens and implicits
In the new system, givens always beat implicits when comparing value types. This is necessary to maintain two invariants in the new system: - When comparing old-style implicits nothing changes, we still use the old rules. - The isAsGood comparison is transitive. Exception: NotGiven has to be treated at lower priority.
1 parent c54dbbf commit b644d30

File tree

1 file changed

+15
-11
lines changed

1 file changed

+15
-11
lines changed

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,8 +1733,6 @@ trait Applications extends Compatibility {
17331733
def compare(alt1: TermRef, alt2: TermRef, preferGeneral: Boolean = false)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
17341734
record("resolveOverloaded.compare")
17351735

1736-
val compareGivens = alt1.symbol.is(Given) && alt2.symbol.is(Given)
1737-
17381736
/** Is alternative `alt1` with type `tp1` as good as alternative
17391737
* `alt2` with type `tp2` ?
17401738
*
@@ -1749,7 +1747,7 @@ trait Applications extends Compatibility {
17491747
* from below by Li and from above by Ui.
17501748
* 3. A member of any other type `tp1` is:
17511749
* a. always as good as a method or a polymorphic method.
1752-
* b. as good as a member of any other type `tp2` is `asGoodValueType(tp1, tp2) = true`
1750+
* b. as good as a member of any other type `tp2` if `asGoodValueType(tp1, tp2) = true`
17531751
*/
17541752
def isAsGood(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
17551753
tp1 match
@@ -1776,13 +1774,17 @@ trait Applications extends Compatibility {
17761774
isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
17771775
}
17781776
case _ => // (3)
1777+
def isGiven(alt: TermRef) =
1778+
alt1.symbol.is(Given) && alt.symbol != defn.NotGivenClass
1779+
def compareValues(tp1: Type, tp2: Type)(using Context) =
1780+
isAsGoodValueType(tp1, tp2, isGiven(alt1), isGiven(alt2))
17791781
tp2 match
17801782
case tp2: MethodType => true // (3a)
17811783
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
17821784
case tp2: PolyType => // (3b)
1783-
explore(isAsGoodValueType(tp1, instantiateWithTypeVars(tp2)))
1785+
explore(compareValues(tp1, instantiateWithTypeVars(tp2)))
17841786
case _ => // 3b)
1785-
isAsGoodValueType(tp1, tp2)
1787+
compareValues(tp1, tp2)
17861788
}
17871789

17881790
/** Test whether value type `tp1` is as good as value type `tp2`.
@@ -1812,6 +1814,7 @@ trait Applications extends Compatibility {
18121814
* for overloading resolution (when `preferGeneral is false), and the opposite relation
18131815
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
18141816
* (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept.
1817+
* If one of the alternatives is a given and the other is an implicit, the given wins.
18151818
*
18161819
* - In Scala 3.5-migration, use the 3.5 scheme normally, and the 3.4 scheme if
18171820
* `Mode.OldImplicitResolution` is on. This is used to highlight differences in the
@@ -1820,7 +1823,7 @@ trait Applications extends Compatibility {
18201823
* Also and only for given resolution: If a compared type refers to a given or its module class, use
18211824
* the intersection of its parent classes instead.
18221825
*/
1823-
def isAsGoodValueType(tp1: Type, tp2: Type)(using Context) =
1826+
def isAsGoodValueType(tp1: Type, tp2: Type, alt1isGiven: Boolean, alt2isGiven: Boolean)(using Context): Boolean =
18241827
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
18251828
if !preferGeneral || Feature.migrateTo3 && oldResolution then
18261829
// Normal specificity test for overloading resolution (where `preferGeneral` is false)
@@ -1838,7 +1841,7 @@ trait Applications extends Compatibility {
18381841

18391842
if Feature.sourceVersion.isAtMost(SourceVersion.`3.4`)
18401843
|| oldResolution
1841-
|| !compareGivens
1844+
|| !alt1isGiven && !alt2isGiven
18421845
then
18431846
// Intermediate rules: better means specialize, but map all type arguments downwards
18441847
// These are enabled for 3.0-3.4, and for all comparisons between old-style implicits,
@@ -1853,8 +1856,9 @@ trait Applications extends Compatibility {
18531856
case _ => mapOver(t)
18541857
(flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2)
18551858
else
1856-
// New rules: better means generalize
1857-
(tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
1859+
// New rules: better means generalize, givens always beat implicits
1860+
if alt1isGiven != alt2isGiven then alt1isGiven
1861+
else (tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
18581862
end isAsGoodValueType
18591863

18601864
/** Widen the result type of synthetic given methods from the implementation class to the
@@ -1907,8 +1911,8 @@ trait Applications extends Compatibility {
19071911
def comparePrefixes =
19081912
val pre1 = widenPrefix(alt1)
19091913
val pre2 = widenPrefix(alt2)
1910-
val winsPrefix1 = isAsGoodValueType(pre1, pre2)
1911-
val winsPrefix2 = isAsGoodValueType(pre2, pre1)
1914+
val winsPrefix1 = isCompatible(pre1, pre2)
1915+
val winsPrefix2 = isCompatible(pre2, pre1)
19121916
if winsPrefix1 == winsPrefix2 then 0
19131917
else if winsPrefix1 then 1
19141918
else -1

0 commit comments

Comments
 (0)