1
1
package dotty .tools .dotc .interactive
2
2
3
- import scala .language .unsafeNulls
4
-
5
3
import dotty .tools .dotc .ast .untpd
4
+ import dotty .tools .dotc .ast .NavigateAST
6
5
import dotty .tools .dotc .config .Printers .interactiv
7
6
import dotty .tools .dotc .core .Contexts ._
8
7
import dotty .tools .dotc .core .Decorators ._
@@ -25,6 +24,10 @@ import dotty.tools.dotc.util.SourcePosition
25
24
26
25
import scala .collection .mutable
27
26
import scala .util .control .NonFatal
27
+ import dotty .tools .dotc .core .ContextOps .localContext
28
+ import dotty .tools .dotc .core .Names
29
+ import dotty .tools .dotc .core .Types
30
+ import dotty .tools .dotc .core .Symbols
28
31
29
32
/**
30
33
* One of the results of a completion query.
@@ -37,18 +40,17 @@ import scala.util.control.NonFatal
37
40
*/
38
41
case class Completion (label : String , description : String , symbols : List [Symbol ])
39
42
40
- object Completion {
43
+ object Completion :
41
44
42
45
import dotty .tools .dotc .ast .tpd ._
43
46
44
47
/** Get possible completions from tree at `pos`
45
48
*
46
49
* @return offset and list of symbols for possible completions
47
50
*/
48
- def completions (pos : SourcePosition )(using Context ): (Int , List [Completion ]) = {
49
- val path = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
51
+ def completions (pos : SourcePosition )(using Context ): (Int , List [Completion ]) =
52
+ val path : List [ Tree ] = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
50
53
computeCompletions(pos, path)(using Interactive .contextOfPath(path).withPhase(Phases .typerPhase))
51
- }
52
54
53
55
/**
54
56
* Inspect `path` to determine what kinds of symbols should be considered.
@@ -60,10 +62,11 @@ object Completion {
60
62
*
61
63
* Otherwise, provide no completion suggestion.
62
64
*/
63
- def completionMode (path : List [Tree ], pos : SourcePosition ): Mode =
64
- path match {
65
- case Ident (_) :: Import (_, _) :: _ => Mode .ImportOrExport
66
- case (ref : RefTree ) :: _ =>
65
+ def completionMode (path : List [untpd.Tree ], pos : SourcePosition ): Mode =
66
+ path match
67
+ case untpd.Ident (_) :: untpd.Import (_, _) :: _ => Mode .ImportOrExport
68
+ case untpd.Ident (_) :: (_ : untpd.ImportSelector ) :: _ => Mode .ImportOrExport
69
+ case (ref : untpd.RefTree ) :: _ =>
67
70
if (ref.name.isTermName) Mode .Term
68
71
else if (ref.name.isTypeName) Mode .Type
69
72
else Mode .None
@@ -72,9 +75,8 @@ object Completion {
72
75
if sel.imported.span.contains(pos.span) then Mode .ImportOrExport
73
76
else Mode .None // Can't help completing the renaming
74
77
75
- case (_ : ImportOrExport ) :: _ => Mode .ImportOrExport
78
+ case (_ : untpd. ImportOrExport ) :: _ => Mode .ImportOrExport
76
79
case _ => Mode .None
77
- }
78
80
79
81
/** When dealing with <errors> in varios palces we check to see if they are
80
82
* due to incomplete backticks. If so, we ensure we get the full prefix
@@ -101,10 +103,13 @@ object Completion {
101
103
case (sel : untpd.ImportSelector ) :: _ =>
102
104
completionPrefix(sel.imported :: Nil , pos)
103
105
106
+ case untpd.Ident (_) :: (sel : untpd.ImportSelector ) :: _ if ! sel.isGiven =>
107
+ completionPrefix(sel.imported :: Nil , pos)
108
+
104
109
case (tree : untpd.ImportOrExport ) :: _ =>
105
- tree.selectors.find(_.span.contains(pos.span)).map { selector =>
110
+ tree.selectors.find(_.span.contains(pos.span)).map: selector =>
106
111
completionPrefix(selector :: Nil , pos)
107
- } .getOrElse(" " )
112
+ .getOrElse(" " )
108
113
109
114
// Foo.`se<TAB> will result in Select(Ident(Foo), <error>)
110
115
case (select : untpd.Select ) :: _ if select.name == nme.ERROR =>
@@ -118,27 +123,65 @@ object Completion {
118
123
if (ref.name == nme.ERROR ) " "
119
124
else ref.name.toString.take(pos.span.point - ref.span.point)
120
125
121
- case _ =>
122
- " "
126
+ case _ => " "
127
+
123
128
end completionPrefix
124
129
125
130
/** Inspect `path` to determine the offset where the completion result should be inserted. */
126
- def completionOffset (path : List [Tree ]): Int =
127
- path match {
128
- case (ref : RefTree ) :: _ => ref.span.point
131
+ def completionOffset (untpdPath : List [untpd. Tree ]): Int =
132
+ untpdPath match {
133
+ case (ref : untpd. RefTree ) :: _ => ref.span.point
129
134
case _ => 0
130
135
}
131
136
132
- private def computeCompletions (pos : SourcePosition , path : List [Tree ])(using Context ): (Int , List [Completion ]) = {
133
- val mode = completionMode(path, pos)
134
- val rawPrefix = completionPrefix(path, pos)
137
+ /** Some information about the trees is lost after Typer such as Extension method construct
138
+ * is expanded into methods. In order to support completions in those cases
139
+ * we have to rely on untyped trees and only when types are necessary use typed trees.
140
+ */
141
+ def resolveTypedOrUntypedPath (tpdPath : List [Tree ], pos : SourcePosition )(using Context ): List [untpd.Tree ] =
142
+ lazy val untpdPath : List [untpd.Tree ] = NavigateAST
143
+ .pathTo(pos.span, List (ctx.compilationUnit.untpdTree), true ).collect:
144
+ case untpdTree : untpd.Tree => untpdTree
145
+
146
+ tpdPath match
147
+ case (_ : Bind ) :: _ => tpdPath
148
+ case (_ : untpd.TypTree ) :: _ => tpdPath
149
+ case _ => untpdPath
150
+
151
+ /** Handle case when cursor position is inside extension method construct.
152
+ * The extension method construct is then desugared into methods, and consturct parameters
153
+ * are no longer a part of a typed tree, but instead are prepended to method parameters.
154
+ *
155
+ * @param untpdPath The typed or untyped path to the tree that is being completed
156
+ * @param tpdPath The typed path that will be returned if no extension method construct is found
157
+ * @param pos The cursor position
158
+ *
159
+ * @return Typed path to the parameter of the extension construct if found or tpdPath
160
+ */
161
+ private def typeCheckExtensionConstructPath (
162
+ untpdPath : List [untpd.Tree ], tpdPath : List [Tree ], pos : SourcePosition
163
+ )(using Context ): List [Tree ] =
164
+ untpdPath.collectFirst:
165
+ case untpd.ExtMethods (paramss, _) =>
166
+ val enclosingParam = paramss.flatten.find(_.span.contains(pos.span))
167
+ enclosingParam.map: param =>
168
+ ctx.typer.index(paramss.flatten)
169
+ val typedEnclosingParam = ctx.typer.typed(param)
170
+ Interactive .pathTo(typedEnclosingParam, pos.span)
171
+ .flatten.getOrElse(tpdPath)
172
+
173
+ private def computeCompletions (pos : SourcePosition , tpdPath : List [Tree ])(using Context ): (Int , List [Completion ]) =
174
+ val path0 = resolveTypedOrUntypedPath(tpdPath, pos)
175
+ val mode = completionMode(path0, pos)
176
+ val rawPrefix = completionPrefix(path0, pos)
135
177
136
178
val hasBackTick = rawPrefix.headOption.contains('`' )
137
179
val prefix = if hasBackTick then rawPrefix.drop(1 ) else rawPrefix
138
180
139
181
val completer = new Completer (mode, prefix, pos)
140
182
141
- val completions = path match {
183
+ val adjustedPath = typeCheckExtensionConstructPath(path0, tpdPath, pos)
184
+ val completions = adjustedPath match
142
185
// Ignore synthetic select from `This` because in code it was `Ident`
143
186
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
144
187
case Select (qual @ This (_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
@@ -147,21 +190,19 @@ object Completion {
147
190
case (tree : ImportOrExport ) :: _ => completer.directMemberCompletions(tree.expr)
148
191
case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer.directMemberCompletions(expr)
149
192
case _ => completer.scopeCompletions
150
- }
151
193
152
194
val describedCompletions = describeCompletions(completions)
153
195
val backtickedCompletions =
154
196
describedCompletions.map(completion => backtickCompletions(completion, hasBackTick))
155
197
156
- val offset = completionOffset(path )
198
+ val offset = completionOffset(path0 )
157
199
158
200
interactiv.println(i """ completion with pos = $pos,
159
201
| prefix = ${completer.prefix},
160
202
| term = ${completer.mode.is(Mode .Term )},
161
203
| type = ${completer.mode.is(Mode .Type )}
162
204
| results = $backtickedCompletions%, % """ )
163
205
(offset, backtickedCompletions)
164
- }
165
206
166
207
def backtickCompletions (completion : Completion , hasBackTick : Boolean ) =
167
208
if hasBackTick || needsBacktick(completion.label) then
@@ -174,17 +215,17 @@ object Completion {
174
215
// https://github.com/scalameta/metals/blob/main/mtags/src/main/scala/scala/meta/internal/mtags/KeywordWrapper.scala
175
216
// https://github.com/com-lihaoyi/Ammonite/blob/73a874173cd337f953a3edc9fb8cb96556638fdd/amm/util/src/main/scala/ammonite/util/Model.scala
176
217
private def needsBacktick (s : String ) =
177
- val chunks = s.split(" _" , - 1 )
218
+ val chunks = s.split(" _" , - 1 ).nn
178
219
179
220
val validChunks = chunks.zipWithIndex.forall { case (chunk, index) =>
180
- chunk.forall(Chars .isIdentifierPart) ||
181
- (chunk.forall(Chars .isOperatorPart) &&
221
+ chunk.nn. forall(Chars .isIdentifierPart) ||
222
+ (chunk.nn. forall(Chars .isOperatorPart) &&
182
223
index == chunks.length - 1 &&
183
224
! (chunks.lift(index - 1 ).contains(" " ) && index - 1 == 0 ))
184
225
}
185
226
186
227
val validStart =
187
- Chars .isIdentifierStart(s(0 )) || chunks(0 ).forall(Chars .isOperatorPart)
228
+ Chars .isIdentifierStart(s(0 )) || chunks(0 ).nn. forall(Chars .isOperatorPart)
188
229
189
230
val valid = validChunks && validStart && ! keywords.contains(s)
190
231
@@ -216,7 +257,7 @@ object Completion {
216
257
* For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map
217
258
* and they never conflict with each other.
218
259
*/
219
- class Completer (val mode : Mode , val prefix : String , pos : SourcePosition ) {
260
+ class Completer (val mode : Mode , val prefix : String , pos : SourcePosition ):
220
261
/** Completions for terms and types that are currently in scope:
221
262
* the members of the current class, local definitions and the symbols that have been imported,
222
263
* recursively adding completions from outer scopes.
@@ -230,7 +271,7 @@ object Completion {
230
271
* (even if the import follows it syntactically)
231
272
* - a more deeply nested import shadowing a member or a local definition causes an ambiguity
232
273
*/
233
- def scopeCompletions (using context : Context ): CompletionMap = {
274
+ def scopeCompletions (using context : Context ): CompletionMap =
234
275
val mappings = collection.mutable.Map .empty[Name , List [ScopedDenotations ]].withDefaultValue(List .empty)
235
276
def addMapping (name : Name , denots : ScopedDenotations ) =
236
277
mappings(name) = mappings(name) :+ denots
@@ -302,7 +343,7 @@ object Completion {
302
343
}
303
344
304
345
resultMappings
305
- }
346
+ end scopeCompletions
306
347
307
348
/** Widen only those types which are applied or are exactly nothing
308
349
*/
@@ -335,16 +376,16 @@ object Completion {
335
376
/** Completions introduced by imports directly in this context.
336
377
* Completions from outer contexts are not included.
337
378
*/
338
- private def importedCompletions (using Context ): CompletionMap = {
379
+ private def importedCompletions (using Context ): CompletionMap =
339
380
val imp = ctx.importInfo
340
381
341
- def fromImport (name : Name , nameInScope : Name ): Seq [(Name , SingleDenotation )] =
342
- imp.site.member(name).alternatives
343
- .collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
344
-
345
382
if imp == null then
346
383
Map .empty
347
384
else
385
+ def fromImport (name : Name , nameInScope : Name ): Seq [(Name , SingleDenotation )] =
386
+ imp.site.member(name).alternatives
387
+ .collect { case denot if include(denot, nameInScope) => nameInScope -> denot }
388
+
348
389
val givenImports = imp.importedImplicits
349
390
.map { ref => (ref.implicitName: Name , ref.underlyingRef.denot.asSingleDenotation) }
350
391
.filter((name, denot) => include(denot, name))
@@ -370,7 +411,7 @@ object Completion {
370
411
}.toSeq.groupByName
371
412
372
413
givenImports ++ wildcardMembers ++ explicitMembers
373
- }
414
+ end importedCompletions
374
415
375
416
/** Completions from implicit conversions including old style extensions using implicit classes */
376
417
private def implicitConversionMemberCompletions (qual : Tree )(using Context ): CompletionMap =
@@ -532,7 +573,6 @@ object Completion {
532
573
extension [N <: Name ](namedDenotations : Seq [(N , SingleDenotation )])
533
574
@ annotation.targetName(" groupByNameTupled" )
534
575
def groupByName : CompletionMap = namedDenotations.groupMap((name, denot) => name)((name, denot) => denot)
535
- }
536
576
537
577
private type CompletionMap = Map [Name , Seq [SingleDenotation ]]
538
578
@@ -545,11 +585,11 @@ object Completion {
545
585
* The completion mode: defines what kinds of symbols should be included in the completion
546
586
* results.
547
587
*/
548
- class Mode (val bits : Int ) extends AnyVal {
588
+ class Mode (val bits : Int ) extends AnyVal :
549
589
def is (other : Mode ): Boolean = (bits & other.bits) == other.bits
550
590
def | (other : Mode ): Mode = new Mode (bits | other.bits)
551
- }
552
- object Mode {
591
+
592
+ object Mode :
553
593
/** No symbol should be included */
554
594
val None : Mode = new Mode (0 )
555
595
@@ -561,6 +601,4 @@ object Completion {
561
601
562
602
/** Both term and type symbols are allowed */
563
603
val ImportOrExport : Mode = new Mode (4 ) | Term | Type
564
- }
565
- }
566
604
0 commit comments