Skip to content

Explicit @field declaration overridden by inherited table<K,V> index semantics in generic class, causing call-non-callable false positive #1104

@ShenzhenGopher

Description

@ShenzhenGopher

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

  1. 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({})
  2. Run the check:

    emmylua_check bug2_repro.lua
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions