@@ -19,6 +19,8 @@ import dotty.tools.dotc.core.TypeError
1919import dotty .tools .dotc .core .Phases
2020import dotty .tools .dotc .core .Types .{AppliedType , ExprType , MethodOrPoly , NameFilter , NoType , RefinedType , TermRef , Type , TypeProxy }
2121import dotty .tools .dotc .parsing .Tokens
22+ import dotty .tools .dotc .typer .Implicits .SearchSuccess
23+ import dotty .tools .dotc .typer .Inferencing
2224import dotty .tools .dotc .util .Chars
2325import dotty .tools .dotc .util .SourcePosition
2426
@@ -28,6 +30,7 @@ import dotty.tools.dotc.core.ContextOps.localContext
2830import dotty .tools .dotc .core .Names
2931import dotty .tools .dotc .core .Types
3032import dotty .tools .dotc .core .Symbols
33+ import dotty .tools .dotc .core .Constants
3134
3235/**
3336 * One of the results of a completion query.
@@ -49,8 +52,31 @@ object Completion:
4952 * @return offset and list of symbols for possible completions
5053 */
5154 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)
5480
5581 /**
5682 * Inspect `path` to determine what kinds of symbols should be considered.
@@ -63,90 +89,69 @@ object Completion:
6389 * Otherwise, provide no completion suggestion.
6490 */
6591 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
7392
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
77102
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
80106
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
96118
97119 /**
98120 * Inspect `path` to determine the completion prefix. Only symbols whose name start with the
99121 * returned prefix should be considered.
100122 */
101123 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+
102129 path match
103130 case (sel : untpd.ImportSelector ) :: _ =>
104131 completionPrefix(sel.imported :: Nil , pos)
105132
106133 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)
108136
109137 case (tree : untpd.ImportOrExport ) :: _ =>
110138 tree.selectors.find(_.span.contains(pos.span)).map: selector =>
111139 completionPrefix(selector :: Nil , pos)
112140 .getOrElse(" " )
113141
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)
121144
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
125146
126- case _ => " "
127147
128148 end completionPrefix
129149
130150 /** Inspect `path` to determine the offset where the completion result should be inserted. */
131151 def completionOffset (untpdPath : List [untpd.Tree ]): Int =
132- untpdPath match {
152+ untpdPath match
133153 case (ref : untpd.RefTree ) :: _ => ref.span.point
134154 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
150155
151156 /** Handle case when cursor position is inside extension method construct.
152157 * The extension method construct is then desugared into methods, and consturct parameters
@@ -170,18 +175,12 @@ object Completion:
170175 Interactive .pathTo(typedEnclosingParam, pos.span)
171176 .flatten.getOrElse(tpdPath)
172177
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 =
178179 val hasBackTick = rawPrefix.headOption.contains('`' )
179180 val prefix = if hasBackTick then rawPrefix.drop(1 ) else rawPrefix
180-
181181 val completer = new Completer (mode, prefix, pos)
182182
183- val adjustedPath = typeCheckExtensionConstructPath(path0, tpdPath, pos)
184- val completions = adjustedPath match
183+ val result = adjustedPath match
185184 // Ignore synthetic select from `This` because in code it was `Ident`
186185 // See example in dotty.tools.languageserver.CompletionTest.syntheticThis
187186 case Select (qual @ This (_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
@@ -191,17 +190,24 @@ object Completion:
191190 case (_ : untpd.ImportSelector ) :: Import (expr, _) :: _ => completer.directMemberCompletions(expr)
192191 case _ => completer.scopeCompletions
193192
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 ]) =
194203 val describedCompletions = describeCompletions(completions)
204+ val hasBackTick = rawPrefix.headOption.contains('`' )
195205 val backtickedCompletions =
196206 describedCompletions.map(completion => backtickCompletions(completion, hasBackTick))
197207
198- val offset = completionOffset(path0 )
208+ interactiv.println( i """ completion resutls = $backtickedCompletions %, % """ )
199209
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)
205211 (offset, backtickedCompletions)
206212
207213 def backtickCompletions (completion : Completion , hasBackTick : Boolean ) =
@@ -415,11 +421,22 @@ object Completion:
415421
416422 /** Completions from implicit conversions including old style extensions using implicit classes */
417423 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+
418435 if qual.tpe.isExactlyNothing || qual.tpe.isNullType then
419436 Map .empty
420437 else
421438 implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState())
422- .flatMap( accessibleMembers)
439+ .flatMap { conversionTarget => accessibleMembers(tryToInstantiateTypeVars(conversionTarget)) }
423440 .toSeq
424441 .groupByName
425442
@@ -551,13 +568,13 @@ object Completion:
551568 * @param qual The argument to which the implicit conversion should be applied.
552569 * @return The set of types after `qual` implicit conversion.
553570 */
554- private def implicitConversionTargets (qual : Tree )(using Context ): Set [Type ] = {
571+ private def implicitConversionTargets (qual : Tree )(using Context ): Set [SearchSuccess ] = {
555572 val typer = ctx.typer
556573 val conversions = new typer.ImplicitSearch (defn.AnyType , qual, pos.span).allImplicits
557574 val targets = conversions.map(_.tree.tpe)
558575
559576 interactiv.println(i " implicit conversion targets considered: ${targets.toList}%, % " )
560- targets
577+ conversions
561578 }
562579
563580 /** Filter for names that should appear when looking for completions. */
@@ -602,3 +619,7 @@ object Completion:
602619 /** Both term and type symbols are allowed */
603620 val ImportOrExport : Mode = new Mode (4 ) | Term | Type
604621
622+ val Scope : Mode = new Mode (8 )
623+
624+ val Member : Mode = new Mode (16 )
625+
0 commit comments