Skip to content

Fix implicit conversion type resolve in completions #15061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
}
Expand Down
21 changes: 21 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down