Area: glua_check (expected to affect the language server too; the repro and diagnostics below are from glua_check)
Summary
A function attached to a global sub-table in one file is reported Undefined field when read from another file, on 1.2.0. The identical workspace is clean on 1.1.2 - so this is a regression, and given the timing it looks like a side effect of the cross-file analysis/indexing rework in #75.
The pattern is the ordinary multi-file namespace idiom: every file opens with MCP.wp = MCP.wp or {}, one file attaches functions to it, others call them.
Underneath (see the type dump below), the cause is that 1.2.0 no longer unifies the type across the repeated MCP.wp = MCP.wp or {} assignments in different files. Each assignment produces its own wp member; on 1.1.2 their types merged, so fields attached in any file were visible from all of them; on 1.2.0 they do not merge, so a file that re-guards the namespace sees only its own fresh empty {} and the field attached elsewhere is dropped.
Regression - same workspace, two versions
$ glua_check --version
glua_check 1.1.2
$ glua_check --warnings-as-errors .
No issues found <- clean
$ glua_check --version
glua_check 1.2.0
$ glua_check --warnings-as-errors .
warning: Undefined field `Foo`. [undefined-field]
--> lua/02_read.lua:23:15 <- fires
It is a warning, so on 1.2.0 it fails --warnings-as-errors with exit 1.
What the type resolves to (root cause)
Adding ---@type integer local _ = MCP to the reader dumps MCP's resolved type in the mismatch message (the two files' MCP.wp = MCP.wp or {} each show up as a wp member, so MCP displays two of them on both versions - that duplication is not the bug):
1.1.2: Cannot assign `{ wp = { Foo = fun() -> 1 }, wp = { Foo = fun() -> 1 } }` to `integer`.
1.2.0: Cannot assign `{ wp = table, wp = table }` to `integer`.
On 1.1.2 both wp members carry Foo; on 1.2.0 both are bare table and Foo is gone. So the regression is the loss of Foo from the type, not the duplicate member.
And it is specifically the reader re-guarding the namespace that drops it. With the reader reading MCP.wp without its own MCP.wp = MCP.wp or {}, both versions resolve a single { wp = { Foo = fun() -> 1 } } and the read is clean. The moment the reader repeats the = MCP.wp or {} guard, 1.2.0 resolves its reads against a fresh, field-less wp.
Minimal repro
Repro: bug4-crossfile-undefined-field.zip
Standalone - no workspace.library and no GLua annotations. .luarc.json is only {"runtime":{"version":"LuaJIT"}}. Two files:
lua/01_define.lua:
MCP = MCP or {}
MCP.wp = MCP.wp or {}
local wp_ = MCP.wp
function wp_.Foo() return 1 end
lua/02_read.lua:
MCP.wp = MCP.wp or {}
local wp_ = MCP.wp
local a = wp_.Foo() -- 1.2.0: Undefined field `Foo`
return a
What the trigger needs (controls I reduced against)
Each of these, applied to the repro, makes 1.2.0 go clean - so all are load-bearing:
01_define.lua must attach through the local alias. Attaching directly - function MCP.wp.Foo() ... end, no local wp_ - is clean.
01_define.lua must re-assign the namespace itself (MCP.wp = MCP.wp or {} in that file). If the guard lives in a third file and 01_define.lua only aliases a pre-existing MCP.wp, it is clean.
02_read.lua must repeat MCP.wp = MCP.wp or {}. Drop that one line - read MCP.wp without re-guarding it - and it is clean.
- The two must be in separate files. The identical sequence in a single file is clean.
It is not order-dependent: renaming the files so the reader sorts first fires just the same (unlike #72, where sort order was the whole difference).
Real-world impact
world-portals is built this way: ~18 files open with the MCP.wp = MCP.wp or {} guard and take local wp_ = MCP.wp, and one file attaches the API onto that alias. On 1.2.0 every cross-file wp_.X() call became Undefined field and failed the build; on 1.1.2 the same tree was clean. We worked around it by declaring a ---@class for the namespace so the fields resolve regardless, but the underlying cross-file resolution regressed.
This is a distinct symptom from the cross-file issues #75 closed (#46 / #72 / #74 were infer-unknown and nil-resolution); I am filing it separately rather than reopening any of them, but flagging the shared area in case the cause is common.
Expected
Foo, attached to MCP.wp in 01_define.lua, resolves when read through MCP.wp (or an alias of it) in 02_read.lua - as it did on 1.1.2.
Environment
glua_check 1.2.0 (regressed) vs 1.1.2 (clean) - both run against the same workspace
- annotations
gluals-annotations-prerelease @ aae7341 (not needed for the repro)
- Windows 11
Disclosure: this report was researched and written by Claude Code working in my repositories. Every claim in it was reproduced against a real workspace rather than asserted - including the 1.1.2-vs-1.2.0 comparison above, run with both cached binaries against the same folder, each of the four controls, and the MCP type dumps (via a throwaway ---@type integer mismatch, left commented in the repro). The MCP.wp / Foo names are a reduction of the real world-portals namespace and its API; the structure (per-file namespace guard, alias, cross-file call) is the same.
Area:
glua_check(expected to affect the language server too; the repro and diagnostics below are fromglua_check)Summary
A function attached to a global sub-table in one file is reported
Undefined fieldwhen read from another file, on 1.2.0. The identical workspace is clean on 1.1.2 - so this is a regression, and given the timing it looks like a side effect of the cross-file analysis/indexing rework in #75.The pattern is the ordinary multi-file namespace idiom: every file opens with
MCP.wp = MCP.wp or {}, one file attaches functions to it, others call them.Underneath (see the type dump below), the cause is that 1.2.0 no longer unifies the type across the repeated
MCP.wp = MCP.wp or {}assignments in different files. Each assignment produces its ownwpmember; on 1.1.2 their types merged, so fields attached in any file were visible from all of them; on 1.2.0 they do not merge, so a file that re-guards the namespace sees only its own fresh empty{}and the field attached elsewhere is dropped.Regression - same workspace, two versions
It is a warning, so on 1.2.0 it fails
--warnings-as-errorswith exit 1.What the type resolves to (root cause)
Adding
---@type integer local _ = MCPto the reader dumpsMCP's resolved type in the mismatch message (the two files'MCP.wp = MCP.wp or {}each show up as awpmember, soMCPdisplays two of them on both versions - that duplication is not the bug):On 1.1.2 both
wpmembers carryFoo; on 1.2.0 both are baretableandFoois gone. So the regression is the loss ofFoofrom the type, not the duplicate member.And it is specifically the reader re-guarding the namespace that drops it. With the reader reading
MCP.wpwithout its ownMCP.wp = MCP.wp or {}, both versions resolve a single{ wp = { Foo = fun() -> 1 } }and the read is clean. The moment the reader repeats the= MCP.wp or {}guard, 1.2.0 resolves its reads against a fresh, field-lesswp.Minimal repro
Repro: bug4-crossfile-undefined-field.zip
Standalone - no
workspace.libraryand no GLua annotations..luarc.jsonis only{"runtime":{"version":"LuaJIT"}}. Two files:lua/01_define.lua:lua/02_read.lua:What the trigger needs (controls I reduced against)
Each of these, applied to the repro, makes 1.2.0 go clean - so all are load-bearing:
01_define.luamust attach through the local alias. Attaching directly -function MCP.wp.Foo() ... end, nolocal wp_- is clean.01_define.luamust re-assign the namespace itself (MCP.wp = MCP.wp or {}in that file). If the guard lives in a third file and01_define.luaonly aliases a pre-existingMCP.wp, it is clean.02_read.luamust repeatMCP.wp = MCP.wp or {}. Drop that one line - readMCP.wpwithout re-guarding it - and it is clean.It is not order-dependent: renaming the files so the reader sorts first fires just the same (unlike #72, where sort order was the whole difference).
Real-world impact
world-portals is built this way: ~18 files open with the
MCP.wp = MCP.wp or {}guard and takelocal wp_ = MCP.wp, and one file attaches the API onto that alias. On 1.2.0 every cross-filewp_.X()call becameUndefined fieldand failed the build; on 1.1.2 the same tree was clean. We worked around it by declaring a---@classfor the namespace so the fields resolve regardless, but the underlying cross-file resolution regressed.This is a distinct symptom from the cross-file issues #75 closed (#46 / #72 / #74 were
infer-unknownand nil-resolution); I am filing it separately rather than reopening any of them, but flagging the shared area in case the cause is common.Expected
Foo, attached toMCP.wpin01_define.lua, resolves when read throughMCP.wp(or an alias of it) in02_read.lua- as it did on 1.1.2.Environment
glua_check1.2.0 (regressed) vs 1.1.2 (clean) - both run against the same workspacegluals-annotations-prerelease@aae7341(not needed for the repro)Disclosure: this report was researched and written by Claude Code working in my repositories. Every claim in it was reproduced against a real workspace rather than asserted - including the 1.1.2-vs-1.2.0 comparison above, run with both cached binaries against the same folder, each of the four controls, and the
MCPtype dumps (via a throwaway---@type integermismatch, left commented in the repro). TheMCP.wp/Foonames are a reduction of the real world-portals namespace and its API; the structure (per-file namespace guard, alias, cross-file call) is the same.