lua-language-server icon indicating copy to clipboard operation
lua-language-server copied to clipboard

Accessing deeply nested variable at 'if' statemet cause type collision

Open TriRozhka opened this issue 1 year ago • 6 comments

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

if-statement-bug

Additional Notes

No response

Log File

No response

TriRozhka avatar Jun 21 '24 22:06 TriRozhka

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

tomlau10 avatar Jun 22 '24 02:06 tomlau10

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

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.

TriRozhka avatar Jun 22 '24 14:06 TriRozhka

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. 🙂

tomlau10 avatar Jun 22 '24 15:06 tomlau10

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. 🙂

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.

TriRozhka avatar Jun 22 '24 20:06 TriRozhka

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

tomlau10 avatar Jun 23 '24 01:06 tomlau10

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!

TriRozhka avatar Jun 23 '24 16:06 TriRozhka