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 3 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
69 changes: 52 additions & 17 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,8 +1813,34 @@ trait Applications extends Compatibility {
else tp
}

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(pre1: Type, pre2: Type) =
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)

Expand Down Expand Up @@ -1838,11 +1870,14 @@ trait Applications extends Compatibility {
val strippedType2 = stripImplicit(fullType2)

val result = compareWithTypes(strippedType1, strippedType2)
if (result != 0) result
else if (strippedType1 eq fullType1)
if (strippedType2 eq fullType2) 0 // no implicits either side: its' a draw
if result != 0 then result
else if strippedType1 eq fullType1 then
if strippedType2 eq fullType2 then
if alt1.symbol != alt2.symbol then 0 // no implicits either side: it's a draw ...
else comparePrefixes( // ... unless the symbol is the same, in which case
widenPrefix(alt1), widenPrefix(alt2)) // we compare prefixes
EugeneFlesselle marked this conversation as resolved.
Show resolved Hide resolved
else 1 // prefer 1st alternative with no implicits
else if (strippedType2 eq fullType2) -1 // prefer 2nd alternative with no implicits
else if strippedType2 eq fullType2 then -1 // prefer 2nd alternative with no implicits
else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters
}
end compare
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
14 changes: 14 additions & 0 deletions tests/pos/implicit-prefix-disambiguation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class I[X]

trait A:
given I[B] = ???
object A extends A

trait B extends A
object B extends B

//import B.given, A.given

def Test = summon[I[B]]


Loading