Skip to content

Commit 87803b0

Browse files
committed
Make behavior dependent on Scala version
- Old behavior: up to 3.4 - New behavior: from 3.5, 3.5-migration warns on behavior change
1 parent c4c80e0 commit 87803b0

File tree

5 files changed

+108
-59
lines changed

5 files changed

+108
-59
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,6 @@ object Mode {
133133
/** We are typing the body of an inline method */
134134
val InlineableBody: Mode = newMode(21, "InlineableBody")
135135

136-
val NewGivenRules: Mode = newMode(22, "NewGivenRules")
137-
138136
/** We are synthesizing the receiver of an extension method */
139137
val SynthesizeExtMethodReceiver: Mode = newMode(23, "SynthesizeExtMethodReceiver")
140138

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

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import ProtoTypes.*
2222
import Inferencing.*
2323
import reporting.*
2424
import Nullables.*, NullOpsDecorator.*
25-
import config.Feature
25+
import config.{Feature, SourceVersion}
2626

2727
import collection.mutable
2828
import config.Printers.{overload, typr, unapp}
@@ -1622,6 +1622,12 @@ trait Applications extends Compatibility {
16221622
/** Compare two alternatives of an overloaded call or an implicit search.
16231623
*
16241624
* @param alt1, alt2 Non-overloaded references indicating the two choices
1625+
* @param preferGeneral When comparing two value types, prefer the more general one
1626+
* over the more specific one iff `preferGeneral` is true.
1627+
* `preferGeneral` is set to `true` when we compare two given values, since
1628+
* then we want the most general evidence that matches the target
1629+
* type. It is set to `false` for overloading resolution, when we want the
1630+
* most specific type instead.
16251631
* @return 1 if 1st alternative is preferred over 2nd
16261632
* -1 if 2nd alternative is preferred over 1st
16271633
* 0 if neither alternative is preferred over the other
@@ -1640,27 +1646,25 @@ trait Applications extends Compatibility {
16401646
def compare(alt1: TermRef, alt2: TermRef, preferGeneral: Boolean = false)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
16411647
record("resolveOverloaded.compare")
16421648

1643-
val newGivenRules =
1644-
ctx.mode.is(Mode.NewGivenRules) && alt1.symbol.is(Given)
1649+
val compareGivens = alt1.symbol.is(Given) || alt2.symbol.is(Given)
16451650

1646-
/** Is alternative `alt1` with type `tp1` as specific as alternative
1651+
/** Is alternative `alt1` with type `tp1` as good as alternative
16471652
* `alt2` with type `tp2` ?
16481653
*
1649-
* 1. A method `alt1` of type `(p1: T1, ..., pn: Tn)U` is as specific as `alt2`
1654+
* 1. A method `alt1` of type `(p1: T1, ..., pn: Tn)U` is as good as `alt2`
16501655
* if `alt1` is nullary or `alt2` is applicable to arguments (p1, ..., pn) of
16511656
* types T1,...,Tn. If the last parameter `pn` has a vararg type T*, then
16521657
* `alt1` must be applicable to arbitrary numbers of `T` parameters (which
16531658
* implies that it must be a varargs method as well).
16541659
* 2. A polymorphic member of type [a1 >: L1 <: U1, ..., an >: Ln <: Un]T is as
1655-
* specific as `alt2` of type `tp2` if T is as specific as `tp2` under the
1660+
* good as `alt2` of type `tp2` if T is as good as `tp2` under the
16561661
* assumption that for i = 1,...,n each ai is an abstract type name bounded
16571662
* from below by Li and from above by Ui.
16581663
* 3. A member of any other type `tp1` is:
1659-
* a. always as specific as a method or a polymorphic method.
1660-
* b. as specific as a member of any other type `tp2` if `tp1` is compatible
1661-
* with `tp2`.
1664+
* a. always as good as a method or a polymorphic method.
1665+
* b. as good as a member of any other type `tp2` is `asGoodValueType(tp1, tp2) = true`
16621666
*/
1663-
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
1667+
def isAsGood(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
16641668
tp1 match
16651669
case tp1: MethodType => // (1)
16661670
tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
@@ -1682,65 +1686,60 @@ trait Applications extends Compatibility {
16821686
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.srcPos)
16831687

16841688
val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1685-
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
1689+
isAsGood(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
16861690
}
16871691
case _ => // (3)
16881692
tp2 match
16891693
case tp2: MethodType => true // (3a)
16901694
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
16911695
case tp2: PolyType => // (3b)
1692-
explore(isAsSpecificValueType(tp1, instantiateWithTypeVars(tp2)))
1696+
explore(isAsGoodValueType(tp1, instantiateWithTypeVars(tp2)))
16931697
case _ => // 3b)
1694-
isAsSpecificValueType(tp1, tp2)
1698+
isAsGoodValueType(tp1, tp2)
16951699
}
16961700

1697-
/** Test whether value type `tp1` is as specific as value type `tp2`.
1698-
* Let's abbreviate this to `tp1 <:s tp2`.
1699-
* Previously, `<:s` was the same as `<:`. This behavior is still
1700-
* available under mode `Mode.OldOverloadingResolution`. The new behavior
1701-
* is different, however. Here, `T <:s U` iff
1701+
/** Test whether value type `tp1` is as good as value type `tp2`.
1702+
* Let's abbreviate this to `tp1 <:p tp2`. The behavior depends on the Scala version
1703+
* and mode.
17021704
*
1703-
* flip(T) <: flip(U)
1705+
* - In Scala 2, `<:p` was the same as `<:`. This behavior is still
1706+
* available in 3.0-migration if mode `Mode.OldOverloadingResolution` is turned on as well.
1707+
* It is used to highlight differences between Scala 2 and 3 behavior.
17041708
*
1705-
* where `flip` changes covariant occurrences of contravariant type parameters to
1706-
* covariant ones. Intuitively `<:s` means subtyping `<:`, except that all arguments
1707-
* to contravariant parameters are compared as if they were covariant. E.g. given class
1709+
* - In Scala 3.0-3.4, the behavior is as follows: `T <:p U` iff there is an impliit conversion
1710+
* from `T` to `U`, or
17081711
*
1709-
* class Cmp[-X]
1712+
* flip(T) <: flip(U)
17101713
*
1711-
* `Cmp[T] <:s Cmp[U]` if `T <: U`. On the other hand, non-variant occurrences
1712-
* of parameters are not affected. So `T <: U` would imply `Set[Cmp[U]] <:s Set[Cmp[T]]`,
1713-
* as usual, because `Set` is non-variant.
1714+
* where `flip` changes covariant occurrences of contravariant type parameters to
1715+
* covariant ones. Intuitively `<:p` means subtyping `<:`, except that all arguments
1716+
* to contravariant parameters are compared as if they were covariant. E.g. given class
17141717
*
1715-
* This relation might seem strange, but it models closely what happens for methods.
1716-
* Indeed, if we integrate the existing rules for methods into `<:s` we have now that
1718+
* class Cmp[-X]
17171719
*
1718-
* (T)R <:s (U)R
1720+
* `Cmp[T] <:p Cmp[U]` if `T <: U`. On the other hand, non-variant occurrences
1721+
* of parameters are not affected. So `T <: U` would imply `Set[Cmp[U]] <:p Set[Cmp[T]]`,
1722+
* as usual, because `Set` is non-variant.
17191723
*
1720-
* iff
1724+
* - From Scala 3.5, `T <:p U` means `T <: U` or `T` convertible to `U`
1725+
* for overloading resolution (when `preferGeneral is false), and the opposite relation
1726+
* `U <: T` or `U convertible to `T` for implicit disambiguation between givens
1727+
* (when `preferGeneral` is true). For old-style implicit values, the 3.4 behavior is kept.
17211728
*
1722-
* T => R <:s U => R
1729+
* - In Scala 3.5-migration, use the 3.5 scheme normally, and the 3.4 scheme if
1730+
* `Mode.OldOverloadingResolution` is on. This is used to highlight differences in the
1731+
* two resolution schemes.
17231732
*
1724-
* Also: If a compared type refers to a given or its module class, use
1733+
* Also and only for given resolution: If a compared type refers to a given or its module class, use
17251734
* the intersection of its parent classes instead.
17261735
*/
1727-
def isAsSpecificValueType(tp1: Type, tp2: Type)(using Context) =
1728-
if !preferGeneral || ctx.mode.is(Mode.OldOverloadingResolution) then
1729-
// Normal specificity test for overloading resultion (where `preferGeneral` is false)
1736+
def isAsGoodValueType(tp1: Type, tp2: Type)(using Context) =
1737+
val oldResolution = ctx.mode.is(Mode.OldOverloadingResolution)
1738+
if !preferGeneral || Feature.migrateTo3 && oldResolution then
1739+
// Normal specificity test for overloading resolution (where `preferGeneral` is false)
17301740
// and in mode Scala3-migration when we compare with the old Scala 2 rules.
17311741
isCompatible(tp1, tp2)
17321742
else
1733-
val flip = new TypeMap {
1734-
def apply(t: Type) = t match {
1735-
case t @ AppliedType(tycon, args) =>
1736-
def mapArg(arg: Type, tparam: TypeParamInfo) =
1737-
if (variance > 0 && tparam.paramVarianceSign < 0) defn.FunctionNOf(arg :: Nil, defn.UnitType)
1738-
else arg
1739-
mapOver(t.derivedAppliedType(tycon, args.zipWithConserve(tycon.typeParams)(mapArg)))
1740-
case _ => mapOver(t)
1741-
}
1742-
}
1743-
17441743
def prepare(tp: Type) = tp.stripTypeVar match
17451744
case tp: NamedType if tp.symbol.is(Module) && tp.symbol.sourceModule.is(Given) =>
17461745
tp.widen.widenToParents
@@ -1749,11 +1748,26 @@ trait Applications extends Compatibility {
17491748

17501749
val tp1p = prepare(tp1)
17511750
val tp2p = prepare(tp2)
1752-
if newGivenRules then
1753-
(tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
1754-
else
1751+
1752+
if Feature.sourceVersion.isAtMost(SourceVersion.`3.3`) // !!! change to 3.4
1753+
|| oldResolution
1754+
|| !compareGivens
1755+
then
1756+
// Intermediate rules: better means specialize, but map all type arguments downwards
1757+
// These are enabled for 3.0-3.4, and in 3.5-migration when we compare with previous rules
1758+
val flip = new TypeMap:
1759+
def apply(t: Type) = t match
1760+
case t @ AppliedType(tycon, args) =>
1761+
def mapArg(arg: Type, tparam: TypeParamInfo) =
1762+
if (variance > 0 && tparam.paramVarianceSign < 0) defn.FunctionNOf(arg :: Nil, defn.UnitType)
1763+
else arg
1764+
mapOver(t.derivedAppliedType(tycon, args.zipWithConserve(tycon.typeParams)(mapArg)))
1765+
case _ => mapOver(t)
17551766
(flip(tp1p) relaxed_<:< flip(tp2p)) || viewExists(tp1, tp2)
1756-
end isAsSpecificValueType
1767+
else
1768+
// New rules: better means generalize
1769+
(tp2p relaxed_<:< tp1p) || viewExists(tp2, tp1)
1770+
end isAsGoodValueType
17571771

17581772
/** Widen the result type of synthetic given methods from the implementation class to the
17591773
* type that's implemented. Example
@@ -1786,8 +1800,8 @@ trait Applications extends Compatibility {
17861800

17871801
def compareWithTypes(tp1: Type, tp2: Type) = {
17881802
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1789-
val winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1790-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1803+
val winsType1 = isAsGood(alt1, tp1, alt2, tp2)
1804+
def winsType2 = isAsGood(alt2, tp2, alt1, tp1)
17911805

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

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import Scopes.newScope
2626
import Typer.BindingPrec, BindingPrec.*
2727
import Hashable.*
2828
import util.{EqHashMap, Stats}
29-
import config.{Config, Feature}
29+
import config.{Config, Feature, SourceVersion}
3030
import Feature.migrateTo3
3131
import config.Printers.{implicits, implicitsDetailed}
3232
import collection.mutable
@@ -1289,14 +1289,29 @@ trait Implicits:
12891289
* 0 if neither alternative is preferred over the other
12901290
*/
12911291
def compareAlternatives(alt1: RefAndLevel, alt2: RefAndLevel): Int =
1292+
def comp(using Context) = explore(compare(alt1.ref, alt2.ref, preferGeneral = true))
1293+
def oldCompare() = comp(using searchContext().addMode(Mode.OldOverloadingResolution))
1294+
def newCompare() = comp(using searchContext())
1295+
12921296
if alt1.ref eq alt2.ref then 0
12931297
else if alt1.level != alt2.level then alt1.level - alt2.level
1294-
else
1295-
val was = explore(compare(alt1.ref, alt2.ref, preferGeneral = true))(using searchContext())
1296-
val now = explore(compare(alt1.ref, alt2.ref, preferGeneral = true))(using searchContext().addMode(Mode.NewGivenRules))
1298+
else if Feature.sourceVersion.isAtMost(SourceVersion.`3.3`) then // !!! change to 3.4
1299+
oldCompare()
1300+
else if Feature.sourceVersion == SourceVersion.`3.5-migration` || true then // !!! drop
1301+
val was = oldCompare()
1302+
val now = newCompare()
12971303
if was != now then
1298-
println(i"change in preference for $pt between ${alt1.ref} and ${alt2.ref}, was: $was, now: $now at $srcPos")
1304+
def choice(cmp: Int) = cmp match
1305+
case -1 => "the second alternative"
1306+
case 1 => "the first alternative"
1307+
case _ => "none - it's ambiguous"
1308+
report.warning(
1309+
em"""Change in given search preference for $pt between alternatives ${alt1.ref} and ${alt2.ref}
1310+
|Previous choice: ${choice(was)}
1311+
|New choice : ${choice(now)}""", srcPos)
12991312
now
1313+
else newCompare()
1314+
end compareAlternatives
13001315

13011316
/** If `alt1` is also a search success, try to disambiguate as follows:
13021317
* - If alt2 is preferred over alt1, pick alt2, otherwise return an

tests/neg/given-triangle.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Error: tests/neg/given-triangle.scala:16:18 -------------------------------------------------------------------------
2+
16 |@main def Test = f // error
3+
| ^
4+
| Change in given search preference for A between alternatives (given_A : A) and (given_B : B)
5+
| Previous choice: the second alternative
6+
| New choice : the first alternative

tests/neg/given-triangle.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -source 3.5-migration -Xfatal-warnings
2+
3+
class A
4+
class B extends A
5+
class C extends A
6+
7+
given A = A()
8+
given B = B()
9+
given C = C()
10+
11+
def f(using a: A, b: B, c: C) =
12+
println(a.getClass)
13+
println(b.getClass)
14+
println(c.getClass)
15+
16+
@main def Test = f // error

0 commit comments

Comments
 (0)