Skip to content

Commit 3680b2f

Browse files
committed
Make implicit search ordering transitive
1 parent e756d62 commit 3680b2f

File tree

2 files changed

+28
-32
lines changed

2 files changed

+28
-32
lines changed

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

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,50 +1350,46 @@ trait Applications extends Compatibility {
13501350
* Module classes also inherit the relationship from their companions. This means,
13511351
* if no direct derivation exists between `sym1` and `sym2` also perform the following
13521352
* tests:
1353-
* - If both sym1 and sym1 are module classes that have companion classes, compare the companions
1354-
* - If sym1 is a module class with a companion, and sym2 is a normal class or trait, compare
1355-
* the companion with sym2.
1353+
* - If both sym1 and sym1 are module classes that have companion classes,
1354+
* and sym2 does not inherit implicit members from a base class (#),
1355+
* compare the companion classes.
1356+
* - If sym1 is a module class with a companion, and sym2 is a normal class or trait,
1357+
* compare the companion with sym2.
13561358
*
1357-
* Note that this makes `compareOwner(_, _) > 0` not transitive! For instance:
1359+
* Condition (#) is necessary to make `compareOwner(_, _) > 0` a transitive relation.
1360+
* For instance:
13581361
*
13591362
* class A extends B
1360-
* object A
1363+
* object A { given a ... }
13611364
* class B
1362-
* object B extends C
1365+
* object B extends C { given b ... }
1366+
* class C { given c }
13631367
*
1364-
* Then compareOwner(A$, B$) = 1 and compareOwner(B$, C) == 1, but
1365-
* compareOwner(A$, C) == 0.
1368+
* Then without (#), and taking A$ for the module class of A,
1369+
* compareOwner(A$, B$) = 1 and compareOwner(B$, C) == 1,
1370+
* but compareOwner(A$, C) == 0.
1371+
* Violating transitivity in this way is bad, since it makes implicit search
1372+
* outcomes compilation order dependent. E.g. if we compare `b` with `c`
1373+
* first, we pick `b`. Then, if we compare `a` and `b`, we pick `a` as
1374+
* solution of the search. But if we start with comparing `a` with `c`,
1375+
* we get an ambiguity.
1376+
*
1377+
* With the added condition (#), compareOwner(A$, B$) == 0.
1378+
* This means we get an ambiguity between `a` and `b` in all cases.
13661379
*/
13671380
def compareOwner(sym1: Symbol, sym2: Symbol)(using Context): Int =
13681381
if sym1 == sym2 then 0
13691382
else if sym1.isSubClass(sym2) then 1
13701383
else if sym2.isSubClass(sym1) then -1
13711384
else if sym1.is(Module) then
1372-
compareOwner(sym1.companionClass, if sym2.is(Module) then sym2.companionClass else sym2)
1385+
val cls1 = sym1.companionClass
1386+
if sym2.is(Module) then
1387+
if sym2.thisType.implicitMembers.forall(_.symbol.owner == sym2) then // test for (#)
1388+
compareOwner(cls1, sym2.companionClass)
1389+
else 0
1390+
else compareOwner(cls1, sym2)
13731391
else 0
13741392

1375-
/** A version of `compareOwner` that is transitive, to be used in sorting
1376-
* It would be nice if we could use this as the general method for comparing
1377-
* owners, but unfortunately this does not compile all existsing code.
1378-
* An example is `enrich-gentraversable.scala`. Here we have
1379-
*
1380-
* trait BuildFrom...
1381-
* object BuildFrom extends BuildFromLowPriority1
1382-
*
1383-
* and we need to pick an implicit in BuildFrom over BuildFromLowPriority1
1384-
* the rules in `compareOwner` give us that, but the rules in `isSubOwner`
1385-
* don't.
1386-
* So we need to stick with `compareOwner` for backwards compatibility, even
1387-
* though it is arguably broken. We can still use `isSubOwner` for sorting
1388-
* since that is just a performance optimization, so if the two methods
1389-
* don't agree sometimes that's OK.
1390-
*/
1391-
def isSubOwner(sym1: Symbol, sym2: Symbol)(using Context): Boolean =
1392-
if sym1.is(Module) && sym1.companionClass.exists then
1393-
isSubOwner(sym1.companionClass, if sym2.is(Module) then sym2.companionClass else sym2)
1394-
else
1395-
sym1 != sym2 && sym1.isSubClass(sym2)
1396-
13971393
/** Compare to alternatives of an overloaded call or an implicit search.
13981394
*
13991395
* @param alt1, alt2 Non-overloaded references indicating the two choices

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ trait Implicits { self: Typer =>
11731173
val arity2 = sym2.info.firstParamTypes.length
11741174
if (arity1 < arity2) return true
11751175
if (arity1 > arity2) return false
1176-
isSubOwner(sym1, sym2)
1176+
compareOwner(sym1, sym2) == 1
11771177
}
11781178

11791179
/** Sort list of implicit references according to `prefer`.

0 commit comments

Comments
 (0)