Skip to content

Commit 3340b4c

Browse files
authored
Prefer extensions over conversions for member selection (#19717)
2 parents 9a79c14 + c79bd93 commit 3340b4c

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
@@ -1308,35 +1308,36 @@ trait Implicits:
13081308
case alt1: SearchSuccess =>
13091309
var diff = compareAlternatives(alt1, alt2)
13101310
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
1311-
if diff == 0 && alt1.isExtension && alt2.isExtension then
1312-
// Fall back: if both results are extension method applications,
1313-
// compare the extension methods instead of their wrappers.
1314-
def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe
1315-
(stripExtension(alt1), stripExtension(alt2)) match
1316-
case (ref1: TermRef, ref2: TermRef) =>
1317-
// ref1 and ref2 might refer to type variables owned by
1318-
// alt1.tstate and alt2.tstate respectively, to compare the
1319-
// alternatives correctly we need a TyperState that includes
1320-
// constraints from both sides, see
1321-
// tests/*/extension-specificity2.scala for test cases.
1322-
val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint
1323-
val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint
1324-
def exploreState(alt: SearchSuccess): TyperState =
1325-
alt.tstate.fresh(committable = false)
1326-
val comparisonState =
1327-
if constraintsIn1 && constraintsIn2 then
1328-
exploreState(alt1).mergeConstraintWith(alt2.tstate)
1329-
else if constraintsIn1 then
1330-
exploreState(alt1)
1331-
else if constraintsIn2 then
1332-
exploreState(alt2)
1333-
else
1334-
ctx.typerState
1335-
1336-
diff = inContext(ctx.withTyperState(comparisonState)) {
1337-
compare(ref1, ref2)
1338-
}
1339-
case _ =>
1311+
if diff == 0 && alt2.isExtension then
1312+
if alt1.isExtension then
1313+
// Fall back: if both results are extension method applications,
1314+
// compare the extension methods instead of their wrappers.
1315+
def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe
1316+
(stripExtension(alt1), stripExtension(alt2)) match
1317+
case (ref1: TermRef, ref2: TermRef) =>
1318+
// ref1 and ref2 might refer to type variables owned by
1319+
// alt1.tstate and alt2.tstate respectively, to compare the
1320+
// alternatives correctly we need a TyperState that includes
1321+
// constraints from both sides, see
1322+
// tests/*/extension-specificity2.scala for test cases.
1323+
val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint
1324+
val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint
1325+
def exploreState(alt: SearchSuccess): TyperState =
1326+
alt.tstate.fresh(committable = false)
1327+
val comparisonState =
1328+
if constraintsIn1 && constraintsIn2 then
1329+
exploreState(alt1).mergeConstraintWith(alt2.tstate)
1330+
else if constraintsIn1 then
1331+
exploreState(alt1)
1332+
else if constraintsIn2 then
1333+
exploreState(alt2)
1334+
else
1335+
ctx.typerState
1336+
1337+
diff = inContext(ctx.withTyperState(comparisonState)):
1338+
compare(ref1, ref2)
1339+
else // alt1 is a conversion, prefer extension alt2 over it
1340+
diff = -1
13401341
if diff < 0 then alt2
13411342
else if diff > 0 then alt1
13421343
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)