Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased
<!-- Add all new changes here. They will be moved under a version at release -->
* `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`
Expand Down
26 changes: 26 additions & 0 deletions script/vm/compiler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions test/definition/luadoc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,15 @@ local t
print(t.<?x?>)
]]

-- Issue #3443: inherited anonymous table fields retain their definition.
TEST [[
---@class GenericSuper<T>: { <!value!>: T }
---@class Specific: GenericSuper<string>
---@type Specific
local t
print(t.<?value?>)
]]

TEST [[
---@alias TT<V> { <!x!>: V }

Expand Down
10 changes: 10 additions & 0 deletions test/hover/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>: { value: T }
---@class Specific: GenericSuper<string>
---@type Specific
local t
print(t.<?value?>)
]]
[[(field) Specific.value: string]]

TEST [[
local function <?x?>(a, b)
end
Expand Down
161 changes: 161 additions & 0 deletions test/type_inference/inherited_table_fields.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
-- Issue #3443: fields declared in an anonymous table base survive inheritance.
TEST 'boolean|string' [[
---@alias UnionBase<T> { value: T }|{ value: boolean }
---@class Specific: UnionBase<string>
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'boolean|string' [[
---@alias UnionBase { value: string }|{ value: boolean }
---@type UnionBase
local t
local <?value?> = t.value
]]

TEST 'boolean|string' [[
---@alias UnionBase { value: string }|{ value: boolean }
---@class Specific: UnionBase
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'unknown' [[
---@class Recursive: Recursive
---@type Recursive
local t
local <?value?> = t.value
]]

TEST 'boolean' [[
---@class GenericSuper<T>: { value: T }
---@field value boolean
---@class Specific: GenericSuper<string>
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class First
---@field value string
---@class Second
---@field value boolean
---@class Specific: First, Second
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class First: { value: string }
---@class Second: { value: boolean }
---@class Specific: First, Second
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class First: Second
---@class Second: First, { value: string }
---@type First
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class GenericSuper<T>: { value: T }
---@class Specific: GenericSuper<string>
---@class Grandchild: Specific
---@type Grandchild
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class GenericSuper<T>: { value: T }
---@class Specific: GenericSuper<string>
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'boolean' [[
---@class Container<K, V>: { key: K, value: V }
---@class Specific: Container<string, boolean>
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class Container<K, V>: { key: K, value: V }
---@class Specific: Container<string, boolean>
---@type Specific
local t
local <?key?> = t.key
]]

TEST 'string[]' [[
---@class GenericSuper<T>: { values: T[] }
---@class Specific: GenericSuper<string>
---@type Specific
local t
local <?values?> = t.values
]]

TEST 'string?' [[
---@class Base: { value?: string }
---@class Specific: Base
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class GenericSuper<T>: { [integer]: T }
---@class Specific: GenericSuper<string>
---@type Specific
local t
local <?value?> = t[1]
]]

TEST 'integer' [[
---@class GenericSuper<T>: { value: T }
---@class StringChild: GenericSuper<string>
---@class IntegerChild: GenericSuper<integer>
---@type StringChild
local a
local value = a.value
---@type IntegerChild
local b
local <?value?> = b.value
]]

TEST 'string' [[
---@class Base: { value: string }
---@class Child: Base
---@class Grandchild: Child
---@type Grandchild
local t
local <?value?> = t.value
]]

TEST 'boolean' [[
---@class GenericSuper<T>: { value: T }
---@class Specific: GenericSuper<string>
---@field value boolean
---@type Specific
local t
local <?value?> = t.value
]]

TEST 'string' [[
---@class GenericSuper<T>: { value: T }
---@type GenericSuper<string>
local t
local <?value?> = t.value
]]
1 change: 1 addition & 0 deletions test/type_inference/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Loading