Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine overloading and implicit disambiguation #20084

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 58 additions & 25 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1614,11 +1614,12 @@ trait Applications extends Compatibility {
* Module classes also inherit the relationship from their companions. This means,
* if no direct derivation exists between `sym1` and `sym2` also perform the following
* tests:
* - If both sym1 and sym1 are module classes that have companion classes,
* and sym2 does not inherit implicit members from a base class (#),
* compare the companion classes.
* - If sym1 is a module class with a companion, and sym2 is a normal class or trait,
* compare the companion with sym2.
* - If both sym1 and sym2 are module classes that have companion classes,
* compare the companion classes. Return the result of that comparison,
* provided the module class with the larger companion class does not itself
* inherit implicit members from a base class (#),
* - If one sym is a module class with a companion, and the other is a normal class or trait,
* compare the companion with the other class or trait.
*
* Condition (#) is necessary to make `compareOwner(_, _) > 0` a transitive relation.
* For instance:
Expand All @@ -1642,17 +1643,22 @@ trait Applications extends Compatibility {
* This means we get an ambiguity between `a` and `b` in all cases.
*/
def compareOwner(sym1: Symbol, sym2: Symbol)(using Context): Int =
def cls1 = sym1.companionClass
def cls2 = sym2.companionClass
if sym1 == sym2 then 0
else if sym1.isSubClass(sym2) then 1
else if sym2.isSubClass(sym1) then -1
else if sym1.is(Module) then
val cls1 = sym1.companionClass
if sym2.is(Module) then
if sym2.thisType.implicitMembers.forall(_.symbol.owner == sym2) then // test for (#)
compareOwner(cls1, sym2.companionClass)
else 0
else compareOwner(cls1, sym2)
else 0
else
if sym1.is(Module) && sym2.is(Module) then
val r = compareOwner(cls1, cls2)
if r == 0 then 0
else
val larger = if r < 0 then sym1 else sym2
if larger.thisType.implicitMembers.forall(_.symbol.owner == larger) then r
else 0
else if sym1.is(Module) then compareOwner(cls1, sym2)
else if sym2.is(Module) then compareOwner(sym1, cls2)
else 0

/** Compare two alternatives of an overloaded call or an implicit search.
*
Expand Down Expand Up @@ -1807,10 +1813,38 @@ trait Applications extends Compatibility {
else tp
}

def compareWithTypes(tp1: Type, tp2: Type) = {
def widenPrefix(alt: TermRef): Type = alt.prefix.widen match
case pre: (TypeRef | ThisType) if pre.typeSymbol.is(Module) =>
pre.parents.reduceLeft(TypeComparer.andType(_, _))
case wpre => wpre

/** If two alternatives have the same symbol, we pick the one with the most
* specific prefix. To determine that, we widen the prefix types and also
* widen module classes to the intersection of their parent classes. Then
* if one of the resulting types is a more specific value type than the other,
* it wins. Example:
*
* trait A { given M = ... }
* trait B extends A
* object a extends A
* object b extends B
*
* In this case `b.M` would be regarded as more specific than `a.M`.
*/
def comparePrefixes =
val pre1 = widenPrefix(alt1)
val pre2 = widenPrefix(alt2)
val winsPrefix1 = isAsSpecificValueType(pre1, pre2)
val winsPrefix2 = isAsSpecificValueType(pre2, pre1)
if winsPrefix1 == winsPrefix2 then 0
else if winsPrefix1 then 1
else -1

def compareWithTypes(tp1: Type, tp2: Type) =
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)

val winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
val winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)

overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
if winsType1 && winsType2
Expand All @@ -1819,15 +1853,14 @@ trait Applications extends Compatibility {
// alternatives are the same after following ExprTypes, pick one of them
// (prefer the one that is not a method, but that's arbitrary).
if alt1.widenExpr =:= alt2 then -1 else 1
else if ownerScore == 1 then
if winsType1 || !winsType2 then 1 else 0
else if ownerScore == -1 then
if winsType2 || !winsType1 then -1 else 0
else if winsType1 then
if winsType2 then 0 else 1
else
if winsType2 then -1 else 0
}
else ownerScore match
case 1 => if winsType1 || !winsType2 then 1 else 0
case -1 => if winsType2 || !winsType1 then -1 else 0
case 0 =>
if winsType1 != winsType2 then if winsType1 then 1 else -1
else if alt1.symbol == alt2.symbol then comparePrefixes
else 0
end compareWithTypes

if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1
else if alt2.symbol.is(ConstructorProxy) && !alt1.symbol.is(ConstructorProxy) then 1
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/util/Signatures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,8 @@ object Signatures {
case res => List(tpe)

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

def toTypeParam(tpe: PolyType): List[Param] =
val evidenceParams = (tpe.paramNamess.flatten zip tpe.paramInfoss.flatten).flatMap:
Expand Down
17 changes: 17 additions & 0 deletions tests/pos/implicit-prefix-disambiguation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

class I[X]
class J[X]

trait A:
given I[B] = ???
given (using I[B]): J[B] = ???
object A extends A

trait B extends A
object B extends B

//import B.given, A.given

def Test =
summon[I[B]]
summon[J[B]]
Loading