1
1
package dotty .tools .pc .completions
2
2
3
+ import scala .util .Try
4
+
3
5
import dotty .tools .dotc .ast .Trees .ValDef
4
6
import dotty .tools .dotc .ast .tpd .*
5
7
import dotty .tools .dotc .core .Constants .Constant
8
+ import dotty .tools .dotc .core .ContextOps .localContext
6
9
import dotty .tools .dotc .core .Contexts .Context
10
+ import dotty .tools .dotc .core .Definitions
7
11
import dotty .tools .dotc .core .Flags
12
+ import dotty .tools .dotc .core .Flags .Method
8
13
import dotty .tools .dotc .core .NameKinds .DefaultGetterName
9
14
import dotty .tools .dotc .core .Names .Name
15
+ import dotty .tools .dotc .core .Symbols
10
16
import 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
11
21
import dotty .tools .dotc .core .Types .Type
22
+ import dotty .tools .dotc .core .Types .TypeBounds
23
+ import dotty .tools .dotc .core .Types .WildcardType
12
24
import dotty .tools .dotc .util .SourcePosition
13
25
import dotty .tools .pc .IndexedContext
14
26
import dotty .tools .pc .utils .MtagsEnrichments .*
@@ -60,6 +72,7 @@ object NamedArgCompletions:
60
72
apply.fun match
61
73
case Select (New (_), _) => false
62
74
case Select (_, name) if name.decoded == " apply" => false
75
+ case Select (This (_), _) => false
63
76
// is a select statement without a dot `qual.name`
64
77
case sel @ Select (qual, _) if ! sel.symbol.is(Flags .Synthetic ) =>
65
78
! (qual.span.end until sel.nameSpan.start)
@@ -72,7 +85,7 @@ object NamedArgCompletions:
72
85
apply : Apply ,
73
86
indexedContext : IndexedContext ,
74
87
clientSupportsSnippets : Boolean
75
- )(using Context ): List [CompletionValue ] =
88
+ )(using context : Context ): List [CompletionValue ] =
76
89
def isUselessLiteral (arg : Tree ): Boolean =
77
90
arg match
78
91
case Literal (Constant (())) => true // unitLiteral
@@ -98,60 +111,131 @@ object NamedArgCompletions:
98
111
end collectArgss
99
112
100
113
val method = apply.fun
101
- val methodSym = method.symbol
102
114
103
- // paramSymss contains both type params and value params
104
- val vparamss =
105
- methodSym.paramSymss.filter(params => params.forall(p => p.isTerm))
106
115
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
139
116
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
+
141
222
baseParams.filterNot(param =>
142
223
isNamed(param.name) ||
143
224
param.denot.is(
144
225
Flags .Synthetic
145
226
) // filter out synthesized param, like evidence
146
227
)
228
+ }
147
229
148
230
val prefix =
149
231
ident
150
232
.map(_.name.toString)
151
233
.getOrElse(" " )
152
234
.replace(Cursor .value, " " )
153
235
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))
155
239
156
240
val completionSymbols = indexedContext.scopeSymbols
157
241
def matchingTypesInScope (paramType : Type ): List [String ] =
@@ -173,11 +257,11 @@ object NamedArgCompletions:
173
257
174
258
def fillAllFields (): List [CompletionValue ] =
175
259
val suffix = " autofill"
176
- val shouldShow =
260
+ def shouldShow =
177
261
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
181
265
then
182
266
val editText = allParams.zipWithIndex
183
267
.collect {
@@ -215,4 +299,59 @@ object NamedArgCompletions:
215
299
) ::: findPossibleDefaults() ::: fillAllFields()
216
300
end contribute
217
301
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))
218
307
end 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