1
1
package dotty .tools .dotc .interactive
2
2
3
- import scala .language .unsafeNulls
4
-
5
3
import dotty .tools .dotc .ast .untpd
6
4
import dotty .tools .dotc .ast .NavigateAST
7
5
import dotty .tools .dotc .config .Printers .interactiv
@@ -38,18 +36,17 @@ import scala.util.control.NonFatal
38
36
*/
39
37
case class Completion (label : String , description : String , symbols : List [Symbol ])
40
38
41
- object Completion {
39
+ object Completion :
42
40
43
41
import dotty .tools .dotc .ast .tpd ._
44
42
45
43
/** Get possible completions from tree at `pos`
46
44
*
47
45
* @return offset and list of symbols for possible completions
48
46
*/
49
- def completions (pos : SourcePosition )(using Context ): (Int , List [Completion ]) = {
50
- val path = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
47
+ def completions (pos : SourcePosition )(using Context ): (Int , List [Completion ]) =
48
+ val path : List [ Tree ] = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
51
49
computeCompletions(pos, path)(using Interactive .contextOfPath(path).withPhase(Phases .typerPhase))
52
- }
53
50
54
51
/**
55
52
* Inspect `path` to determine what kinds of symbols should be considered.
@@ -61,10 +58,11 @@ object Completion {
61
58
*
62
59
* Otherwise, provide no completion suggestion.
63
60
*/
64
- def completionMode (path : List [Tree ], pos : SourcePosition ): Mode =
65
- path match {
66
- case Ident (_) :: Import (_, _) :: _ => Mode .ImportOrExport
67
- case (ref : RefTree ) :: _ =>
61
+ def completionMode (path : List [untpd.Tree ], pos : SourcePosition ): Mode =
62
+ path match
63
+ case untpd.Ident (_) :: untpd.Import (_, _) :: _ => Mode .ImportOrExport
64
+ case untpd.Ident (_) :: (_ : untpd.ImportSelector ) :: _ => Mode .ImportOrExport
65
+ case (ref : untpd.RefTree ) :: _ =>
68
66
if (ref.name.isTermName) Mode .Term
69
67
else if (ref.name.isTypeName) Mode .Type
70
68
else Mode .None
@@ -73,9 +71,8 @@ object Completion {
73
71
if sel.imported.span.contains(pos.span) then Mode .ImportOrExport
74
72
else Mode .None // Can't help completing the renaming
75
73
76
- case (_ : ImportOrExport ) :: _ => Mode .ImportOrExport
74
+ case (_ : untpd. ImportOrExport ) :: _ => Mode .ImportOrExport
77
75
case _ => Mode .None
78
- }
79
76
80
77
/** When dealing with <errors> in varios palces we check to see if they are
81
78
* due to incomplete backticks. If so, we ensure we get the full prefix
@@ -97,15 +94,18 @@ object Completion {
97
94
* Inspect `path` to determine the completion prefix. Only symbols whose name start with the
98
95
* returned prefix should be considered.
99
96
*/
100
- def completionPrefix (path : List [untpd.Tree ], pos : SourcePosition )(using Context ): String =
97
+ def completionPrefix (path : List [untpd.Tree ], pos : SourcePosition )(using Context ): String =
101
98
path match
102
99
case (sel : untpd.ImportSelector ) :: _ =>
103
100
completionPrefix(sel.imported :: Nil , pos)
104
101
102
+ case untpd.Ident (_) :: (sel : untpd.ImportSelector ) :: _ if ! sel.isGiven =>
103
+ completionPrefix(sel.imported :: Nil , pos)
104
+
105
105
case (tree : untpd.ImportOrExport ) :: _ =>
106
- tree.selectors.find(_.span.contains(pos.span)).map { selector =>
106
+ tree.selectors.find(_.span.contains(pos.span)).map: selector =>
107
107
completionPrefix(selector :: Nil , pos)
108
- } .getOrElse(" " )
108
+ .getOrElse(" " )
109
109
110
110
// Foo.`se<TAB> will result in Select(Ident(Foo), <error>)
111
111
case (select : untpd.Select ) :: _ if select.name == nme.ERROR =>
@@ -119,29 +119,34 @@ object Completion {
119
119
if (ref.name == nme.ERROR ) " "
120
120
else ref.name.toString.take(pos.span.point - ref.span.point)
121
121
122
- case _ =>
123
- " "
122
+ case _ => " "
123
+
124
124
end completionPrefix
125
125
126
126
/** Inspect `path` to determine the offset where the completion result should be inserted. */
127
- def completionOffset (path : List [Tree ]): Int =
128
- path match {
129
- case (ref : RefTree ) :: _ => ref.span.point
127
+ def completionOffset (untpdPath : List [untpd. Tree ]): Int =
128
+ untpdPath match {
129
+ case (ref : untpd. RefTree ) :: _ => ref.span.point
130
130
case _ => 0
131
131
}
132
132
133
- /**
134
- * Inspect `path` to deterimine whether enclosing tree is a result of tree extension.
135
- * If so, completion should use untyped path containing tree before extension to get proper results .
133
+ /** Some information about the trees is lost after Typer such as Extension method definitions
134
+ * are expanded into methods. In order to support completions in those cases
135
+ * we have to rely on untyped trees and only when types are necessary use typed trees .
136
136
*/
137
- def pathBeforeDesugaring (path : List [Tree ], pos : SourcePosition )(using Context ): List [Tree ] =
138
- val hasUntypedTree = path.headOption.forall(NavigateAST .untypedPath(_, exactMatch = true ).nonEmpty)
139
- if hasUntypedTree then path
140
- else NavigateAST .untypedPath(pos.span).collect:
141
- case tree : untpd.Tree => tree
142
-
143
- private def computeCompletions (pos : SourcePosition , path : List [Tree ])(using Context ): (Int , List [Completion ]) = {
144
- val path0 = pathBeforeDesugaring(path, pos)
137
+ def resolveTypedOrUntypedPath (tpdPath : List [Tree ], pos : SourcePosition )(using Context ): List [untpd.Tree ] =
138
+ lazy val untpdPath : List [untpd.Tree ] = NavigateAST
139
+ .pathTo(pos.span, List (ctx.compilationUnit.untpdTree), true ).collect:
140
+ case untpdTree : untpd.Tree => untpdTree
141
+
142
+
143
+ tpdPath match
144
+ case (_ : Bind ) :: _ => tpdPath
145
+ case (_ : untpd.TypTree ) :: _ => tpdPath
146
+ case _ => untpdPath
147
+
148
+ private def computeCompletions (pos : SourcePosition , tpdPath : List [Tree ])(using Context ): (Int , List [Completion ]) =
149
+ val path0 = resolveTypedOrUntypedPath(tpdPath, pos)
145
150
val mode = completionMode(path0, pos)
146
151
val rawPrefix = completionPrefix(path0, pos)
147
152
@@ -150,16 +155,15 @@ object Completion {
150
155
151
156
val completer = new Completer (mode, prefix, pos)
152
157
153
- val completions = path0 match {
154
- // Ignore synthetic select from `This` because in code it was `Ident`
155
- // See example in dotty.tools.languageserver.CompletionTest.syntheticThis
156
- case Select (qual @ This (_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
157
- case Select (qual, _) :: _ if qual.tpe.hasSimpleKind => completer.selectionCompletions(qual)
158
- case Select (qual, _) :: _ => Map .empty
159
- case (tree : ImportOrExport ) :: _ => completer.directMemberCompletions(tree.expr)
160
- case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer.directMemberCompletions(expr)
161
- case _ => completer.scopeCompletions
162
- }
158
+ val completions = tpdPath match
159
+ // Ignore synthetic select from `This` because in code it was `Ident`
160
+ // See example in dotty.tools.languageserver.CompletionTest.syntheticThis
161
+ case Select (qual @ This (_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
162
+ case Select (qual, _) :: _ if qual.tpe.hasSimpleKind => completer.selectionCompletions(qual)
163
+ case Select (qual, _) :: _ => Map .empty
164
+ case (tree : ImportOrExport ) :: _ => completer.directMemberCompletions(tree.expr)
165
+ case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer.directMemberCompletions(expr)
166
+ case _ => completer.scopeCompletions
163
167
164
168
val describedCompletions = describeCompletions(completions)
165
169
val backtickedCompletions =
@@ -173,7 +177,6 @@ object Completion {
173
177
| type = ${completer.mode.is(Mode .Type )}
174
178
| results = $backtickedCompletions%, % """ )
175
179
(offset, backtickedCompletions)
176
- }
177
180
178
181
def backtickCompletions (completion : Completion , hasBackTick : Boolean ) =
179
182
if hasBackTick || needsBacktick(completion.label) then
@@ -186,17 +189,17 @@ object Completion {
186
189
// https://github.com/scalameta/metals/blob/main/mtags/src/main/scala/scala/meta/internal/mtags/KeywordWrapper.scala
187
190
// https://github.com/com-lihaoyi/Ammonite/blob/73a874173cd337f953a3edc9fb8cb96556638fdd/amm/util/src/main/scala/ammonite/util/Model.scala
188
191
private def needsBacktick (s : String ) =
189
- val chunks = s.split(" _" , - 1 )
192
+ val chunks = s.split(" _" , - 1 ).nn
190
193
191
194
val validChunks = chunks.zipWithIndex.forall { case (chunk, index) =>
192
- chunk.forall(Chars .isIdentifierPart) ||
193
- (chunk.forall(Chars .isOperatorPart) &&
195
+ chunk.nn. forall(Chars .isIdentifierPart) ||
196
+ (chunk.nn. forall(Chars .isOperatorPart) &&
194
197
index == chunks.length - 1 &&
195
198
! (chunks.lift(index - 1 ).contains(" " ) && index - 1 == 0 ))
196
199
}
197
200
198
201
val validStart =
199
- Chars .isIdentifierStart(s(0 )) || chunks(0 ).forall(Chars .isOperatorPart)
202
+ Chars .isIdentifierStart(s(0 )) || chunks(0 ).nn. forall(Chars .isOperatorPart)
200
203
201
204
val valid = validChunks && validStart && ! keywords.contains(s)
202
205
@@ -228,7 +231,7 @@ object Completion {
228
231
* For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map
229
232
* and they never conflict with each other.
230
233
*/
231
- class Completer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
234
+ class Completer (val mode : Mode , val prefix : String , pos : SourcePosition ):
232
235
/** Completions for terms and types that are currently in scope:
233
236
* the members of the current class, local definitions and the symbols that have been imported,
234
237
* recursively adding completions from outer scopes.
@@ -242,7 +245,7 @@ object Completion {
242
245
* (even if the import follows it syntactically)
243
246
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
244
247
*/
245
- def scopeCompletions (using context : Context ): CompletionMap = {
248
+ def scopeCompletions (using context : Context ): CompletionMap =
246
249
val mappings = collection.mutable.Map .empty[Name , List [ScopedDenotations ]].withDefaultValue(List .empty)
247
250
def addMapping (name : Name , denots : ScopedDenotations ) =
248
251
mappings(name) = mappings(name) :+ denots
@@ -314,7 +317,7 @@ object Completion {
314
317
}
315
318
316
319
resultMappings
317
- }
320
+ end scopeCompletions
318
321
319
322
/** Widen only those types which are applied or are exactly nothing
320
323
*/
@@ -347,16 +350,16 @@ object Completion {
347
350
/** Completions introduced by imports directly in this context.
348
351
* Completions from outer contexts are not included.
349
352
*/
350
- private def importedCompletions (using Context ): CompletionMap = {
353
+ private def importedCompletions (using Context ): CompletionMap =
351
354
val imp = ctx.importInfo
352
355
353
- def fromImport (name : Name , nameInScope : Name ): Seq [(Name , SingleDenotation )] =
354
- imp.site.member(name).alternatives
355
- .collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
356
-
357
356
if imp == null then
358
357
Map .empty
359
358
else
359
+ def fromImport (name : Name , nameInScope : Name ): Seq [(Name , SingleDenotation )] =
360
+ imp.site.member(name).alternatives
361
+ .collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
362
+
360
363
val givenImports = imp.importedImplicits
361
364
.map { ref => (ref.implicitName: Name , ref.underlyingRef.denot.asSingleDenotation) }
362
365
.filter((name, denot) => include(denot, name))
@@ -382,7 +385,7 @@ object Completion {
382
385
}.toSeq.groupByName
383
386
384
387
givenImports ++ wildcardMembers ++ explicitMembers
385
- }
388
+ end importedCompletions
386
389
387
390
/** Completions from implicit conversions including old style extensions using implicit classes */
388
391
private def implicitConversionMemberCompletions (qual : Tree )(using Context ): CompletionMap =
@@ -544,7 +547,6 @@ object Completion {
544
547
extension [N <: Name ](namedDenotations : Seq [(N , SingleDenotation )])
545
548
@ annotation.targetName(" groupByNameTupled" )
546
549
def groupByName : CompletionMap = namedDenotations.groupMap((name, denot) => name)((name, denot) => denot)
547
- }
548
550
549
551
private type CompletionMap = Map [Name , Seq [SingleDenotation ]]
550
552
@@ -557,11 +559,11 @@ object Completion {
557
559
* The completion mode: defines what kinds of symbols should be included in the completion
558
560
* results.
559
561
*/
560
- class Mode (val bits : Int ) extends AnyVal {
562
+ class Mode (val bits : Int ) extends AnyVal :
561
563
def is (other : Mode ): Boolean = (bits & other.bits) == other.bits
562
564
def | (other : Mode ): Mode = new Mode (bits | other.bits)
563
- }
564
- object Mode {
565
+
566
+ object Mode :
565
567
/** No symbol should be included */
566
568
val None : Mode = new Mode (0 )
567
569
@@ -573,6 +575,4 @@ object Completion {
573
575
574
576
/** Both term and type symbols are allowed */
575
577
val ImportOrExport : Mode = new Mode (4 ) | Term | Type
576
- }
577
- }
578
578
0 commit comments