Skip to content

Commit 7c9a24f

Browse files
authored
Merge pull request #2768 from NeOzay/cast-table-to-class
check that the shape of the table corresponds to the class
2 parents b71cb7a + 2c79870 commit 7c9a24f

File tree

4 files changed

+239
-5
lines changed

4 files changed

+239
-5
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
<!-- Add all new changes here. They will be moved under a version at release -->
5+
* `NEW` Add matching checks between the shape of tables and classes, during type checking. [#2768](https://github.com/LuaLS/lua-language-server/pull/2768
56
* `FIX` Error `attempt to index a nil value` when `Lua.hint.semicolon == 'All'` [#2788](https://github.com/LuaLS/lua-language-server/issues/2788)
67

78
## 3.10.3

script/vm/type.lua

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ end
148148
---@param mark table
149149
---@param errs? typecheck.err[]
150150
---@return boolean?
151-
local function checkChildEnum(childName, parent , uri, mark, errs)
151+
local function checkChildEnum(childName, parent, uri, mark, errs)
152152
if mark[childName] then
153153
return
154154
end
@@ -168,7 +168,7 @@ local function checkChildEnum(childName, parent , uri, mark, errs)
168168
end
169169
mark[childName] = true
170170
for _, enum in ipairs(enums) do
171-
if not vm.isSubType(uri, vm.compileNode(enum), parent, mark ,errs) then
171+
if not vm.isSubType(uri, vm.compileNode(enum), parent, mark, errs) then
172172
mark[childName] = nil
173173
return false
174174
end
@@ -325,10 +325,30 @@ function vm.isSubType(uri, child, parent, mark, errs)
325325
return true
326326
else
327327
local weakNil = config.get(uri, 'Lua.type.weakNilCheck')
328+
local skipTable
328329
for n in child:eachObject() do
330+
if skipTable == nil and n.type == "table" and parent.type == "vm.node" then -- skip table type check if child has class
331+
---@cast parent vm.node
332+
for _, c in ipairs(child) do
333+
if c.type == 'global' and c.cate == 'type' then
334+
for _, set in ipairs(c:getSets(uri)) do
335+
if set.type == 'doc.class' then
336+
skipTable = true
337+
break
338+
end
339+
end
340+
end
341+
if skipTable then
342+
break
343+
end
344+
end
345+
if not skipTable then
346+
skipTable = false
347+
end
348+
end
329349
local nodeName = vm.getNodeName(n)
330350
if nodeName
331-
and not (nodeName == 'nil' and weakNil)
351+
and not (nodeName == 'nil' and weakNil) and not (skipTable and n.type == 'table')
332352
and vm.isSubType(uri, n, parent, mark, errs) == false then
333353
if errs then
334354
errs[#errs+1] = 'TYPE_ERROR_UNION_DISMATCH'
@@ -463,6 +483,67 @@ function vm.isSubType(uri, child, parent, mark, errs)
463483
return true
464484
end
465485
if childName == 'table' and not guide.isBasicType(parentName) then
486+
local set = parent:getSets(uri)
487+
local missedKeys = {}
488+
local failedCheck
489+
local myKeys
490+
for _, def in ipairs(set) do
491+
if not def.fields or #def.fields == 0 then
492+
goto continue
493+
end
494+
if not myKeys then
495+
myKeys = {}
496+
for _, field in ipairs(child) do
497+
local key = vm.getKeyName(field) or field.tindex
498+
if key then
499+
myKeys[key] = vm.compileNode(field)
500+
end
501+
end
502+
end
503+
504+
for _, field in ipairs(def.fields) do
505+
local key = vm.getKeyName(field)
506+
if not key then
507+
local fieldnode = vm.compileNode(field.field)[1]
508+
if fieldnode and fieldnode.type == 'doc.type.integer' then
509+
---@cast fieldnode parser.object
510+
key = vm.getKeyName(fieldnode)
511+
end
512+
end
513+
if not key then
514+
goto continue
515+
end
516+
517+
local ok
518+
local nodeField = vm.compileNode(field)
519+
if myKeys[key] then
520+
ok = vm.isSubType(uri, myKeys[key], nodeField, mark, errs)
521+
if ok == false then
522+
errs[#errs+1] = 'TYPE_ERROR_PARENT_ALL_DISMATCH' -- error display can be greatly improved
523+
errs[#errs+1] = myKeys[key]
524+
errs[#errs+1] = nodeField
525+
failedCheck = true
526+
end
527+
elseif not nodeField:isNullable() then
528+
if type(key) == "number" then
529+
missedKeys[#missedKeys+1] = ('`[%s]`'):format(key)
530+
else
531+
missedKeys[#missedKeys+1] = ('`%s`'):format(key)
532+
end
533+
failedCheck = true
534+
end
535+
end
536+
::continue::
537+
end
538+
if #missedKeys > 0 then
539+
errs[#errs+1] = 'DIAG_MISSING_FIELDS'
540+
errs[#errs+1] = parent
541+
errs[#errs+1] = table.concat(missedKeys, ', ')
542+
end
543+
if failedCheck then
544+
return false
545+
end
546+
466547
return true
467548
end
468549

@@ -570,11 +651,11 @@ function vm.getTableValue(uri, tnode, knode, inversion)
570651
and field.value
571652
and field.tindex == 1 then
572653
if inversion then
573-
if vm.isSubType(uri, 'integer', knode) then
654+
if vm.isSubType(uri, 'integer', knode) then
574655
result:merge(vm.compileNode(field.value))
575656
end
576657
else
577-
if vm.isSubType(uri, knode, 'integer') then
658+
if vm.isSubType(uri, knode, 'integer') then
578659
result:merge(vm.compileNode(field.value))
579660
end
580661
end
@@ -692,6 +773,7 @@ function vm.canCastType(uri, defNode, refNode, errs)
692773
return true
693774
end
694775

776+
695777
return false
696778
end
697779

@@ -713,6 +795,7 @@ local ErrorMessageMap = {
713795
TYPE_ERROR_NUMBER_LITERAL_TO_INTEGER = {'child'},
714796
TYPE_ERROR_NUMBER_TYPE_TO_INTEGER = {},
715797
TYPE_ERROR_DISMATCH = {'child', 'parent'},
798+
DIAG_MISSING_FIELDS = {"1", "2"},
716799
}
717800

718801
---@param uri uri

test/diagnostics/cast-local-type.lua

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,69 @@ local x
332332
- 类型 `nil` 无法匹配 `'B'`
333333
- 类型 `nil` 无法匹配 `'A'`]])
334334
end)
335+
336+
TEST [[
337+
---@class A
338+
---@field x string
339+
---@field y number
340+
341+
local a = {x = "", y = 0}
342+
343+
---@type A
344+
local v
345+
v = a
346+
]]
347+
348+
TEST [[
349+
---@class A
350+
---@field x string
351+
---@field y number
352+
353+
local a = {x = ""}
354+
355+
---@type A
356+
local v
357+
<!v!> = a
358+
]]
359+
360+
TEST [[
361+
---@class A
362+
---@field x string
363+
---@field y number
364+
365+
local a = {x = "", y = ""}
366+
367+
---@type A
368+
local v
369+
<!v!> = a
370+
]]
371+
372+
TEST [[
373+
---@class A
374+
---@field x string
375+
---@field y? B
376+
377+
---@class B
378+
---@field x string
379+
380+
local a = {x = "b", y = {x = "c"}}
381+
382+
---@type A
383+
local v
384+
v = a
385+
]]
386+
387+
TEST [[
388+
---@class A
389+
---@field x string
390+
---@field y B
391+
392+
---@class B
393+
---@field x string
394+
395+
local a = {x = "b", y = {}}
396+
397+
---@type A
398+
local v
399+
<!v!> = a
400+
]]

test/diagnostics/param-type-mismatch.lua

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,87 @@ local function f(v) end
264264
f 'x'
265265
f 'y'
266266
]]
267+
268+
TEST [[
269+
---@class A
270+
---@field x string
271+
---@field y number
272+
273+
local a = {x = "", y = 0}
274+
275+
---@param a A
276+
function f(a) end
277+
278+
f(a)
279+
]]
280+
281+
TEST [[
282+
---@class A
283+
---@field x string
284+
---@field y number
285+
286+
local a = {x = ""}
287+
288+
---@param a A
289+
function f(a) end
290+
291+
f(<!a!>)
292+
]]
293+
294+
TEST [[
295+
---@class A
296+
---@field x string
297+
---@field y number
298+
299+
local a = {x = "", y = ""}
300+
301+
---@param a A
302+
function f(a) end
303+
304+
f(<!a!>)
305+
]]
306+
307+
TEST [[
308+
---@class A
309+
---@field x string
310+
---@field y? B
311+
312+
---@class B
313+
---@field x string
314+
315+
local a = {x = "b", y = {x = "c"}}
316+
317+
---@param a A
318+
function f(a) end
319+
320+
f(a)
321+
]]
322+
323+
TEST [[
324+
---@class A
325+
---@field x string
326+
---@field y B
327+
328+
---@class B
329+
---@field x string
330+
331+
local a = {x = "b", y = {}}
332+
333+
---@param a A
334+
function f(a) end
335+
336+
f(<!a!>)
337+
]]
338+
339+
TEST [[
340+
---@class A
341+
---@field x string
342+
343+
---@type A
344+
local a = {}
345+
346+
---@param a A
347+
function f(a) end
348+
349+
f(a)
350+
]]

0 commit comments

Comments
 (0)