11package dotty .tools .pc .completions
22
3+ import scala .util .Try
4+
35import dotty .tools .dotc .ast .Trees .ValDef
46import dotty .tools .dotc .ast .tpd .*
57import dotty .tools .dotc .core .Constants .Constant
8+ import dotty .tools .dotc .core .ContextOps .localContext
69import dotty .tools .dotc .core .Contexts .Context
10+ import dotty .tools .dotc .core .Definitions
711import dotty .tools .dotc .core .Flags
12+ import dotty .tools .dotc .core .Flags .Method
813import dotty .tools .dotc .core .NameKinds .DefaultGetterName
914import dotty .tools .dotc .core .Names .Name
15+ import dotty .tools .dotc .core .Symbols
1016import dotty .tools .dotc .core .Symbols .Symbol
17+ import dotty .tools .dotc .core .Types .AndType
18+ import dotty .tools .dotc .core .Types .AppliedType
19+ import dotty .tools .dotc .core .Types .OrType
20+ import dotty .tools .dotc .core .Types .TermRef
1121import dotty .tools .dotc .core .Types .Type
22+ import dotty .tools .dotc .core .Types .TypeBounds
23+ import dotty .tools .dotc .core .Types .WildcardType
1224import dotty .tools .dotc .util .SourcePosition
1325import dotty .tools .pc .IndexedContext
1426import dotty .tools .pc .utils .MtagsEnrichments .*
@@ -60,6 +72,7 @@ object NamedArgCompletions:
6072 apply.fun match
6173 case Select (New (_), _) => false
6274 case Select (_, name) if name.decoded == " apply" => false
75+ case Select (This (_), _) => false
6376 // is a select statement without a dot `qual.name`
6477 case sel @ Select (qual, _) if ! sel.symbol.is(Flags .Synthetic ) =>
6578 ! (qual.span.end until sel.nameSpan.start)
@@ -72,7 +85,7 @@ object NamedArgCompletions:
7285 apply : Apply ,
7386 indexedContext : IndexedContext ,
7487 clientSupportsSnippets : Boolean
75- )(using Context ): List [CompletionValue ] =
88+ )(using context : Context ): List [CompletionValue ] =
7689 def isUselessLiteral (arg : Tree ): Boolean =
7790 arg match
7891 case Literal (Constant (())) => true // unitLiteral
@@ -98,60 +111,131 @@ object NamedArgCompletions:
98111 end collectArgss
99112
100113 val method = apply.fun
101- val methodSym = method.symbol
102114
103- // paramSymss contains both type params and value params
104- val vparamss =
105- methodSym.paramSymss.filter(params => params.forall(p => p.isTerm))
106115 val argss = collectArgss(apply)
107- // get params and args we are interested in
108- // e.g.
109- // in the following case, the interesting args and params are
110- // - params: [apple, banana]
111- // - args: [apple, b]
112- // ```
113- // def curry(x; Int)(apple: String, banana: String) = ???
114- // curry(1)(apple = "test", b@@)
115- // ```
116- val (baseParams, baseArgs) =
117- vparamss.zip(argss).lastOption.getOrElse((Nil , Nil ))
118-
119- val args = ident
120- .map(i => baseArgs.filterNot(_ == i))
121- .getOrElse(baseArgs)
122- .filterNot(isUselessLiteral)
123-
124- val isNamed : Set [Name ] = args.iterator
125- .zip(baseParams.iterator)
126- // filter out synthesized args and default arg getters
127- .filterNot {
128- case (arg, _) if arg.symbol.denot.is(Flags .Synthetic ) => true
129- case (Ident (name), _) => name.is(DefaultGetterName ) // default args
130- case (Select (Ident (_), name), _) =>
131- name.is(DefaultGetterName ) // default args for apply method
132- case _ => false
133- }
134- .map {
135- case (NamedArg (name, _), _) => name
136- case (_, param) => param.name
137- }
138- .toSet
139116
140- val allParams : List [Symbol ] =
117+ // fallback for when multiple overloaded methods match the supplied args
118+ def fallbackFindMatchingMethods () =
119+ def maybeNameAndIndexedContext (
120+ method : Tree
121+ ): Option [(Name , IndexedContext )] =
122+ method match
123+ case Ident (name) => Some ((name, indexedContext))
124+ case Select (This (_), name) => Some ((name, indexedContext))
125+ case Select (from, name) =>
126+ val symbol = from.symbol
127+ val ownerSymbol =
128+ if symbol.is(Method ) && symbol.owner.isClass then
129+ Some (symbol.owner)
130+ else Try (symbol.info.classSymbol).toOption
131+ ownerSymbol.map(sym =>
132+ (name, IndexedContext (context.localContext(from, sym)))
133+ )
134+ case Apply (fun, _) => maybeNameAndIndexedContext(fun)
135+ case _ => None
136+ val matchingMethods =
137+ for
138+ (name, indxContext) <- maybeNameAndIndexedContext(method)
139+ potentialMatches <- indxContext.findSymbol(name)
140+ yield potentialMatches.collect {
141+ case m
142+ if m.is(Flags .Method ) &&
143+ m.vparamss.length >= argss.length &&
144+ Try (m.isAccessibleFrom(apply.symbol.info)).toOption
145+ .getOrElse(false ) &&
146+ m.vparamss
147+ .zip(argss)
148+ .reverse
149+ .zipWithIndex
150+ .forall { case (pair, index) =>
151+ FuzzyArgMatcher (m.tparams)
152+ .doMatch(allArgsProvided = index != 0 )
153+ .tupled(pair)
154+ } =>
155+ m
156+ }
157+ matchingMethods.getOrElse(Nil )
158+ end fallbackFindMatchingMethods
159+
160+ val matchingMethods : List [Symbols .Symbol ] =
161+ if method.symbol.paramSymss.nonEmpty
162+ then
163+ val allArgsAreSupplied =
164+ val vparamss = method.symbol.vparamss
165+ vparamss.length == argss.length && vparamss
166+ .zip(argss)
167+ .lastOption
168+ .exists { case (baseParams, baseArgs) =>
169+ baseArgs.length == baseParams.length
170+ }
171+ // ```
172+ // m(arg : Int)
173+ // m(arg : Int, anotherArg : Int)
174+ // m(a@@)
175+ // ```
176+ // complier will choose the first `m`, so we need to manually look for the other one
177+ if allArgsAreSupplied then
178+ val foundPotential = fallbackFindMatchingMethods()
179+ if foundPotential.contains(method.symbol) then foundPotential
180+ else method.symbol :: foundPotential
181+ else List (method.symbol)
182+ else fallbackFindMatchingMethods()
183+ end if
184+ end matchingMethods
185+
186+ val allParams = matchingMethods.flatMap { methodSym =>
187+ val vparamss = methodSym.vparamss
188+
189+ // get params and args we are interested in
190+ // e.g.
191+ // in the following case, the interesting args and params are
192+ // - params: [apple, banana]
193+ // - args: [apple, b]
194+ // ```
195+ // def curry(x: Int)(apple: String, banana: String) = ???
196+ // curry(1)(apple = "test", b@@)
197+ // ```
198+ val (baseParams, baseArgs) =
199+ vparamss.zip(argss).lastOption.getOrElse((Nil , Nil ))
200+
201+ val args = ident
202+ .map(i => baseArgs.filterNot(_ == i))
203+ .getOrElse(baseArgs)
204+ .filterNot(isUselessLiteral)
205+
206+ val isNamed : Set [Name ] = args.iterator
207+ .zip(baseParams.iterator)
208+ // filter out synthesized args and default arg getters
209+ .filterNot {
210+ case (arg, _) if arg.symbol.denot.is(Flags .Synthetic ) => true
211+ case (Ident (name), _) => name.is(DefaultGetterName ) // default args
212+ case (Select (Ident (_), name), _) =>
213+ name.is(DefaultGetterName ) // default args for apply method
214+ case _ => false
215+ }
216+ .map {
217+ case (NamedArg (name, _), _) => name
218+ case (_, param) => param.name
219+ }
220+ .toSet
221+
141222 baseParams.filterNot(param =>
142223 isNamed(param.name) ||
143224 param.denot.is(
144225 Flags .Synthetic
145226 ) // filter out synthesized param, like evidence
146227 )
228+ }
147229
148230 val prefix =
149231 ident
150232 .map(_.name.toString)
151233 .getOrElse(" " )
152234 .replace(Cursor .value, " " )
153235 val params : List [Symbol ] =
154- allParams.filter(param => param.name.startsWith(prefix))
236+ allParams
237+ .filter(param => param.name.startsWith(prefix))
238+ .distinctBy(sym => (sym.name, sym.info))
155239
156240 val completionSymbols = indexedContext.scopeSymbols
157241 def matchingTypesInScope (paramType : Type ): List [String ] =
@@ -173,11 +257,11 @@ object NamedArgCompletions:
173257
174258 def fillAllFields (): List [CompletionValue ] =
175259 val suffix = " autofill"
176- val shouldShow =
260+ def shouldShow =
177261 allParams.exists(param => param.name.startsWith(prefix))
178- val isExplicitlyCalled = suffix.startsWith(prefix)
179- val hasParamsToFill = allParams.count(! _.is(Flags .HasDefault )) > 1
180- if (shouldShow || isExplicitlyCalled) && hasParamsToFill && clientSupportsSnippets
262+ def isExplicitlyCalled = suffix.startsWith(prefix)
263+ def hasParamsToFill = allParams.count(! _.is(Flags .HasDefault )) > 1
264+ if clientSupportsSnippets && matchingMethods.length == 1 && (shouldShow || isExplicitlyCalled) && hasParamsToFill
181265 then
182266 val editText = allParams.zipWithIndex
183267 .collect {
@@ -215,4 +299,59 @@ object NamedArgCompletions:
215299 ) ::: findPossibleDefaults() ::: fillAllFields()
216300 end contribute
217301
302+ extension (method : Symbols .Symbol )
303+ def vparamss (using Context ) = method.filteredParamss(_.isTerm)
304+ def tparams (using Context ) = method.filteredParamss(_.isType).flatten
305+ def filteredParamss (f : Symbols .Symbol => Boolean )(using Context ) =
306+ method.paramSymss.filter(params => params.forall(f))
218307end NamedArgCompletions
308+
309+ class FuzzyArgMatcher (tparams : List [Symbols .Symbol ])(using Context ):
310+
311+ /**
312+ * A heuristic for checking if the passed arguments match the method's arguments' types.
313+ * For non-polymorphic methods we use the subtype relation (`<:<`)
314+ * and for polymorphic methods we use a heuristic.
315+ * We check the args types not the result type.
316+ */
317+ def doMatch (
318+ allArgsProvided : Boolean
319+ )(expectedArgs : List [Symbols .Symbol ], actualArgs : List [Tree ]) =
320+ (expectedArgs.length == actualArgs.length ||
321+ (! allArgsProvided && expectedArgs.length >= actualArgs.length)) &&
322+ actualArgs.zipWithIndex.forall {
323+ case (Ident (name), _) if name.endsWith(Cursor .value) => true
324+ case (NamedArg (name, arg), _) =>
325+ expectedArgs.exists { expected =>
326+ expected.name == name && (! arg.hasType || arg.typeOpt.unfold
327+ .fuzzyArg_<:< (expected.info))
328+ }
329+ case (arg, i) =>
330+ ! arg.hasType || arg.typeOpt.unfold.fuzzyArg_<:< (expectedArgs(i).info)
331+ }
332+
333+ extension (arg : Type )
334+ def fuzzyArg_<:< (expected : Type ) =
335+ if tparams.isEmpty then arg <:< expected
336+ else arg <:< substituteTypeParams(expected)
337+ def unfold =
338+ arg match
339+ case arg : TermRef => arg.underlying
340+ case e => e
341+
342+ private def substituteTypeParams (t : Type ): Type =
343+ t match
344+ case e if tparams.exists(_ == e.typeSymbol) =>
345+ val matchingParam = tparams.find(_ == e.typeSymbol).get
346+ matchingParam.info match
347+ case b @ TypeBounds (_, _) => WildcardType (b)
348+ case _ => WildcardType
349+ case o @ OrType (e1, e2) =>
350+ OrType (substituteTypeParams(e1), substituteTypeParams(e2), o.isSoft)
351+ case AndType (e1, e2) =>
352+ AndType (substituteTypeParams(e1), substituteTypeParams(e2))
353+ case AppliedType (et, eparams) =>
354+ AppliedType (et, eparams.map(substituteTypeParams))
355+ case _ => t
356+
357+ end FuzzyArgMatcher
0 commit comments