fix: prioritize explicit @field over inherited table<K,V> index in infer_generic_member#1105
Conversation
…fer_generic_member When a generic class inherits table<K,V> (K=string), infer_generic_member calls infer_generic_members_from_super_generics before checking the type's own @field declarations. This causes string-keyed lookups to return V instead of the declared @field type, producing call-non-callable false positives. Move the explicit @field lookup before the super-generics check so that intentional field declarations always take precedence over inherited table index semantics.
There was a problem hiding this comment.
Code Review: infer_generic_member Changes
Issue Identified:
Logic Error - Missing Return on Success
The new code block attempts to find a member directly on the type, but if the lookup succeeds, it returns the result. However, if the lookup fails (member not found), execution falls through to the original infer_generic_members_from_super_generics call. This is correct behavior.
Potential Issue: Redundant Lookup
The original code already calls infer_member_by_lookup which may perform similar lookups. Adding this explicit check before the original logic could cause:
- Duplicate work when the member is found via the new path
- Different behavior if
infer_member_by_lookuphas additional logic (e.g., error handling, type coercion)
Missing Error Handling
The get_member_item call returns Option, and the resolve_type call returns Result. If resolve_type fails, the error is silently ignored and execution continues to the original logic. This might hide real issues.
Recommendations:
-
Consider merging with existing logic - Instead of adding a separate early return, integrate this check into
infer_member_by_lookupor the existing flow to avoid duplication. -
Add error logging - If
resolve_typefails, log the error for debugging:if let Ok(member_type) = member_item.resolve_type(db) { return Ok(instantiate_type_generic(db, &member_type, &substitutor)); } else { // Log error: failed to resolve member type }
-
Verify priority semantics - Ensure this change doesn't break cases where inherited table index semantics should take priority (e.g., metatable
__index). The comment says "explicit @field declarations take priority," but confirm this matches the intended design. -
Add tests - Include test cases for:
- Explicit
@fielddeclarations on generic types - Cases where both
@fieldand inherited index methods exist - Error cases (e.g., unresolvable member types)
- Explicit
Security/Performance:
- No security issues identified
- Performance impact is minimal (one extra hash lookup per generic member inference)
There was a problem hiding this comment.
Code Review
This pull request updates the generic member inference logic to prioritize explicit @field declarations directly on the type over inherited table index semantics. The reviewer suggested propagating errors from resolve_type using the ? operator rather than silently falling back to super generics, ensuring robust error handling for explicit declarations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| let owner = LuaMemberOwner::Type(base_type_decl_id.clone()); | ||
| if let Some(member_item) = db.get_member_index().get_member_item(&owner, &lookup.key) { | ||
| if let Ok(member_type) = member_item.resolve_type(db) { | ||
| return Ok(instantiate_type_generic(db, &member_type, &substitutor)); | ||
| } | ||
| } |
There was a problem hiding this comment.
If resolve_type fails (returns an Err), the current implementation silently ignores the error and falls back to infer_generic_members_from_super_generics. However, since the member is explicitly declared on the type itself, any resolution failure should be propagated rather than falling back to the parent's generic index semantics (which would re-introduce the exact overriding/shadowing issue this PR aims to fix).
Using the ? operator to propagate the error is consistent with how member resolution is handled in other parts of the codebase (e.g., infer_custom_type_member).
let owner = LuaMemberOwner::Type(base_type_decl_id.clone());
if let Some(member_item) = db.get_member_index().get_member_item(&owner, &lookup.key) {
let member_type = member_item.resolve_type(db)?;
return Ok(instantiate_type_generic(db, &member_type, &substitutor));
}
Summary
Fixes #xxx
When a generic class inherits
table<K,V>(K=string), explicit@fielddeclarations are overridden by the parent's index semantics, causingcall-non-callablefalse positives.Root Cause
In
infer_generic_member(crates/emmylua_code_analysis/src/semantic/infer/infer_index/mod.rs, line 688),infer_generic_members_from_super_genericsis called before checking the type's own@fielddeclarations. When K=string and the lookup key is a string (e.g."create"), the parent'stable<string, V>index rule returnsSome(V)immediately, making the subsequent@fieldcheck unreachable.Changes
crates/emmylua_code_analysis/src/semantic/infer/infer_index/mod.rsinfer_generic_member, move explicit@fieldlookup (get_member_item+resolve_type) beforeinfer_generic_members_from_super_genericscall (+9 lines)Rationale
Explicit
@fielddeclarations represent the developer's intentional type specification for a specific member. They should always take precedence over inherited generic index semantics, following the standard OOP principle that subclass members override parent behavior.Example
Verification
Before fix (commit
d7d5dcbf):After fix: