Skip to content

Commit 6331820

Browse files
committed
Fix regression in overload resolution picking eta-expanded method
1 parent f7f51ed commit 6331820

File tree

6 files changed

+79
-33
lines changed

6 files changed

+79
-33
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -802,11 +802,11 @@ object Contexts {
802802
final def retractMode(mode: Mode): c.type = c.setMode(c.mode &~ mode)
803803
}
804804

805-
/** Run `op` with a pool-allocated context that has an ExporeTyperState. */
805+
/** Run `op` with a pool-allocated context that has an ExploreTyperState. */
806806
inline def explore[T](inline op: Context ?=> T)(using Context): T =
807807
exploreInFreshCtx(op)
808808

809-
/** Run `op` with a pool-allocated FreshContext that has an ExporeTyperState. */
809+
/** Run `op` with a pool-allocated FreshContext that has an ExploreTyperState. */
810810
inline def exploreInFreshCtx[T](inline op: FreshContext ?=> T)(using Context): T =
811811
val pool = ctx.base.exploreContextPool
812812
val nestedCtx = pool.next()
@@ -931,7 +931,7 @@ object Contexts {
931931
FreshContext(ctx.base).init(ctx, ctx)
932932

933933
private var inUse: Int = 0
934-
private var pool = new mutable.ArrayBuffer[FreshContext]
934+
private val pool = new mutable.ArrayBuffer[FreshContext]
935935

936936
def next()(using Context): FreshContext =
937937
val base = ctx.base

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,17 @@ object Types extends TypeUtils {
16301630
NoType
16311631
}
16321632

1633+
/** Follow proxies and approximate type paramrefs by their upper bound
1634+
* in the current constraint in order to figure out robustly
1635+
* whether an expected type is some sort of function type.
1636+
*/
1637+
def underlyingApplied(using Context): Type = this.stripTypeVar match
1638+
case tp: RefinedType => tp
1639+
case tp: AppliedType => tp
1640+
case tp: TypeParamRef => TypeComparer.bounds(tp).hi.underlyingApplied
1641+
case tp: TypeProxy => tp.superType.underlyingApplied
1642+
case _ => this
1643+
16331644
/** The iterator of underlying types as long as type is a TypeProxy.
16341645
* Useful for diagnostics
16351646
*/

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

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,7 +1803,8 @@ trait Applications extends Compatibility {
18031803
* an alternative that takes more implicit parameters wins over one
18041804
* that takes fewer.
18051805
*/
1806-
def compare(alt1: TermRef, alt2: TermRef, preferGeneral: Boolean = false)(using Context): Int = trace(i"compare($alt1, $alt2)", overload) {
1806+
def compare(alt1: TermRef, alt2: TermRef, preferGeneral: Boolean = false, needsEta: Boolean = false)(using Context): Int =
1807+
trace(i"compare($alt1, $alt2)", overload) {
18071808
record("resolveOverloaded.compare")
18081809
val scheme =
18091810
val oldResolution = ctx.mode.is(Mode.OldImplicitResolution)
@@ -1818,30 +1819,34 @@ trait Applications extends Compatibility {
18181819
/** Is alternative `alt1` with type `tp1` as good as alternative
18191820
* `alt2` with type `tp2` ?
18201821
*
1821-
* 1. A method `alt1` of type `(p1: T1, ..., pn: Tn)U` is as good as `alt2`
1822-
* if `alt1` is nullary or `alt2` is applicable to arguments (p1, ..., pn) of
1823-
* types T1,...,Tn. If the last parameter `pn` has a vararg type T*, then
1822+
* 1. A method `alt1` of type `(p1: T1, ..., pn: Tn)U` is as good as `alt2` if:
1823+
* a. `alt1` is nullary, or
1824+
* b. `alt2` is applicable to arguments (p1, ..., pn) of types T1,...,Tn, or
1825+
* c. eta-expanded `alt1` is as good as `alt2` (when needing eta-expansion).
1826+
* When testing method applicability (1b), if the last parameter `pn` has a vararg type T*, then
18241827
* `alt1` must be applicable to arbitrary numbers of `T` parameters (which
18251828
* implies that it must be a varargs method as well).
18261829
* 2. A polymorphic member of type [a1 >: L1 <: U1, ..., an >: Ln <: Un]T is as
18271830
* good as `alt2` of type `tp2` if T is as good as `tp2` under the
18281831
* assumption that for i = 1,...,n each ai is an abstract type name bounded
18291832
* from below by Li and from above by Ui.
18301833
* 3. A member of any other type `tp1` is:
1831-
* a. always as good as a method or a polymorphic method.
1832-
* b. as good as a member of any other type `tp2` if `asGoodValueType(tp1, tp2) = true`
1834+
* a. as good as an eta-expanded `tp2` method (when needing eta-expansion)
1835+
* b. always as good as a (not eta-expanded) method or a polymorphic method.
1836+
* c. as good as a member of any other type `tp2` if `asGoodValueType(tp1, tp2) = true`
18331837
*/
18341838
def isAsGood(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsGood $tp1 $tp2", overload) {
18351839
tp1 match
18361840
case tp1: MethodType => // (1)
1837-
tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1841+
tp1.paramInfos.isEmpty && tp2.isInstanceOf[MethodOrPoly] // (1a)
18381842
|| {
18391843
if tp1.isVarArgsMethod then
18401844
tp2.isVarArgsMethod
1841-
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible)
1845+
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType, ArgMatch.Compatible) // (1b)
18421846
else
1843-
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible)
1847+
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType, ArgMatch.Compatible) // (1b)
18441848
}
1849+
|| needsEta && !tp2.isInstanceOf[MethodOrPoly] && isAsGood(alt1, tp1.toFunctionType(), alt2, tp2) // (1c)
18451850
case tp1: PolyType => // (2)
18461851
inContext(ctx.fresh.setExploreTyperState()) {
18471852
// Fully define the PolyType parameters so that the infos of the
@@ -1859,11 +1864,12 @@ trait Applications extends Compatibility {
18591864
def compareValues(tp2: Type)(using Context) =
18601865
isAsGoodValueType(tp1, tp2, alt1.symbol.is(Implicit))
18611866
tp2 match
1862-
case tp2: MethodType => true // (3a)
1863-
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
1864-
case tp2: PolyType => // (3b)
1867+
case tp2: MethodType if needsEta => isAsGood(alt1, tp1, alt2, tp2.toFunctionType()) // (3a)
1868+
case tp2: MethodType => true // (3b)
1869+
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3b)
1870+
case tp2: PolyType => // (3c)
18651871
explore(compareValues(instantiateWithTypeVars(tp2)))
1866-
case _ => // 3b)
1872+
case _ => // (3c)
18671873
compareValues(tp2)
18681874
}
18691875

@@ -2037,21 +2043,21 @@ trait Applications extends Compatibility {
20372043
}
20382044
end compare
20392045

2040-
def narrowMostSpecific(alts: List[TermRef])(using Context): List[TermRef] = {
2046+
def narrowMostSpecific(alts: List[TermRef], needsEta: Boolean)(using Context): List[TermRef] = {
20412047
record("narrowMostSpecific")
20422048
alts match {
20432049
case Nil => alts
20442050
case _ :: Nil => alts
20452051
case alt1 :: alt2 :: Nil =>
2046-
compare(alt1, alt2) match {
2052+
compare(alt1, alt2, needsEta = needsEta) match {
20472053
case 1 => alt1 :: Nil
20482054
case -1 => alt2 :: Nil
20492055
case 0 => alts
20502056
}
20512057
case alt :: alts1 =>
20522058
def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match {
20532059
case alt :: alts1 =>
2054-
compare(previous.head, alt) match {
2060+
compare(previous.head, alt, needsEta = needsEta) match {
20552061
case 1 => survivors(previous, alts1)
20562062
case -1 => survivors(alt :: previous.tail, alts1)
20572063
case 0 => survivors(alt :: previous, alts1)
@@ -2061,7 +2067,7 @@ trait Applications extends Compatibility {
20612067
val best :: rest = survivors(alt :: Nil, alts1): @unchecked
20622068
def asGood(alts: List[TermRef]): List[TermRef] = alts match {
20632069
case alt :: alts1 =>
2064-
if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1)
2070+
if (compare(alt, best, needsEta = needsEta) < 0) asGood(alts1) else alt :: asGood(alts1)
20652071
case nil =>
20662072
Nil
20672073
}
@@ -2374,7 +2380,8 @@ trait Applications extends Compatibility {
23742380
// If `pt` is erroneous, don't try to go further; report the error in `pt` instead.
23752381
candidates
23762382
else
2377-
val found = narrowMostSpecific(candidates)
2383+
val needsEta = defn.isFunctionNType(pt.underlyingApplied)
2384+
val found = narrowMostSpecific(candidates, needsEta)
23782385
if found.length <= 1 then found
23792386
else
23802387
val deepPt = pt.deepenProto

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4471,17 +4471,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
44714471
tree
44724472
}
44734473

4474-
// Follow proxies and approximate type paramrefs by their upper bound
4475-
// in the current constraint in order to figure out robustly
4476-
// whether an expected type is some sort of function type.
4477-
def underlyingApplied(tp: Type): Type = tp.stripTypeVar match {
4478-
case tp: RefinedType => tp
4479-
case tp: AppliedType => tp
4480-
case tp: TypeParamRef => underlyingApplied(TypeComparer.bounds(tp).hi)
4481-
case tp: TypeProxy => underlyingApplied(tp.superType)
4482-
case _ => tp
4483-
}
4484-
44854474
// If the expected type is a selection of an extension method, deepen it
44864475
// to also propagate the argument type (which is the receiver we have
44874476
// typechecked already). This is needed for i8311.scala. Doing so
@@ -4494,7 +4483,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
44944483
case _ => pt
44954484

44964485
def adaptNoArgs(wtp: Type): Tree = {
4497-
val ptNorm = underlyingApplied(pt)
4486+
val ptNorm = pt.underlyingApplied
44984487
def functionExpected = defn.isFunctionNType(ptNorm)
44994488
def needsEta = pt.revealIgnored match
45004489
case _: SingletonType | _: FunOrPolyProto => false

tests/pos/i21727.min.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.language.implicitConversions
2+
3+
type UUID = String
4+
object MyId:
5+
def fromUUID[F[_]: Functor: UUIDGen]: F[String] =
6+
toFunctorOps(UUIDGen[F].randomUUID).map(fromUUID) // error
7+
private def fromUUID(id: UUID): String = ???
8+
9+
object UUIDGen:
10+
def apply[F[_]](implicit ev: UUIDGen[F]): UUIDGen[F] = ev
11+
trait UUIDGen[F[_]]:
12+
def randomUUID: F[UUID]
13+
14+
trait Functor[F[_]]
15+
implicit def toFunctorOps[F[_], A](target: F[A])(implicit tc: Functor[F]): Ops[F, A] { type TypeClassType = Functor[F]} =
16+
new Ops[F, A] { type TypeClassType = Functor[F] }
17+
18+
trait Ops[F[_], A] {
19+
type TypeClassType <: Functor[F]
20+
def map[B](f: A => B): F[B] = ???
21+
}

tests/pos/i21727.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
trait ExMap1[K1, +V1] extends PartialFunction[K1, V1]
2+
trait ExMap2[K2, +V2] extends PartialFunction[K2, V2]
3+
4+
trait Gen[L[_]] { def make: L[Unit] }
5+
6+
trait Functor[M[_]]:
7+
def map[A, B](ma: M[A])(f: A => B): M[B]
8+
object Functor:
9+
implicit def inst1[K3]: Functor[[V3] =>> ExMap1[K3, V3]] = ???
10+
implicit def inst2[K4]: Functor[[V4] =>> ExMap2[K4, V4]] = ???
11+
12+
class Test:
13+
def foo(x: Unit): String = x.toString()
14+
def foo[F[_]](using F: Functor[F], G: Gen[F]): F[String] =
15+
val res1: F[String] = F.map[Unit, String](G.make)(foo) // was: error
16+
val res2: F[String] = F.map[Unit, String](G.make)(foo(_)) // was: ok
17+
18+
??? : F[String]

0 commit comments

Comments
 (0)