Summary
When a generic class inherits from table<K,V> where K is string, accessing an explicitly declared @field member via string key causes emmylua to infer the type as V (the table's value type) instead of the declared @field type. This results in call-non-callable false positives when the field is a function.
Minimal Reproduction
---@class Container<K,V> : table<K,V>
---@field create fun(data: table): Container<K,V>
local Container = {}
function Container:create(_data)
return self
end
---@type Container<string, boolean>
local c
c.create({}) -- ❌ False positive: Cannot call expression of type `boolean` [call-non-callable]
Key observations:
- The bug only triggers when
K is string (or any type that matches the lookup key). When K is integer, the bug does not occur because "create" (a string) doesn't match the integer index rule, so emmylua falls through to the @field declaration correctly.
- The bug occurs regardless of whether the function has an implementation body. Even with only the
@field declaration (no function Container:create()), the same false positive occurs.
- Using dot syntax (
c.create) triggers the bug. The issue is in member type inference, not in call-site analysis.
Expected Behavior
c.create should resolve to the type declared by @field create fun(data: table): Container<K,V>, which after generic instantiation becomes fun(data: table): Container<string, boolean>. Calling c.create({}) should produce no warnings.
Actual Behavior
warning: Cannot call expression of type `boolean`. [call-non-callable]
EmmyLua infers c.create as boolean (the V type parameter from table<string, boolean>) instead of the declared function type.
Reproduction Steps
-
Create a minimal reproduction file bug2_repro.lua:
---@class Container<K,V> : table<K,V>
---@field create fun(data: table): Container<K,V>
local Container = {}
function Container:create(_data) return self end
---@type Container<string, boolean>
local c
c.create({})
-
Run the check:
emmylua_check bug2_repro.lua
-
Observe the false positive:
--- [1 warning]
warning: Cannot call expression of type `boolean`. [call-non-callable]
--> :7:1
6 | local c
7 | c.create({})
Summary
1 warning
Check completed with warnings
Check finished
Environment
- emmylua_check version: based on commit
d7d5dcbf
- OS: Linux
Workaround (temporary)
As suggested by @CppCXY, using an explicit index signature @field [K] V instead of inheriting table<K,V> can avoid this issue:
-- ❌ Triggers the bug
---@class Container<K,V> : table<K,V>
---@field create fun(data: table): Container<K,V>
-- ✅ Temporary workaround: no inheritance, explicit index signature
---@class Container<K,V>
---@field [K] V
---@field create fun(data: table): Container<K,V>
However, this is only a workaround, not a proper fix. The : table<K,V> inheritance pattern is widely used, and explicit @field declarations should intuitively take precedence over inherited index semantics regardless of the pattern used. This issue should still be fixed in the analyzer itself.
Summary
When a generic class inherits from
table<K,V>whereKisstring, accessing an explicitly declared@fieldmember via string key causes emmylua to infer the type asV(the table's value type) instead of the declared@fieldtype. This results incall-non-callablefalse positives when the field is a function.Minimal Reproduction
Key observations:
Kisstring(or any type that matches the lookup key). WhenKisinteger, the bug does not occur because"create"(a string) doesn't match the integer index rule, so emmylua falls through to the@fielddeclaration correctly.@fielddeclaration (nofunction Container:create()), the same false positive occurs.c.create) triggers the bug. The issue is in member type inference, not in call-site analysis.Expected Behavior
c.createshould resolve to the type declared by@field create fun(data: table): Container<K,V>, which after generic instantiation becomesfun(data: table): Container<string, boolean>. Callingc.create({})should produce no warnings.Actual Behavior
EmmyLua infers
c.createasboolean(theVtype parameter fromtable<string, boolean>) instead of the declared function type.Reproduction Steps
Create a minimal reproduction file
bug2_repro.lua:Run the check:
Observe the false positive:
Environment
d7d5dcbfWorkaround (temporary)
As suggested by @CppCXY, using an explicit index signature
@field [K] Vinstead of inheritingtable<K,V>can avoid this issue:However, this is only a workaround, not a proper fix. The
: table<K,V>inheritance pattern is widely used, and explicit@fielddeclarations should intuitively take precedence over inherited index semantics regardless of the pattern used. This issue should still be fixed in the analyzer itself.