Skip to content

Commit 8f93965

Browse files
oderskyWojciechMazur
authored andcommitted
Prefer extensions over conversions for member selection
Fixes #19715 [Cherry-picked c79bd93]
1 parent 403262c commit 8f93965

File tree

2 files changed

+45
-29
lines changed

2 files changed

+45
-29
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+30-29
Original file line numberDiff line numberDiff line change
@@ -1275,35 +1275,36 @@ trait Implicits:
12751275
case alt1: SearchSuccess =>
12761276
var diff = compareAlternatives(alt1, alt2)
12771277
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
1278-
if diff == 0 && alt1.isExtension && alt2.isExtension then
1279-
// Fall back: if both results are extension method applications,
1280-
// compare the extension methods instead of their wrappers.
1281-
def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe
1282-
(stripExtension(alt1), stripExtension(alt2)) match
1283-
case (ref1: TermRef, ref2: TermRef) =>
1284-
// ref1 and ref2 might refer to type variables owned by
1285-
// alt1.tstate and alt2.tstate respectively, to compare the
1286-
// alternatives correctly we need a TyperState that includes
1287-
// constraints from both sides, see
1288-
// tests/*/extension-specificity2.scala for test cases.
1289-
val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint
1290-
val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint
1291-
def exploreState(alt: SearchSuccess): TyperState =
1292-
alt.tstate.fresh(committable = false)
1293-
val comparisonState =
1294-
if constraintsIn1 && constraintsIn2 then
1295-
exploreState(alt1).mergeConstraintWith(alt2.tstate)
1296-
else if constraintsIn1 then
1297-
exploreState(alt1)
1298-
else if constraintsIn2 then
1299-
exploreState(alt2)
1300-
else
1301-
ctx.typerState
1302-
1303-
diff = inContext(ctx.withTyperState(comparisonState)) {
1304-
compare(ref1, ref2)
1305-
}
1306-
case _ =>
1278+
if diff == 0 && alt2.isExtension then
1279+
if alt1.isExtension then
1280+
// Fall back: if both results are extension method applications,
1281+
// compare the extension methods instead of their wrappers.
1282+
def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe
1283+
(stripExtension(alt1), stripExtension(alt2)) match
1284+
case (ref1: TermRef, ref2: TermRef) =>
1285+
// ref1 and ref2 might refer to type variables owned by
1286+
// alt1.tstate and alt2.tstate respectively, to compare the
1287+
// alternatives correctly we need a TyperState that includes
1288+
// constraints from both sides, see
1289+
// tests/*/extension-specificity2.scala for test cases.
1290+
val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint
1291+
val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint
1292+
def exploreState(alt: SearchSuccess): TyperState =
1293+
alt.tstate.fresh(committable = false)
1294+
val comparisonState =
1295+
if constraintsIn1 && constraintsIn2 then
1296+
exploreState(alt1).mergeConstraintWith(alt2.tstate)
1297+
else if constraintsIn1 then
1298+
exploreState(alt1)
1299+
else if constraintsIn2 then
1300+
exploreState(alt2)
1301+
else
1302+
ctx.typerState
1303+
1304+
diff = inContext(ctx.withTyperState(comparisonState)):
1305+
compare(ref1, ref2)
1306+
else // alt1 is a conversion, prefer extension alt2 over it
1307+
diff = -1
13071308
if diff < 0 then alt2
13081309
else if diff > 0 then alt1
13091310
else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument), span)

Diff for: tests/pos/i19715.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Tup():
2+
def app(n: Int): String = "a"
3+
4+
class NT(t: Tup):
5+
def toTup = t
6+
object NT:
7+
extension (x: NT)
8+
def app(n: Int): Boolean = true
9+
given Conversion[NT, Tup] = _.toTup
10+
11+
def test =
12+
val nt = new NT(Tup())
13+
val x = nt.app(3)
14+
val _: Boolean = x
15+

0 commit comments

Comments
 (0)