Skip to content

Commit cdd1b98

Browse files
committed
narrow overload candidates by preceding argument types
1 parent a9aa09a commit cdd1b98

6 files changed

Lines changed: 165 additions & 13 deletions

File tree

changelog.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
## Unreleased
44
<!-- Add all new changes here. They will be moved under a version at release -->
5+
* `NEW` Narrow overload candidates by the types of the preceding arguments, for completion and `param-type-mismatch`
6+
```lua
7+
---@class A.Component
8+
---@class B.Component
9+
10+
---@overload fun(c: A.Component, field: "hp"|"max_hp")
11+
---@overload fun(c: B.Component, field: "mana"|"cooldown")
12+
local function setValue(...) end
13+
14+
---@type A.Component
15+
local a
16+
17+
setValue(a, "mana") --> now warns (`param-type-mismatch`); completion inside the quotes only suggests "hp" and "max_hp"
18+
```
519
* `NEW` Support type inference for `@field` and `@type` function declarations in method overrides [#3367](https://github.com/LuaLS/lua-language-server/issues/3367)
620
* `FIX` Deduplicate documentation bindings for parameters
721
* `FIX` Correct `math.type` meta return annotation to use `nil` instead of the string literal `'nil'`

script/core/diagnostics/param-type-mismatch.lua

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,41 @@ local function getReceiverGenericMap(uri, source)
6666
return nil
6767
end
6868

69+
---@param uri uri
6970
---@param funcNode vm.node
71+
---@param callArgs parser.object[]
7072
---@param i integer
71-
---@param classGenericMap table<string, vm.node>?
72-
---@return vm.node?
73-
local function getDefNode(funcNode, i, classGenericMap)
74-
local defNode = vm.createNode()
73+
---@return parser.object[]
74+
local function getCheckableFunctions(uri, funcNode, callArgs, i)
75+
local funcs = {}
7576
for src in funcNode:eachObject() do
7677
if src.type == 'function'
7778
or src.type == 'doc.type.function' then
79+
funcs[#funcs+1] = src
80+
end
81+
end
82+
if #funcs > 1 then
83+
local matched = {}
84+
for _, src in ipairs(funcs) do
85+
if vm.isPriorArgsMatched(uri, src, callArgs, i) then
86+
matched[#matched+1] = src
87+
end
88+
end
89+
if #matched > 0 then
90+
funcs = matched
91+
end
92+
end
93+
return funcs
94+
end
95+
96+
---@param funcs parser.object[]
97+
---@param i integer
98+
---@param classGenericMap table<string, vm.node>?
99+
---@return vm.node?
100+
local function getDefNode(funcs, i, classGenericMap)
101+
local defNode = vm.createNode()
102+
for _, src in ipairs(funcs) do
103+
if src.args then
78104
local param = src.args and src.args[i]
79105
if param then
80106
local paramNode = vm.compileNode(param)
@@ -108,14 +134,13 @@ local function getDefNode(funcNode, i, classGenericMap)
108134
return defNode
109135
end
110136

111-
---@param funcNode vm.node
137+
---@param funcs parser.object[]
112138
---@param i integer
113139
---@return vm.node
114-
local function getRawDefNode(funcNode, i)
140+
local function getRawDefNode(funcs, i)
115141
local defNode = vm.createNode()
116-
for f in funcNode:eachObject() do
117-
if f.type == 'function'
118-
or f.type == 'doc.type.function' then
142+
for _, f in ipairs(funcs) do
143+
if f.args then
119144
local param = f.args and f.args[i]
120145
if param then
121146
defNode:merge(vm.compileNode(param))
@@ -146,7 +171,8 @@ return function (uri, callback)
146171
if not refNode then
147172
goto CONTINUE
148173
end
149-
local defNode = getDefNode(funcNode, i, classGenericMap)
174+
local funcs = getCheckableFunctions(uri, funcNode, source.args, i)
175+
local defNode = getDefNode(funcs, i, classGenericMap)
150176
if not defNode then
151177
goto CONTINUE
152178
end
@@ -159,7 +185,7 @@ return function (uri, callback)
159185
end
160186
local errs = {}
161187
if not vm.canCastType(uri, defNode, refNode, errs) then
162-
local rawDefNode = getRawDefNode(funcNode, i)
188+
local rawDefNode = getRawDefNode(funcs, i)
163189
assert(errs)
164190
callback {
165191
start = arg.start,

script/vm/compiler.lua

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,23 +1208,41 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex)
12081208
end
12091209
end
12101210

1211+
local docFuncs = {}
12111212
for n in callNode:eachObject() do
12121213
if n.type == 'function' then
12131214
---@cast n parser.object
12141215
dealFunction(n)
12151216
elseif n.type == 'doc.type.function' then
12161217
---@cast n parser.object
1217-
dealDocFunc(n)
1218+
docFuncs[#docFuncs+1] = n
12181219
elseif n.type == 'global' and n.cate == 'type' then
12191220
---@cast n vm.global
12201221
local overloads = vm.getOverloadsByTypeName(n.name, guide.getUri(arg))
12211222
if overloads then
12221223
for _, func in ipairs(overloads) do
1223-
dealDocFunc(func)
1224+
docFuncs[#docFuncs+1] = func
12241225
end
12251226
end
12261227
end
12271228
end
1229+
1230+
if #docFuncs > 1 and call.args then
1231+
local uri = guide.getUri(arg)
1232+
local matched = {}
1233+
for _, n in ipairs(docFuncs) do
1234+
if vm.isPriorArgsMatched(uri, n, call.args, myIndex, fixIndex) then
1235+
matched[#matched+1] = n
1236+
end
1237+
end
1238+
if #matched > 0 then
1239+
docFuncs = matched
1240+
end
1241+
end
1242+
1243+
for _, n in ipairs(docFuncs) do
1244+
dealDocFunc(n)
1245+
end
12281246
end
12291247

12301248
---@param arg parser.object

script/vm/function.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,44 @@ local function isAllParamMatched(uri, args, params)
353353
return true
354354
end
355355

356+
---@param param parser.object
357+
---@return boolean
358+
local function isVarargParam(param)
359+
return param.type == '...'
360+
or (param.name and param.name[1] == '...')
361+
end
362+
363+
---@param uri uri
364+
---@param func parser.object -- `function` or `doc.type.function`
365+
---@param callArgs parser.object[]
366+
---@param myIndex integer
367+
---@param fixIndex? integer
368+
---@return boolean
369+
function vm.isPriorArgsMatched(uri, func, callArgs, myIndex, fixIndex)
370+
fixIndex = fixIndex or 0
371+
local params = func.args
372+
if not params then
373+
return true
374+
end
375+
for i = 1, myIndex - 1 do
376+
local callArg = callArgs[i + fixIndex]
377+
local param = params[i]
378+
if not callArg or not param then
379+
break
380+
end
381+
if callArg.type ~= '...'
382+
and param.type ~= 'self'
383+
and not isVarargParam(param) then
384+
local defNode = vm.compileNode(param)
385+
local refNode = vm.compileNode(callArg)
386+
if not vm.canCastType(uri, defNode, refNode) then
387+
return false
388+
end
389+
end
390+
end
391+
return true
392+
end
393+
356394
---@param uri uri
357395
---@param args parser.object[]
358396
---@param func parser.object

test/completion/common.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4613,3 +4613,29 @@ print(a:<??>)
46134613
kind = define.CompletionItemKind.Method,
46144614
},
46154615
}
4616+
4617+
TEST [[
4618+
---@class A.Component
4619+
---@class B.Component
4620+
4621+
---@overload fun(c: A.Component, field: "hp"|"max_hp", value: any)
4622+
---@overload fun(c: B.Component, field: "mana"|"cooldown", value: any)
4623+
local function setValue(...) end
4624+
4625+
---@type A.Component
4626+
local a
4627+
4628+
setValue(a, '<??>')
4629+
]]
4630+
{
4631+
{
4632+
label = "'hp'",
4633+
kind = define.CompletionItemKind.EnumMember,
4634+
textEdit = EXISTS,
4635+
},
4636+
{
4637+
label = "'max_hp'",
4638+
kind = define.CompletionItemKind.EnumMember,
4639+
textEdit = EXISTS,
4640+
},
4641+
}

test/diagnostics/param-type-mismatch.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,33 @@ f(x)
395395
]]
396396

397397
config.set(nil, 'Lua.type.checkTableShape', false)
398+
399+
TEST [[
400+
---@class A.Component
401+
---@class B.Component
402+
403+
---@overload fun(c: A.Component, field: "hp"|"max_hp")
404+
---@overload fun(c: B.Component, field: "mana"|"cooldown")
405+
local function setValue(...) end
406+
407+
---@type A.Component
408+
local a
409+
410+
setValue(a, 'hp')
411+
setValue(a, <!'mana'!>)
412+
]]
413+
414+
TEST [[
415+
---@class A.Component
416+
---@class B.Component
417+
418+
---@overload fun(c: A.Component, field: "hp")
419+
---@overload fun(c: B.Component, field: "mana")
420+
local function setValue(...) end
421+
422+
---@type A.Component|B.Component
423+
local c
424+
425+
setValue(c, 'hp')
426+
setValue(c, 'mana')
427+
]]

0 commit comments

Comments
 (0)