Skip to content

Commit f07374c

Browse files
committed
Fix isAsSpecific
Fix isAsSpecific if one of the two alternatives has apply methods but not the other.
1 parent a7622b2 commit f07374c

File tree

3 files changed

+102
-44
lines changed

3 files changed

+102
-44
lines changed

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

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,9 +1205,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12051205
case mt: MethodicType =>
12061206
p(mt.narrow)
12071207
case _ =>
1208-
followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
1208+
followApply && hasApplyWith(tp)(p)
12091209
}
12101210

1211+
private def hasApplyWith(tp: Type)(p: TermRef => Boolean)(implicit ctx: Context): Boolean =
1212+
tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
1213+
12111214
/** Does `tp` have an extension method named `name` with this-argument `argType` and
12121215
* result matching `resultType`?
12131216
*/
@@ -1270,46 +1273,78 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12701273
* assumption that for i = 1,...,n each ai is an abstract type name bounded
12711274
* from below by Li and from above by Ui.
12721275
* 3. A member of any other type `tp1` is:
1273-
* a. always as specific as a method or a polymorphic method.
1276+
* a. always as specific as a method or a polymorphic method
12741277
* b. as specific as a member of any other type `tp2` if `tp1` is compatible
12751278
* with `tp2`.
1279+
*
1280+
* If followApply is true, and one of the alternatives is not a method, we test instead
1281+
* whether isAsSpecific is true for all apply methods in that alternative.
1282+
* Note that this is errs on the side of not being comparable in the following case:
1283+
*
1284+
* Alternatives
1285+
*
1286+
* val x: A
1287+
* def x(y: S): B
1288+
*
1289+
* where A has members
1290+
*
1291+
* def apply(y: S1): B1
1292+
* def apply(y: S2): B2
1293+
*
1294+
* and only one of the two `apply` methods (say the first) is applicable. If the first `apply`
1295+
* is as specific as the method `x`, but not the second, we still judge the two `x`'s
1296+
* as incomparable. If we had used an "exists an apply method" instead of the "forall"
1297+
* then value `x` would be picked over method `x` instead. On the other hand, if
1298+
* the first `apply` was NOT applicable but the second one was, then we would still pick
1299+
* pick value `x` over method `x` even though the applicable second apply method was is not
1300+
* more specific than the `x` method. So in going with "forall" instead of "exists" we
1301+
* err on the side of treating alternatives as incomparable, instead of potentially
1302+
* picking the wrong one.
12761303
*/
1277-
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match {
1278-
case tp1: MethodType => // (1)
1279-
val formals1 =
1280-
if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle)
1281-
else tp1.paramInfos
1282-
val isAsSpecificMethod =
1283-
if (followApply) isApplicableType(alt2, formals1, WildcardType)
1284-
else isApplicableMethodRef(alt2, formals1, WildcardType)
1285-
isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1286-
case tp1: PolyType => // (2)
1287-
val nestedCtx = ctx.fresh.setExploreTyperState()
1288-
1289-
{
1290-
implicit val ctx = nestedCtx
1291-
1292-
// Fully define the PolyType parameters so that the infos of the
1293-
// tparams created below never contain TypeRefs whose underling types
1294-
// contain uninstantiated TypeVars, this could lead to cycles in
1295-
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1296-
// part of.
1297-
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1298-
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1299-
1300-
val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1301-
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
1302-
}
1303-
case _ => // (3)
1304-
tp2 match {
1305-
case tp2: MethodType => true // (3a)
1306-
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
1307-
case tp2: PolyType => // (3b)
1308-
ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType))
1309-
case _ => // (3b)
1310-
isAsSpecificValueType(tp1, tp2)
1311-
}
1312-
}}
1304+
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type, followApply: Boolean): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
1305+
if (followApply) {
1306+
val isMethod1 = tp1.stripPoly.isInstanceOf[MethodType]
1307+
val isMethod2 = tp2.stripPoly.isInstanceOf[MethodType]
1308+
if (!isMethod1 && isMethod2)
1309+
return !hasApplyWith(tp1)(alt1app => !isAsSpecific(alt1app, alt1app.widen, alt2, tp2, false))
1310+
if (!isMethod2 && isMethod1)
1311+
return !hasApplyWith(tp2)(alt2app => !isAsSpecific(alt1, tp1, alt2app, alt2app.widen, false))
1312+
}
1313+
tp1 match {
1314+
case tp1: MethodType => // (1)
1315+
val formals1 =
1316+
if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle)
1317+
else tp1.paramInfos
1318+
val isAsSpecificMethod = isApplicableMethodRef(alt2, formals1, WildcardType)
1319+
isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1320+
case tp1: PolyType => // (2)
1321+
val nestedCtx = ctx.fresh.setExploreTyperState()
1322+
1323+
{
1324+
implicit val ctx = nestedCtx
1325+
1326+
// Fully define the PolyType parameters so that the infos of the
1327+
// tparams created below never contain TypeRefs whose underling types
1328+
// contain uninstantiated TypeVars, this could lead to cycles in
1329+
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1330+
// part of.
1331+
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1332+
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1333+
1334+
val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1335+
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2, followApply)
1336+
}
1337+
case _ => // (3)
1338+
tp2 match {
1339+
case tp2: MethodType => true
1340+
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true
1341+
case tp2: PolyType => // (3b)
1342+
ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType))
1343+
case _ => // (3b)
1344+
isAsSpecificValueType(tp1, tp2)
1345+
}
1346+
}
1347+
}
13131348

13141349
/** Test whether value type `tp1` is as specific as value type `tp2`.
13151350
* Let's abbreviate this to `tp1 <:s tp2`.
@@ -1400,8 +1435,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14001435

14011436
def compareWithTypes(tp1: Type, tp2: Type) = {
14021437
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1403-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1404-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1438+
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2, followApply)
1439+
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1, followApply)
14051440

14061441
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
14071442
if (ownerScore == 1)
@@ -1479,7 +1514,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14791514
altType.widen match {
14801515
case tp: PolyType => recur(constrained(tp).resultType, followApply)
14811516
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
1482-
case _ => !followApply || onMethod(altType, followApply)(recur(_, followApply = false))
1517+
case _ => !followApply || hasApplyWith(altType)(recur(_, followApply = false))
14831518
}
14841519
case _ => true
14851520
}
@@ -1580,7 +1615,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15801615
else isVarArgs || hasDefault
15811616
case tp =>
15821617
numArgs == 0 ||
1583-
followApply && onMethod(tp, followApply = true)(sizeFits(_, followApply = false))
1618+
followApply && hasApplyWith(tp)(sizeFits(_, followApply = false))
15841619
}
15851620

15861621
def narrowBySize(alts: List[TermRef]): List[TermRef] =

tests/neg/i6450.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,26 @@ object Test {
1313
val f: Foo123 = f(1) // error: Not found: type Foo123
1414

1515
}
16+
17+
18+
object Test1 {
19+
trait A {
20+
def apply(x: Integer): A
21+
}
22+
object B {
23+
def f(e: String): A = ???
24+
val f: A = ???
25+
val g: A = f(null) // error: ambiguous
26+
}
27+
}
28+
29+
object Test2 {
30+
trait A {
31+
def apply(x: String): A = {println(2); null}
32+
}
33+
34+
object B {
35+
def f(e: Integer): A = {println(1); null}
36+
val f: A = f(null) // error: ambiguous
37+
}
38+
}

tests/pos/i6450.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Foo {
66
def B(i: Any) = i
77
object B {
88
def apply(i: Int) = i+1
9+
def apply(b: Float) = true
910
}
1011
def C(i: Int) = i + 1
1112
object C {
@@ -79,7 +80,6 @@ object Test5 {
7980
}
8081
}
8182

82-
8383
object Test6 {
8484
trait A {
8585
def apply(x: CharSequence): Int
@@ -88,4 +88,4 @@ object Test6 {
8888
def f(e: String): A = ???
8989
val f: A = f(null)
9090
}
91-
}
91+
}

0 commit comments

Comments
 (0)