Skip to content

Commit f3e984d

Browse files
kasiaMarekKordyjan
authored andcommitted
update presentation compiler with changes from PR 5287 (#18301)
update scala3 presentation compiler with changes from scalameta/metals#5287 [Cherry-picked fa38cb8]
1 parent 97e2aa1 commit f3e984d

File tree

2 files changed

+388
-44
lines changed

2 files changed

+388
-44
lines changed

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala

+182-43
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
package dotty.tools.pc.completions
22

3+
import scala.util.Try
4+
35
import dotty.tools.dotc.ast.Trees.ValDef
46
import dotty.tools.dotc.ast.tpd.*
57
import dotty.tools.dotc.core.Constants.Constant
8+
import dotty.tools.dotc.core.ContextOps.localContext
69
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Definitions
711
import dotty.tools.dotc.core.Flags
12+
import dotty.tools.dotc.core.Flags.Method
813
import dotty.tools.dotc.core.NameKinds.DefaultGetterName
914
import dotty.tools.dotc.core.Names.Name
15+
import dotty.tools.dotc.core.Symbols
1016
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
1121
import dotty.tools.dotc.core.Types.Type
22+
import dotty.tools.dotc.core.Types.TypeBounds
23+
import dotty.tools.dotc.core.Types.WildcardType
1224
import dotty.tools.dotc.util.SourcePosition
1325
import dotty.tools.pc.IndexedContext
1426
import 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))
218307
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

Comments
 (0)