Skip to content

feat: resolve class generic parameters in @operator declarations#3437

Open
RomanSpector wants to merge 1 commit into
LuaLS:masterfrom
RomanSpector:feat/generic-operator-call
Open

feat: resolve class generic parameters in @operator declarations#3437
RomanSpector wants to merge 1 commit into
LuaLS:masterfrom
RomanSpector:feat/generic-operator-call

Conversation

@RomanSpector

Copy link
Copy Markdown
Contributor

Summary

@operator declarations on generic classes now resolve the class type parameters through the instantiated type, and generic parameter names used inside @operator annotations are recognized (and highlighted) as generics instead of undefined type names.

---@class SingleSelector<T> : UIObject
---@operator call(): T?
local SingleSelector

---@generic T
---@param items T[]
---@return SingleSelector<T>
local function AddSingleSelector(items) end

local items = {} ---@type string[]
local selector = AddSingleSelector(items) -- SingleSelector<string>
local value = selector()                  -- string?  (was: unknown)

Previously vm.runOperator only understood plain type globals in the callee's node, so every operator (call, add, concat, ...) was silently skipped for parameterized types like Box<string>, and T inside @operator stayed an undefined doc.type.name.

Implementation

  • script/parser/luadoc.lua (1 line): bindGeneric already collects generic names from @generic and from class/alias signs and rewrites doc.type.namedoc.generic.name inside doc.param, doc.return, doc.field, doc.overload, etc. — but doc.operator was missing from the list. Adding it fixes both the semantic highlighting (the existing doc.generic.name token path applies) and the generic detection for inference.
  • script/vm/operator.lua: vm.runOperator handles doc.type.sign objects in the callee node: the class global is looked up by the sign's base name and its operators are checked with the sign's type arguments. checkOperators substitutes class type parameters in operator.exp / operator.extends via the existing vm.getClassGenericMap + vm.cloneObject machinery (same pattern as generic field resolution). When matching the operand of a binary operator, a doc.type.sign value is matched by its base class, since vm.isSubType has no notion of parameterized types (type arguments are not compared — same as everywhere else in the codebase).
  • script/vm/compiler.lua (3 lines): expose the existing local containsGenericName as vm.containsGenericName for reuse.

Behavior notes

  • An unparameterized reference (---@type Box on ---@class Box<T>) now renders the operator result as <T>? instead of T? — the standard display for an unresolved generic, instead of pretending an undefined class T exists.
  • Operator inheritance from parent classes is unchanged (not supported before, not supported now).
  • The typeGeneric branch in core/semantic-tokens.lua remains unused by this PR (nothing sets that flag today); the highlight works through the regular doc.generic.name token path.

Tests

New test/type_inference/generic_operator.lua:

  • call/add on instantiated generics, multi-parameter classes (Pair<K, V>), nested signs (Box<Box<string>>), generic operand types (add(Box<T>): T), a real-world shape with inheritance + generic factory, and parity cases (non-generic baseline, signs on a non-generic class, unparameterized reference).
  • AST assertions that sign names inside @field/@operator/inheritance arguments become doc.generic.name (these use guide.eachSource rather than eachSourceType, because the type cache of bound docs predates the bindGeneric rewrite).

Full test suite passes on win32-x64.

🤖 Generated with Claude Code

`vm.runOperator` only understood plain type globals in the callee node,
so every operator was silently skipped for parameterized types like
`Box<string>`, and `T` inside `@operator call(): T?` was treated as an
undefined type name.

- luadoc: add doc.operator to the bindGeneric rewrite list so sign
  names inside operator annotations become doc.generic.name - this
  both enables generic detection and gives them generic highlighting
- vm: handle doc.type.sign in runOperator and substitute class type
  parameters in operator.exp/operator.extends via getClassGenericMap
  and cloneObject; match sign operands by their base class
- expose containsGenericName as vm.containsGenericName

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant