From d7ef55e8817627af0f25dc2bb9ed9927d0049d6a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 20 Apr 2026 15:22:32 +0100 Subject: [PATCH] fix(vim.iter): add richer generic annotations Improve the vim.iter annotations with richer generics that track element and tuple types through iterator pipelines, including multi-value stages and list-specific methods. Extend the LuaCATS parser and vimdoc generator so those richer generic classes and overloads round-trip into the generated help. These annotations are only supported by EmmyLua, so LuaLS still uses a broader fallback in _meta.lua. AI-assisted: Codex --- runtime/doc/deprecated.txt | 2 +- runtime/doc/lua.txt | 410 +++++++------- runtime/lua/vim/_meta.lua | 59 +- runtime/lua/vim/iter.lua | 507 ++++++++++-------- src/gen/luacats_grammar.lua | 20 +- src/gen/luacats_parser.lua | 2 + .../script/luacats_grammar_spec.lua | 26 + 7 files changed, 602 insertions(+), 424 deletions(-) diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 4f98976ecf..1d9efd91de 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -180,7 +180,7 @@ LSP LUA • *vim.loop* Use |vim.uv| instead. • *vim.tbl_add_reverse_lookup()* -• *vim.tbl_flatten()* Use |Iter:flatten()| instead. +• *vim.tbl_flatten()* Use |IterArray:flatten()| instead. • *vim.tbl_islist()* Use |vim.islist()| instead. OPTIONS diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 8208386c49..03a8e5be20 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -128,7 +128,7 @@ iterator functions, tables implementing the |__call()| metamethod, and *list-iterator* Iterators on |lua-list| tables have a "middle" and "end", whereas iterators in general may be logically infinite. Therefore some |vim.iter| operations (e.g. -|Iter:rev()|) make sense only on list-like tables (which are finite by +|IterArray:rev()|) make sense only on list-like tables (which are finite by definition). *lua-function-call* @@ -3028,7 +3028,7 @@ The iterator pipeline terminates when the underlying |iterable| is exhausted Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of |list-iterator| -operations such as |Iter:rev()|). +operations such as |IterArray:rev()|). Examples: >lua local it = vim.iter({ 1, 2, 3, 4, 5 }) @@ -3077,9 +3077,9 @@ Iter:all({pred}) *Iter:all()* Since: 0.10.0 Parameters: ~ - • {pred} (`fun(...):boolean`) Predicate function. Takes all values - returned from the previous stage in the pipeline as arguments - and returns true if the predicate matches. + • {pred} (`fun(v: V1, ...: V...): boolean`) Predicate function. Takes + all values returned from the previous stage in the pipeline as + arguments and returns true if the predicate matches. Iter:any({pred}) *Iter:any()* Returns true if any of the items in the iterator match the given @@ -3089,9 +3089,9 @@ Iter:any({pred}) *Iter:any()* Since: 0.10.0 Parameters: ~ - • {pred} (`fun(...):boolean`) Predicate function. Takes all values - returned from the previous stage in the pipeline as arguments - and returns true if the predicate matches. + • {pred} (`fun(v: V1, ...: V...): boolean`) Predicate function. Takes + all values returned from the previous stage in the pipeline as + arguments and returns true if the predicate matches. Iter:each({f}) *Iter:each()* Calls a function once for each item in the pipeline, draining the @@ -3104,9 +3104,9 @@ Iter:each({f}) *Iter:each()* Since: 0.10.0 Parameters: ~ - • {f} (`fun(...)`) Function to execute for each item in the pipeline. - Takes all of the values returned by the previous stage in the - pipeline as arguments. + • {f} (`fun(v: V1, ...: V...)`) Function to execute for each item in + the pipeline. Takes all of the values returned by the previous + stage in the pipeline as arguments. Iter:enumerate() *Iter:enumerate()* Yields the item index (count) and value for each item of an iterator @@ -3134,8 +3134,11 @@ Iter:enumerate() *Iter:enumerate()* Attributes: ~ Since: 0.10.0 + Overloads: ~ + • `fun(self: vim.IterArray): vim.IterArray` + Return: ~ - (`Iter`) + (`vim.Iter`) Iter:filter({f}) *Iter:filter()* Filters an iterator pipeline. @@ -3145,12 +3148,15 @@ Iter:filter({f}) *Iter:filter()* < Parameters: ~ - • {f} (`fun(...):boolean`) Takes all values returned from the previous - stage in the pipeline and returns false or nil if the current - iterator element should be removed. + • {f} (`fun(v: V1, ...: V...): boolean`) Takes all values returned from + the previous stage in the pipeline and returns false or nil if + the current iterator element should be removed. + + Overloads: ~ + • `fun(self: vim.IterArray, f: fun(v: V1, ...: V...): boolean): vim.IterArray` Return: ~ - (`Iter`) + (`vim.Iter`) Iter:find({f}) *Iter:find()* Find the first value in the iterator that satisfies the given predicate. @@ -3177,35 +3183,14 @@ Iter:find({f}) *Iter:find()* Since: 0.10.0 Parameters: ~ - • {f} (`any`) + • {f} (`(fun(v: V1, ...: V...): boolean)|any`) - Return: ~ - (`any`) + Overloads: ~ + • `fun(self: vim.IterArray, f: V1|fun(v: V1, ...: V...): boolean): V1?, V...` -Iter:flatten({depth}) *Iter:flatten()* - Flattens a |list-iterator|, un-nesting nested values up to the given - {depth}. Errors if it attempts to flatten a dict-like value. - - Examples: >lua - vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable() - -- { 1, 2, { 3 } } - - vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable() - -- { 1, { a = 2 }, 3 } - - vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable() - -- error: attempt to flatten a dict-like table -< - - Attributes: ~ - Since: 0.10.0 - - Parameters: ~ - • {depth} (`number?`) Depth to which |list-iterator| should be - flattened (defaults to 1) - - Return: ~ - (`Iter`) + Return (multiple): ~ + (`V1?`) + (`V...`) Iter:fold({init}, {f}) *Iter:fold()* Folds ("reduces") an iterator into a single value. *Iter:reduce()* @@ -3232,7 +3217,7 @@ Iter:fold({init}, {f}) *Iter:fold()* Parameters: ~ • {init} (`any`) Initial value of the accumulator. - • {f} (`fun(acc:A, ...):A`) Accumulation function. + • {f} (`fun(acc: A, v: V1, ...: V...): A`) Accumulation function. Return: ~ (`any`) @@ -3270,11 +3255,12 @@ Iter:last() *Iter:last()* Attributes: ~ Since: 0.10.0 - Return: ~ - (`any`) + Return (multiple): ~ + (`V1?`) + (`V...`) See also: ~ - • |Iter:rpeek()| + • |IterArray:rpeek()| Iter:map({f}) *Iter:map()* Maps the items of an iterator pipeline to the values returned by `f`. @@ -3295,13 +3281,17 @@ Iter:map({f}) *Iter:map()* Since: 0.10.0 Parameters: ~ - • {f} (`fun(...):...:any`) Mapping function. Takes all values returned - from the previous stage in the pipeline as arguments and returns - one or more new values, which are used in the next pipeline - stage. Nil return values are filtered from the output. + • {f} (`fun(v: V1, ...: V...): K2, V2...`) Mapping function. Takes all + values returned from the previous stage in the pipeline as + arguments and returns one or more new values, which are used in + the next pipeline stage. Nil return values are filtered from the + output. + + Overloads: ~ + • `fun(self: vim.IterArray, f: fun(v: V1, ...: V...): K1, K...): vim.IterArray` Return: ~ - (`Iter`) + (`vim.Iter`) Iter:next() *Iter:next()* Gets the next value from the iterator. @@ -3320,8 +3310,9 @@ Iter:next() *Iter:next()* Attributes: ~ Since: 0.10.0 - Return: ~ - (`any`) + Return (multiple): ~ + (`V1?`) + (`V...`) Iter:nth({n}) *Iter:nth()* Gets the nth value of an iterator (and advances to it). @@ -3346,11 +3337,12 @@ Iter:nth({n}) *Iter:nth()* Since: 0.10.0 Parameters: ~ - • {n} (`number`) Index of the value to return. May be negative if the + • {n} (`integer`) Index of the value to return. May be negative if the source is a |list-iterator|. - Return: ~ - (`any`) + Return (multiple): ~ + (`V1?`) + (`V...`) Iter:peek() *Iter:peek()* Gets the next value from the iterator without consuming it. @@ -3372,112 +3364,9 @@ Iter:peek() *Iter:peek()* Attributes: ~ Since: 0.10.0 - Return: ~ - (`any`) - -Iter:pop() *Iter:pop()* - "Pops" a value from a |list-iterator| (gets the last value and decrements - the tail). - - Example: >lua - local it = vim.iter({1, 2, 3, 4}) - it:pop() - -- 4 - it:pop() - -- 3 -< - - Attributes: ~ - Since: 0.10.0 - - Return: ~ - (`any`) - -Iter:rev() *Iter:rev()* - Reverses a |list-iterator| pipeline. - - Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }):rev() - it:totable() - -- { 12, 9, 6, 3 } -< - - Attributes: ~ - Since: 0.10.0 - - Return: ~ - (`Iter`) - -Iter:rfind({f}) *Iter:rfind()* - Gets the first value satisfying a predicate, from the end of a - |list-iterator|. - - Advances the iterator. Returns nil and drains the iterator if no value is - found. - - Examples: >lua - - local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() - it:rfind(1) - -- 5 1 - it:rfind(1) - -- 1 1 -< - - Attributes: ~ - Since: 0.10.0 - - Parameters: ~ - • {f} (`any`) - - Return: ~ - (`any`) - - See also: ~ - • |Iter:find()| - -Iter:rpeek() *Iter:rpeek()* - Gets the last value of a |list-iterator| without consuming it. - - Example: >lua - local it = vim.iter({1, 2, 3, 4}) - it:rpeek() - -- 4 - it:rpeek() - -- 4 - it:pop() - -- 4 -< - - Attributes: ~ - Since: 0.10.0 - - Return: ~ - (`any`) - - See also: ~ - • |Iter:last()| - -Iter:rskip({n}) *Iter:rskip()* - Discards `n` values from the end of a |list-iterator| pipeline. - - Example: >lua - local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2) - it:next() - -- 1 - it:pop() - -- 3 -< - - Attributes: ~ - Since: 0.10.0 - - Parameters: ~ - • {n} (`number`) Number of values to skip. - - Return: ~ - (`Iter`) + Return (multiple): ~ + (`V1?`) + (`V...`) Iter:skip({n}) *Iter:skip()* Skips `n` values of an iterator pipeline, or skips values while a @@ -3503,26 +3392,14 @@ Iter:skip({n}) *Iter:skip()* Since: 0.10.0 Parameters: ~ - • {n} (`integer|fun(...):boolean`) Number of values to skip or a - predicate. + • {n} (`integer|fun(v: V1, ...: V...): boolean`) Number of values to + skip or a predicate. + + Overloads: ~ + • `fun(self: vim.IterArray, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray` Return: ~ - (`Iter`) - -Iter:slice({first}, {last}) *Iter:slice()* - Sets the start and end of a |list-iterator| pipeline. - - Equivalent to `:skip(first - 1):rskip(len - last + 1)`. - - Attributes: ~ - Since: 0.10.0 - - Parameters: ~ - • {first} (`number`) - • {last} (`number`) - - Return: ~ - (`Iter`) + (`vim.Iter`) Iter:take({n}) *Iter:take()* Transforms an iterator to yield only the first n values, or all values @@ -3549,11 +3426,14 @@ Iter:take({n}) *Iter:take()* Since: 0.10.0 Parameters: ~ - • {n} (`integer|fun(...):boolean`) Number of values to take or a - predicate. + • {n} (`integer|fun(v: V1, ...: V...): boolean`) Number of values to + take or a predicate. + + Overloads: ~ + • `fun(self: vim.IterArray, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray` Return: ~ - (`Iter`) + (`vim.Iter`) Iter:totable() *Iter:totable()* Collect the iterator into a table. @@ -3581,8 +3461,12 @@ Iter:totable() *Iter:totable()* Attributes: ~ Since: 0.10.0 + Overloads: ~ + • `fun(self: vim.Iter): T[]` + • `fun(self: vim.Iter): [V1, V2, V...][]` + Return: ~ - (`table`) + (`any[]`) Iter:unique({key}) *Iter:unique()* Removes duplicate values from an iterator pipeline. @@ -3615,15 +3499,165 @@ Iter:unique({key}) *Iter:unique()* Since: 0.12.0 Parameters: ~ - • {key} (`fun(...):any?`) Optional hash function to determine - uniqueness of values. + • {key} (`fun(v: V1, ...: V...): any?`) Optional hash function to + determine uniqueness of values. + + Overloads: ~ + • `fun(self: vim.IterArray, key: fun(v: V1, ...: V...): any?): vim.IterArray` Return: ~ - (`Iter`) + (`vim.Iter`) See also: ~ • |vim.list.unique()| +IterArray:flatten({depth}) *IterArray:flatten()* + Flattens a |list-iterator|, un-nesting nested values up to the given + {depth}. Errors if it attempts to flatten a dict-like value. + + Examples: >lua + vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable() + -- { 1, 2, { 3 } } + + vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable() + -- { 1, { a = 2 }, 3 } + + vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable() + -- error: attempt to flatten a dict-like table +< + + Attributes: ~ + Since: 0.10.0 + + Parameters: ~ + • {depth} (`integer?`) Depth to which |list-iterator| should be + flattened (defaults to 1) + + Return: ~ + (`vim.IterArray`) + +IterArray:pop() *IterArray:pop()* + "Pops" a value from a |list-iterator| (gets the last value and decrements + the tail). + + Example: >lua + local it = vim.iter({1, 2, 3, 4}) + it:pop() + -- 4 + it:pop() + -- 3 +< + + Attributes: ~ + Since: 0.10.0 + + Return (multiple): ~ + (`V1?`) + (`V...`) + +IterArray:rev() *IterArray:rev()* + Reverses a |list-iterator| pipeline. + + Example: >lua + + local it = vim.iter({ 3, 6, 9, 12 }):rev() + it:totable() + -- { 12, 9, 6, 3 } +< + + Attributes: ~ + Since: 0.10.0 + + Return: ~ + (`vim.IterArray`) + +IterArray:rfind({f}) *IterArray:rfind()* + Gets the first value satisfying a predicate, from the end of a + |list-iterator|. + + Advances the iterator. Returns nil and drains the iterator if no value is + found. + + Examples: >lua + + local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() + it:rfind(1) + -- 5 1 + it:rfind(1) + -- 1 1 +< + + Attributes: ~ + Since: 0.10.0 + + Parameters: ~ + • {f} (`V1|fun(v: V1, ...: V...): boolean`) + + Return (multiple): ~ + (`V1?`) + (`V...`) + + See also: ~ + • |Iter:find()| + +IterArray:rpeek() *IterArray:rpeek()* + Gets the last value of a |list-iterator| without consuming it. + + Example: >lua + local it = vim.iter({1, 2, 3, 4}) + it:rpeek() + -- 4 + it:rpeek() + -- 4 + it:pop() + -- 4 +< + + Attributes: ~ + Since: 0.10.0 + + Return (multiple): ~ + (`V1?`) + (`V...`) + + See also: ~ + • |Iter:last()| + +IterArray:rskip({n}) *IterArray:rskip()* + Discards `n` values from the end of a |list-iterator| pipeline. + + Example: >lua + local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2) + it:next() + -- 1 + it:pop() + -- 3 +< + + Attributes: ~ + Since: 0.10.0 + + Parameters: ~ + • {n} (`integer`) + + Return: ~ + (`vim.IterArray`) + +IterArray:slice({first}, {last}) *IterArray:slice()* + Sets the start and end of a |list-iterator| pipeline. + + Equivalent to `:skip(first - 1):rskip(len - last + 1)`. + + Attributes: ~ + Since: 0.10.0 + + Parameters: ~ + • {first} (`integer`) + • {last} (`integer`) + + Return: ~ + (`vim.IterArray`) + ============================================================================== Lua module: vim.json *vim.json* diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 6ffe21a702..04de486982 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -7,6 +7,59 @@ error('Cannot require a meta file') ---@type uv vim.uv = ... +--- LuaLS fallback surface for the richer iterator annotations in `vim.iter`. +--- EmmyLua reads the precise generics from `runtime/lua/vim/iter.lua`; LuaLS uses +--- these broader shapes for downstream type-checking. +--- @class vim.Iter +--- @field filter fun(self: vim.Iter, f: fun(...): boolean): vim.Iter +--- @field unique fun(self: vim.Iter, key?: fun(...): any): vim.Iter +--- @field flatten fun(self: vim.Iter, depth?: integer): vim.IterArray +--- @field map fun(self: vim.Iter, f: fun(...): ...): vim.Iter +--- @field each fun(self: vim.Iter, f: fun(...)): nil +--- @field totable fun(self: vim.Iter): table +--- @field join fun(self: vim.Iter, delim: string): string +--- @field fold fun(self: vim.Iter, init: any, f: fun(acc: any, ...): any): any +--- @field next fun(self: vim.Iter): any +--- @field rev fun(self: vim.Iter): vim.IterArray +--- @field peek fun(self: vim.Iter): any +--- @field find fun(self: vim.Iter, f: any): any +--- @field rfind fun(self: vim.Iter, f: any): any +--- @field take fun(self: vim.Iter, n: integer|fun(...): boolean): vim.Iter +--- @field pop fun(self: vim.Iter): any +--- @field rpeek fun(self: vim.Iter): any +--- @field skip fun(self: vim.Iter, n: integer|fun(...): boolean): vim.Iter +--- @field rskip fun(self: vim.Iter, n: integer): vim.IterArray +--- @field nth fun(self: vim.Iter, n: integer): any +--- @field slice fun(self: vim.Iter, first: integer, last: integer): vim.IterArray +--- @field any fun(self: vim.Iter, pred: fun(...): boolean): boolean +--- @field all fun(self: vim.Iter, pred: fun(...): boolean): boolean +--- @field last fun(self: vim.Iter): any +--- @field enumerate fun(self: vim.Iter): vim.Iter + +--- @class vim.IterArray : vim.Iter +--- @field filter fun(self: vim.IterArray, f: fun(...): boolean): vim.IterArray +--- @field unique fun(self: vim.IterArray, key?: fun(...): any): vim.IterArray +--- @field flatten fun(self: vim.IterArray, depth?: integer): vim.IterArray +--- @field map fun(self: vim.IterArray, f: fun(...): ...): vim.IterArray +--- @field totable fun(self: vim.IterArray): table +--- @field fold fun(self: vim.IterArray, init: any, f: fun(acc: any, ...): any): any +--- @field next fun(self: vim.IterArray): any +--- @field rev fun(self: vim.IterArray): vim.IterArray +--- @field peek fun(self: vim.IterArray): any +--- @field find fun(self: vim.IterArray, f: any): any +--- @field rfind fun(self: vim.IterArray, f: any): any +--- @field take fun(self: vim.IterArray, n: integer|fun(...): boolean): vim.IterArray +--- @field pop fun(self: vim.IterArray): any +--- @field rpeek fun(self: vim.IterArray): any +--- @field skip fun(self: vim.IterArray, n: integer|fun(...): boolean): vim.IterArray +--- @field rskip fun(self: vim.IterArray, n: integer): vim.IterArray +--- @field slice fun(self: vim.IterArray, first: integer, last: integer): vim.IterArray +--- @field last fun(self: vim.IterArray): any +--- @field enumerate fun(self: vim.IterArray): vim.IterArray + +--- @class vim.IterModule +--- @operator call: fun(src: any, ...): vim.Iter + --- The following modules are loaded specially in _init_packages.lua vim.F = require('vim.F') @@ -18,7 +71,11 @@ vim.func = require('vim.func') vim.glob = require('vim.glob') vim.health = require('vim.health') vim.hl = require('vim.hl') -vim.iter = require('vim.iter') +local iter = require('vim.iter') +-- `require('vim.iter')` carries the richer EmmyLua generic surface. Force +-- LuaLS onto the fallback module shape above so `make luals` stays clean. +---@cast iter vim.IterModule +vim.iter = iter vim.keymap = require('vim.keymap') vim.loader = require('vim.loader') vim.lsp = require('vim.lsp') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 1bcfcaffa0..c16de3433b 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -21,7 +21,7 @@ --- --- Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you --- can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of ---- |list-iterator| operations such as |Iter:rev()|). +--- |list-iterator| operations such as |IterArray:rev()|). --- --- Examples: --- @@ -64,36 +64,38 @@ --- -- { "a", "b" } --- ``` ---- LuaLS is bad at generics which this module mostly deals with ---- @diagnostic disable:no-unknown - ----@nodoc ----@class IterMod ----@operator call:Iter +-- LuaLS cannot model the variadic EmmyLua generics used by this module. +---@diagnostic disable: no-unknown, undefined-doc-name, luadoc-miss-symbol, missing-return, missing-return-value, param-type-mismatch, return-type-mismatch, redundant-return-value, undefined-field +--- @nodoc +--- @class vim.IterModule +--- @operator call: vim.Iter +--- @overload fun(src: T[]): vim.IterArray +--- @overload fun(src: table): vim.Iter +--- @overload fun(src: table, ...): vim.Iter +--- @overload fun(src: function, ...): vim.Iter local M = {} ----@nodoc ----@class Iter ----@field _peeked any ----@field _next fun():... The underlying function that returns the next value(s) from the source. +--- @nodoc +--- @class vim.Iter +--- @field private _peeked V1|[V1, V...]? Cached peek value, if any. Nil if no value is peeked. +--- @field private _next fun(): V1, V... The underlying function that returns the next packed value from the source. +--- @overload fun(self: vim.Iter): V1?, V... local Iter = {} Iter.__index = Iter Iter.__call = function(self) return self:next() end ---- Special case implementations for iterators on list tables. ----@nodoc ----@class ArrayIter : Iter ----@field _table table Underlying table data ----@field _head number Index to the front of a table iterator ----@field _tail number Index to the end of a table iterator (exclusive) -local ArrayIter = {} -ArrayIter.__index = setmetatable(ArrayIter, Iter) -ArrayIter.__call = function(self) - return self:next() -end +-- Special case implementations for iterators on list tables. +--- @nodoc +--- @class vim.IterArray : vim.Iter +--- @field private _table (V1|[V1, V...])[] Underlying table data. Items may be packed tuples after transforms. +--- @field private _head integer Index to the front of a table iterator +--- @field private _tail integer Index to the end of a table iterator (exclusive) +local IterArray = setmetatable({}, Iter) +IterArray.__index = IterArray +IterArray.__call = Iter.__call --- Packed tables use this as their metatable local packedmt = {} @@ -105,6 +107,9 @@ local function unpack(t) return t end +--- @generic T... +--- @param ... T... Values to pack +--- @return T|[T...] local function pack(...) local n = select('#', ...) if n > 1 then @@ -124,11 +129,11 @@ end --- Flattens a single array-like table. Errors if it attempts to flatten a --- dict-like table ----@param t table table which should be flattened ----@param max_depth number depth to which the table should be flattened ----@param depth number current iteration depth ----@param result table output table that contains flattened result ----@return table|nil flattened table if it can be flattened, otherwise nil +--- @param t table table which should be flattened +--- @param max_depth integer depth to which the table should be flattened +--- @param depth integer current iteration depth +--- @param result table output table that contains flattened result +--- @return table? flattened table if it can be flattened, otherwise nil local function flatten(t, max_depth, depth, result) if depth < max_depth and type(t) == 'table' then for k, v in pairs(t) do @@ -154,9 +159,9 @@ end --- and stop the current iterator stage. Otherwise, return true to signal that --- the current stage should continue. --- ----@param ... any Function arguments. ----@return boolean True if the iterator stage should continue, false otherwise ----@return any Function arguments. +--- @param ... any Function arguments. +--- @return boolean True if the iterator stage should continue, false otherwise +--- @return any Function arguments. local function continue(...) if select(1, ...) ~= nil then return false, ... @@ -169,10 +174,11 @@ end --- f. If that function returns no values, the current iterator stage continues. --- Otherwise, those values are returned. --- ----@param f function Function to call with the given arguments ----@param ... any Arguments to apply to f ----@return boolean True if the iterator pipeline should continue, false otherwise ----@return any Return values of f +--- @generic T, R +--- @param f fun(...:T...): R... Function to call with the given arguments +--- @param ... T... Arguments to apply to f +--- @return boolean : True if the iterator pipeline should continue, false otherwise +--- @return R... : Return values of f local function apply(f, ...) if select(1, ...) ~= nil then return continue(f(...)) @@ -188,10 +194,11 @@ end --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) --- ``` --- ----@param f fun(...):boolean Takes all values returned from the previous stage +--- @param f fun(v: V1, ...: V...): boolean Takes all values returned from the previous stage --- in the pipeline and returns false or nil if the --- current iterator element should be removed. ----@return Iter +--- @return vim.Iter +--- @overload fun(self: vim.IterArray, f: fun(v: V1, ...: V...): boolean): vim.IterArray function Iter:filter(f) return self:map(function(...) if f(...) then @@ -200,8 +207,10 @@ function Iter:filter(f) end) end ----@private -function ArrayIter:filter(f) +--- @nodoc +--- @param f fun(v: V1, ...: V...): boolean +--- @return vim.IterArray +function IterArray:filter(f) local inc = self._head < self._tail and 1 or -1 local n = self._head for i = self._head, self._tail - inc, inc do @@ -243,10 +252,11 @@ end --- -- { {id=1}, {id=2} } --- ``` --- ----@since 14 ----@param key? fun(...):any Optional hash function to determine uniqueness of values. ----@return Iter ----@see |vim.list.unique()| +--- @since 14 +--- @param key? fun(v: V1, ...: V...): any Optional hash function to determine uniqueness of values. +--- @return vim.Iter +--- @overload fun(self: vim.IterArray, key: fun(v: V1, ...: V...): any?): vim.IterArray +--- @see |vim.list.unique()| function Iter:unique(key) local seen = {} --- @type table @@ -267,6 +277,12 @@ function Iter:unique(key) end) end +--- @nodoc +--- @diagnostic disable-next-line:unused-local +function Iter:flatten(depth) + error('flatten() requires an array-like table') +end + --- Flattens a |list-iterator|, un-nesting nested values up to the given {depth}. --- Errors if it attempts to flatten a dict-like value. --- @@ -283,17 +299,11 @@ end --- -- error: attempt to flatten a dict-like table --- ``` --- ----@since 12 ----@param depth? number Depth to which |list-iterator| should be flattened +--- @since 12 +--- @param depth? integer Depth to which |list-iterator| should be flattened --- (defaults to 1) ----@return Iter ----@diagnostic disable-next-line:unused-local -function Iter:flatten(depth) -- luacheck: no unused args - error('flatten() requires an array-like table') -end - ----@private -function ArrayIter:flatten(depth) +--- @return vim.IterArray +function IterArray:flatten(depth) depth = depth or 1 local inc = self._head < self._tail and 1 or -1 local target = {} @@ -333,13 +343,15 @@ end --- -- { 6, 12 } --- ``` --- ----@since 12 ----@param f fun(...):...:any Mapping function. Takes all values returned from +--- @since 12 +--- @generic K2, V2... +--- @param f fun(v: V1, ...: V...): K2, V2... Mapping function. Takes all values returned from --- the previous stage in the pipeline as arguments --- and returns one or more new values, which are used --- in the next pipeline stage. Nil return values --- are filtered from the output. ----@return Iter +--- @return vim.Iter +--- @overload fun(self: vim.IterArray, f: fun(v: V1, ...: V...): K1, K...): vim.IterArray function Iter:map(f) -- Implementation note: the reader may be forgiven for observing that this -- function appears excessively convoluted. The problem to solve is that each @@ -364,12 +376,12 @@ function Iter:map(f) --- false, which indicates that the rest of the arguments should be returned --- as the values for the current iteration stage. --- - ---@param cont boolean If true, the current iterator stage should continue to + --- @param cont boolean If true, the current iterator stage should continue to --- pull values from its upstream pipeline stage. --- Otherwise, this stage is complete and returns the --- values passed. - ---@param ... any Values to return if cont is false. - ---@return any + --- @param ... any Values to return if cont is false. + --- @return any local function fn(cont, ...) if cont then return fn(apply(f, next(self))) @@ -377,14 +389,18 @@ function Iter:map(f) return ... end + --- @diagnostic disable-next-line: duplicate-set-field self.next = function() return fn(apply(f, next(self))) end return self end ----@private -function ArrayIter:map(f) +--- @nodoc +--- @generic K1, K... +--- @param f fun(v: V1, ...: V...): K1, K... +--- @return vim.IterArray +function IterArray:map(f) local inc = self._head < self._tail and 1 or -1 local n = self._head for i = self._head, self._tail - inc, inc do @@ -402,8 +418,8 @@ end --- --- For functions with side effects. To modify the values in the iterator, use |Iter:map()|. --- ----@since 12 ----@param f fun(...) Function to execute for each item in the pipeline. +--- @since 12 +--- @param f fun(v: V1, ...: V...) Function to execute for each item in the pipeline. --- Takes all of the values returned by the previous stage --- in the pipeline as arguments. function Iter:each(f) @@ -417,8 +433,9 @@ function Iter:each(f) end end ----@private -function ArrayIter:each(f) +--- @private +--- @param f fun(v: V1, ...: V...) +function IterArray:each(f) local inc = self._head < self._tail and 1 or -1 for i = self._head, self._tail - inc, inc do f(unpack(self._table[i])) @@ -450,8 +467,10 @@ end --- To create a map-like table with arbitrary keys, use |Iter:fold()|. --- --- ----@since 12 ----@return table +--- @since 12 +--- @overload fun(self: vim.Iter): T[] +--- @overload fun(self: vim.Iter): [V1, V2, V...][] +--- @return any[] function Iter:totable() local t = {} @@ -466,9 +485,12 @@ function Iter:totable() return t end ----@private -function ArrayIter:totable() - if self.next ~= ArrayIter.next or self._head >= self._tail then +--- @nodoc +--- @overload fun(self: vim.IterArray): T[] +--- @overload fun(self: vim.IterArray): [V1, V2, V...][] +--- @return any[] +function IterArray:totable() + if self.next ~= IterArray.next or self._head >= self._tail then return Iter.totable(self) end @@ -531,12 +553,12 @@ end --- end) --> { max = 42 } --- ``` --- ----@generic A --- ----@since 12 ----@param init A Initial value of the accumulator. ----@param f fun(acc:A, ...):A Accumulation function. ----@return A +--- @since 12 +--- @generic A +--- @param init A Initial value of the accumulator. +--- @param f fun(acc: A, v: V1, ...: V...): A Accumulation function. +--- @return A function Iter:fold(init, f) local acc = init @@ -553,8 +575,12 @@ function Iter:fold(init, f) return acc end ----@private -function ArrayIter:fold(init, f) +--- @nodoc +--- @generic A +--- @param init A +--- @param f fun(acc: A, v: V1, ...: V...): A +--- @return A +function IterArray:fold(init, f) local acc = init local inc = self._head < self._tail and 1 or -1 for i = self._head, self._tail - inc, inc do @@ -563,6 +589,7 @@ function ArrayIter:fold(init, f) return acc end +--- @diagnostic disable-next-line: duplicate-set-field --- Gets the next value from the iterator. --- --- Example: @@ -579,8 +606,8 @@ end --- --- ``` --- ----@since 12 ----@return any +--- @since 12 +--- @return V1?, V... function Iter:next() if self._peeked then local v = self._peeked @@ -592,8 +619,9 @@ function Iter:next() return self._next() end ----@private -function ArrayIter:next() +--- @package +--- @return V1?, V... +function IterArray:next() if self._head ~= self._tail then local v = self._table[self._head] local inc = self._head < self._tail and 1 or -1 @@ -602,6 +630,12 @@ function ArrayIter:next() end end +--- @nodoc +--- @diagnostic disable-next-line: unused-local +function Iter:rev() + error('rev() requires an array-like table') +end + --- Reverses a |list-iterator| pipeline. --- --- Example: @@ -614,14 +648,9 @@ end --- --- ``` --- ----@since 12 ----@return Iter -function Iter:rev() - error('rev() requires an array-like table') -end - ----@private -function ArrayIter:rev() +--- @since 12 +--- @return vim.IterArray +function IterArray:rev() local inc = self._head < self._tail and 1 or -1 self._head, self._tail = self._tail - inc, self._head - inc return self @@ -646,8 +675,8 @@ end --- --- ``` --- ----@since 12 ----@return any +--- @since 12 +--- @return V1?, V... function Iter:peek() if not self._peeked then self._peeked = pack(self:next()) @@ -656,10 +685,11 @@ function Iter:peek() return unpack(self._peeked) end ----@private -function ArrayIter:peek() +--- @private +--- @return V1?, V... +function IterArray:peek() if self._head ~= self._tail then - return self._table[self._head] + return unpack(self._table[self._head]) end end @@ -684,9 +714,10 @@ end --- -- 12 --- --- ``` ----@since 12 ----@param f any ----@return any +--- @since 12 +--- @param f (fun(v: V1, ...: V...): boolean)|any +--- @overload fun(self: vim.IterArray, f: V1|fun(v: V1, ...: V...): boolean): V1?, V... +--- @return V1?, V... function Iter:find(f) if type(f) ~= 'function' then local val = f @@ -713,6 +744,12 @@ function Iter:find(f) return unpack(result) end +--- @nodoc +--- @diagnostic disable-next-line:unused-local +function Iter:rfind(f) + error('rfind() requires an array-like table') +end + --- Gets the first value satisfying a predicate, from the end of a |list-iterator|. --- --- Advances the iterator. Returns nil and drains the iterator if no value is found. @@ -729,18 +766,12 @@ end --- --- ``` --- ----@see |Iter:find()| +--- @see |Iter:find()| --- ----@since 12 ----@param f any ----@return any ----@diagnostic disable-next-line: unused-local -function Iter:rfind(f) -- luacheck: no unused args - error('rfind() requires an array-like table') -end - ----@private -function ArrayIter:rfind(f) +--- @since 12 +--- @param f V1|fun(v: V1, ...: V...): boolean +--- @return V1?, V... +function IterArray:rfind(f) if type(f) ~= 'function' then local val = f f = function(v) @@ -781,21 +812,25 @@ end --- -- nil --- ``` --- ----@since 12 ----@param n integer|fun(...):boolean Number of values to take or a predicate. ----@return Iter +--- @since 12 +--- @param n integer|fun(v: V1, ...: V...): boolean Number of values to take or a predicate. +--- @return vim.Iter +--- @overload fun(self: vim.IterArray, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray function Iter:take(n) local i = 0 - local f = n - if type(n) ~= 'function' then - f = function() - return i < n + local pred --- @type fun(...): boolean + if type(n) == 'function' then + pred = n + else + local limit = n + pred = function() + return i < limit end end local stop = false local function fn(...) - if not stop and select(1, ...) ~= nil and f(...) then + if not stop and select(1, ...) ~= nil and pred(...) then i = i + 1 return ... else @@ -804,14 +839,17 @@ function Iter:take(n) end local next = self.next + --- @diagnostic disable-next-line:duplicate-set-field self.next = function() return fn(next(self)) end return self end ----@private -function ArrayIter:take(n) +--- @private +--- @param n integer|fun(v: V1, ...: V...): boolean +--- @return vim.IterArray +function IterArray:take(n) if type(n) == 'function' then local inc = self._head < self._tail and 1 or -1 for i = self._head, self._tail, inc do @@ -829,6 +867,12 @@ function ArrayIter:take(n) return self end +--- @nodoc +--- @diagnostic disable-next-line: unused-local +function Iter:pop() + error('pop() requires an array-like table') +end + --- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail). --- --- Example: @@ -841,21 +885,22 @@ end --- -- 3 --- ``` --- ----@since 12 ----@return any -function Iter:pop() - error('pop() requires an array-like table') -end - ---- @nodoc -function ArrayIter:pop() +--- @since 12 +--- @return V1?, V... +function IterArray:pop() if self._head ~= self._tail then local inc = self._head < self._tail and 1 or -1 self._tail = self._tail - inc - return self._table[self._tail] + return unpack(self._table[self._tail]) end end +--- @nodoc +--- @diagnostic disable-next-line: unused-local +function Iter:rpeek() + error('rpeek() requires an array-like table') +end + --- Gets the last value of a |list-iterator| without consuming it. --- --- Example: @@ -870,19 +915,14 @@ end --- -- 4 --- ``` --- ----@see |Iter:last()| +--- @see |Iter:last()| --- ----@since 12 ----@return any -function Iter:rpeek() - error('rpeek() requires an array-like table') -end - ----@nodoc -function ArrayIter:rpeek() +--- @since 12 +--- @return V1?, V... +function IterArray:rpeek() if self._head ~= self._tail then local inc = self._head < self._tail and 1 or -1 - return self._table[self._tail - inc] + return unpack(self._table[self._tail - inc]) end end @@ -906,9 +946,10 @@ end --- -- 12 --- ``` --- ----@since 12 ----@param n integer|fun(...):boolean Number of values to skip or a predicate. ----@return Iter +--- @since 12 +--- @param n integer|fun(v: V1, ...: V...): boolean Number of values to skip or a predicate. +--- @return vim.Iter +--- @overload fun(self: vim.IterArray, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray function Iter:skip(n) if type(n) == 'number' then for _ = 1, n do @@ -918,6 +959,7 @@ function Iter:skip(n) elseif type(n) == 'function' then local next = self.next + --- @diagnostic disable-next-line:duplicate-set-field self.next = function() while true do local peeked = self._peeked or pack(next(self)) @@ -938,8 +980,10 @@ function Iter:skip(n) return self end ----@private -function ArrayIter:skip(n) +--- @private +--- @param n integer|fun(v: V1, ...: V...): boolean +--- @return vim.IterArray +function IterArray:skip(n) if type(n) == 'function' then while self._head ~= self._tail do local v = self._table[self._head] @@ -960,6 +1004,12 @@ function ArrayIter:skip(n) return self end +--- @nodoc +--- @diagnostic disable-next-line:unused-local +function Iter:rskip(n) + error('rskip() requires an array-like table') +end + --- Discards `n` values from the end of a |list-iterator| pipeline. --- --- Example: @@ -972,18 +1022,12 @@ end --- -- 3 --- ``` --- ----@since 12 ----@param n number Number of values to skip. ----@return Iter ----@diagnostic disable-next-line: unused-local -function Iter:rskip(n) -- luacheck: no unused args - error('rskip() requires an array-like table') -end - ----@private -function ArrayIter:rskip(n) - local inc = self._head < self._tail and n or -n - self._tail = self._tail - inc +--- @since 12 +--- @param n integer +--- @return vim.IterArray +function IterArray:rskip(n) + local inc = self._head < self._tail and n or vim._assert_integer(-n) + self._tail = vim._assert_integer(self._tail - inc) if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then self._head = self._tail end @@ -1010,9 +1054,9 @@ end --- -- 3 --- ``` --- ----@since 12 ----@param n number Index of the value to return. May be negative if the source is a |list-iterator|. ----@return any +--- @since 12 +--- @param n integer Index of the value to return. May be negative if the source is a |list-iterator|. +--- @return V1?, V... function Iter:nth(n) if n > 0 then return self:skip(n - 1):next() @@ -1021,28 +1065,30 @@ function Iter:nth(n) end end +--- @nodoc +--- @diagnostic disable-next-line:unused-local +function Iter:slice(first, last) + error('slice() requires an array-like table') +end + --- Sets the start and end of a |list-iterator| pipeline. --- --- Equivalent to `:skip(first - 1):rskip(len - last + 1)`. --- ----@since 12 ----@param first number ----@param last number ----@return Iter ----@diagnostic disable-next-line: unused-local -function Iter:slice(first, last) -- luacheck: no unused args - error('slice() requires an array-like table') -end - ----@private -function ArrayIter:slice(first, last) - return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1)) +--- @since 12 +--- @param first integer +--- @param last integer +--- @return vim.IterArray +function IterArray:slice(first, last) + local left = vim._assert_integer(math.max(0, first - 1)) + local right = vim._assert_integer(math.max(0, self._tail - last - 1)) + return self:skip(left):rskip(right) end --- Returns true if any of the items in the iterator match the given predicate. --- ----@since 12 ----@param pred fun(...):boolean Predicate function. Takes all values returned from the previous +--- @since 12 +--- @param pred fun(v: V1, ...: V...): boolean Predicate function. Takes all values returned from the previous --- stage in the pipeline as arguments and returns true if the --- predicate matches. function Iter:any(pred) @@ -1066,8 +1112,8 @@ end --- Returns true if all items in the iterator match the given predicate. --- ----@since 12 ----@param pred fun(...):boolean Predicate function. Takes all values returned from the previous +--- @since 12 +--- @param pred fun(v: V1, ...: V...): boolean Predicate function. Takes all values returned from the previous --- stage in the pipeline as arguments and returns true if the --- predicate matches. function Iter:all(pred) @@ -1104,10 +1150,9 @@ end --- --- ``` --- ----@since 12 ----@see |Iter:rpeek()| ---- ----@return any +--- @since 12 +--- @see |IterArray:rpeek()| +--- @return V1?, V... function Iter:last() local last = self:next() local cur = self:next() @@ -1118,15 +1163,16 @@ function Iter:last() return last end ----@private -function ArrayIter:last() - if self._head >= self._tail then +--- @private +--- @return V1?, V... +function IterArray:last() + if self._head == self._tail then return nil end local inc = self._head < self._tail and 1 or -1 local v = self._table[self._tail - inc] self._head = self._tail - return v + return unpack(v) end --- Yields the item index (count) and value for each item of an iterator pipeline. @@ -1157,8 +1203,9 @@ end --- --- ``` --- ----@since 12 ----@return Iter +--- @since 12 +--- @overload fun(self: vim.IterArray): vim.IterArray +--- @return vim.Iter function Iter:enumerate() local i = 0 return self:map(function(...) @@ -1167,33 +1214,38 @@ function Iter:enumerate() end) end ----@private -function ArrayIter:enumerate() +--- @private +--- @return vim.IterArray +function IterArray:enumerate() local inc = self._head < self._tail and 1 or -1 + local n = 0 for i = self._head, self._tail - inc, inc do + n = n + 1 local v = self._table[i] - self._table[i] = pack(i, v) + self._table[i] = pack(n, unpack(v)) end return self end --- Creates a new Iter object from a table or other |iterable|. --- ----@param src table|function Table or iterator to drain values from ----@return Iter ----@private +--- @generic R1, R... +--- @param src table|fun(s: table, v: any): R1, R... Table or iterator to drain values from +--- @return vim.Iter +--- @overload fun(src: T[]): vim.IterArray +--- @overload fun(src: table): vim.Iter +--- @private function Iter.new(src, ...) local it = {} if type(src) == 'table' then local mt = getmetatable(src) if mt and type(mt.__call) == 'function' then - ---@private + --- @private it._next = function() - return src() + return mt.__call(src) end - setmetatable(it, Iter) - return it + return setmetatable(it, Iter) end local t = {} @@ -1205,51 +1257,48 @@ function Iter.new(src, ...) end t[#t + 1] = v -- Coerce to list-like table. end - return ArrayIter.new(t) - end - - if type(src) == 'function' then - local s, var = ... - - --- Use a closure to handle var args returned from iterator - local function fn(...) - -- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is - -- nil (even if there are other, non-nil return values). See |for-in|. - if select(1, ...) ~= nil then - var = select(1, ...) - return ... - end - end - - ---@private - it._next = function() - return fn(src(s, var)) - end - - setmetatable(it, Iter) - else + return IterArray.new(t) + elseif type(src) ~= 'function' then error('src must be a table or function') end - return it + + local s, var = ... + + --- Use a closure to handle var args returned from iterator + local function fn(...) + -- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is + -- nil (even if there are other, non-nil return values). See |for-in|. + if select(1, ...) ~= nil then + var = select(1, ...) + return ... + end + end + + --- @private + it._next = function() + return fn(src(s, var)) + end + + return setmetatable(it, Iter) end ---- Create a new ArrayIter +--- Create a new IterArray --- ----@param t table Array-like table. Caller guarantees that this table is a valid array. Can have ---- holes (nil values). ----@return Iter ----@private -function ArrayIter.new(t) - local it = {} - it._table = t - it._head = 1 - it._tail = #t + 1 - setmetatable(it, ArrayIter) - return it +--- @generic V1, V... +--- @param t (V1|[V1, V...])[] List-like table. Caller guarantees that this table is a valid +--- internal list buffer. +--- @return vim.IterArray +--- @private +function IterArray.new(t) + return setmetatable({ + _table = t, + _head = 1, + _tail = #t + 1, + }, IterArray) end return setmetatable(M, { __call = function(_, ...) return Iter.new(...) end, -}) --[[@as IterMod]] +}) diff --git a/src/gen/luacats_grammar.lua b/src/gen/luacats_grammar.lua index f013dae04d..151e8103d3 100644 --- a/src/gen/luacats_grammar.lua +++ b/src/gen/luacats_grammar.lua @@ -88,7 +88,9 @@ local v = setmetatable({}, { --- @class nvim.luacats.Class --- @field kind 'class' --- @field name string +--- @field generics? string[] --- @field parent? string +--- @field parent_generics? string[] --- @field access? 'private'|'protected'|'package' --- @class nvim.luacats.Field @@ -149,7 +151,7 @@ local typedef = P({ + (v.types * opt_postfix) + (P(ty_ident) * P('...')) -- Generic vararg + v.types, - types = v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + v.fun + ty_prims, + types = v.fun + v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + ty_prims, tuple = Pf('[') * comma1(v.type) * Plf(']'), dict = Pf('{') * comma1(Pf('[') * v.type * Pf(']') * colon * v.type) * Plf('}'), @@ -157,10 +159,18 @@ local typedef = P({ table_literal = Pf('{') * comma1(opt_ident * Pf(':') * v.type) * Plf('}'), fun_param = (opt_ident + ellipsis) * opt(colon * v.type), fun_ret = v.type + (ellipsis * opt(colon * v.type)), - fun = opt(Pf('async')) * Pf('fun') * paren(comma(v.fun_param)) * opt(Pf(':') * comma1(v.fun_ret)), + generic_decl_list = Pf('<') * comma1(ty_ident * opt(ellipsis) * opt(colon * v.type)) * Plf('>'), + fun = opt(Pf('async') + Pf('sync')) * Pf('fun') * opt(v.generic_decl_list) * paren( + comma(v.fun_param) + ) * opt(Pf(':') * comma1(v.fun_ret)), generics = P(ty_ident) * Pf('<') * comma1(v.type) * Plf('>'), }) / function(match) - return vim.trim(match):gsub('^%((.*)%)$', '%1'):gsub('%?+', '?') + return (vim.trim(match):gsub('^%((.*)%)$', '%1'):gsub('%?+', '?')) +end + +--- @param name string +local function generic_opt(name) + return (Pf('<') * Cg(Ct(comma1(typedef)), name) * Plf('>')) + -Plf('<') end local access = P('private') + P('protected') + P('package') @@ -170,7 +180,7 @@ local desc_delim = Sf '#:' + ws local desc = Cg(rep(any), 'desc') local opt_desc = opt(desc_delim * desc) local ty_name = Cg(ty_ident, 'name') -local opt_parent = opt(colon * Cg(ty_ident, 'parent')) +local opt_parent = opt(colon * Cg(ty_ident, 'parent') * generic_opt('parent_generics')) local lname = (ident + ellipsis) * opt(P('?')) -- stylua: ignore @@ -182,7 +192,7 @@ local grammar = P { + annot('type', comma1(Ct(v.ctype)) * opt_desc) + annot('cast', ty_name * ws * opt(Sf('+-')) * v.ctype) + annot('generic', ty_name * opt(colon * v.ctype)) - + annot('class', opt(paren(cattr)) * fill * ty_name * opt_parent) + + annot('class', opt(paren(cattr)) * fill * ty_name * generic_opt('generics') * opt_parent) + annot('field', opt(caccess * ws) * v.field_name * ws * v.ctype * opt_desc) + annot('operator', ty_name * opt(paren(Cg(v.ctype, 'argtype'))) * colon * v.ctype) + annot(access) diff --git a/src/gen/luacats_parser.lua b/src/gen/luacats_parser.lua index 796dcd0b93..8fabd1e6aa 100644 --- a/src/gen/luacats_parser.lua +++ b/src/gen/luacats_parser.lua @@ -143,7 +143,9 @@ local function process_doc_line(line, state) --- @cast parsed nvim.luacats.Class cur_obj.kind = 'class' cur_obj.name = parsed.name + cur_obj.generics = parsed.generics cur_obj.parent = parsed.parent + cur_obj.parent_generics = parsed.parent_generics cur_obj.access = parsed.access cur_obj.desc = state.doc_lines and table.concat(state.doc_lines, '\n') or nil state.doc_lines = nil diff --git a/test/functional/script/luacats_grammar_spec.lua b/test/functional/script/luacats_grammar_spec.lua index 6d1037b79c..467d85d3a6 100644 --- a/test/functional/script/luacats_grammar_spec.lua +++ b/test/functional/script/luacats_grammar_spec.lua @@ -178,6 +178,32 @@ describe('luacats grammar', function() parent = 'vim.diagnostic.GetOpts', }) + test('@class vim.Iter', { + kind = 'class', + name = 'vim.Iter', + generics = { 'V1', 'V...' }, + }) + + test('@class vim.IterArray : vim.Iter', { + kind = 'class', + name = 'vim.IterArray', + generics = { 'T' }, + parent = 'vim.Iter', + parent_generics = { 'T', 'never' }, + }) + + test('@class vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>', { + kind = 'class', + name = 'vim.lsp.Client.Progress', + parent = 'vim.Ringbuf', + parent_generics = { '{token: integer|string, value: any}' }, + }) + + test('@overload fun(self: vim.Iter): [V1, V2, V...][]', { + kind = 'overload', + type = 'fun(self: vim.Iter): [V1, V2, V...][]', + }) + test('@param opt? { cmd?: string[] } Options', { kind = 'param', name = 'opt?',