From 989163e451ddd93d5e97ae6f53f7bf7aac2f8b2a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 May 2019 16:10:15 +0200 Subject: [PATCH 1/8] Fix #6450: Don't drop apply alternatives in overloading resolution The overloading resolution algorithm dropped alternatives reachable through applys when testing for applicability. It did so in three situations - when comparing the arities of alternatives - when testing via isDirectlyApplicable - when testing via isApplicable The third problem stemmed from confusion between overloaded versions of `isApplicable` (it's ironical that overzealous use of overloading broke overloading resolution in the compiler!). The version for TermRefs would not consider `apply` insertion, but the version for Types would. This means that the overloading broke the Liskov substitution principle. If a general type happened to be a TermRef the more specific isApplicable version would kick in, which however did less than the original. --- .../dotty/tools/dotc/typer/Applications.scala | 44 ++++++++++--------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- tests/pos/i6450.scala | 26 +++++++++++ 3 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 tests/pos/i6450.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 704e69b5ea69..efef88a072ec 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1163,36 +1163,38 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Is given method reference applicable to type arguments `targs` and argument trees `args`? * @param resultType The expected result type of the application */ - def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { + def isApplicableMethodRef(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { def isApp(implicit ctx: Context): Boolean = new ApplicableToTrees(methRef, targs, args, resultType).success if (keepConstraint) isApp else ctx.test(implicit ctx => isApp) } - /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? - * @param resultType The expected result type of the application - */ - def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) - /** Is given method reference applicable to argument types `args`? * @param resultType The expected result type of the application */ - def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = + def isApplicableMethodRef(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = ctx.test(implicit ctx => new ApplicableToTypes(methRef, args, resultType).success) /** Is given type applicable to type arguments `targs` and argument trees `args`, * possibly after inserting an `apply`? * @param resultType The expected result type of the application */ - def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = - onMethod(tp, isApplicable(_, targs, args, resultType, keepConstraint)) + def isApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = + onMethod(tp, isApplicableMethodRef(_, targs, args, resultType, keepConstraint)) /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? * @param resultType The expected result type of the application */ - def isApplicable(tp: Type, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = - onMethod(tp, isApplicable(_, args, resultType)) + def isApplicableType(tp: Type, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = + onMethod(tp, isApplicableMethodRef(_, args, resultType)) + + /** Is given method type applicable to type arguments `targs` and argument trees `args` without inferring views, + * possibly after inserting an `apply`? + * @param resultType The expected result type of the application + */ + def isDirectlyApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = + onMethod(tp, methRef => + ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)) private def onMethod(tp: Type, p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] => @@ -1208,7 +1210,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => */ def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { def qualifies(mbr: Denotation) = - mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) + mbr.exists && isApplicableType(tp.select(name, mbr), argType :: Nil, resultType) tp.memberBasedOnFlags(name, required = ExtensionMethod) match { case mbr: SingleDenotation => qualifies(mbr) case mbr => mbr.hasAltWith(qualifies(_)) @@ -1272,7 +1274,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val formals1 = if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) else tp1.paramInfos - isApplicable(alt2, formals1, WildcardType) || + isApplicableMethodRef(alt2, formals1, WildcardType) || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] case tp1: PolyType => // (2) val nestedCtx = ctx.fresh.setExploreTyperState() @@ -1541,7 +1543,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = - alts filter (isApplicable(_, argTypes, resultType)) + alts filter (isApplicableType(_, argTypes, resultType)) val candidates = pt match { case pt @ FunProto(args, resultType) => @@ -1551,7 +1553,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case x => x } - def sizeFits(alt: TermRef, tp: Type): Boolean = tp.stripPoly match { + def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { case tp: MethodType => val ptypes = tp.paramInfos val numParams = ptypes.length @@ -1561,12 +1563,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else if (numParams < numArgs) isVarArgs else if (numParams > numArgs + 1) hasDefault else isVarArgs || hasDefault - case _ => - numArgs == 0 + case tp => + numArgs == 0 || onMethod(tp, sizeFits) } def narrowBySize(alts: List[TermRef]): List[TermRef] = - alts filter (alt => sizeFits(alt, alt.widen)) + alts.filter(sizeFits) def narrowByShapes(alts: List[TermRef]): List[TermRef] = { if (normArgs exists untpd.isFunctionWithUnknownParamType) @@ -1578,11 +1580,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = { val alts2 = alts.filter(alt => - isDirectlyApplicable(alt, targs, args, resultType) + isDirectlyApplicableType(alt, targs, args, resultType) ) if (alts2.isEmpty && !ctx.isAfterTyper) alts.filter(alt => - isApplicable(alt, targs, args, resultType, keepConstraint = false) + isApplicableType(alt, targs, args, resultType, keepConstraint = false) ) else alts2 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index d2736866121a..3ff91be93a3e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -253,7 +253,7 @@ object ProtoTypes { def isPoly(tree: Tree) = tree.tpe.widenSingleton.isInstanceOf[PolyType] // See remark in normalizedCompatible for why we can't keep the constraint // if one of the arguments has a PolyType. - typer.isApplicable(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly)) + typer.isApplicableType(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly)) } def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto = @@ -399,7 +399,7 @@ object ProtoTypes { override def resultType(implicit ctx: Context): Type = resType def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = - ctx.typer.isApplicable(tp, argType :: Nil, resultType) || { + ctx.typer.isApplicableType(tp, argType :: Nil, resultType) || { resType match { case SelectionProto(name: TermName, mbrType, _, _) => ctx.typer.hasExtensionMethod(tp, name, argType, mbrType) diff --git a/tests/pos/i6450.scala b/tests/pos/i6450.scala new file mode 100644 index 000000000000..7f137638d6f9 --- /dev/null +++ b/tests/pos/i6450.scala @@ -0,0 +1,26 @@ +class Foo { + def A(i: Double) = i + object A { + def apply(i: Int): Int = i+1 + } + def B(i: Any) = i + object B { + def apply(i: Int) = i+1 + } + def C(i: Int) = i + 1 + object C { + def apply(i: Double): Double = i + } + def foo = A(0) + def bar = B(1) + def baz = C(2) +} + +object Test { + val x = new Foo().foo + val y = new Foo().bar + val z = new Foo().baz + val x1: Int = x + val y1: Int = y + val z1: Int = z +} From 487d2ae580bf9108b6978aecd2f2e5a1b98087a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 May 2019 08:58:02 +0200 Subject: [PATCH 2/8] Refine treatment of apply members in overloading resolution Consider apply members in overloading resolution only if arguments are passed --- .../dotty/tools/dotc/typer/Applications.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index efef88a072ec..8c92441b68b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1180,29 +1180,34 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * @param resultType The expected result type of the application */ def isApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = - onMethod(tp, isApplicableMethodRef(_, targs, args, resultType, keepConstraint)) + onMethod(tp, targs.nonEmpty || args.nonEmpty) { + isApplicableMethodRef(_, targs, args, resultType, keepConstraint) + } /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? * @param resultType The expected result type of the application */ def isApplicableType(tp: Type, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = - onMethod(tp, isApplicableMethodRef(_, args, resultType)) + onMethod(tp, args.nonEmpty) { + isApplicableMethodRef(_, args, resultType) + } /** Is given method type applicable to type arguments `targs` and argument trees `args` without inferring views, * possibly after inserting an `apply`? * @param resultType The expected result type of the application */ def isDirectlyApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - onMethod(tp, methRef => - ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)) + onMethod(tp, targs.nonEmpty || args.nonEmpty) { methRef => + ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) + } - private def onMethod(tp: Type, p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { + private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] => p(methRef) case mt: MethodicType => p(mt.narrow) case _ => - tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) + followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) } /** Does `tp` have an extension method named `name` with this-argument `argType` and @@ -1564,7 +1569,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else if (numParams > numArgs + 1) hasDefault else isVarArgs || hasDefault case tp => - numArgs == 0 || onMethod(tp, sizeFits) + numArgs == 0 || onMethod(tp, followApply = true)(sizeFits) } def narrowBySize(alts: List[TermRef]): List[TermRef] = From 767b4b8d9c65f309ded96ce5bb9f1b958e4996be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 May 2019 20:15:18 +0200 Subject: [PATCH 3/8] Fix isAsSpecific isAsSpecific is now dependent on whether we are looking for a method to apply or a value that does not get applied. Only in the first case, we should consider apply members of an alternative. --- .../dotty/tools/dotc/typer/Applications.scala | 26 ++++++++++++------- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/neg/i6450.scala | 9 +++++++ tests/pos/i6450.scala | 12 +++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 tests/neg/i6450.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8c92441b68b2..5dcc55c2ba2b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1254,8 +1254,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * If that tournament yields a draw, a tiebreak is applied where * an alternative that takes more implicit parameters wins over one * that takes fewer. + * + * @param followApply if true consider `apply` members when comparing with a method reference */ - def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) { + def compare(alt1: TermRef, alt2: TermRef, followApply: Boolean)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) { assert(alt1 ne alt2) @@ -1279,8 +1281,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val formals1 = if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) else tp1.paramInfos - isApplicableMethodRef(alt2, formals1, WildcardType) || - tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] + val isAsSpecificMethod = + if (followApply) isApplicableType(alt2, formals1, WildcardType) + else isApplicableMethodRef(alt2, formals1, WildcardType) + isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] case tp1: PolyType => // (2) val nestedCtx = ctx.fresh.setExploreTyperState() @@ -1426,12 +1430,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters }} - def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { + def narrowMostSpecific(alts: List[TermRef], followApply: Boolean)(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { alts match { case Nil => alts case _ :: Nil => alts case alt1 :: alt2 :: Nil => - compare(alt1, alt2) match { + compare(alt1, alt2, followApply) match { case 1 => alt1 :: Nil case -1 => alt2 :: Nil case 0 => alts @@ -1439,7 +1443,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case alt :: alts1 => def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - compare(previous.head, alt) match { + compare(previous.head, alt, followApply) match { case 1 => survivors(previous, alts1) case -1 => survivors(alt :: previous.tail, alts1) case 0 => survivors(alt :: previous, alts1) @@ -1449,7 +1453,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val best :: rest = survivors(alt :: Nil, alts1) def asGood(alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1) + if (compare(alt, best, followApply) < 0) asGood(alts1) else alt :: asGood(alts1) case nil => Nil } @@ -1550,9 +1554,13 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = alts filter (isApplicableType(_, argTypes, resultType)) + val numArgs = pt match { + case pt @ FunProto(args, resultType) => args.length + case _ => 0 + } + val candidates = pt match { case pt @ FunProto(args, resultType) => - val numArgs = args.length val normArgs = args.mapConserve { case Block(Nil, expr) => expr case x => x @@ -1660,7 +1668,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => candidates.flatMap(cloneCandidate) } - val found = narrowMostSpecific(candidates) + val found = narrowMostSpecific(candidates, followApply = numArgs != 0) if (found.length <= 1) found else pt match { case pt @ FunProto(_, resType: FunProto) => diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1fa53e79582a..0d68f8ff2ce3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1142,7 +1142,7 @@ trait Implicits { self: Typer => def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int = if (prev.ref eq ref) 0 else if (prev.level != level) prev.level - level - else nestedContext().test(implicit ctx => compare(prev.ref, ref)) + else nestedContext().test(implicit ctx => compare(prev.ref, ref, followApply = false)) /** If `alt1` is also a search success, try to disambiguate as follows: * - If alt2 is preferred over alt1, pick alt2, otherwise return an diff --git a/tests/neg/i6450.scala b/tests/neg/i6450.scala new file mode 100644 index 000000000000..66489dcc1c9c --- /dev/null +++ b/tests/neg/i6450.scala @@ -0,0 +1,9 @@ +trait A { + def apply(x: Any): Int = 1 +} + +object B { + def f(x: Any): Int = 2 + lazy val f = new A {} + val x = f(null) // error: ambiguous +} \ No newline at end of file diff --git a/tests/pos/i6450.scala b/tests/pos/i6450.scala index 7f137638d6f9..a7a5b17bb6ee 100644 --- a/tests/pos/i6450.scala +++ b/tests/pos/i6450.scala @@ -24,3 +24,15 @@ object Test { val y1: Int = y val z1: Int = z } + +object Test2 { + + trait A { + def apply(x: Any): Int = 1 + } + + object B { + def f(x: Any): A = new A {} + lazy val f: A = f(null) + } +} \ No newline at end of file From 23089ad45b321acc3d349210175b284b7c17d512 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 May 2019 13:08:35 +0200 Subject: [PATCH 4/8] Avoid deep recursion in sizeFits Only a single `apply` is ever inserted, so `apply` members of `apply` methods never should count as applicable. --- .../dotty/tools/dotc/typer/Applications.scala | 7 ++- tests/neg/i6450.scala | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5dcc55c2ba2b..ab62ac7defde 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1566,7 +1566,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case x => x } - def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { + def sizeFits(alt: TermRef, followApply: Boolean): Boolean = alt.widen.stripPoly match { case tp: MethodType => val ptypes = tp.paramInfos val numParams = ptypes.length @@ -1577,11 +1577,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else if (numParams > numArgs + 1) hasDefault else isVarArgs || hasDefault case tp => - numArgs == 0 || onMethod(tp, followApply = true)(sizeFits) + numArgs == 0 || + followApply && onMethod(tp, followApply = true)(sizeFits(_, followApply = false)) } def narrowBySize(alts: List[TermRef]): List[TermRef] = - alts.filter(sizeFits) + alts.filter(sizeFits(_, followApply = true)) def narrowByShapes(alts: List[TermRef]): List[TermRef] = { if (normArgs exists untpd.isFunctionWithUnknownParamType) diff --git a/tests/neg/i6450.scala b/tests/neg/i6450.scala index 66489dcc1c9c..41b465efd480 100644 --- a/tests/neg/i6450.scala +++ b/tests/neg/i6450.scala @@ -6,4 +6,62 @@ object B { def f(x: Any): Int = 2 lazy val f = new A {} val x = f(null) // error: ambiguous +} + +object Test { + def f(x: Any) = 10 + val f: Foo123 = f(1) // error: Not found: type Foo123 + +} + +object Test2 { + trait A { + def apply(x: AnyRef): Int + } + object B { + def f(e: Any): A = ??? + val f: A = f(null) + } +} + +object Test3 { + trait A { + def apply(x: String): Int + } + object B { + def f(e: CharSequence): A = ??? + val f: A = f(null) + } +} + + +object Test4 { + trait A { + def apply(x: Any): Int + } + object B { + def f(e: Any): A = ??? + val f: A = f(null) + } +} + +object Test5 { + trait A { + def apply(x: Any): Int + } + object B { + def f(e: AnyRef): A = ??? + val f: A = f(null) + } +} + + +object Test6 { + trait A { + def apply(x: CharSequence): Int + } + object B { + def f(e: String): A = ??? + val f: A = f(null) + } } \ No newline at end of file From 8284252641fa63076ca28c1a3a38e139d1b421d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 May 2019 14:51:41 +0200 Subject: [PATCH 5/8] Consider apply members in constrainResult So, now we were mis-approximating in the other direction by letting non-methodic alternatives always pass through the resultConforms check. This would let members with apply methods pass where they should not. The problem was previously hidden since members with apply methods tended to be already filtered out in the isApplicable check. --- .../dotty/tools/dotc/typer/Applications.scala | 12 ++-- tests/neg/i6450.scala | 52 ------------------ tests/pos/i6450.scala | 55 ++++++++++++++++++- 3 files changed, 62 insertions(+), 57 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ab62ac7defde..607d5940efc8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1472,17 +1472,21 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. + * If `alt`does not have method or poly type, and `followApply` is true, consider + * all apply members instead. In all other cases return `true`. */ - def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = - resultType.revealIgnored match { + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = { + def recur(altType: Type, followApply: Boolean): Boolean = resultType.revealIgnored match { case resultType: ValueType => altType.widen match { - case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) + case tp: PolyType => recur(constrained(tp).resultType, followApply) case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) - case _ => true + case _ => !followApply || onMethod(altType, followApply)(recur(_, followApply = false)) } case _ => true } + recur(altType, followApply = true) + } /** If the `chosen` alternative has a result type incompatible with the expected result * type `pt`, run overloading resolution again on all alternatives that do match `pt`. diff --git a/tests/neg/i6450.scala b/tests/neg/i6450.scala index 41b465efd480..bf12a41653a5 100644 --- a/tests/neg/i6450.scala +++ b/tests/neg/i6450.scala @@ -13,55 +13,3 @@ object Test { val f: Foo123 = f(1) // error: Not found: type Foo123 } - -object Test2 { - trait A { - def apply(x: AnyRef): Int - } - object B { - def f(e: Any): A = ??? - val f: A = f(null) - } -} - -object Test3 { - trait A { - def apply(x: String): Int - } - object B { - def f(e: CharSequence): A = ??? - val f: A = f(null) - } -} - - -object Test4 { - trait A { - def apply(x: Any): Int - } - object B { - def f(e: Any): A = ??? - val f: A = f(null) - } -} - -object Test5 { - trait A { - def apply(x: Any): Int - } - object B { - def f(e: AnyRef): A = ??? - val f: A = f(null) - } -} - - -object Test6 { - trait A { - def apply(x: CharSequence): Int - } - object B { - def f(e: String): A = ??? - val f: A = f(null) - } -} \ No newline at end of file diff --git a/tests/pos/i6450.scala b/tests/pos/i6450.scala index a7a5b17bb6ee..80a856f54981 100644 --- a/tests/pos/i6450.scala +++ b/tests/pos/i6450.scala @@ -25,7 +25,7 @@ object Test { val z1: Int = z } -object Test2 { +object Test1 { trait A { def apply(x: Any): Int = 1 @@ -35,4 +35,57 @@ object Test2 { def f(x: Any): A = new A {} lazy val f: A = f(null) } +} + +object Test2 { + trait A { + def apply(x: AnyRef): Int + } + object B { + def f(e: Any): A = ??? + val f: A = ??? + val g: A = f(null) + } +} + +object Test3 { + trait A { + def apply(x: String): Int + } + object B { + def f(e: CharSequence): A = ??? + val f: A = f(null) + } +} + + +object Test4 { + trait A { + def apply(x: Any): Int + } + object B { + def f(e: Any): A = ??? + val f: A = f(null) + } +} + +object Test5 { + trait A { + def apply(x: Any): Int + } + object B { + def f(e: AnyRef): A = ??? + val f: A = f(null) + } +} + + +object Test6 { + trait A { + def apply(x: CharSequence): Int + } + object B { + def f(e: String): A = ??? + val f: A = f(null) + } } \ No newline at end of file From 17fa2458ad47bea9340baecdd8042579e27aacb3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 May 2019 18:30:17 +0200 Subject: [PATCH 6/8] Fix isAsSpecific Fix isAsSpecific if one of the two alternatives has apply methods but not the other. --- .../dotty/tools/dotc/typer/Applications.scala | 119 +++++++++++------- tests/neg/i6450.scala | 23 ++++ tests/pos/i6450.scala | 4 +- 3 files changed, 102 insertions(+), 44 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 607d5940efc8..73de95093c9f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1207,9 +1207,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case mt: MethodicType => p(mt.narrow) case _ => - followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) + followApply && hasApplyWith(tp)(p) } + private def hasApplyWith(tp: Type)(p: TermRef => Boolean)(implicit ctx: Context): Boolean = + tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) + /** Does `tp` have an extension method named `name` with this-argument `argType` and * result matching `resultType`? */ @@ -1272,46 +1275,78 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * assumption that for i = 1,...,n each ai is an abstract type name bounded * from below by Li and from above by Ui. * 3. A member of any other type `tp1` is: - * a. always as specific as a method or a polymorphic method. + * a. always as specific as a method or a polymorphic method * b. as specific as a member of any other type `tp2` if `tp1` is compatible * with `tp2`. + * + * If followApply is true, and one of the alternatives is not a method, we test instead + * whether isAsSpecific is true for all apply methods in that alternative. + * Note that this is errs on the side of not being comparable in the following case: + * + * Alternatives + * + * val x: A + * def x(y: S): B + * + * where A has members + * + * def apply(y: S1): B1 + * def apply(y: S2): B2 + * + * and only one of the two `apply` methods (say the first) is applicable. If the first `apply` + * is as specific as the method `x`, but not the second, we still judge the two `x`'s + * as incomparable. If we had used an "exists an apply method" instead of the "forall" + * then value `x` would be picked over method `x` instead. On the other hand, if + * the first `apply` was NOT applicable but the second one was, then we would still pick + * pick value `x` over method `x` even though the applicable second apply method was is not + * more specific than the `x` method. So in going with "forall" instead of "exists" we + * err on the side of treating alternatives as incomparable, instead of potentially + * picking the wrong one. */ - def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { - case tp1: MethodType => // (1) - val formals1 = - if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) - else tp1.paramInfos - val isAsSpecificMethod = - if (followApply) isApplicableType(alt2, formals1, WildcardType) - else isApplicableMethodRef(alt2, formals1, WildcardType) - isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] - case tp1: PolyType => // (2) - val nestedCtx = ctx.fresh.setExploreTyperState() - - { - implicit val ctx = nestedCtx - - // Fully define the PolyType parameters so that the infos of the - // tparams created below never contain TypeRefs whose underling types - // contain uninstantiated TypeVars, this could lead to cycles in - // `isSubType` as a TypeVar might get constrained by a TypeRef it's - // part of. - val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) - fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span) - - val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) - isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2) - } - case _ => // (3) - tp2 match { - case tp2: MethodType => true // (3a) - case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a) - case tp2: PolyType => // (3b) - ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType)) - case _ => // (3b) - isAsSpecificValueType(tp1, tp2) - } - }} + def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type, followApply: Boolean): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { + if (followApply) { + val isMethod1 = tp1.stripPoly.isInstanceOf[MethodType] + val isMethod2 = tp2.stripPoly.isInstanceOf[MethodType] + if (!isMethod1 && isMethod2) + return !hasApplyWith(tp1)(alt1app => !isAsSpecific(alt1app, alt1app.widen, alt2, tp2, false)) + if (!isMethod2 && isMethod1) + return !hasApplyWith(tp2)(alt2app => !isAsSpecific(alt1, tp1, alt2app, alt2app.widen, false)) + } + tp1 match { + case tp1: MethodType => // (1) + val formals1 = + if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) + else tp1.paramInfos + val isAsSpecificMethod = isApplicableMethodRef(alt2, formals1, WildcardType) + isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] + case tp1: PolyType => // (2) + val nestedCtx = ctx.fresh.setExploreTyperState() + + { + implicit val ctx = nestedCtx + + // Fully define the PolyType parameters so that the infos of the + // tparams created below never contain TypeRefs whose underling types + // contain uninstantiated TypeVars, this could lead to cycles in + // `isSubType` as a TypeVar might get constrained by a TypeRef it's + // part of. + val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) + fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span) + + val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) + isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2, followApply) + } + case _ => // (3) + tp2 match { + case tp2: MethodType => true + case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true + case tp2: PolyType => // (3b) + ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType)) + case _ => // (3b) + isAsSpecificValueType(tp1, tp2) + } + } + } /** Test whether value type `tp1` is as specific as value type `tp2`. * Let's abbreviate this to `tp1 <:s tp2`. @@ -1402,8 +1437,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def compareWithTypes(tp1: Type, tp2: Type) = { val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner) - def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) - def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) + def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2, followApply) + def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1, followApply) overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") if (ownerScore == 1) @@ -1481,7 +1516,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => altType.widen match { case tp: PolyType => recur(constrained(tp).resultType, followApply) case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) - case _ => !followApply || onMethod(altType, followApply)(recur(_, followApply = false)) + case _ => !followApply || hasApplyWith(altType)(recur(_, followApply = false)) } case _ => true } @@ -1582,7 +1617,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else isVarArgs || hasDefault case tp => numArgs == 0 || - followApply && onMethod(tp, followApply = true)(sizeFits(_, followApply = false)) + followApply && hasApplyWith(tp)(sizeFits(_, followApply = false)) } def narrowBySize(alts: List[TermRef]): List[TermRef] = diff --git a/tests/neg/i6450.scala b/tests/neg/i6450.scala index bf12a41653a5..5bcec288c5ab 100644 --- a/tests/neg/i6450.scala +++ b/tests/neg/i6450.scala @@ -13,3 +13,26 @@ object Test { val f: Foo123 = f(1) // error: Not found: type Foo123 } + + +object Test1 { + trait A { + def apply(x: Integer): A + } + object B { + def f(e: String): A = ??? + val f: A = ??? + val g: A = f(null) // error: ambiguous + } +} + +object Test2 { + trait A { + def apply(x: String): A = {println(2); null} + } + + object B { + def f(e: Integer): A = {println(1); null} + val f: A = f(null) // error: ambiguous + } +} \ No newline at end of file diff --git a/tests/pos/i6450.scala b/tests/pos/i6450.scala index 80a856f54981..64beed570cfd 100644 --- a/tests/pos/i6450.scala +++ b/tests/pos/i6450.scala @@ -6,6 +6,7 @@ class Foo { def B(i: Any) = i object B { def apply(i: Int) = i+1 + def apply(b: Float) = true } def C(i: Int) = i + 1 object C { @@ -79,7 +80,6 @@ object Test5 { } } - object Test6 { trait A { def apply(x: CharSequence): Int @@ -88,4 +88,4 @@ object Test6 { def f(e: String): A = ??? val f: A = f(null) } -} \ No newline at end of file +} From 8c35a6e1eb18e858edc85c8ce1bd4f9968d8b75b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 9 May 2019 09:07:45 +0200 Subject: [PATCH 7/8] Better scheme for handling overloaded applys - expand before resolving, retract afterwards. - that way, we avoid a lot of fragile and incomplete logic in the resolver. --- .../dotty/tools/dotc/typer/Applications.scala | 219 ++++++++---------- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/pos/i2774.scala | 3 +- tests/run/eq-xmethod.scala | 17 ++ 4 files changed, 118 insertions(+), 123 deletions(-) create mode 100644 tests/run/eq-xmethod.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 73de95093c9f..9a26b9e64ad8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1169,6 +1169,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => if (keepConstraint) isApp else ctx.test(implicit ctx => isApp) } + /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? + * @param resultType The expected result type of the application + */ + def isDirectlyApplicableMethodRef(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = + ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) + /** Is given method reference applicable to argument types `args`? * @param resultType The expected result type of the application */ @@ -1192,27 +1198,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => isApplicableMethodRef(_, args, resultType) } - /** Is given method type applicable to type arguments `targs` and argument trees `args` without inferring views, - * possibly after inserting an `apply`? - * @param resultType The expected result type of the application - */ - def isDirectlyApplicableType(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - onMethod(tp, targs.nonEmpty || args.nonEmpty) { methRef => - ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) - } - private def onMethod(tp: Type, followApply: Boolean)(p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] => p(methRef) case mt: MethodicType => p(mt.narrow) case _ => - followApply && hasApplyWith(tp)(p) + followApply && tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) } - private def hasApplyWith(tp: Type)(p: TermRef => Boolean)(implicit ctx: Context): Boolean = - tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) - /** Does `tp` have an extension method named `name` with this-argument `argType` and * result matching `resultType`? */ @@ -1257,10 +1251,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * If that tournament yields a draw, a tiebreak is applied where * an alternative that takes more implicit parameters wins over one * that takes fewer. - * - * @param followApply if true consider `apply` members when comparing with a method reference */ - def compare(alt1: TermRef, alt2: TermRef, followApply: Boolean)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) { + def compare(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Int = track("compare") { trace(i"compare($alt1, $alt2)", overload) { assert(alt1 ne alt2) @@ -1275,78 +1267,44 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * assumption that for i = 1,...,n each ai is an abstract type name bounded * from below by Li and from above by Ui. * 3. A member of any other type `tp1` is: - * a. always as specific as a method or a polymorphic method + * a. always as specific as a method or a polymorphic method. * b. as specific as a member of any other type `tp2` if `tp1` is compatible * with `tp2`. - * - * If followApply is true, and one of the alternatives is not a method, we test instead - * whether isAsSpecific is true for all apply methods in that alternative. - * Note that this is errs on the side of not being comparable in the following case: - * - * Alternatives - * - * val x: A - * def x(y: S): B - * - * where A has members - * - * def apply(y: S1): B1 - * def apply(y: S2): B2 - * - * and only one of the two `apply` methods (say the first) is applicable. If the first `apply` - * is as specific as the method `x`, but not the second, we still judge the two `x`'s - * as incomparable. If we had used an "exists an apply method" instead of the "forall" - * then value `x` would be picked over method `x` instead. On the other hand, if - * the first `apply` was NOT applicable but the second one was, then we would still pick - * pick value `x` over method `x` even though the applicable second apply method was is not - * more specific than the `x` method. So in going with "forall" instead of "exists" we - * err on the side of treating alternatives as incomparable, instead of potentially - * picking the wrong one. */ - def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type, followApply: Boolean): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { - if (followApply) { - val isMethod1 = tp1.stripPoly.isInstanceOf[MethodType] - val isMethod2 = tp2.stripPoly.isInstanceOf[MethodType] - if (!isMethod1 && isMethod2) - return !hasApplyWith(tp1)(alt1app => !isAsSpecific(alt1app, alt1app.widen, alt2, tp2, false)) - if (!isMethod2 && isMethod1) - return !hasApplyWith(tp2)(alt2app => !isAsSpecific(alt1, tp1, alt2app, alt2app.widen, false)) - } - tp1 match { - case tp1: MethodType => // (1) - val formals1 = - if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) - else tp1.paramInfos - val isAsSpecificMethod = isApplicableMethodRef(alt2, formals1, WildcardType) - isAsSpecificMethod || tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] - case tp1: PolyType => // (2) - val nestedCtx = ctx.fresh.setExploreTyperState() - - { - implicit val ctx = nestedCtx - - // Fully define the PolyType parameters so that the infos of the - // tparams created below never contain TypeRefs whose underling types - // contain uninstantiated TypeVars, this could lead to cycles in - // `isSubType` as a TypeVar might get constrained by a TypeRef it's - // part of. - val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) - fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span) - - val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) - isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2, followApply) - } - case _ => // (3) - tp2 match { - case tp2: MethodType => true - case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true - case tp2: PolyType => // (3b) - ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType)) - case _ => // (3b) - isAsSpecificValueType(tp1, tp2) - } - } - } + def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { + case tp1: MethodType => // (1) + val formals1 = + if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle) + else tp1.paramInfos + isApplicableMethodRef(alt2, formals1, WildcardType) || + tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType] + case tp1: PolyType => // (2) + val nestedCtx = ctx.fresh.setExploreTyperState() + + { + implicit val ctx = nestedCtx + + // Fully define the PolyType parameters so that the infos of the + // tparams created below never contain TypeRefs whose underling types + // contain uninstantiated TypeVars, this could lead to cycles in + // `isSubType` as a TypeVar might get constrained by a TypeRef it's + // part of. + val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) + fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span) + + val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) + isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2) + } + case _ => // (3) + tp2 match { + case tp2: MethodType => true // (3a) + case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a) + case tp2: PolyType => // (3b) + ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType)) + case _ => // (3b) + isAsSpecificValueType(tp1, tp2) + } + }} /** Test whether value type `tp1` is as specific as value type `tp2`. * Let's abbreviate this to `tp1 <:s tp2`. @@ -1437,8 +1395,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def compareWithTypes(tp1: Type, tp2: Type) = { val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner) - def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2, followApply) - def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1, followApply) + def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) + def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") if (ownerScore == 1) @@ -1465,12 +1423,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else compareWithTypes(fullType1, fullType2) // continue by comparing implicits parameters }} - def narrowMostSpecific(alts: List[TermRef], followApply: Boolean)(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { + def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { alts match { case Nil => alts case _ :: Nil => alts case alt1 :: alt2 :: Nil => - compare(alt1, alt2, followApply) match { + compare(alt1, alt2) match { case 1 => alt1 :: Nil case -1 => alt2 :: Nil case 0 => alts @@ -1478,7 +1436,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case alt :: alts1 => def survivors(previous: List[TermRef], alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - compare(previous.head, alt, followApply) match { + compare(previous.head, alt) match { case 1 => survivors(previous, alts1) case -1 => survivors(alt :: previous.tail, alts1) case 0 => survivors(alt :: previous, alts1) @@ -1488,7 +1446,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val best :: rest = survivors(alt :: Nil, alts1) def asGood(alts: List[TermRef]): List[TermRef] = alts match { case alt :: alts1 => - if (compare(alt, best, followApply) < 0) asGood(alts1) else alt :: asGood(alts1) + if (compare(alt, best) < 0) asGood(alts1) else alt :: asGood(alts1) case nil => Nil } @@ -1507,21 +1465,17 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. - * If `alt`does not have method or poly type, and `followApply` is true, consider - * all apply members instead. In all other cases return `true`. */ - def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = { - def recur(altType: Type, followApply: Boolean): Boolean = resultType.revealIgnored match { + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = + resultType.revealIgnored match { case resultType: ValueType => altType.widen match { - case tp: PolyType => recur(constrained(tp).resultType, followApply) + case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) - case _ => !followApply || hasApplyWith(altType)(recur(_, followApply = false)) + case _ => true } case _ => true } - recur(altType, followApply = true) - } /** If the `chosen` alternative has a result type incompatible with the expected result * type `pt`, run overloading resolution again on all alternatives that do match `pt`. @@ -1535,7 +1489,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * probability of pruning the search. result type comparisons are neither cheap nor * do they prune much, on average. */ - def adaptByResult(chosen: TermRef) = pt match { + def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match { case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen.symbol, chosen, pt.resultType)) => val conformingAlts = alts.filter(alt => (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt.symbol, alt, pt.resultType))) @@ -1551,13 +1505,42 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case _ => chosen } - var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled)) - if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled)) - found = resolveOverloaded(alts, pt, Nil) - found match { - case alt :: Nil => adaptByResult(alt) :: Nil - case _ => found + def resolve(alts: List[TermRef]) = { + var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled)) + if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled)) + found = resolveOverloaded(alts, pt, Nil) + found match { + case alt :: Nil => adaptByResult(alt, alts) :: Nil + case _ => found + } + } + + /** Try an apply method, if + * - the result is applied to value arguments and alternative is not a method, or + * - the result is applied to type arguments and alternatuve is not polymorphic + */ + val tryApply: Type => Boolean = alt => pt match { + case pt: FunProto => !alt.widen.stripPoly.isInstanceOf[MethodType] + case pt: PolyProto => !alt.widen.isInstanceOf[PolyType] + case _ => false } + + /** Replace each alternative by its apply members where necesssary */ + def applyMembers(alt: TermRef): List[TermRef] = + if (tryApply(alt)) alt.member(nme.apply).alternatives.map(TermRef(alt, nme.apply, _)) + else alt :: Nil + + /** Fall back from an apply method to its original alternative */ + def retract(alt: TermRef): TermRef = + if (alt.name == nme.apply && !alts.contains(alt)) + alts.find(_.symbol == alt.prefix.termSymbol).getOrElse(alt) + else alt + + if (alts.exists(tryApply)) { + val expanded = alts.flatMap(applyMembers) + resolve(expanded).map(retract) + } + else resolve(alts) } /** This private version of `resolveOverloaded` does the bulk of the work of @@ -1591,21 +1574,17 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = - alts filter (isApplicableType(_, argTypes, resultType)) - - val numArgs = pt match { - case pt @ FunProto(args, resultType) => args.length - case _ => 0 - } + alts filter (isApplicableMethodRef(_, argTypes, resultType)) val candidates = pt match { case pt @ FunProto(args, resultType) => + val numArgs = args.length val normArgs = args.mapConserve { case Block(Nil, expr) => expr case x => x } - def sizeFits(alt: TermRef, followApply: Boolean): Boolean = alt.widen.stripPoly match { + def sizeFits(alt: TermRef): Boolean = alt.widen.stripPoly match { case tp: MethodType => val ptypes = tp.paramInfos val numParams = ptypes.length @@ -1615,13 +1594,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => else if (numParams < numArgs) isVarArgs else if (numParams > numArgs + 1) hasDefault else isVarArgs || hasDefault - case tp => - numArgs == 0 || - followApply && hasApplyWith(tp)(sizeFits(_, followApply = false)) + case _ => + numArgs == 0 } def narrowBySize(alts: List[TermRef]): List[TermRef] = - alts.filter(sizeFits(_, followApply = true)) + alts.filter(sizeFits(_)) def narrowByShapes(alts: List[TermRef]): List[TermRef] = { if (normArgs exists untpd.isFunctionWithUnknownParamType) @@ -1633,11 +1611,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = { val alts2 = alts.filter(alt => - isDirectlyApplicableType(alt, targs, args, resultType) + isDirectlyApplicableMethodRef(alt, targs, args, resultType) ) if (alts2.isEmpty && !ctx.isAfterTyper) alts.filter(alt => - isApplicableType(alt, targs, args, resultType, keepConstraint = false) + isApplicableMethodRef(alt, targs, args, resultType, keepConstraint = false) ) else alts2 @@ -1708,7 +1686,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => candidates.flatMap(cloneCandidate) } - val found = narrowMostSpecific(candidates, followApply = numArgs != 0) + val found = narrowMostSpecific(candidates) if (found.length <= 1) found else pt match { case pt @ FunProto(_, resType: FunProto) => @@ -1880,4 +1858,3 @@ trait Applications extends Compatibility { self: Typer with Dynamic => app } } - diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0d68f8ff2ce3..1fa53e79582a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1142,7 +1142,7 @@ trait Implicits { self: Typer => def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int = if (prev.ref eq ref) 0 else if (prev.level != level) prev.level - level - else nestedContext().test(implicit ctx => compare(prev.ref, ref, followApply = false)) + else nestedContext().test(implicit ctx => compare(prev.ref, ref)) /** If `alt1` is also a search success, try to disambiguate as follows: * - If alt2 is preferred over alt1, pick alt2, otherwise return an diff --git a/tests/pos/i2774.scala b/tests/pos/i2774.scala index 16a37bb963db..0ddd557013d4 100644 --- a/tests/pos/i2774.scala +++ b/tests/pos/i2774.scala @@ -6,7 +6,8 @@ object Test { val i1: Int = a given (new T{}) implied for T = new T {} val i2: Int = a + val i3: Int = a2 - def a given (t: T) given (q: Q): Int = 1 + def a2 given (t: T) given (q: Q): Int = 1 } diff --git a/tests/run/eq-xmethod.scala b/tests/run/eq-xmethod.scala new file mode 100644 index 000000000000..6e9bfa5dd855 --- /dev/null +++ b/tests/run/eq-xmethod.scala @@ -0,0 +1,17 @@ +object Test extends App { + + class R { + def _eq(that: R | N): Boolean = this eq that + } + class N + object N extends N + + def (x: N) _eq (y: R | N) = y eq N + + val r1, r2 = new R + assert(r1 _eq r1) + assert(!(r1 _eq r2)) + assert(!(r1 _eq N)) + assert(!(N _eq r1)) + assert(N _eq N) +} \ No newline at end of file From 84f79c1c11237c9a0a37563014e5f502c702395c Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Thu, 9 May 2019 17:00:31 +0200 Subject: [PATCH 8/8] Fix typos --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 9a26b9e64ad8..db904879c29e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1458,7 +1458,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * possibly also type argument `targs` that need to be applied to each alternative * to form the method type. * Two trials: First, without implicits or SAM conversions enabled. Then, - * if the fist finds no eligible candidates, with implicits and SAM conversions enabled. + * if the first finds no eligible candidates, with implicits and SAM conversions enabled. */ def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { @@ -1517,7 +1517,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Try an apply method, if * - the result is applied to value arguments and alternative is not a method, or - * - the result is applied to type arguments and alternatuve is not polymorphic + * - the result is applied to type arguments and alternative is not polymorphic */ val tryApply: Type => Boolean = alt => pt match { case pt: FunProto => !alt.widen.stripPoly.isInstanceOf[MethodType] @@ -1525,7 +1525,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case _ => false } - /** Replace each alternative by its apply members where necesssary */ + /** Replace each alternative by its apply members where necessary */ def applyMembers(alt: TermRef): List[TermRef] = if (tryApply(alt)) alt.member(nme.apply).alternatives.map(TermRef(alt, nme.apply, _)) else alt :: Nil