From 14ffb35d05f36e6b4f794b2e8271e596b6a85b75 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 12:55:25 +0200 Subject: [PATCH 1/2] Insert conversions also on selections wrapped in type applications In i12708.scala, the problematic function was a selection `qual.m[tvs]` that was already applied to type variables. In that case we need to backtrack, forget the type variables and try to insert a conversion or extension method on `qual`. Fixes #12708 --- .../src/dotty/tools/dotc/typer/Typer.scala | 11 +++--- tests/pos/i12708.scala | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i12708.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bce9aff02be1..885dc86a7fd4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3045,12 +3045,12 @@ class Typer extends Namer } } - /** If this tree is a select node `qual.name` that does not conform to `pt`, - * try to insert an implicit conversion `c` around `qual` so that - * `c(qual).name` conforms to `pt`. + /** If this tree is a select node `qual.name` (possibly applied to type variables) + * that does not conform to `pt`, try to insert an implicit conversion `c` around + * `qual` so that `c(qual).name` conforms to `pt`. */ def tryInsertImplicitOnQualifier(tree: Tree, pt: Type, locked: TypeVars)(using Context): Option[Tree] = trace(i"try insert impl on qualifier $tree $pt") { - tree match { + tree match case tree @ Select(qual, name) if name != nme.CONSTRUCTOR => val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false) if selProto.isMatchedBy(qual.tpe) then None @@ -3061,8 +3061,9 @@ class Typer extends Namer else Some(adapt(tree1, pt, locked)) } { (_, _) => None } + case TypeApply(fn, args) if args.forall(_.isInstanceOf[TypeVarBinder[_]]) => + tryInsertImplicitOnQualifier(fn, pt, locked) case _ => None - } } /** Given a selection `qual.name`, try to convert to an extension method diff --git a/tests/pos/i12708.scala b/tests/pos/i12708.scala new file mode 100644 index 000000000000..f8149f0732d0 --- /dev/null +++ b/tests/pos/i12708.scala @@ -0,0 +1,37 @@ +import language.implicitConversions + +trait AdditiveSemigroup[A] + +final class AdditiveSemigroupOps[A](lhs: A)(implicit as: AdditiveSemigroup[A]) { + def +(rhs: A): A = ??? + def ^(rhs: A): A = ??? +} + +trait AdditiveSemigroupSyntax { + implicit def additiveSemigroupOps[A: AdditiveSemigroup](a: A): AdditiveSemigroupOps[A] = + new AdditiveSemigroupOps(a) +} + +object syntax { + object additiveSemigroup extends AdditiveSemigroupSyntax +} + +object App { + + def main(args: Array[String]): Unit = { + import syntax.additiveSemigroup._ + + implicit def IntAlgebra[A]: AdditiveSemigroup[Map[Int, A]] = ??? + + def res[A]: Map[Int, A] = { + val a: Map[Int, A] = Map.empty + val b: Map[Int, A] = Map.empty + // Calls the operator on AdditiveSemigroupOps + a ^ b + // Calls the operator + on AdditiveSemigroupOps only in Scala 2 + // In Scala 3 tries to call `+` on Map + a + b + } + } + +} \ No newline at end of file From 03f88ef679ef832a205a20f557dd39f36c002dee Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 13:55:54 +0200 Subject: [PATCH 2/2] Fix tests --- tests/neg/i8861.scala | 2 +- tests/{neg => pos}/zipped.scala | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) rename tests/{neg => pos}/zipped.scala (71%) diff --git a/tests/neg/i8861.scala b/tests/neg/i8861.scala index 87f1884f6155..744b49b0107b 100644 --- a/tests/neg/i8861.scala +++ b/tests/neg/i8861.scala @@ -20,7 +20,7 @@ object Test { ) def minimalFail[M](c: Container { type A = M }): M = c.visit( int = vi => vi.i : vi.A, - str = vs => vs.t : vs.A // error + str = vs => vs.t : vs.A // error // error ) def main(args: Array[String]): Unit = { diff --git a/tests/neg/zipped.scala b/tests/pos/zipped.scala similarity index 71% rename from tests/neg/zipped.scala rename to tests/pos/zipped.scala index c7d55da6000a..2453b880cbc3 100644 --- a/tests/neg/zipped.scala +++ b/tests/pos/zipped.scala @@ -22,17 +22,14 @@ object Test { xs.lazyZip(xs).lazyZip(xs) .map( (x: Int, y: Int, z: Int) => x + y + z ) // OK - // 4. The single parameter map does not work. + // 4. The single parameter map works through an inserted conversion xs.lazyZip(xs).lazyZip(xs) - .map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // error + .map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // now also OK - // 5. If we leave out the parameter type, we get a "Wrong number of parameters" error instead + // 5. If we leave out the parameter type, it now works as well. xs.lazyZip(xs).lazyZip(xs) - .map( x => x match { case (x, y, z) => x + y + z }) // error + .map( x => x match { case (x, y, z) => x + y + z }) // now also OK - // This means that the following works in Dotty in normal mode, since a `withFilter` - // is inserted. But it does no work under -strict. And it will not work in Scala 3.1. - // The reason is that without -strict, the code below is mapped to (1), but with -strict - // it is mapped to (5). + // This means that the following works in Dotty 3.0 as well as 3.x for ((x, y, z) <- xs.lazyZip(xs).lazyZip(xs)) yield x + y + z } \ No newline at end of file