From 14e67221b2b7ebecee1d24ee7d64d16639a53023 Mon Sep 17 00:00:00 2001 From: JasperSurmont Date: Mon, 28 Sep 2026 11:22:53 +0200 Subject: [PATCH 1/2] fix: resolve fields of constrained generic `T: Class` via its constraint --- changelog.md | 1 + script/vm/compiler.lua | 16 +++++++++++++++- test/diagnostics/undefined-field.lua | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f712d4813..d2603329f 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ * `FIX` A `.gitignore` line containing only whitespace no longer ignores the entire workspace [#3458](https://github.com/LuaLS/lua-language-server/issues/3458) * `FIX` `need-check-nil` diagnostic is no longer reported on safe navigation access (e.g. `x?.field`, `f?.()`, `t?.[key]`), since the optional access itself already handles the nil check. Note that a non-safe access chained after a safe one (e.g. `x.upper()?.field`) still reports, because the safe access only protects its own result. +* `FIX` Accessing fields on a value typed as a constrained generic (e.g. `---@generic T: SomeClass` with `---@param self T`) no longer reports `undefined-field`; fields are now looked up on the constraint ## 3.19.1 `2026-08-14` diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index a648c196c..4eff68127 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -355,7 +355,8 @@ local function resolveGenericField(uri, classGlobal, field, signs) } end -local searchFieldSwitch = util.switch() +local searchFieldSwitch +searchFieldSwitch = util.switch() : case 'table' : call(function (_suri, source, key, pushResult) if type(key) == 'string' @@ -515,6 +516,19 @@ local searchFieldSwitch = util.switch() vm.getClassFields(suri, node, key, pushResult) end end) + : case 'doc.generic.name' + : call(function (suri, source, key, pushResult) + searchFieldByLocalID(source, key, pushResult) + searchFieldByGlobalID(suri, source, key, pushResult) + -- unresolved generic `T: SomeClass`: search fields of its constraint + local constraint = source.generic and source.generic.extends + if not constraint then + return + end + for n in vm.compileNode(constraint):eachObject() do + searchFieldSwitch(n.type, suri, n, key, pushResult) + end + end) : default(function (suri, source, key, pushResult) searchFieldByLocalID(source, key, pushResult) searchFieldByGlobalID(suri, source, key, pushResult) diff --git a/test/diagnostics/undefined-field.lua b/test/diagnostics/undefined-field.lua index 24232eb57..903f9aa8c 100644 --- a/test/diagnostics/undefined-field.lua +++ b/test/diagnostics/undefined-field.lua @@ -156,3 +156,18 @@ X = { print(X.) ]] + +TEST [[ +---@class SomeClass +---@field someExistingVar integer +local SomeClass = {} + +---@generic T: SomeClass +---@param self T +---@return T +function SomeClass:xyz() + print(self.someExistingVar) + print(self.) + return self +end +]] From bccd3a93bfbbbc718303a7e1195e28c38bc849e6 Mon Sep 17 00:00:00 2001 From: JasperSurmont Date: Mon, 28 Sep 2026 11:54:58 +0200 Subject: [PATCH 2/2] test: cover union and self-referential generic constraints --- changelog.md | 2 +- test/diagnostics/undefined-field.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d2603329f..05bf272f8 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ * `FIX` A `.gitignore` line containing only whitespace no longer ignores the entire workspace [#3458](https://github.com/LuaLS/lua-language-server/issues/3458) * `FIX` `need-check-nil` diagnostic is no longer reported on safe navigation access (e.g. `x?.field`, `f?.()`, `t?.[key]`), since the optional access itself already handles the nil check. Note that a non-safe access chained after a safe one (e.g. `x.upper()?.field`) still reports, because the safe access only protects its own result. -* `FIX` Accessing fields on a value typed as a constrained generic (e.g. `---@generic T: SomeClass` with `---@param self T`) no longer reports `undefined-field`; fields are now looked up on the constraint +* `FIX` Fields accessed on a value typed as a constrained generic (e.g. `---@generic T: SomeClass` with `---@param self T`) are now looked up on the constraint, so existing fields no longer report `undefined-field` (unknown fields still do) [#2640](https://github.com/LuaLS/lua-language-server/discussions/2640) ## 3.19.1 `2026-08-14` diff --git a/test/diagnostics/undefined-field.lua b/test/diagnostics/undefined-field.lua index 903f9aa8c..9ca4936f0 100644 --- a/test/diagnostics/undefined-field.lua +++ b/test/diagnostics/undefined-field.lua @@ -171,3 +171,29 @@ function SomeClass:xyz() return self end ]] + +TEST [[ +---@class A +---@field a integer + +---@class B +---@field b integer + +---@generic T: A|B +---@param x T +local function f(x) + print(x.a, x.b, x.) +end +]] + +TEST [[ +---@class A +local A = {} + +-- self-referential constraint must not recurse forever +---@generic T: T +---@param self T +function A:f() + print(self.) +end +]]