Accessing deeply nested variable at 'if' statemet cause type collision
How are you using the lua-language-server?
Visual Studio Code Extension (sumneko.lua)
Which OS are you using?
Windows
What is the issue affecting?
Type Checking
Expected Behaviour
Type checking does not breaks after accessing variable at 'if' statement.
Actual Behaviour
2nd nested variable type became '<type> | unknown' so accessing variable or function which not exists in this class does not cause warning.
Reproduction steps
---@class DemoD
---@field c integer
local DemoD = {}
DemoD.c = 0
---@class DemoC
local DemoC = {}
DemoC.D = DemoD
---@class DemoB
local DemoB = {}
DemoB.C = DemoC
---@class DemoA
local DemoA = {}
DemoA.B = DemoB
---@class DemoRoot
DemoRoot = {}
DemoRoot.A = DemoA
local d = DemoRoot.A.B.C.D
-- if d.c > 1 then end -- if we replace 'DemoRoot.A.B.C.D' with 'd' then F is undefined in all 4 cases
if DemoRoot.A.B.C.D.c > 1 then end
DemoRoot.F() -- warning: F is undefined
DemoRoot.A.F() -- warning: F is undefined
DemoRoot.A.B.F() -- no warning
DemoRoot.A.B.C.F() -- no warning
Additional Notes
No response
Log File
No response
That's interesting 😕 I just did some testing and if you add local to DemoRoot, the subsequent undefined checking will also be working.
---@class DemoRoot
local DemoRoot = {} --<< add `local` here
That's interesting 😕 I just did some testing and if you add
localtoDemoRoot, the subsequent undefined checking will also be working.---@class DemoRoot local DemoRoot = {} --<< add `local` here
The problem is in my project there is global variable forwarded from C-code using lua-bridge. If i declare it as local then code analyzer wont be able to recognize it and produce 1000+ warnings. I was looking for examples of how to define library or global. I found that globals should be ok:
In any case 'if' statement should not affect type analyzer.
I do a lot of code migration from C to Lua, simply by copy-paste and fixing syntax. And when unforwarded functions not produce warning then it can shoot me a foot.
Edit:
Oops... I found that when testing the if case in another file, then my suggested workaround will not be working... ☹️
still have to add a local DemoRoot = DemoRoot to get it work in that new file
(the non perfect "workaround") I think I just come up with a workaround 😂
- first you define it as local
-
then export it as global using
_G.*
---@class DemoD
---@field c integer
local DemoD = {}
DemoD.c = 0
---@class DemoC
local DemoC = {}
DemoC.D = DemoD
---@class DemoB
local DemoB = {}
DemoB.C = DemoC
---@class DemoA
local DemoA = {}
DemoA.B = DemoB
---@class DemoRoot
local DemoRoot = {} --<< define it as local first
_G.DemoRoot = DemoRoot --<< then export it as global using `_G.*`
DemoRoot.A = DemoA
local d = DemoRoot.A.B.C.D
-- if d.c > 1 then end -- this line doesn't matter now :D
if DemoRoot.A.B.C.D.c > 1 then end
DemoRoot.F() -- warning: F is undefined
DemoRoot.A.F() -- warning: F is undefined
DemoRoot.A.B.F() -- warning: F is undefined
DemoRoot.A.B.C.F() -- warning: F is undefined
In any case 'if' statement should not affect type analyzer.
I agree that this is definitely a bug related to global variable and undefined field checking. 😕 But before it is fixed, seems this workaround might suit you need. 🙂
Edit: Oops... I found that when testing the if case in another file, then my suggested workaround will not be working... ☹️ still have to add a
local DemoRoot = DemoRootto get it work in that new file(the non perfect "workaround") I think I just come up with a workaround 😂
* **first** you define it as local * **then** export it as global using `_G.*`---@class DemoD ---@field c integer local DemoD = {} DemoD.c = 0 ---@class DemoC local DemoC = {} DemoC.D = DemoD ---@class DemoB local DemoB = {} DemoB.C = DemoC ---@class DemoA local DemoA = {} DemoA.B = DemoB ---@class DemoRoot local DemoRoot = {} --<< define it as local first _G.DemoRoot = DemoRoot --<< then export it as global using `_G.*` DemoRoot.A = DemoA local d = DemoRoot.A.B.C.D -- if d.c > 1 then end -- this line doesn't matter now :D if DemoRoot.A.B.C.D.c > 1 then end DemoRoot.F() -- warning: F is undefined DemoRoot.A.F() -- warning: F is undefined DemoRoot.A.B.F() -- warning: F is undefined DemoRoot.A.B.C.F() -- warning: F is undefinedIn any case 'if' statement should not affect type analyzer.
I agree that this is definitely a bug related to global variable and undefined field checking. 😕 But before it is fixed, seems this workaround might suit you need. 🙂
Unfortunately it works only inside of a same file. If you move logic:
if DemoRoot.A.B.C.D.c > 1 then end
DemoRoot.F() -- warning: F is undefined
DemoRoot.A.F() -- no warning
DemoRoot.A.B.F() -- no warning
DemoRoot.A.B.C.F() -- no warning
to a different file, then it breaks at 2nd inherited class.
Unfortunately it works only inside of a same file. If you move logic:
Yes, and in that separate file you have to add a local DemoRoot = DemoRoot to make it work ☹️ .
local DemoRoot = DemoRoot --<<add this
--- your same test code below
But guess what? I think I have found ANOTHER WORKAROUND 😂
- When defining your demo classes, declare all of them in the FULL QUALIFIED NAME, like this:
---@meta
---@class DemoRoot
DemoRoot = {}
---@class DemoA
DemoRoot.A = {}
---@class DemoB
DemoRoot.A.B = {}
---@class DemoC
DemoRoot.A.B.C = {}
---@class DemoD
---@field c integer
DemoRoot.A.B.C.D = {}
DemoRoot.A.B.C.D.c = 0
- now even if your test code is in another file, all 4 lines should be throwing warnings 🎉 (without the need to use a local variable)
if DemoRoot.A.B.C.D.c then end
DemoRoot.F() -- warning: F is undefined
DemoRoot.A.F() -- warning: F is undefined
DemoRoot.A.B.F() -- warning: F is undefined
DemoRoot.A.B.C.F() -- warning: F is undefined
Confirmed. Full qualified name workaround helped. It works even if i let inherited classes to be local defined. The only side effect is function type became 'function | unknown' and for some reason inherited class suddenly defined as global. But type checking works even for bugged function arguments.
Thank you for help!