diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index af3cf679c161..133e366ff199 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -384,9 +384,10 @@ object Completion { if qual.tpe.isExactlyNothing || qual.tpe.isNullType then Map.empty else - val membersFromConversion = - implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState()).flatMap(accessibleMembers) - membersFromConversion.toSeq.groupByName + implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState()) + .flatMap(accessibleMembers) + .toSeq + .groupByName /** Completions from extension methods */ private def extensionCompletions(qual: Tree)(using Context): CompletionMap = @@ -492,15 +493,17 @@ object Completion { /** * Given `qual` of type T, finds all the types S such that there exists an implicit conversion - * from T to S. + * from T to S. It then applies conversion method for proper type parameter resolution. * * @param qual The argument to which the implicit conversion should be applied. - * @return The set of types that `qual` can be converted to. + * @return The set of types after `qual` implicit conversion. */ private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] = { val typer = ctx.typer val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits - val targets = conversions.map(_.widen.finalResultType) + val convertedTrees = conversions.flatMap(typer.tryApplyingImplicitConversion(_, qual)) + val targets = convertedTrees.map(_.tpe.finalResultType) + interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %") targets } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index d80d4f366636..a2c314e12ccf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2289,6 +2289,27 @@ trait Applications extends Compatibility { catch case NonFatal(_) => None + /** Tries applying conversion method reference to a provided receiver + * + * returns converted tree in case of success. + * None is returned if conversion method application fails. + */ + def tryApplyingImplicitConversion(conversionMethodRef: TermRef, receiver: Tree)(using Context): Option[Tree] = + val conversionMethodTree = ref(conversionMethodRef, needLoad = false) + val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter)) + + try + val appliedTree = inContext(newCtx) { + typed(untpd.Apply(conversionMethodTree, untpd.TypedSplice(receiver) :: Nil)) + } + + if appliedTree.tpe.exists && !appliedTree.tpe.isError then + Some(appliedTree) + else + None + catch + case NonFatal(x) => None + def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean = methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType && tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 757ac736b6fa..fd6b4c958278 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1150,27 +1150,62 @@ class CompletionTest { .completion(("map", Method, "[B](f: Int => B): Foo[B]")) } - // This test is not passing due to https://github.com/lampepfl/dotty/issues/14687 - // @Test def higherKindedMatchTypeImplicitConversionCompletion: Unit = { - // val expected = Set( - // ("mapBoo", Method, "[B](op: Int => B): Boo[B]"), - // ("mapFoo", Method, "[B](op: Int => B): Foo[B]"), - // ) - // code"""import scala.language.implicitConversions - // |case class Foo[A](x: A) { - // | def mapFoo[B](op: A => B): Foo[B] = ??? - // |} - // |case class Boo[A](x: A) { - // | def mapBoo[B](op: A => B): Boo[B] = ??? - // |} - // |type M[A] = A match { - // | case Int => Foo[Int] - // |} - // |implicit def fooToBoo[A](x: Foo[A]): Boo[A] = Boo(x.x) - // |case class Bar[F[_]](bar: F[Int]) - // |def foo(x: Bar[M]) = x.bar.m${m1}""" - // .completion(m1, expected) - // } + @Test def higherKindedMatchTypeImplicitConversionCompletion: Unit = { + code"""import scala.language.implicitConversions + |case class Foo[A](x: A) { + | def mapFoo[B](op: A => B): Foo[B] = ??? + |} + |case class Boo[A](x: A) { + | def mapBoo[B](op: A => B): Boo[B] = ??? + |} + |type M[A] = A match { + | case Int => Foo[Int] + |} + |implicit def fooToBoo[A](x: Foo[A]): Boo[A] = Boo(x.x) + |case class Bar[F[_]](bar: F[Int]) + |def foo(x: Bar[M]) = x.bar.m${m1}""" + .completion( + ("mapBoo", Method, "[B](op: Int => B): Boo[B]"), + ("mapFoo", Method, "[B](op: Int => B): Foo[B]") + ) + } + + @Test def higherKindedTypeInferenceTest: Unit = { + val expected = Set( + ("fooTest", Method, "(x: Int): String") + ) + code"""class Test[A, B] { + | def fooTest(x: A): B = ??? + |} + | + |object M: + | val test = new Test[Int, String] {} + | test.foo${m1} + | (new Test[Int, String] {}).foo${m2}""" + .completion(m1, expected) + .completion(m2, expected) + } + + @Test def higherKindedImplicitConversionsCompletions: Unit = { + val expected = Set( + ("mapBoo", Method, "[B](f: Int => B): Boo[B]"), + ("mapFoo", Method, "[B](f: Int => B): Foo[B]"), + ) + code"""import scala.language.implicitConversions + |case class Foo[A](x: A) { + | def mapFoo[B](f: A => B): Foo[B] = ??? + |} + |case class Boo[C](x: C) { + | def mapBoo[B](f: C => B): Boo[B] = ??? + |} + |implicit def fooToBoo[D](x: Foo[D]): Boo[D] = Boo(x.x) + |object Test: + | val x = Foo(1) + | x.ma${m1} + | Foo(1).ma${m2}""" + .completion(m1, expected) + .completion(m2, expected) + } @Test def higherKindedMatchTypeExtensionMethodCompletion: Unit = { code"""trait Foo[A] {