Skip to content

Commit 17fa245

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

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
@@ -1207,9 +1207,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12071207
case mt: MethodicType =>
12081208
p(mt.narrow)
12091209
case _ =>
1210-
followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d)))
1210+
followApply && hasApplyWith(tp)(p)
12111211
}
12121212

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

13161351
/** Test whether value type `tp1` is as specific as value type `tp2`.
13171352
* Let's abbreviate this to `tp1 <:s tp2`.
@@ -1402,8 +1437,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14021437

14031438
def compareWithTypes(tp1: Type, tp2: Type) = {
14041439
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
1405-
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
1406-
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1)
1440+
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2, followApply)
1441+
def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1, followApply)
14071442

14081443
overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2")
14091444
if (ownerScore == 1)
@@ -1481,7 +1516,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14811516
altType.widen match {
14821517
case tp: PolyType => recur(constrained(tp).resultType, followApply)
14831518
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
1484-
case _ => !followApply || onMethod(altType, followApply)(recur(_, followApply = false))
1519+
case _ => !followApply || hasApplyWith(altType)(recur(_, followApply = false))
14851520
}
14861521
case _ => true
14871522
}
@@ -1582,7 +1617,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15821617
else isVarArgs || hasDefault
15831618
case tp =>
15841619
numArgs == 0 ||
1585-
followApply && onMethod(tp, followApply = true)(sizeFits(_, followApply = false))
1620+
followApply && hasApplyWith(tp)(sizeFits(_, followApply = false))
15861621
}
15871622

15881623
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)