From 45650dbb9257b14a0292b8d3c6273519098cd4d6 Mon Sep 17 00:00:00 2001 From: Tom Lau Date: Sat, 5 Jul 2025 20:31:16 +0800 Subject: [PATCH] fix: typed `@field` should not override other defined field --- changelog.md | 1 + script/vm/compiler.lua | 91 ++++++++++++++++++++-------------- test/type_inference/common.lua | 19 +++++++ 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/changelog.md b/changelog.md index 03f3e7c03..48be6dbfc 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `FIX` Typed `@field` (eg `---@field [string] boolean`) should not override other defined field [#2171](https://github.com/LuaLS/lua-language-server/issues/2171), [#2711](https://github.com/LuaLS/lua-language-server/issues/2711) ## 3.15.0 `2025-6-25` diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 986e85e2d..c1cc37fe2 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -434,44 +434,6 @@ function vm.getClassFields(suri, object, key, pushResult) pushResult(field, true) goto CONTINUE end - if hasFounded[key] then - goto CONTINUE - end - local keyType = type(key) - if keyType == 'table' then - -- ---@field [integer] boolean -> class[integer] - local fieldNode = vm.compileNode(field.field) - if vm.isSubType(suri, key.name, fieldNode) then - local nkey = '|' .. key.name - if not searchedFields[nkey] then - pushResult(field, true) - hasFounded[nkey] = true - end - end - else - local keyObject - if keyType == 'number' then - if math.tointeger(key) then - keyObject = { type = 'integer', [1] = key } - else - keyObject = { type = 'number', [1] = key } - end - elseif keyType == 'boolean' - or keyType == 'string' then - keyObject = { type = keyType, [1] = key } - end - if keyObject and field.field.type ~= 'doc.field.name' then - -- ---@field [integer] boolean -> class[1] - local fieldNode = vm.compileNode(field.field) - if vm.isSubType(suri, keyObject, fieldNode) then - local nkey = '|' .. keyType - if not searchedFields[nkey] then - pushResult(field, true) - hasFounded[nkey] = true - end - end - end - end ::CONTINUE:: end end @@ -547,6 +509,59 @@ function vm.getClassFields(suri, object, key, pushResult) end copyToSearched() + -- search for typed @field, eg: ---@field [string] boolean + -- only if type for this field key is not found + if not searchedFields[key] and key ~= vm.ANY and key ~= vm.ANYDOC then + for _, set in ipairs(sets) do + if set.type == 'doc.class' then + for _, field in ipairs(set.fields) do + local fieldKey = guide.getKeyName(field) + if fieldKey then + -- already processed above + goto CONTINUE + end + local keyType = type(key) + if keyType == 'table' then + -- ---@field [integer] boolean -> class[integer] + local fieldNode = vm.compileNode(field.field) + if vm.isSubType(suri, key.name, fieldNode) then + local nkey = '|' .. key.name + if not searchedFields[nkey] then + pushResult(field, true) + hasFounded[nkey] = true + end + end + else + local keyObject + if keyType == 'number' then + if math.tointeger(key) then + keyObject = { type = 'integer', [1] = key } + else + keyObject = { type = 'number', [1] = key } + end + elseif keyType == 'boolean' + or keyType == 'string' then + keyObject = { type = keyType, [1] = key } + end + if keyObject and field.field.type ~= 'doc.field.name' then + -- ---@field [integer] boolean -> class[1] + local fieldNode = vm.compileNode(field.field) + if vm.isSubType(suri, keyObject, fieldNode) then + local nkey = '|' .. keyType + if not searchedFields[nkey] then + pushResult(field, true) + hasFounded[nkey] = true + end + end + end + end + ::CONTINUE:: + end + end + end + copyToSearched() + end + for _, set in ipairs(sets) do if set.type == 'doc.class' then -- look into extends(if field not found) diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua index 8376396ac..2404620c1 100644 --- a/test/type_inference/common.lua +++ b/test/type_inference/common.lua @@ -3608,6 +3608,25 @@ local t local = t.n ]] +TEST 'function' [[ +---@class A +---@field [string] boolean +local t +function t.f() end + +local = t.f +]] + +TEST 'function' [[ +---@class A +---@field [string] boolean +local t = { + f = function () end +} + +local = t.f +]] + TEST 'string' [[ ---@class string ---@operator mod: string