diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 61dbdd0fd9bf..bb552191fc36 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -131,40 +131,18 @@ object Completion { /** * Return the list of code completions with descriptions based on a mapping from names to the denotations they refer to. - * If several denotations share the same name, the type denotations appear before term denotations inside - * the same `Completion`. + * If several denotations share the same name, each denotation will be transformed into a separate completion item. */ - def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = { - completions - .toList.groupBy(_._1.toTermName) // don't distinguish between names of terms and types - .toList.map { (name, namedDenots) => - val denots = namedDenots.flatMap(_._2) - val typesFirst = denots.sortWith((d1, d2) => d1.isType && !d2.isType) - val desc = description(typesFirst) - Completion(name.show, desc, typesFirst.map(_.symbol)) - } - } - - /** - * A description for completion result that represents `symbols`. - * - * If `denots` contains a single denotation, show its full name in case it's a type, or its type if - * it's a term. - * - * When there are multiple denotations, show their kinds. - */ - def description(denots: List[SingleDenotation])(using Context): String = - denots match { - case denot :: Nil => - if (denot.isType) denot.symbol.showFullName - else denot.info.widenTermRefExpr.show - - case denot :: _ => - denots.map(den => ctx.printer.kindString(den.symbol)).distinct.mkString("", " and ", s" ${denot.name.show}") + def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = + for + (name, denots) <- completions.toList + denot <- denots + yield + Completion(name.show, description(denot), List(denot.symbol)) - case Nil => - "" - } + def description(denot: SingleDenotation)(using Context): String = + if denot.isType then denot.symbol.showFullName + else denot.info.widenTermRefExpr.show /** Computes code completions depending on the context in which completion is requested * @param mode Should complete names of terms, types or both @@ -195,8 +173,9 @@ object Completion { var ctx = context while ctx ne NoContext do + given Context = ctx if ctx.isImportContext then - importedCompletions(using ctx).foreach { (name, denots) => + importedCompletions.foreach { (name, denots) => addMapping(name, ScopedDenotations(denots, ctx)) } else if ctx.owner.isClass then diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 7ad47f6a8cfd..7abaa86a77dc 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -179,11 +179,10 @@ class ReplDriver(settings: Array[String], /** Extract possible completions at the index of `cursor` in `expr` */ protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = { - def makeCandidate(completion: Completion) = { - val displ = completion.label + def makeCandidate(label: String) = { new Candidate( - /* value = */ displ, - /* displ = */ displ, // displayed value + /* value = */ label, + /* displ = */ label, // displayed value /* group = */ null, // can be used to group completions together /* descr = */ null, // TODO use for documentation? /* suffix = */ null, @@ -201,7 +200,7 @@ class ReplDriver(settings: Array[String], given Context = state.context.fresh.setCompilationUnit(unit) val srcPos = SourcePosition(file, Span(cursor)) val (_, completions) = Completion.completions(srcPos) - completions.map(makeCandidate) + completions.map(_.label).distinct.map(makeCandidate) } .getOrElse(Nil) } diff --git a/compiler/test/dotty/tools/dotc/interactive/CustomCompletion.scala b/compiler/test/dotty/tools/dotc/interactive/CustomCompletion.scala index 82f13b5fbf44..7b422a1164ae 100644 --- a/compiler/test/dotty/tools/dotc/interactive/CustomCompletion.scala +++ b/compiler/test/dotty/tools/dotc/interactive/CustomCompletion.scala @@ -63,14 +63,11 @@ object CustomCompletion { private type CompletionMap = Map[Name, Seq[SingleDenotation]] private def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = { - completions - .toList.groupBy(_._1.toTermName) // don't distinguish between names of terms and types - .toList.map { (name, namedDenots) => - val denots = namedDenots.flatMap(_._2) - val typesFirst = denots.sortWith((d1, d2) => d1.isType && !d2.isType) - val desc = Completion.description(typesFirst) - Completion(label(name), desc, typesFirst.map(_.symbol)) - } + for + (name, denots) <- completions.toList + denot <- denots + yield + Completion(label(name), Completion.description(denot), List(denot.symbol)) } class DeepCompleter(mode: Completion.Mode, prefix: String, pos: SourcePosition) extends Completion.Completer(mode, prefix, pos): diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 28e9f1a37882..ffb3f22d1ce1 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -19,7 +19,8 @@ class CompletionTest { .completion(m1, Set( ("print", Method, "(x: Any): Unit"), ("printf", Method, "(text: String, xs: Any*): Unit"), - ("println", Method, "method println") + ("println", Method, "(x: Any): Unit"), + ("println", Method, "(): Unit") )) } @@ -35,17 +36,22 @@ class CompletionTest { @Test def completionFromScalaPackageObject: Unit = { code"class Foo { val foo: BigD${m1} }".withSource - .completion(m1, Set(("BigDecimal", Field, "type and getter BigDecimal"))) + .completion(m1, Set(("BigDecimal", Field, "scala.BigDecimal"), + ("BigDecimal", Method, "=> math.BigDecimal.type"))) } @Test def completionFromSyntheticPackageObject: Unit = { code"class Foo { val foo: IArr${m1} }".withSource - .completion(m1, Set(("IArray", Field, "type and object IArray"))) + .completion(m1, Set(("IArray", Field, "scala.IArray"), + ("IArray", Module, "scala.IArray$package.IArray$"))) } @Test def completionFromJavaDefaults: Unit = { code"class Foo { val foo: Runn${m1} }".withSource - .completion(m1, Set(("Runnable", Class, "trait and object Runnable"))) + .completion(m1, Set( + ("Runnable", Class, "java.lang.Runnable"), + ("Runnable", Module, "Runnable$") + )) } @Test def completionWithImplicitConversion: Unit = { @@ -125,7 +131,8 @@ class CompletionTest { object Foo""", code"""package pgk1 import pkg0.F${m1}""" - ).completion(m1, Set(("Foo", Class, "class and object Foo"))) + ).completion(m1, Set(("Foo", Class, "pkg0.Foo"), + ("Foo", Module, "pkg0.Foo$"))) } @Test def importCompleteIncludePackage: Unit = { @@ -157,7 +164,8 @@ class CompletionTest { @Test def importJavaClass: Unit = { code"""import java.io.FileDesc${m1}""".withSource - .completion(m1, Set(("FileDescriptor", Class, "class and object FileDescriptor"))) + .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), + ("FileDescriptor", Module, "java.io.FileDescriptor$"))) } @Test def importJavaStaticMethod: Unit = { @@ -187,7 +195,8 @@ class CompletionTest { @Test def importRename: Unit = { code"""import java.io.{FileDesc${m1} => Foo}""".withSource - .completion(m1, Set(("FileDescriptor", Class, "class and object FileDescriptor"))) + .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), + ("FileDescriptor", Module, "java.io.FileDescriptor$"))) } @Test def importGivenByType: Unit = { @@ -254,7 +263,8 @@ class CompletionTest { @Test def completionOnRenamedImport: Unit = { code"""import java.io.{FileDescriptor => AwesomeStuff} trait Foo { val x: Awesom$m1 }""".withSource - .completion(m1, Set(("AwesomeStuff", Class, "class and object FileDescriptor"))) + .completion(m1, Set(("AwesomeStuff", Class, "java.io.FileDescriptor"), + ("AwesomeStuff", Module, "java.io.FileDescriptor$"))) } @Test def completionOnRenamedImport2: Unit = { @@ -263,7 +273,8 @@ class CompletionTest { import java.io.{FileDescriptor => MyImportedSymbol} val x: MyImp$m1 }""".withSource - .completion(m1, Set(("MyImportedSymbol", Class, "class and object FileDescriptor"))) + .completion(m1, Set(("MyImportedSymbol", Class, "java.io.FileDescriptor"), + ("MyImportedSymbol", Module, "java.io.FileDescriptor$"))) } @Test def completionRenamedAndOriginalNames: Unit = { @@ -272,8 +283,10 @@ class CompletionTest { | import java.util.{HashMap => HashMap2} | val x: Hash$m1 |}""".withSource - .completion(m1, Set(("HashMap", Class, "class and object HashMap"), - ("HashMap2", Class, "class and object HashMap"))) + .completion(m1, Set(("HashMap", Class, "java.util.HashMap"), + ("HashMap", Module, "java.util.HashMap$"), + ("HashMap2", Class, "java.util.HashMap"), + ("HashMap2", Module, "java.util.HashMap$"))) } @Test def completionRenamedThrice: Unit = { @@ -283,9 +296,12 @@ class CompletionTest { | import java.util.{HashMap => MyHashMap3} | val x: MyHash$m1 |}""".withSource - .completion(m1, Set(("MyHashMap", Class, "class and object HashMap"), - ("MyHashMap2", Class, "class and object HashMap"), - ("MyHashMap3", Class, "class and object HashMap"))) + .completion(m1, Set(("MyHashMap", Class, "java.util.HashMap"), + ("MyHashMap", Module, "java.util.HashMap$"), + ("MyHashMap2", Class, "java.util.HashMap"), + ("MyHashMap2", Module, "java.util.HashMap$"), + ("MyHashMap3", Class, "java.util.HashMap"), + ("MyHashMap3", Module, "java.util.HashMap$"))) } @Test def completeFromWildcardImports: Unit = { @@ -369,7 +385,8 @@ class CompletionTest { |object Test extends Foo, Bar { | val x = xx$m1 |}""".withSource - .completion(m1, Set(("xxxx", Method, "method xxxx"))) // 2 different signatures are merged into one generic description + .completion(m1, Set(("xxxx", Method, "(s: String): String"), + ("xxxx", Method, "(i: Int): Int"))) } @Test def dontCompleteFromAmbiguousImportsForEqualNestingLevels: Unit = { @@ -482,7 +499,8 @@ class CompletionTest { | def bar(i: Int) = 0 |} |import Foo.b$m1""".withSource - .completion(m1, Set(("bar", Class, "class and method bar"))) + .completion(m1, Set(("bar", Class, "Foo.bar"), + ("bar", Method, "(i: Int): Int"))) } @Test def completionTypeAndLazyValue: Unit = { @@ -491,7 +509,8 @@ class CompletionTest { | lazy val bar = 3 |} |import Foo.b$m1""".withSource - .completion(m1, Set(("bar", Field, "type and lazy value bar"))) + .completion(m1, Set(("bar", Field, "Foo.bar"), + ("bar", Field, "Int"))) } @Test def keepTrackOfTermsAndTypesSeparately: Unit = { @@ -506,7 +525,8 @@ class CompletionTest { | type ZZZZ = YY$m2 |}""".withSource .completion(m1, Set(("YYYY", Field, "Int$"))) - .completion(m2, Set(("YYYY", Field, "type and value YYYY"))) + .completion(m2, Set(("YYYY", Field, "XXXX.YYYY"), + ("YYYY", Field, "Int$"))) } @Test def completeRespectingAccessModifiers: Unit = { @@ -535,6 +555,29 @@ class CompletionTest { .completion(m1, Set(("xxxx", Method, "(a: Int): Int"))) } + @Test def completePrimaryConstructorParameter: Unit = { + code"""class Foo(abc: Int) { + | ab$m1 + | def method1: Int = { + | ab$m2 + | 42 + | } + | def method2: Int = { + | val smth = ab$m3 + | 42 + | } + |}""".withSource + .completion(m1, Set(("abc", Field, "Int"))) + .completion(m2, Set(("abc", Field, "Int"))) + .completion(m2, Set(("abc", Field, "Int"))) + } + + @Test def completeExtensionReceiver: Unit = { + code"""extension (string: String) def xxxx = str$m1""" + .withSource + .completion(m1, Set(("string", Field, "String"))) + } + @Test def completeExtensionMethodWithoutParameter: Unit = { code"""object Foo |extension (foo: Foo.type) def xxxx = 1