Skip to content

Commit 3ff472f

Browse files
committed
Don't merge code completion items having the same name.
Also fix completion of references to primary constructor parameters
1 parent 13de192 commit 3ff472f

File tree

4 files changed

+82
-64
lines changed

4 files changed

+82
-64
lines changed

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

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -131,40 +131,18 @@ object Completion {
131131

132132
/**
133133
* Return the list of code completions with descriptions based on a mapping from names to the denotations they refer to.
134-
* If several denotations share the same name, the type denotations appear before term denotations inside
135-
* the same `Completion`.
134+
* If several denotations share the same name, each denotation will be transformed into a separate completion item.
136135
*/
137-
def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = {
138-
completions
139-
.toList.groupBy(_._1.toTermName) // don't distinguish between names of terms and types
140-
.toList.map { (name, namedDenots) =>
141-
val denots = namedDenots.flatMap(_._2)
142-
val typesFirst = denots.sortWith((d1, d2) => d1.isType && !d2.isType)
143-
val desc = description(typesFirst)
144-
Completion(name.show, desc, typesFirst.map(_.symbol))
145-
}
146-
}
147-
148-
/**
149-
* A description for completion result that represents `symbols`.
150-
*
151-
* If `denots` contains a single denotation, show its full name in case it's a type, or its type if
152-
* it's a term.
153-
*
154-
* When there are multiple denotations, show their kinds.
155-
*/
156-
def description(denots: List[SingleDenotation])(using Context): String =
157-
denots match {
158-
case denot :: Nil =>
159-
if (denot.isType) denot.symbol.showFullName
160-
else denot.info.widenTermRefExpr.show
161-
162-
case denot :: _ =>
163-
denots.map(den => ctx.printer.kindString(den.symbol)).distinct.mkString("", " and ", s" ${denot.name.show}")
136+
def describeCompletions(completions: CompletionMap)(using Context): List[Completion] =
137+
for
138+
(name, denots) <- completions.toList
139+
denot <- denots
140+
yield
141+
Completion(name.show, description(denot), List(denot.symbol))
164142

165-
case Nil =>
166-
""
167-
}
143+
def description(denot: SingleDenotation)(using Context): String =
144+
if denot.isType then denot.symbol.showFullName
145+
else denot.info.widenTermRefExpr.show
168146

169147
/** Computes code completions depending on the context in which completion is requested
170148
* @param mode Should complete names of terms, types or both
@@ -195,8 +173,9 @@ object Completion {
195173
var ctx = context
196174

197175
while ctx ne NoContext do
176+
given Context = ctx
198177
if ctx.isImportContext then
199-
importedCompletions(using ctx).foreach { (name, denots) =>
178+
importedCompletions.foreach { (name, denots) =>
200179
addMapping(name, ScopedDenotations(denots, ctx))
201180
}
202181
else if ctx.owner.isClass then

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,10 @@ class ReplDriver(settings: Array[String],
179179

180180
/** Extract possible completions at the index of `cursor` in `expr` */
181181
protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = {
182-
def makeCandidate(completion: Completion) = {
183-
val displ = completion.label
182+
def makeCandidate(label: String) = {
184183
new Candidate(
185-
/* value = */ displ,
186-
/* displ = */ displ, // displayed value
184+
/* value = */ label,
185+
/* displ = */ label, // displayed value
187186
/* group = */ null, // can be used to group completions together
188187
/* descr = */ null, // TODO use for documentation?
189188
/* suffix = */ null,
@@ -201,7 +200,7 @@ class ReplDriver(settings: Array[String],
201200
given Context = state.context.fresh.setCompilationUnit(unit)
202201
val srcPos = SourcePosition(file, Span(cursor))
203202
val (_, completions) = Completion.completions(srcPos)
204-
completions.map(makeCandidate)
203+
completions.map(_.label).distinct.map(makeCandidate)
205204
}
206205
.getOrElse(Nil)
207206
}

compiler/test/dotty/tools/dotc/interactive/CustomCompletion.scala

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,11 @@ object CustomCompletion {
6363
private type CompletionMap = Map[Name, Seq[SingleDenotation]]
6464

6565
private def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = {
66-
completions
67-
.toList.groupBy(_._1.toTermName) // don't distinguish between names of terms and types
68-
.toList.map { (name, namedDenots) =>
69-
val denots = namedDenots.flatMap(_._2)
70-
val typesFirst = denots.sortWith((d1, d2) => d1.isType && !d2.isType)
71-
val desc = Completion.description(typesFirst)
72-
Completion(label(name), desc, typesFirst.map(_.symbol))
73-
}
66+
for
67+
(name, denots) <- completions.toList
68+
denot <- denots
69+
yield
70+
Completion(label(name), Completion.description(denot), List(denot.symbol))
7471
}
7572

7673
class DeepCompleter(mode: Completion.Mode, prefix: String, pos: SourcePosition) extends Completion.Completer(mode, prefix, pos):

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

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class CompletionTest {
1919
.completion(m1, Set(
2020
("print", Method, "(x: Any): Unit"),
2121
("printf", Method, "(text: String, xs: Any*): Unit"),
22-
("println", Method, "method println")
22+
("println", Method, "(x: Any): Unit"),
23+
("println", Method, "(): Unit")
2324
))
2425
}
2526

@@ -35,17 +36,22 @@ class CompletionTest {
3536

3637
@Test def completionFromScalaPackageObject: Unit = {
3738
code"class Foo { val foo: BigD${m1} }".withSource
38-
.completion(m1, Set(("BigDecimal", Field, "type and getter BigDecimal")))
39+
.completion(m1, Set(("BigDecimal", Field, "scala.BigDecimal"),
40+
("BigDecimal", Method, "=> math.BigDecimal.type")))
3941
}
4042

4143
@Test def completionFromSyntheticPackageObject: Unit = {
4244
code"class Foo { val foo: IArr${m1} }".withSource
43-
.completion(m1, Set(("IArray", Field, "type and object IArray")))
45+
.completion(m1, Set(("IArray", Field, "scala.IArray"),
46+
("IArray", Module, "scala.IArray$package.IArray$")))
4447
}
4548

4649
@Test def completionFromJavaDefaults: Unit = {
4750
code"class Foo { val foo: Runn${m1} }".withSource
48-
.completion(m1, Set(("Runnable", Class, "trait and object Runnable")))
51+
.completion(m1, Set(
52+
("Runnable", Class, "java.lang.Runnable"),
53+
("Runnable", Module, "Runnable$")
54+
))
4955
}
5056

5157
@Test def completionWithImplicitConversion: Unit = {
@@ -125,7 +131,8 @@ class CompletionTest {
125131
object Foo""",
126132
code"""package pgk1
127133
import pkg0.F${m1}"""
128-
).completion(m1, Set(("Foo", Class, "class and object Foo")))
134+
).completion(m1, Set(("Foo", Class, "pkg0.Foo"),
135+
("Foo", Module, "pkg0.Foo$")))
129136
}
130137

131138
@Test def importCompleteIncludePackage: Unit = {
@@ -157,7 +164,8 @@ class CompletionTest {
157164

158165
@Test def importJavaClass: Unit = {
159166
code"""import java.io.FileDesc${m1}""".withSource
160-
.completion(m1, Set(("FileDescriptor", Class, "class and object FileDescriptor")))
167+
.completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"),
168+
("FileDescriptor", Module, "java.io.FileDescriptor$")))
161169
}
162170

163171
@Test def importJavaStaticMethod: Unit = {
@@ -187,7 +195,8 @@ class CompletionTest {
187195

188196
@Test def importRename: Unit = {
189197
code"""import java.io.{FileDesc${m1} => Foo}""".withSource
190-
.completion(m1, Set(("FileDescriptor", Class, "class and object FileDescriptor")))
198+
.completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"),
199+
("FileDescriptor", Module, "java.io.FileDescriptor$")))
191200
}
192201

193202
@Test def importGivenByType: Unit = {
@@ -254,7 +263,8 @@ class CompletionTest {
254263
@Test def completionOnRenamedImport: Unit = {
255264
code"""import java.io.{FileDescriptor => AwesomeStuff}
256265
trait Foo { val x: Awesom$m1 }""".withSource
257-
.completion(m1, Set(("AwesomeStuff", Class, "class and object FileDescriptor")))
266+
.completion(m1, Set(("AwesomeStuff", Class, "java.io.FileDescriptor"),
267+
("AwesomeStuff", Module, "java.io.FileDescriptor$")))
258268
}
259269

260270
@Test def completionOnRenamedImport2: Unit = {
@@ -263,7 +273,8 @@ class CompletionTest {
263273
import java.io.{FileDescriptor => MyImportedSymbol}
264274
val x: MyImp$m1
265275
}""".withSource
266-
.completion(m1, Set(("MyImportedSymbol", Class, "class and object FileDescriptor")))
276+
.completion(m1, Set(("MyImportedSymbol", Class, "java.io.FileDescriptor"),
277+
("MyImportedSymbol", Module, "java.io.FileDescriptor$")))
267278
}
268279

269280
@Test def completionRenamedAndOriginalNames: Unit = {
@@ -272,8 +283,10 @@ class CompletionTest {
272283
| import java.util.{HashMap => HashMap2}
273284
| val x: Hash$m1
274285
|}""".withSource
275-
.completion(m1, Set(("HashMap", Class, "class and object HashMap"),
276-
("HashMap2", Class, "class and object HashMap")))
286+
.completion(m1, Set(("HashMap", Class, "java.util.HashMap"),
287+
("HashMap", Module, "java.util.HashMap$"),
288+
("HashMap2", Class, "java.util.HashMap"),
289+
("HashMap2", Module, "java.util.HashMap$")))
277290
}
278291

279292
@Test def completionRenamedThrice: Unit = {
@@ -283,9 +296,12 @@ class CompletionTest {
283296
| import java.util.{HashMap => MyHashMap3}
284297
| val x: MyHash$m1
285298
|}""".withSource
286-
.completion(m1, Set(("MyHashMap", Class, "class and object HashMap"),
287-
("MyHashMap2", Class, "class and object HashMap"),
288-
("MyHashMap3", Class, "class and object HashMap")))
299+
.completion(m1, Set(("MyHashMap", Class, "java.util.HashMap"),
300+
("MyHashMap", Module, "java.util.HashMap$"),
301+
("MyHashMap2", Class, "java.util.HashMap"),
302+
("MyHashMap2", Module, "java.util.HashMap$"),
303+
("MyHashMap3", Class, "java.util.HashMap"),
304+
("MyHashMap3", Module, "java.util.HashMap$")))
289305
}
290306

291307
@Test def completeFromWildcardImports: Unit = {
@@ -369,7 +385,8 @@ class CompletionTest {
369385
|object Test extends Foo, Bar {
370386
| val x = xx$m1
371387
|}""".withSource
372-
.completion(m1, Set(("xxxx", Method, "method xxxx"))) // 2 different signatures are merged into one generic description
388+
.completion(m1, Set(("xxxx", Method, "(s: String): String"),
389+
("xxxx", Method, "(i: Int): Int")))
373390
}
374391

375392
@Test def dontCompleteFromAmbiguousImportsForEqualNestingLevels: Unit = {
@@ -482,7 +499,8 @@ class CompletionTest {
482499
| def bar(i: Int) = 0
483500
|}
484501
|import Foo.b$m1""".withSource
485-
.completion(m1, Set(("bar", Class, "class and method bar")))
502+
.completion(m1, Set(("bar", Class, "Foo.bar"),
503+
("bar", Method, "(i: Int): Int")))
486504
}
487505

488506
@Test def completionTypeAndLazyValue: Unit = {
@@ -491,7 +509,8 @@ class CompletionTest {
491509
| lazy val bar = 3
492510
|}
493511
|import Foo.b$m1""".withSource
494-
.completion(m1, Set(("bar", Field, "type and lazy value bar")))
512+
.completion(m1, Set(("bar", Field, "Foo.bar"),
513+
("bar", Field, "Int")))
495514
}
496515

497516
@Test def keepTrackOfTermsAndTypesSeparately: Unit = {
@@ -506,7 +525,8 @@ class CompletionTest {
506525
| type ZZZZ = YY$m2
507526
|}""".withSource
508527
.completion(m1, Set(("YYYY", Field, "Int$")))
509-
.completion(m2, Set(("YYYY", Field, "type and value YYYY")))
528+
.completion(m2, Set(("YYYY", Field, "XXXX.YYYY"),
529+
("YYYY", Field, "Int$")))
510530
}
511531

512532
@Test def completeRespectingAccessModifiers: Unit = {
@@ -535,6 +555,29 @@ class CompletionTest {
535555
.completion(m1, Set(("xxxx", Method, "(a: Int): Int")))
536556
}
537557

558+
@Test def completePrimaryConstructorParameter: Unit = {
559+
code"""class Foo(abc: Int) {
560+
| ab$m1
561+
| def method1: Int = {
562+
| ab$m2
563+
| 42
564+
| }
565+
| def method2: Int = {
566+
| val smth = ab$m3
567+
| 42
568+
| }
569+
|}""".withSource
570+
.completion(m1, Set(("abc", Field, "Int")))
571+
.completion(m2, Set(("abc", Field, "Int")))
572+
.completion(m2, Set(("abc", Field, "Int")))
573+
}
574+
575+
@Test def completeExtensionReceiver: Unit = {
576+
code"""extension (string: String) def xxxx = str$m1"""
577+
.withSource
578+
.completion(m1, Set(("string", Field, "String")))
579+
}
580+
538581
@Test def completeExtensionMethodWithoutParameter: Unit = {
539582
code"""object Foo
540583
|extension (foo: Foo.type) def xxxx = 1

0 commit comments

Comments
 (0)