From d2d307e66400d5ca5337732b659ec49b8af911e0 Mon Sep 17 00:00:00 2001 From: sogav21 <223452947+sogav21@users.noreply.github.com> Date: Wed, 11 Nov 2026 09:42:54 +0200 Subject: [PATCH] fix: preserve inherited generic table fields --- changelog.md | 1 + script/vm/compiler.lua | 26 +++ test/definition/luadoc.lua | 9 + test/hover/init.lua | 10 ++ .../type_inference/inherited_table_fields.lua | 161 ++++++++++++++++++ test/type_inference/init.lua | 1 + 6 files changed, 208 insertions(+) create mode 100644 test/type_inference/inherited_table_fields.lua diff --git a/changelog.md b/changelog.md index daefdb5ae..24bfd30b9 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `FIX` Preserve inherited fields and their generic types when a class extends an anonymous table type [#3443](https://github.com/LuaLS/lua-language-server/issues/3443) ## 3.19.2 `2026-10-13` diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 20ab3ac41..4ef269a1a 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -774,6 +774,32 @@ function vm.getClassFields(suri, object, key, pushResult) end end) end + -- Compiling a named base also resolves its anonymous + -- table bases, including the supplied generic arguments. + if extend.type == 'doc.extends.name' + or extend.type == 'doc.type.sign' then + local hasTable = false + for node in vm.compileNode(extend):eachObject() do + if node.type == 'doc.type.table' then + if not hasTable then + copyToSearched() + hasTable = true + end + searchFieldSwitch(node.type, suri, node, key, function (field, isMark) + local fieldKey = guide.getKeyName(field) + if not fieldKey or not searchedFields[fieldKey] then + if fieldKey then + hasFounded[fieldKey] = true + end + pushResult(field, isMark) + end + end) + end + end + if hasTable then + copyToSearched() + end + end end end end diff --git a/test/definition/luadoc.lua b/test/definition/luadoc.lua index e7c2be22d..d88d9a00b 100644 --- a/test/definition/luadoc.lua +++ b/test/definition/luadoc.lua @@ -764,6 +764,15 @@ local t print(t.) ]] +-- Issue #3443: inherited anonymous table fields retain their definition. +TEST [[ +---@class GenericSuper: { : T } +---@class Specific: GenericSuper +---@type Specific +local t +print(t.) +]] + TEST [[ ---@alias TT { : V } diff --git a/test/hover/init.lua b/test/hover/init.lua index dbe245ad4..04b22c9c5 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -32,6 +32,16 @@ end config.set(nil, 'Lua.hover.previewFields', 50) +-- Issue #3443: preserve fields from a generic anonymous table base. +TEST [[ +---@class GenericSuper: { value: T } +---@class Specific: GenericSuper +---@type Specific +local t +print(t.) +]] +[[(field) Specific.value: string]] + TEST [[ local function (a, b) end diff --git a/test/type_inference/inherited_table_fields.lua b/test/type_inference/inherited_table_fields.lua new file mode 100644 index 000000000..6f8f63f6f --- /dev/null +++ b/test/type_inference/inherited_table_fields.lua @@ -0,0 +1,161 @@ +-- Issue #3443: fields declared in an anonymous table base survive inheritance. +TEST 'boolean|string' [[ +---@alias UnionBase { value: T }|{ value: boolean } +---@class Specific: UnionBase +---@type Specific +local t +local = t.value +]] + +TEST 'boolean|string' [[ +---@alias UnionBase { value: string }|{ value: boolean } +---@type UnionBase +local t +local = t.value +]] + +TEST 'boolean|string' [[ +---@alias UnionBase { value: string }|{ value: boolean } +---@class Specific: UnionBase +---@type Specific +local t +local = t.value +]] + +TEST 'unknown' [[ +---@class Recursive: Recursive +---@type Recursive +local t +local = t.value +]] + +TEST 'boolean' [[ +---@class GenericSuper: { value: T } +---@field value boolean +---@class Specific: GenericSuper +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class First +---@field value string +---@class Second +---@field value boolean +---@class Specific: First, Second +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class First: { value: string } +---@class Second: { value: boolean } +---@class Specific: First, Second +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class First: Second +---@class Second: First, { value: string } +---@type First +local t +local = t.value +]] + +TEST 'string' [[ +---@class GenericSuper: { value: T } +---@class Specific: GenericSuper +---@class Grandchild: Specific +---@type Grandchild +local t +local = t.value +]] + +TEST 'string' [[ +---@class GenericSuper: { value: T } +---@class Specific: GenericSuper +---@type Specific +local t +local = t.value +]] + +TEST 'boolean' [[ +---@class Container: { key: K, value: V } +---@class Specific: Container +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class Container: { key: K, value: V } +---@class Specific: Container +---@type Specific +local t +local = t.key +]] + +TEST 'string[]' [[ +---@class GenericSuper: { values: T[] } +---@class Specific: GenericSuper +---@type Specific +local t +local = t.values +]] + +TEST 'string?' [[ +---@class Base: { value?: string } +---@class Specific: Base +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class GenericSuper: { [integer]: T } +---@class Specific: GenericSuper +---@type Specific +local t +local = t[1] +]] + +TEST 'integer' [[ +---@class GenericSuper: { value: T } +---@class StringChild: GenericSuper +---@class IntegerChild: GenericSuper +---@type StringChild +local a +local value = a.value +---@type IntegerChild +local b +local = b.value +]] + +TEST 'string' [[ +---@class Base: { value: string } +---@class Child: Base +---@class Grandchild: Child +---@type Grandchild +local t +local = t.value +]] + +TEST 'boolean' [[ +---@class GenericSuper: { value: T } +---@class Specific: GenericSuper +---@field value boolean +---@type Specific +local t +local = t.value +]] + +TEST 'string' [[ +---@class GenericSuper: { value: T } +---@type GenericSuper +local t +local = t.value +]] diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index 0236f7e9f..a87a58018 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -47,4 +47,5 @@ end require 'type_inference.common' require 'type_inference.param_match' require 'type_inference.field_override' +require 'type_inference.inherited_table_fields' require 'type_inference.generic_operator'