diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index c97cd065699f..0692862c58ed 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -310,14 +310,22 @@ object Completion { resultMappings } + /** Replaces underlying type with reduced one, when it's MatchType */ + def reduceUnderlyingMatchType(qual: Tree)(using Context): Tree= + qual.tpe.widen match + case ctx.typer.MatchTypeInDisguise(mt) => qual.withType(mt) + case _ => qual + /** Completions for selections from a term. * Direct members take priority over members from extensions * and so do members from extensions over members from implicit conversions */ def selectionCompletions(qual: Tree)(using Context): CompletionMap = - implicitConversionMemberCompletions(qual) ++ - extensionCompletions(qual) ++ - directMemberCompletions(qual) + val reducedQual = reduceUnderlyingMatchType(qual) + + implicitConversionMemberCompletions(reducedQual) ++ + extensionCompletions(reducedQual) ++ + directMemberCompletions(reducedQual) /** Completions for members of `qual`'s type. * These include inherited definitions but not members added by extensions or implicit conversions diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 29a303ee6718..fee8e52ccfe8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1515,6 +1515,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer assignType(cpy.Closure(tree)(env1, meth1, target), meth1, target) } + /** Extractor for match types hidden behind an AppliedType/MatchAlias */ + object MatchTypeInDisguise { + def unapply(tp: AppliedType)(using Context): Option[MatchType] = tp match { + case AppliedType(tycon: TypeRef, args) => + tycon.info match { + case MatchAlias(alias) => + alias.applyIfParameterized(args) match { + case mt: MatchType => Some(mt) + case _ => None + } + case _ => None + } + case _ => None + } + } + def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = tree.selector match { case EmptyTree => @@ -1542,21 +1558,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val selType = rawSelectorTpe match case c: ConstantType if tree.isInline => c case otherTpe => otherTpe.widen - /** Extractor for match types hidden behind an AppliedType/MatchAlias */ - object MatchTypeInDisguise { - def unapply(tp: AppliedType): Option[MatchType] = tp match { - case AppliedType(tycon: TypeRef, args) => - tycon.info match { - case MatchAlias(alias) => - alias.applyIfParameterized(args) match { - case mt: MatchType => Some(mt) - case _ => None - } - case _ => None - } - case _ => None - } - } /** Does `tree` has the same shape as the given match type? * We only support typed patterns with empty guards, but diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 5b8f306b4e68..062fa3db11fa 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1130,4 +1130,98 @@ class CompletionTest { code"""import scala.util.chaining.`s${m1}""" .withSource.completion(m1, expected) } + + @Test def matchTypeCompletions: Unit = { + val expected = Set( + ("fooTest", Method, "(y: Int): Int"), + ) + code"""case class Foo(x: Int) { + | def fooTest(y: Int): Int = ??? + |} + |type Elem[X] = X match { + | case Int => Foo + | case Any => X + |} + |def elem[X](x: X): Elem[X] = x match { + | case x: Int => Foo(x) + | case x: Any => x + |} + |object Test: + | elem(1).foo${m1}""" + .withSource.completion(m1, expected) + } + + @Test def higherKindedMatchTypeDeclaredCompletion: Unit = { + val expected = Set( + ("map", Method, "[B](f: Int => B): Foo[B]"), + ) + code"""trait Foo[A] { + | def map[B](f: A => B): Foo[B] = ??? + |} + |case class Bar[F[_]](bar: F[Int]) + |type M[T] = T match { + | case Int => Foo[Int] + |} + |object Test: + | val x = Bar[M](new Foo[Int]{}) + | x.bar.m${m1}""" + .withSource.completion(m1, expected) + } + + @Test def higherKindedMatchTypeLazyCompletion: Unit = { + val expected = Set( + ("map", Method, "[B](f: Int => B): Foo[B]"), + ) + code"""trait Foo[A] { + | def map[B](f: A => B): Foo[B] = ??? + |} + |case class Bar[F[_]](bar: F[Int]) + |type M[T] = T match { + | case Int => Foo[Int] + |} + |def foo(x: Bar[M]) = x.bar.m${m1}""" + .withSource.completion(m1, expected) + } + + // 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}""" + // .withSource.completion(m1, expected) + // } + + @Test def higherKindedMatchTypeExtensionMethodCompletion: Unit = { + val expected = Set( + ("mapFoo", Method, "[B](f: Int => B): Foo[B]"), + ("mapExtensionMethod", Method, "[B](f: Int => B): Foo[B]"), + ) + code"""trait Foo[A] { + | def mapFoo[B](f: A => B): Foo[B] = ??? + |} + |extension[A] (x: Foo[A]) { + | def mapExtensionMethod[B](f: A => B): Foo[B] = ??? + |} + |case class Baz[F[_]](baz: F[Int]) + |type M[T] = T match { + | case Int => Foo[Int] + |} + |case class Bar[F[_]](bar: F[Int]) + |def foo(x: Bar[M]) = x.bar.ma${m1}""" + .withSource.completion(m1, expected) + } }