Area: glua_check (also shows as an inlay hint in VS Code; the diagnostics below are from glua_check)
Summary
infer-unknown reports a value as "inferred from usage context and may be incorrect" when every type in its derivation is declared. The value is arithmetic derived from a method call on a receiver whose type came from a fun(...) callback slot: frame is declared Frame by the slot, frame:GetWide() is a declared number, so frame:GetWide() - 1 is number - yet passing it into a number parameter is flagged. The diagnostic even names the type correctly (number), so this is a false positive, not a genuinely-unknown type.
It is hint-level, but it fires wherever this shape occurs and is visible during normal editing (inlay hint) as well as in glua_check.
Minimal repro
Repro: bug5-infer-unknown-callback-receiver.zip
Standalone - no workspace.library and no GLua annotations. .luarc.json is only {"runtime":{"version":"LuaJIT"}}.
---@class Frame
---@field GetWide fun(self: Frame): number
---@param n number
local function sink(n) end
---@param func fun(frame: Frame)
local function AddScreen(func) end
AddScreen(function(frame)
local w = frame:GetWide() -- declared number
local x = w - 1 -- declared number - 1 -> number
sink(x) -- hint: Type `number` was inferred from usage context
end)
glua_check --warnings-as-errors . on the repro reports exactly one diagnostic (line 38 is the sink(x) line in repro.lua, below the header comment):
hint: Type `number` was inferred from usage context and may be incorrect. [infer-unknown]
--> repro.lua:38:10
Three controls that isolate it
All three are in the repro file, and all three are clean:
- A
---@param frame Frame receiver with the identical body, instead of the callback slot.
- The same callback slot, but
---@type number on the intermediate (local x) - this is the workaround we actually use.
- The same callback slot, but no arithmetic - the method return (
w) passed straight into sink.
The first control is the decisive one: ---@param frame Frame and fun(frame: Frame) both declare frame as Frame, and the bodies are identical - yet only the callback-slot form is treated as "inferred from usage context". The third shows the method return is fine on its own; it is the arithmetic on it that the analyzer then treats as inferred.
Probing the resolved types (a throwaway ---@type nil local _ = v dumps each in the mismatch message) gives frame -> Frame, w -> number, x -> number - all concrete, with no unknown in any of them - and the hint still fires on x. So the value genuinely is number, exactly as the diagnostic names it.
Real-world impact
This is the single largest source of new hints for us on 1.2.0. TARDIS registers screens as TARDIS:AddScreen(name, opts, func), where func is fun(self, ext, int, frame: tardis_screen_frame, screen). Screen-layout code is full of arithmetic on frame:GetWide() / frame:GetTall() - e.g. local listW = (frame:GetWide() - 4 * gap) / 3 - and each such local trips this. We carry a ---@type number on roughly a dozen of them (across cl_screen_chameleon.lua and other screen files) purely to silence it.
Expected
x is number (as the diagnostic itself names it) and passes into a number parameter without a hint - matching the ---@param-receiver form, which is already clean. A parameter typed by a fun(...) callback slot is a declaration, not a guess, and values derived from it should not be treated as inferred.
Environment
glua_check 1.2.0
- 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 on 1.2.0 rather than asserted, and the three controls were run before filing. The Frame / AddScreen names are a reduction of TARDIS's real screen-registration callback (fun(..., frame: tardis_screen_frame, ...)); the structure - a callback-slot-typed receiver, a method call, arithmetic, then a typed sink - is the same.
Area:
glua_check(also shows as an inlay hint in VS Code; the diagnostics below are fromglua_check)Summary
infer-unknownreports a value as "inferred from usage context and may be incorrect" when every type in its derivation is declared. The value is arithmetic derived from a method call on a receiver whose type came from afun(...)callback slot:frameis declaredFrameby the slot,frame:GetWide()is a declarednumber, soframe:GetWide() - 1isnumber- yet passing it into anumberparameter is flagged. The diagnostic even names the type correctly (number), so this is a false positive, not a genuinely-unknown type.It is hint-level, but it fires wherever this shape occurs and is visible during normal editing (inlay hint) as well as in
glua_check.Minimal repro
Repro: bug5-infer-unknown-callback-receiver.zip
Standalone - no
workspace.libraryand no GLua annotations..luarc.jsonis only{"runtime":{"version":"LuaJIT"}}.glua_check --warnings-as-errors .on the repro reports exactly one diagnostic (line 38 is thesink(x)line inrepro.lua, below the header comment):Three controls that isolate it
All three are in the repro file, and all three are clean:
---@param frame Framereceiver with the identical body, instead of the callback slot.---@type numberon the intermediate (local x) - this is the workaround we actually use.w) passed straight intosink.The first control is the decisive one:
---@param frame Frameandfun(frame: Frame)both declareframeasFrame, and the bodies are identical - yet only the callback-slot form is treated as "inferred from usage context". The third shows the method return is fine on its own; it is the arithmetic on it that the analyzer then treats as inferred.Probing the resolved types (a throwaway
---@type nil local _ = vdumps each in the mismatch message) givesframe->Frame,w->number,x->number- all concrete, with nounknownin any of them - and the hint still fires onx. So the value genuinely isnumber, exactly as the diagnostic names it.Real-world impact
This is the single largest source of new hints for us on 1.2.0. TARDIS registers screens as
TARDIS:AddScreen(name, opts, func), wherefuncisfun(self, ext, int, frame: tardis_screen_frame, screen). Screen-layout code is full of arithmetic onframe:GetWide()/frame:GetTall()- e.g.local listW = (frame:GetWide() - 4 * gap) / 3- and each such local trips this. We carry a---@type numberon roughly a dozen of them (acrosscl_screen_chameleon.luaand other screen files) purely to silence it.Expected
xisnumber(as the diagnostic itself names it) and passes into anumberparameter without a hint - matching the---@param-receiver form, which is already clean. A parameter typed by afun(...)callback slot is a declaration, not a guess, and values derived from it should not be treated as inferred.Environment
glua_check1.2.0gluals-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 on 1.2.0 rather than asserted, and the three controls were run before filing. The
Frame/AddScreennames are a reduction of TARDIS's real screen-registration callback (fun(..., frame: tardis_screen_frame, ...)); the structure - a callback-slot-typed receiver, a method call, arithmetic, then a typed sink - is the same.