Skip to content

Commit 8a83f90

Browse files
authored
Merge pull request #15061 from rochala/fix-implicit-conversion-type-resolve-in-completions
Fix implicit conversion type resolve in completions
2 parents 7720bc6 + ef0e791 commit 8a83f90

File tree

3 files changed

+86
-27
lines changed

3 files changed

+86
-27
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,10 @@ object Completion {
384384
if qual.tpe.isExactlyNothing || qual.tpe.isNullType then
385385
Map.empty
386386
else
387-
val membersFromConversion =
388-
implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState()).flatMap(accessibleMembers)
389-
membersFromConversion.toSeq.groupByName
387+
implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState())
388+
.flatMap(accessibleMembers)
389+
.toSeq
390+
.groupByName
390391

391392
/** Completions from extension methods */
392393
private def extensionCompletions(qual: Tree)(using Context): CompletionMap =
@@ -492,15 +493,17 @@ object Completion {
492493

493494
/**
494495
* Given `qual` of type T, finds all the types S such that there exists an implicit conversion
495-
* from T to S.
496+
* from T to S. It then applies conversion method for proper type parameter resolution.
496497
*
497498
* @param qual The argument to which the implicit conversion should be applied.
498-
* @return The set of types that `qual` can be converted to.
499+
* @return The set of types after `qual` implicit conversion.
499500
*/
500501
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] = {
501502
val typer = ctx.typer
502503
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
503-
val targets = conversions.map(_.widen.finalResultType)
504+
val convertedTrees = conversions.flatMap(typer.tryApplyingImplicitConversion(_, qual))
505+
val targets = convertedTrees.map(_.tpe.finalResultType)
506+
504507
interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %")
505508
targets
506509
}

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,27 @@ trait Applications extends Compatibility {
22892289
catch
22902290
case NonFatal(_) => None
22912291

2292+
/** Tries applying conversion method reference to a provided receiver
2293+
*
2294+
* returns converted tree in case of success.
2295+
* None is returned if conversion method application fails.
2296+
*/
2297+
def tryApplyingImplicitConversion(conversionMethodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
2298+
val conversionMethodTree = ref(conversionMethodRef, needLoad = false)
2299+
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))
2300+
2301+
try
2302+
val appliedTree = inContext(newCtx) {
2303+
typed(untpd.Apply(conversionMethodTree, untpd.TypedSplice(receiver) :: Nil))
2304+
}
2305+
2306+
if appliedTree.tpe.exists && !appliedTree.tpe.isError then
2307+
Some(appliedTree)
2308+
else
2309+
None
2310+
catch
2311+
case NonFatal(x) => None
2312+
22922313
def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
22932314
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
22942315
tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty

language-server/test/dotty/tools/languageserver/CompletionTest.scala

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,27 +1150,62 @@ class CompletionTest {
11501150
.completion(("map", Method, "[B](f: Int => B): Foo[B]"))
11511151
}
11521152

1153-
// This test is not passing due to https://github.com/lampepfl/dotty/issues/14687
1154-
// @Test def higherKindedMatchTypeImplicitConversionCompletion: Unit = {
1155-
// val expected = Set(
1156-
// ("mapBoo", Method, "[B](op: Int => B): Boo[B]"),
1157-
// ("mapFoo", Method, "[B](op: Int => B): Foo[B]"),
1158-
// )
1159-
// code"""import scala.language.implicitConversions
1160-
// |case class Foo[A](x: A) {
1161-
// | def mapFoo[B](op: A => B): Foo[B] = ???
1162-
// |}
1163-
// |case class Boo[A](x: A) {
1164-
// | def mapBoo[B](op: A => B): Boo[B] = ???
1165-
// |}
1166-
// |type M[A] = A match {
1167-
// | case Int => Foo[Int]
1168-
// |}
1169-
// |implicit def fooToBoo[A](x: Foo[A]): Boo[A] = Boo(x.x)
1170-
// |case class Bar[F[_]](bar: F[Int])
1171-
// |def foo(x: Bar[M]) = x.bar.m${m1}"""
1172-
// .completion(m1, expected)
1173-
// }
1153+
@Test def higherKindedMatchTypeImplicitConversionCompletion: Unit = {
1154+
code"""import scala.language.implicitConversions
1155+
|case class Foo[A](x: A) {
1156+
| def mapFoo[B](op: A => B): Foo[B] = ???
1157+
|}
1158+
|case class Boo[A](x: A) {
1159+
| def mapBoo[B](op: A => B): Boo[B] = ???
1160+
|}
1161+
|type M[A] = A match {
1162+
| case Int => Foo[Int]
1163+
|}
1164+
|implicit def fooToBoo[A](x: Foo[A]): Boo[A] = Boo(x.x)
1165+
|case class Bar[F[_]](bar: F[Int])
1166+
|def foo(x: Bar[M]) = x.bar.m${m1}"""
1167+
.completion(
1168+
("mapBoo", Method, "[B](op: Int => B): Boo[B]"),
1169+
("mapFoo", Method, "[B](op: Int => B): Foo[B]")
1170+
)
1171+
}
1172+
1173+
@Test def higherKindedTypeInferenceTest: Unit = {
1174+
val expected = Set(
1175+
("fooTest", Method, "(x: Int): String")
1176+
)
1177+
code"""class Test[A, B] {
1178+
| def fooTest(x: A): B = ???
1179+
|}
1180+
|
1181+
|object M:
1182+
| val test = new Test[Int, String] {}
1183+
| test.foo${m1}
1184+
| (new Test[Int, String] {}).foo${m2}"""
1185+
.completion(m1, expected)
1186+
.completion(m2, expected)
1187+
}
1188+
1189+
@Test def higherKindedImplicitConversionsCompletions: Unit = {
1190+
val expected = Set(
1191+
("mapBoo", Method, "[B](f: Int => B): Boo[B]"),
1192+
("mapFoo", Method, "[B](f: Int => B): Foo[B]"),
1193+
)
1194+
code"""import scala.language.implicitConversions
1195+
|case class Foo[A](x: A) {
1196+
| def mapFoo[B](f: A => B): Foo[B] = ???
1197+
|}
1198+
|case class Boo[C](x: C) {
1199+
| def mapBoo[B](f: C => B): Boo[B] = ???
1200+
|}
1201+
|implicit def fooToBoo[D](x: Foo[D]): Boo[D] = Boo(x.x)
1202+
|object Test:
1203+
| val x = Foo(1)
1204+
| x.ma${m1}
1205+
| Foo(1).ma${m2}"""
1206+
.completion(m1, expected)
1207+
.completion(m2, expected)
1208+
}
11741209

11751210
@Test def higherKindedMatchTypeExtensionMethodCompletion: Unit = {
11761211
code"""trait Foo[A] {

0 commit comments

Comments
 (0)