Skip to content

Commit 080d672

Browse files
committed
Add setting: Lua.type.checkTableShape
1 parent 18ae29c commit 080d672

File tree

9 files changed

+102
-66
lines changed

9 files changed

+102
-66
lines changed

changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +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)
5+
* `NEW` Setting: `Lua.type.checkTableShape`: Add matching checks between the shape of tables and classes, during type checking. [#2768](https://github.com/LuaLS/lua-language-server/pull/2768)
66
* `FIX` Error `attempt to index a nil value` when `Lua.hint.semicolon == 'All'` [#2788](https://github.com/LuaLS/lua-language-server/issues/2788)
77
* `FIX` Incorrect LuaCats parsing for `"'"`
88
* `FIX` Incorrect indent fixings

locale/en-us/setting.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ When a parameter type is not annotated, it is inferred from the function's call
301301
302302
When this setting is `false`, the type of the parameter is `any` when it is not annotated.
303303
]]
304+
config.type.checkTableShape =
305+
[[
306+
Strictly check the shape of the table.
307+
]]
304308
config.doc.privateName =
305309
'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.'
306310
config.doc.protectedName =

locale/pt-br/setting.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ When the parameter type is not annotated, the parameter type is inferred from th
301301
302302
When this setting is `false`, the type of the parameter is `any` when it is not annotated.
303303
]]
304+
config.type.checkTableShape = -- TODO: need translate!
305+
[[
306+
对表的形状进行严格检查。
307+
]]
304308
config.doc.privateName = -- TODO: need translate!
305309
'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.'
306310
config.doc.protectedName = -- TODO: need translate!

locale/zh-cn/setting.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ config.type.inferParamType =
300300
301301
如果设置为 "false",则在未注释时,参数类型为 "any"。
302302
]]
303+
config.type.checkTableShape =
304+
[[
305+
对表的形状进行严格检查。
306+
]]
303307
config.doc.privateName =
304308
'将特定名称的字段视为私有,例如 `m_*` 意味着 `XXX.m_id` 与 `XXX.m_type` 是私有字段,只能在定义所在的类中访问。'
305309
config.doc.protectedName =

locale/zh-tw/setting.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ config.type.inferParamType = -- TODO: need translate!
300300
301301
如果设置为 "false",则在未注释时,参数类型为 "any"。
302302
]]
303+
config.type.checkTableShape = -- TODO: need translate!
304+
[[
305+
对表的形状进行严格检查。
306+
]]
303307
config.doc.privateName = -- TODO: need translate!
304308
'Treat specific field names as private, e.g. `m_*` means `XXX.m_id` and `XXX.m_type` are private, witch can only be accessed in the class where the definition is located.'
305309
config.doc.protectedName = -- TODO: need translate!

script/config/template.lua

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ local diag = require 'proto.diagnostic'
44

55
---@class config.unit
66
---@field caller function
7+
---@field loader function
78
---@field _checker fun(self: config.unit, value: any): boolean
89
---@field name string
9-
---@field [string] config.unit
1010
---@operator shl: config.unit
1111
---@operator shr: config.unit
1212
---@operator call: config.unit
@@ -57,7 +57,8 @@ local function register(name, default, checker, loader, caller)
5757
}
5858
end
5959

60-
---@type config.unit
60+
---@class config.master
61+
---@field [string] config.unit
6162
local Type = setmetatable({}, { __index = function (_, name)
6263
local unit = {}
6364
for k, v in pairs(units[name]) do
@@ -398,7 +399,8 @@ local template = {
398399
['Lua.type.castNumberToInteger'] = Type.Boolean >> true,
399400
['Lua.type.weakUnionCheck'] = Type.Boolean >> false,
400401
['Lua.type.weakNilCheck'] = Type.Boolean >> false,
401-
['Lua.type.inferParamType'] = Type.Boolean >> false,
402+
['Lua.type.inferParamType'] = Type.Boolean >> false,
403+
['Lua.type.checkTableShape'] = Type.Boolean >> false,
402404
['Lua.doc.privateName'] = Type.Array(Type.String),
403405
['Lua.doc.protectedName'] = Type.Array(Type.String),
404406
['Lua.doc.packageName'] = Type.Array(Type.String),

script/vm/type.lua

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,71 @@ local function isAlias(name, suri)
284284
return false
285285
end
286286

287+
local function checkTableShape(parent, child, uri, mark, errs)
288+
local set = parent:getSets(uri)
289+
local missedKeys = {}
290+
local failedCheck
291+
local myKeys
292+
for _, def in ipairs(set) do
293+
if not def.fields or #def.fields == 0 then
294+
goto continue
295+
end
296+
if not myKeys then
297+
myKeys = {}
298+
for _, field in ipairs(child) do
299+
local key = vm.getKeyName(field) or field.tindex
300+
if key then
301+
myKeys[key] = vm.compileNode(field)
302+
end
303+
end
304+
end
305+
306+
for _, field in ipairs(def.fields) do
307+
local key = vm.getKeyName(field)
308+
if not key then
309+
local fieldnode = vm.compileNode(field.field)[1]
310+
if fieldnode and fieldnode.type == 'doc.type.integer' then
311+
---@cast fieldnode parser.object
312+
key = vm.getKeyName(fieldnode)
313+
end
314+
end
315+
if not key then
316+
goto continue
317+
end
318+
319+
local ok
320+
local nodeField = vm.compileNode(field)
321+
if myKeys[key] then
322+
ok = vm.isSubType(uri, myKeys[key], nodeField, mark, errs)
323+
if ok == false then
324+
errs[#errs+1] = 'TYPE_ERROR_PARENT_ALL_DISMATCH' -- error display can be greatly improved
325+
errs[#errs+1] = myKeys[key]
326+
errs[#errs+1] = nodeField
327+
failedCheck = true
328+
end
329+
elseif not nodeField:isNullable() then
330+
if type(key) == "number" then
331+
missedKeys[#missedKeys+1] = ('`[%s]`'):format(key)
332+
else
333+
missedKeys[#missedKeys+1] = ('`%s`'):format(key)
334+
end
335+
failedCheck = true
336+
end
337+
end
338+
::continue::
339+
end
340+
if #missedKeys > 0 then
341+
errs[#errs+1] = 'DIAG_MISSING_FIELDS'
342+
errs[#errs+1] = parent
343+
errs[#errs+1] = table.concat(missedKeys, ', ')
344+
end
345+
if failedCheck then
346+
return false
347+
end
348+
349+
return true
350+
end
351+
287352
---@param uri uri
288353
---@param child vm.node|string|vm.node.object
289354
---@param parent vm.node|string|vm.node.object
@@ -483,68 +548,11 @@ function vm.isSubType(uri, child, parent, mark, errs)
483548
return true
484549
end
485550
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
551+
if config.get(uri, 'Lua.type.checkTableShape') then
552+
return checkTableShape(parent, child, uri, mark, errs)
553+
else
554+
return true
545555
end
546-
547-
return true
548556
end
549557

550558
-- check class parent

test/diagnostics/cast-local-type.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local config = require "config.config"
12
TEST [[
23
local x = 0
34
@@ -345,6 +346,8 @@ local v
345346
v = a
346347
]]
347348

349+
config.set(nil, 'Lua.type.checkTableShape', true)
350+
348351
TEST [[
349352
---@class A
350353
---@field x string
@@ -398,3 +401,5 @@ local a = {x = "b", y = {}}
398401
local v
399402
<!v!> = a
400403
]]
404+
405+
config.set(nil, 'Lua.type.checkTableShape', false)

test/diagnostics/param-type-mismatch.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local config = require "config.config"
12
TEST [[
23
---@param x number
34
local function f(x) end
@@ -278,6 +279,8 @@ function f(a) end
278279
f(a)
279280
]]
280281

282+
config.set(nil, 'Lua.type.checkTableShape', true)
283+
281284
TEST [[
282285
---@class A
283286
---@field x string
@@ -347,4 +350,6 @@ local a = {}
347350
function f(a) end
348351
349352
f(a)
350-
]]
353+
]]
354+
355+
config.set(nil, 'Lua.type.checkTableShape', false)

0 commit comments

Comments
 (0)