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
This commit is contained in:
Lewis Russell
2026-04-20 15:22:32 +01:00
committed by Lewis Russell
parent 2b7a00746d
commit d7ef55e881
7 changed files with 602 additions and 424 deletions

View File

@@ -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<any, any...>
--- @overload fun<T>(src: T[]): vim.IterArray<T>
--- @overload fun<K, V>(src: table<K, V>): vim.Iter<K, V>
--- @overload fun(src: table, ...): vim.Iter<any, any...>
--- @overload fun(src: function, ...): vim.Iter<any, any...>
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<V1, V...>
--- @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...>): 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<V1, V...> : vim.Iter<V1, V...>
--- @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<V1, V...>
--- @overload fun<V1, V...>(self: vim.IterArray<V1, V...>, f: fun(v: V1, ...: V...): boolean): vim.IterArray<V1, V...>
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<V1, V...>
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<V1, V...>
--- @overload fun<V1, V...>(self: vim.IterArray<V1, V...>, key: fun(v: V1, ...: V...): any?): vim.IterArray<V1, V...>
--- @see |vim.list.unique()|
function Iter:unique(key)
local seen = {} --- @type table<any,boolean>
@@ -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<V1, V...>
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<K2, V2...>
--- @overload fun<V1, V..., K1, K...>(self: vim.IterArray<V1, V...>, f: fun(v: V1, ...: V...): K1, K...): vim.IterArray<K1, K...>
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<K1, K...>
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<T>(self: vim.Iter<T>): T[]
--- @overload fun<V1, V2, V...>(self: vim.Iter<V1, V2, V...>): [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<T>(self: vim.IterArray<T>): T[]
--- @overload fun<V1, V2, V...>(self: vim.IterArray<V1, V2, V...>): [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<V1, V...>
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<V1, V...>(self: vim.IterArray<V1, V...>, 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<V1, V...>
--- @overload fun<V1, V...>(self: vim.IterArray<V1, V...>, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray<V1, V...>
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<V1, V...>
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<V1, V...>
--- @overload fun<V1, V...>(self: vim.IterArray<V1, V...>, n: integer|fun(v: V1, ...: V...): boolean): vim.IterArray<V1, V...>
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<V1, V...>
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<V1, V...>
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<V1, V...>
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<V1, V...>(self: vim.IterArray<V1, V...>): vim.IterArray<integer, V1, V...>
--- @return vim.Iter<integer, V1, V...>
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<integer, V1, V...>
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<R1, R>|fun(s: table, v: any): R1, R... Table or iterator to drain values from
--- @return vim.Iter<R1, R...>
--- @overload fun<T>(src: T[]): vim.IterArray<T>
--- @overload fun<K, V>(src: table<K, V>): vim.Iter<K, V>
--- @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<V1, V...>
--- @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]]
})