diff options
-rw-r--r-- | src/dotty/tools/dotc/ast/tpd.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 138 | ||||
-rw-r--r-- | tests/pos/hkgadt.scala | 9 | ||||
-rw-r--r-- | tests/pos/i618.scala | 3 | ||||
-rw-r--r-- | tests/pos/t2660.scala (renamed from tests/neg/t2660.scala) | 8 | ||||
-rw-r--r-- | tests/run/t1381.check | 7 | ||||
-rw-r--r-- | tests/run/t1381.scala | 59 |
7 files changed, 151 insertions, 76 deletions
diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 4593b9554..2b0e63a19 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -854,8 +854,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { var allAlts = denot.alternatives .map(_.termRef).filter(tr => typeParamCount(tr) == targs.length) if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType]) - val alternatives = - ctx.typer.resolveOverloaded(allAlts, proto, Nil) + val alternatives = ctx.typer.resolveOverloaded(allAlts, proto) assert(alternatives.size == 1, i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " + i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." + diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 6e78a570d..3444b0786 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -1047,31 +1047,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * to form the method type. * todo: use techniques like for implicits to pick candidates quickly? */ - def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type] = Nil)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { - - def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty - - /** The shape of given tree as a type; cannot handle named arguments. */ - def typeShape(tree: untpd.Tree): Type = tree match { - case untpd.Function(args, body) => - defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body)) - case _ => - defn.NothingType - } - - /** The shape of given tree as a type; is more expensive than - * typeShape but can can handle named arguments. - */ - def treeShape(tree: untpd.Tree): Tree = tree match { - case NamedArg(name, arg) => - val argShape = treeShape(arg) - cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe) - case _ => - dummyTreeOfType(typeShape(tree)) - } - - def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = - alts filter (isApplicable(_, argTypes, resultType)) + def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { /** Is `alt` a method or polytype whose result type after the first value parameter * section conforms to the expected type `resultType`? If `resultType` @@ -1100,23 +1076,63 @@ 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(alts: List[TermRef], chosen: TermRef) = { - def nestedCtx = ctx.fresh.setExploreTyperState - pt match { - case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) => - alts.filter(alt => - (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match { - case Nil => chosen - case alt2 :: Nil => alt2 - case alts2 => - resolveOverloaded(alts2, pt) match { - case alt2 :: Nil => alt2 - case _ => chosen - } - } - case _ => chosen - } + def adaptByResult(chosen: TermRef) = { + def nestedCtx = ctx.fresh.setExploreTyperState + pt match { + case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) => + alts.filter(alt => + (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match { + case Nil => chosen + case alt2 :: Nil => alt2 + case alts2 => + resolveOverloaded(alts2, pt) match { + case alt2 :: Nil => alt2 + case _ => chosen + } + } + 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 + } + } + + /** This private version of `resolveOverloaded` does the bulk of the work of + * overloading resolution, but does not do result adaptation. It might be + * called twice from the public `resolveOverloaded` method, once with + * implicits enabled, and once without. + */ + private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { + + def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty + + /** The shape of given tree as a type; cannot handle named arguments. */ + def typeShape(tree: untpd.Tree): Type = tree match { + case untpd.Function(args, body) => + defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body)) + case _ => + defn.NothingType + } + + /** The shape of given tree as a type; is more expensive than + * typeShape but can can handle named arguments. + */ + def treeShape(tree: untpd.Tree): Tree = tree match { + case NamedArg(name, arg) => + val argShape = treeShape(arg) + cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe) + case _ => + dummyTreeOfType(typeShape(tree)) + } + + def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = + alts filter (isApplicable(_, argTypes, resultType)) val candidates = pt match { case pt @ FunProto(args, resultType, _) => @@ -1176,9 +1192,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } } - case pt @ PolyProto(targs, pt1) => + case pt @ PolyProto(targs1, pt1) => + assert(targs.isEmpty) val alts1 = alts filter pt.isMatchedBy - resolveOverloaded(alts1, pt1, targs) + resolveOverloaded(alts1, pt1, targs1) case defn.FunctionOf(args, resultType) => narrowByTypes(alts, args, resultType) @@ -1186,23 +1203,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case pt => alts filter (normalizedCompatible(_, pt)) } - narrowMostSpecific(candidates) match { - case Nil => Nil - case alt :: Nil => - adaptByResult(alts, alt) :: Nil - // why `alts` and not `candidates`? pos/array-overload.scala gives a test case. - // Here, only the Int-apply is a candidate, but it is not compatible with the result - // type. Picking the Byte-apply as the only result-compatible solution then forces - // the arguments (which are constants) to be adapted to Byte. If we had picked - // `candidates` instead, no solution would have been found. - case alts => - val noDefaults = alts.filter(!_.symbol.hasDefaultParams) - if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists - else { - val deepPt = pt.deepenProto - if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs) - else alts - } + val found = narrowMostSpecific(candidates) + if (found.length <= 1) found + else { + val noDefaults = alts.filter(!_.symbol.hasDefaultParams) + if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists + else { + val deepPt = pt.deepenProto + if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs) + else alts + } } } @@ -1305,11 +1315,3 @@ trait Applications extends Compatibility { self: Typer with Dynamic => harmonizeWith(tpes)(identity, (tp, pt) => pt) } -/* - def typedApply(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = track("typedApply") { - new ApplyToTyped(app, fun, methRef, args, resultType).result - } - - def typedApply(fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context): Tree = - typedApply(untpd.Apply(untpd.TypedSplice(fun), args), fun, methRef, args, resultType) -*/ diff --git a/tests/pos/hkgadt.scala b/tests/pos/hkgadt.scala new file mode 100644 index 000000000..ac8caa6f3 --- /dev/null +++ b/tests/pos/hkgadt.scala @@ -0,0 +1,9 @@ +object HKGADT { + sealed trait Foo[F[_]] + final case class Bar() extends Foo[List] + + def frob[F[_]](foo: Foo[F]) = + foo match { + case Bar() => () + } +} diff --git a/tests/pos/i618.scala b/tests/pos/i618.scala new file mode 100644 index 000000000..70be56cc2 --- /dev/null +++ b/tests/pos/i618.scala @@ -0,0 +1,3 @@ +class C(val f: Any*) + +class D(override val f: Nothing) extends C(f) diff --git a/tests/neg/t2660.scala b/tests/pos/t2660.scala index 17fe26258..695db67b9 100644 --- a/tests/neg/t2660.scala +++ b/tests/pos/t2660.scala @@ -1,5 +1,3 @@ -// Dotty deviation. The calls here now are classified as ambiguous. - package hoho class G @@ -22,9 +20,7 @@ class A[T](x: T) { object T { def main(args: Array[String]): Unit = { implicit def g2h(g: G): H = new H - new A[Int](new H, 23) // error - // in the context here, either secondary constructor is applicable - // to the other, due to the implicit in scope. So the call is ambiguous. + new A[Int](new H, 23) } } @@ -40,7 +36,7 @@ object X { object T2 { def main(args: Array[String]): Unit = { implicit def g2h(g: G): H = new H - X.f(new H, 23) // error + X.f(new H, 23) } } diff --git a/tests/run/t1381.check b/tests/run/t1381.check new file mode 100644 index 000000000..84aec1df2 --- /dev/null +++ b/tests/run/t1381.check @@ -0,0 +1,7 @@ +4 +3 +2 +A +B +frA +frB diff --git a/tests/run/t1381.scala b/tests/run/t1381.scala new file mode 100644 index 000000000..c7f49c6c3 --- /dev/null +++ b/tests/run/t1381.scala @@ -0,0 +1,59 @@ +object Test { + def main(args: Array[String]): Unit = { + Test1.test() + Test2.test() + Test3.test() + } +} + +object Test1 { + class Bar[T](n: Int) { + println(n) + } + implicit def const[T](x: T): Bar[T] = new Bar[T](1) + + def bar[T](e: T): Any = new Bar[T](2) + def bar[T](e: Bar[T]): Any = new Bar[T](3) + + val b: Bar[Int] = new Bar[Int](4) + + def test(): Unit = { + bar(b) + bar(5) + } +} + +object Test2 { + trait A; trait B + class C1 { + def f(x: A): Unit = println("A") + } + class C2 extends C1 { + def f(x: B): Unit = println("B") + } + object Test extends C2 with App { + implicit def a2b(x: A): B = new B {} + def test(): Unit = { + f(new A {}) + f(new B {}) + } + } + def test(): Unit = Test.test() +} + +object Test3 { + trait A; trait B + class C extends A with B + def fr(x: A): A = { + println("frA") + x + } + def fr(x: B): B = { + println("frB") + x + } + def test(): Unit = { + val a: A = fr(new C) + val b: B = fr(new C) + } +} |