From 530f1385357b276dfc0878532d0d613653512d2a Mon Sep 17 00:00:00 2001 From: rochala Date: Tue, 8 Mar 2022 10:13:11 +0100 Subject: [PATCH 1/3] add completions for specific MatchType cases --- .../tools/dotc/interactive/Completion.scala | 27 +++++++++++++++++++ .../tools/languageserver/CompletionTest.scala | 16 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 02386c04d708..37082e831f36 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -23,6 +23,10 @@ import dotty.tools.dotc.util.SourcePosition import scala.collection.mutable import scala.util.control.NonFatal +import dotty.tools.dotc.core.Types.MatchType +import dotty.tools.dotc.core.Types.AppliedType +import dotty.tools.dotc.core.Types.TypeRef +import dotty.tools.dotc.core.Types.MatchAlias /** * One of the results of a completion query. @@ -302,6 +306,7 @@ object Completion { def selectionCompletions(qual: Tree)(using Context): CompletionMap = implicitConversionMemberCompletions(qual) ++ extensionCompletions(qual) ++ + matchTypeCompletions(qual) ++ directMemberCompletions(qual) /** Completions for members of `qual`'s type. @@ -362,6 +367,28 @@ object Completion { implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState()).flatMap(accessibleMembers) membersFromConversion.toSeq.groupByName + /** Completions for derived members of `MatchType`'s type. */ + def matchTypeCompletions(qual: Tree)(using Context): CompletionMap = + /** 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 + } + } + + qual.tpe.widenDealias match + case MatchTypeInDisguise(mt) => accessibleMembers(mt.reduced).groupByName + case _ => Map.empty + /** Completions from extension methods */ private def extensionCompletions(qual: Tree)(using Context): CompletionMap = def asDefLikeType(tpe: Type): Type = tpe match diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 683432ae2a1c..dbe1dc4b11dd 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1121,4 +1121,20 @@ class CompletionTest { | val x = Bar.`fo${m1}""" .withSource.completion(m1, expected) } + + @Test def matchTypeCompletion: 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) + + } } From 59d86d4fb19d84cc708a7008793e12eca991b3aa Mon Sep 17 00:00:00 2001 From: rochala Date: Tue, 8 Mar 2022 10:36:17 +0100 Subject: [PATCH 2/3] change MatchTypeInDisguise scope --- .../tools/dotc/interactive/Completion.scala | 22 +------------ .../src/dotty/tools/dotc/typer/Typer.scala | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 37082e831f36..a7d735c5b4a7 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -23,10 +23,6 @@ import dotty.tools.dotc.util.SourcePosition import scala.collection.mutable import scala.util.control.NonFatal -import dotty.tools.dotc.core.Types.MatchType -import dotty.tools.dotc.core.Types.AppliedType -import dotty.tools.dotc.core.Types.TypeRef -import dotty.tools.dotc.core.Types.MatchAlias /** * One of the results of a completion query. @@ -369,24 +365,8 @@ object Completion { /** Completions for derived members of `MatchType`'s type. */ def matchTypeCompletions(qual: Tree)(using Context): CompletionMap = - /** 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 - } - } - qual.tpe.widenDealias match - case MatchTypeInDisguise(mt) => accessibleMembers(mt.reduced).groupByName + case ctx.typer.MatchTypeInDisguise(mt) => accessibleMembers(mt.reduced).groupByName case _ => Map.empty /** Completions from extension methods */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e5aefd8a13d9..9c3252658c55 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1499,6 +1499,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 => @@ -1526,21 +1542,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 From 61822d2b1f8f8fa1469f5f205a57fd4e339e8292 Mon Sep 17 00:00:00 2001 From: rochala Date: Tue, 15 Mar 2022 09:03:56 +0100 Subject: [PATCH 3/3] add more tests, reduce Tree --- .../tools/dotc/interactive/Completion.scala | 21 ++--- .../tools/languageserver/CompletionTest.scala | 85 ++++++++++++++++++- 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index a7d735c5b4a7..cfe735bff6d9 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -295,15 +295,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) ++ - matchTypeCompletions(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 @@ -363,12 +370,6 @@ object Completion { implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState()).flatMap(accessibleMembers) membersFromConversion.toSeq.groupByName - /** Completions for derived members of `MatchType`'s type. */ - def matchTypeCompletions(qual: Tree)(using Context): CompletionMap = - qual.tpe.widenDealias match - case ctx.typer.MatchTypeInDisguise(mt) => accessibleMembers(mt.reduced).groupByName - case _ => Map.empty - /** Completions from extension methods */ private def extensionCompletions(qual: Tree)(using Context): CompletionMap = def asDefLikeType(tpe: Type): Type = tpe match diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index dbe1dc4b11dd..9cef519a36ba 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -17,7 +17,7 @@ class CompletionTest { @Test def completionFromScalaPredef: Unit = { code"class Foo { def foo: Unit = prin${m1} }".withSource .completion(m1, Set( - ("print", Method, "(x: Any): Unit"), + ("print", Method, "(x: Any): Unit"), ("printf", Method, "(text: String, xs: Any*): Unit"), ("println", Method, "(x: Any): Unit"), ("println", Method, "(): Unit") @@ -1122,12 +1122,49 @@ class CompletionTest { .withSource.completion(m1, expected) } - @Test def matchTypeCompletion: Unit = { + @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] + | 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 { @@ -1135,6 +1172,48 @@ class CompletionTest { |} |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) } + }