Skip to content

Commit d2a6392

Browse files
authored
Refine overloading and implicit disambiguation (#20084)
We sometimes have two alternatives a.m and b.m with the same symbol and type but different prefixes. Previously these would always be ambiguous. We now try to disambiguate this so that the alternative with the more specific prefix wins. To determine this, we widen prefixes also going from module classes to their parents and then compare the resulting types. This might fix a problem in ScalaTest that popped up after #20054.
2 parents 321d0d2 + 822eec6 commit d2a6392

File tree

3 files changed

+77
-27
lines changed

3 files changed

+77
-27
lines changed

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

+58-25
Original file line numberDiff line numberDiff line change
@@ -1615,11 +1615,12 @@ trait Applications extends Compatibility {
16151615
* Module classes also inherit the relationship from their companions. This means,
16161616
* if no direct derivation exists between `sym1` and `sym2` also perform the following
16171617
* tests:
1618-
* - If both sym1 and sym1 are module classes that have companion classes,
1619-
* and sym2 does not inherit implicit members from a base class (#),
1620-
* compare the companion classes.
1621-
* - If sym1 is a module class with a companion, and sym2 is a normal class or trait,
1622-
* compare the companion with sym2.
1618+
* - If both sym1 and sym2 are module classes that have companion classes,
1619+
* compare the companion classes. Return the result of that comparison,
1620+
* provided the module class with the larger companion class does not itself
1621+
* inherit implicit members from a base class (#),
1622+
* - If one sym is a module class with a companion, and the other is a normal class or trait,
1623+
* compare the companion with the other class or trait.
16231624
*
16241625
* Condition (#) is necessary to make `compareOwner(_, _) > 0` a transitive relation.
16251626
* For instance:
@@ -1643,17 +1644,22 @@ trait Applications extends Compatibility {
16431644
* This means we get an ambiguity between `a` and `b` in all cases.
16441645
*/
16451646
def compareOwner(sym1: Symbol, sym2: Symbol)(using Context): Int =
1647+
def cls1 = sym1.companionClass
1648+
def cls2 = sym2.companionClass
16461649
if sym1 == sym2 then 0
16471650
else if sym1.isSubClass(sym2) then 1
16481651
else if sym2.isSubClass(sym1) then -1
1649-
else if sym1.is(Module) then
1650-
val cls1 = sym1.companionClass
1651-
if sym2.is(Module) then
1652-
if sym2.thisType.implicitMembers.forall(_.symbol.owner == sym2) then // test for (#)
1653-
compareOwner(cls1, sym2.companionClass)
1654-
else 0
1655-
else compareOwner(cls1, sym2)
1656-
else 0
1652+
else
1653+
if sym1.is(Module) && sym2.is(Module) then
1654+
val r = compareOwner(cls1, cls2)
1655+
if r == 0 then 0
1656+
else
1657+
val larger = if r < 0 then sym1 else sym2
1658+
if larger.thisType.implicitMembers.forall(_.symbol.owner == larger) then r
1659+
else 0
1660+
else if sym1.is(Module) then compareOwner(cls1, sym2)
1661+
else if sym2.is(Module) then compareOwner(sym1, cls2)
1662+
else 0
16571663

16581664
/** Compare two alternatives of an overloaded call or an implicit search.
16591665
*
@@ -1808,10 +1814,38 @@ trait Applications extends Compatibility {
18081814
else tp
18091815
}
18101816

1811-
def compareWithTypes(tp1: Type, tp2: Type) = {
1817+
def widenPrefix(alt: TermRef): Type = alt.prefix.widen match
1818+
case pre: (TypeRef | ThisType) if pre.typeSymbol.is(Module) =>
1819+
pre.parents.reduceLeft(TypeComparer.andType(_, _))
1820+
case wpre => wpre
1821+
1822+
/** If two alternatives have the same symbol, we pick the one with the most
1823+
* specific prefix. To determine that, we widen the prefix types and also
1824+
* widen module classes to the intersection of their parent classes. Then
1825+
* if one of the resulting types is a more specific value type than the other,
1826+
* it wins. Example:
1827+
*
1828+
* trait A { given M = ... }
1829+
* trait B extends A
1830+
* object a extends A
1831+
* object b extends B
1832+
*
1833+
* In this case `b.M` would be regarded as more specific than `a.M`.
1834+
*/
1835+
def comparePrefixes =
1836+
val pre1 = widenPrefix(alt1)
1837+
val pre2 = widenPrefix(alt2)
1838+
val winsPrefix1 = isAsSpecificValueType(pre1, pre2)
1839+
val winsPrefix2 = isAsSpecificValueType(pre2, pre1)
1840+
if winsPrefix1 == winsPrefix2 then 0
1841+
else if winsPrefix1 then 1
1842+
else -1
1843+
1844+
def compareWithTypes(tp1: Type, tp2: Type) =
18121845
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1813-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1814-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1846+
1847+
val winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1848+
val winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
18151849

18161850
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
18171851
if winsType1 && winsType2
@@ -1820,15 +1854,14 @@ trait Applications extends Compatibility {
18201854
// alternatives are the same after following ExprTypes, pick one of them
18211855
// (prefer the one that is not a method, but that's arbitrary).
18221856
if alt1.widenExpr =:= alt2 then -1 else 1
1823-
else if ownerScore == 1 then
1824-
if winsType1 || !winsType2 then 1 else 0
1825-
else if ownerScore == -1 then
1826-
if winsType2 || !winsType1 then -1 else 0
1827-
else if winsType1 then
1828-
if winsType2 then 0 else 1
1829-
else
1830-
if winsType2 then -1 else 0
1831-
}
1857+
else ownerScore match
1858+
case 1 => if winsType1 || !winsType2 then 1 else 0
1859+
case -1 => if winsType2 || !winsType1 then -1 else 0
1860+
case 0 =>
1861+
if winsType1 != winsType2 then if winsType1 then 1 else -1
1862+
else if alt1.symbol == alt2.symbol then comparePrefixes
1863+
else 0
1864+
end compareWithTypes
18321865

18331866
if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1
18341867
else if alt2.symbol.is(ConstructorProxy) && !alt1.symbol.is(ConstructorProxy) then 1

compiler/src/dotty/tools/dotc/util/Signatures.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ object Signatures {
495495
case res => List(tpe)
496496

497497
def isSyntheticEvidence(name: String) =
498-
if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
499-
symbol.paramSymss.flatten.find(_.name.show == name).exists(_.flags.is(Flags.Implicit))
498+
name.startsWith(NameKinds.ContextBoundParamName.separator)
499+
&& symbol.paramSymss.flatten.find(_.name.show == name).exists(_.flags.is(Flags.Implicit))
500500

501501
def toTypeParam(tpe: PolyType): List[Param] =
502502
val evidenceParams = (tpe.paramNamess.flatten zip tpe.paramInfoss.flatten).flatMap:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
class I[X]
3+
class J[X]
4+
5+
trait A:
6+
given I[B] = ???
7+
given (using I[B]): J[B] = ???
8+
object A extends A
9+
10+
trait B extends A
11+
object B extends B
12+
13+
//import B.given, A.given
14+
15+
def Test =
16+
summon[I[B]]
17+
summon[J[B]]

0 commit comments

Comments
 (0)