@@ -19,6 +19,8 @@ import dotty.tools.dotc.core.TypeError
19
19
import dotty .tools .dotc .core .Phases
20
20
import dotty .tools .dotc .core .Types .{AppliedType , ExprType , MethodOrPoly , NameFilter , NoType , RefinedType , TermRef , Type , TypeProxy }
21
21
import dotty .tools .dotc .parsing .Tokens
22
+ import dotty .tools .dotc .typer .Implicits .SearchSuccess
23
+ import dotty .tools .dotc .typer .Inferencing
22
24
import dotty .tools .dotc .util .Chars
23
25
import dotty .tools .dotc .util .SourcePosition
24
26
@@ -28,6 +30,7 @@ import dotty.tools.dotc.core.ContextOps.localContext
28
30
import dotty .tools .dotc .core .Names
29
31
import dotty .tools .dotc .core .Types
30
32
import dotty .tools .dotc .core .Symbols
33
+ import dotty .tools .dotc .core .Constants
31
34
32
35
/**
33
36
* One of the results of a completion query.
@@ -49,8 +52,31 @@ object Completion:
49
52
* @return offset and list of symbols for possible completions
50
53
*/
51
54
def completions (pos : SourcePosition )(using Context ): (Int , List [Completion ]) =
52
- val path : List [Tree ] = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
53
- computeCompletions(pos, path)(using Interactive .contextOfPath(path).withPhase(Phases .typerPhase))
55
+ val tpdPath = Interactive .pathTo(ctx.compilationUnit.tpdTree, pos.span)
56
+ val completionContext = Interactive .contextOfPath(tpdPath).withPhase(Phases .typerPhase)
57
+ inContext(completionContext):
58
+ val untpdPath = Interactive .resolveTypedOrUntypedPath(tpdPath, pos)
59
+ val mode = completionMode(untpdPath, pos)
60
+ val rawPrefix = completionPrefix(untpdPath, pos)
61
+ val completions = rawCompletions(pos, mode, rawPrefix, tpdPath, untpdPath)
62
+
63
+ postProcessCompletions(untpdPath, completions, rawPrefix)
64
+
65
+
66
+ /** Get possible completions from tree at `pos`
67
+ * This method requires manually computing the mode, prefix and paths.
68
+ *
69
+ * @return completion map of name to list of denotations
70
+ */
71
+ def rawCompletions (
72
+ pos : SourcePosition ,
73
+ mode : Mode ,
74
+ rawPrefix : String ,
75
+ tpdPath : List [Tree ],
76
+ untpdPath : List [untpd.Tree ]
77
+ )(using Context ): CompletionMap =
78
+ val adjustedPath = typeCheckExtensionConstructPath(untpdPath, tpdPath, pos)
79
+ computeCompletions(pos, mode, rawPrefix, adjustedPath)
54
80
55
81
/**
56
82
* Inspect `path` to determine what kinds of symbols should be considered.
@@ -63,90 +89,69 @@ object Completion:
63
89
* Otherwise, provide no completion suggestion.
64
90
*/
65
91
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 ) :: _ =>
70
- if (ref.name.isTermName) Mode .Term
71
- else if (ref.name.isTypeName) Mode .Type
72
- else Mode .None
73
92
74
- case (sel : untpd.ImportSelector ) :: _ =>
75
- if sel.imported.span.contains(pos.span) then Mode .ImportOrExport
76
- else Mode .None // Can't help completing the renaming
93
+ val completionSymbolKind : Mode =
94
+ path match
95
+ case untpd.Ident (_) :: untpd.Import (_, _) :: _ => Mode .ImportOrExport
96
+ case untpd.Ident (_) :: (_ : untpd.ImportSelector ) :: _ => Mode .ImportOrExport
97
+ case Literal (Constants .Constant (_ : String )) :: _ => Mode .Term // literal completions
98
+ case (ref : untpd.RefTree ) :: _ =>
99
+ if (ref.name.isTermName) Mode .Term
100
+ else if (ref.name.isTypeName) Mode .Type
101
+ else Mode .None
77
102
78
- case (_ : untpd.ImportOrExport ) :: _ => Mode .ImportOrExport
79
- case _ => Mode .None
103
+ case (sel : untpd.ImportSelector ) :: _ =>
104
+ if sel.imported.span.contains(pos.span) then Mode .ImportOrExport
105
+ else Mode .None // Can't help completing the renaming
80
106
81
- /** When dealing with <errors> in varios palces we check to see if they are
82
- * due to incomplete backticks. If so, we ensure we get the full prefix
83
- * including the backtick.
84
- *
85
- * @param content The source content that we'll check the positions for the prefix
86
- * @param start The start position we'll start to look for the prefix at
87
- * @param end The end position we'll look for the prefix at
88
- * @return Either the full prefix including the ` or an empty string
89
- */
90
- private def checkBacktickPrefix (content : Array [Char ], start : Int , end : Int ): String =
91
- content.lift(start) match
92
- case Some (char) if char == '`' =>
93
- content.slice(start, end).mkString
94
- case _ =>
95
- " "
107
+ case (_ : untpd.ImportOrExport ) :: _ => Mode .ImportOrExport
108
+ case _ => Mode .None
109
+
110
+ val completionKind : Mode =
111
+ path match
112
+ case Nil | (_ : PackageDef ) :: _ => Mode .None
113
+ case untpd.Ident (_) :: (_ : untpd.ImportSelector ) :: _ => Mode .Member
114
+ case (_ : Select ) :: _ => Mode .Member
115
+ case _ => Mode .Scope
116
+
117
+ completionSymbolKind | completionKind
96
118
97
119
/**
98
120
* Inspect `path` to determine the completion prefix. Only symbols whose name start with the
99
121
* returned prefix should be considered.
100
122
*/
101
123
def completionPrefix (path : List [untpd.Tree ], pos : SourcePosition )(using Context ): String =
124
+ def fallback : Int =
125
+ var i = pos.point - 1
126
+ while i >= 0 && Chars .isIdentifierPart(pos.source.content()(i)) do i -= 1
127
+ i + 1
128
+
102
129
path match
103
130
case (sel : untpd.ImportSelector ) :: _ =>
104
131
completionPrefix(sel.imported :: Nil , pos)
105
132
106
133
case untpd.Ident (_) :: (sel : untpd.ImportSelector ) :: _ if ! sel.isGiven =>
107
- completionPrefix(sel.imported :: Nil , pos)
134
+ if sel.isWildcard then pos.source.content()(pos.point - 1 ).toString
135
+ else completionPrefix(sel.imported :: Nil , pos)
108
136
109
137
case (tree : untpd.ImportOrExport ) :: _ =>
110
138
tree.selectors.find(_.span.contains(pos.span)).map: selector =>
111
139
completionPrefix(selector :: Nil , pos)
112
140
.getOrElse(" " )
113
141
114
- // Foo.`se<TAB> will result in Select(Ident(Foo), <error>)
115
- case (select : untpd.Select ) :: _ if select.name == nme.ERROR =>
116
- checkBacktickPrefix(select.source.content(), select.nameSpan.start, select.span.end)
117
-
118
- // import scala.util.chaining.`s<TAB> will result in a Ident(<error>)
119
- case (ident : untpd.Ident ) :: _ if ident.name == nme.ERROR =>
120
- checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end)
142
+ case (tree : untpd.RefTree ) :: _ if tree.name != nme.ERROR =>
143
+ tree.name.toString.take(pos.span.point - tree.span.point)
121
144
122
- case (ref : untpd.RefTree ) :: _ =>
123
- if (ref.name == nme.ERROR ) " "
124
- else ref.name.toString.take(pos.span.point - ref.span.point)
145
+ case _ => pos.source.content.slice(fallback, pos.point).mkString
125
146
126
- case _ => " "
127
147
128
148
end completionPrefix
129
149
130
150
/** Inspect `path` to determine the offset where the completion result should be inserted. */
131
151
def completionOffset (untpdPath : List [untpd.Tree ]): Int =
132
- untpdPath match {
152
+ untpdPath match
133
153
case (ref : untpd.RefTree ) :: _ => ref.span.point
134
154
case _ => 0
135
- }
136
-
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
155
151
156
/** Handle case when cursor position is inside extension method construct.
152
157
* The extension method construct is then desugared into methods, and consturct parameters
@@ -170,18 +175,12 @@ object Completion:
170
175
Interactive .pathTo(typedEnclosingParam, pos.span)
171
176
.flatten.getOrElse(tpdPath)
172
177
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)
177
-
178
+ private def computeCompletions (pos : SourcePosition , mode : Mode , rawPrefix : String , adjustedPath : List [Tree ])(using Context ): CompletionMap =
178
179
val hasBackTick = rawPrefix.headOption.contains('`' )
179
180
val prefix = if hasBackTick then rawPrefix.drop(1 ) else rawPrefix
180
-
181
181
val completer = new Completer (mode, prefix, pos)
182
182
183
- val adjustedPath = typeCheckExtensionConstructPath(path0, tpdPath, pos)
184
- val completions = adjustedPath match
183
+ val result = adjustedPath match
185
184
// Ignore synthetic select from `This` because in code it was `Ident`
186
185
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
187
186
case Select (qual @ This (_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
@@ -191,17 +190,24 @@ object Completion:
191
190
case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer.directMemberCompletions(expr)
192
191
case _ => completer.scopeCompletions
193
192
193
+ interactiv.println(i """ completion info with pos = $pos,
194
+ | prefix = ${completer.prefix},
195
+ | term = ${completer.mode.is(Mode .Term )},
196
+ | type = ${completer.mode.is(Mode .Type )},
197
+ | scope = ${completer.mode.is(Mode .Scope )},
198
+ | member = ${completer.mode.is(Mode .Member )}""" )
199
+
200
+ result
201
+
202
+ def postProcessCompletions (path : List [untpd.Tree ], completions : CompletionMap , rawPrefix : String )(using Context ): (Int , List [Completion ]) =
194
203
val describedCompletions = describeCompletions(completions)
204
+ val hasBackTick = rawPrefix.headOption.contains('`' )
195
205
val backtickedCompletions =
196
206
describedCompletions.map(completion => backtickCompletions(completion, hasBackTick))
197
207
198
- val offset = completionOffset(path0 )
208
+ interactiv.println( i """ completion resutls = $backtickedCompletions %, % """ )
199
209
200
- interactiv.println(i """ completion with pos = $pos,
201
- | prefix = ${completer.prefix},
202
- | term = ${completer.mode.is(Mode .Term )},
203
- | type = ${completer.mode.is(Mode .Type )}
204
- | results = $backtickedCompletions%, % """ )
210
+ val offset = completionOffset(path)
205
211
(offset, backtickedCompletions)
206
212
207
213
def backtickCompletions (completion : Completion , hasBackTick : Boolean ) =
@@ -415,11 +421,22 @@ object Completion:
415
421
416
422
/** Completions from implicit conversions including old style extensions using implicit classes */
417
423
private def implicitConversionMemberCompletions (qual : Tree )(using Context ): CompletionMap =
424
+
425
+ def tryToInstantiateTypeVars (conversionTarget : SearchSuccess ): Type =
426
+ try
427
+ val typingCtx = ctx.fresh
428
+ inContext(typingCtx):
429
+ val methodRefTree = ref(conversionTarget.ref, needLoad = false )
430
+ val convertedTree = ctx.typer.typedAheadExpr(untpd.Apply (untpd.TypedSplice (methodRefTree), untpd.TypedSplice (qual) :: Nil ))
431
+ Inferencing .fullyDefinedType(convertedTree.tpe, " " , pos)
432
+ catch
433
+ case error => conversionTarget.tree.tpe // fallback to not fully defined type
434
+
418
435
if qual.tpe.isExactlyNothing || qual.tpe.isNullType then
419
436
Map .empty
420
437
else
421
438
implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState())
422
- .flatMap( accessibleMembers)
439
+ .flatMap { conversionTarget => accessibleMembers(tryToInstantiateTypeVars(conversionTarget)) }
423
440
.toSeq
424
441
.groupByName
425
442
@@ -551,13 +568,13 @@ object Completion:
551
568
* @param qual The argument to which the implicit conversion should be applied.
552
569
* @return The set of types after `qual` implicit conversion.
553
570
*/
554
- private def implicitConversionTargets (qual : Tree )(using Context ): Set [Type ] = {
571
+ private def implicitConversionTargets (qual : Tree )(using Context ): Set [SearchSuccess ] = {
555
572
val typer = ctx.typer
556
573
val conversions = new typer.ImplicitSearch (defn.AnyType , qual, pos.span).allImplicits
557
574
val targets = conversions.map(_.tree.tpe)
558
575
559
576
interactiv.println(i " implicit conversion targets considered: ${targets.toList}%, % " )
560
- targets
577
+ conversions
561
578
}
562
579
563
580
/** Filter for names that should appear when looking for completions. */
@@ -602,3 +619,7 @@ object Completion:
602
619
/** Both term and type symbols are allowed */
603
620
val ImportOrExport : Mode = new Mode (4 ) | Term | Type
604
621
622
+ val Scope : Mode = new Mode (8 )
623
+
624
+ val Member : Mode = new Mode (16 )
625
+
0 commit comments