diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index fb92ad411e..08022c3ed6 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -3207,13 +3207,18 @@ receives as input the output values from the prior stage. The values used in the first stage of the pipeline depend on the type passed to this function: -• List tables pass only the value of each element -• Non-list (dict) tables pass both the key and value of each element +• List tables (arrays) pass only the value of each element +• Non-list tables (dictionaries) pass both the key and value of each + element • Function |iterator|s pass all of the values returned by their respective function • Tables with a metatable implementing |__call()| are treated as function iterators +The iterator pipeline terminates when the original table or function +iterator runs out of values (for function iterators, this means that the +first value returned by the function is nil). + Examples: >lua local it = vim.iter({ 1, 2, 3, 4, 5 }) @@ -3225,6 +3230,7 @@ Examples: >lua it:totable() -- { 9, 6, 3 } + -- ipairs() is a function iterator which returns both the index (i) and the value (v) vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) if i > 2 then return v end end):totable() diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index a278e96a5f..56c130dd0c 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -6,11 +6,14 @@ --- Each pipeline stage receives as input the output values from the prior stage. The values used in --- the first stage of the pipeline depend on the type passed to this function: --- ---- - List tables pass only the value of each element ---- - Non-list (dict) tables pass both the key and value of each element +--- - List tables (arrays) pass only the value of each element +--- - Non-list tables (dictionaries) pass both the key and value of each element --- - Function |iterator|s pass all of the values returned by their respective function --- - Tables with a metatable implementing |__call()| are treated as function iterators --- +--- The iterator pipeline terminates when the original table or function iterator runs out of values +--- (for function iterators, this means that the first value returned by the function is nil). +--- --- Examples: ---
lua
 ---   local it = vim.iter({ 1, 2, 3, 4, 5 })
@@ -22,6 +25,7 @@
 ---   it:totable()
 ---   -- { 9, 6, 3 }
 ---
+---   -- ipairs() is a function iterator which returns both the index (i) and the value (v)
 ---   vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
 ---     if i > 2 then return v end
 ---   end):totable()
@@ -111,7 +115,7 @@ end
 ---@return boolean True if the iterator stage should continue, false otherwise
 ---@return any Function arguments.
 local function continue(...)
-  if select('#', ...) > 0 then
+  if select(1, ...) ~= nil then
     return false, ...
   end
   return true
@@ -127,7 +131,7 @@ end
 ---@return boolean True if the iterator pipeline should continue, false otherwise
 ---@return any Return values of f
 local function apply(f, ...)
-  if select('#', ...) > 0 then
+  if select(1, ...) ~= nil then
     return continue(f(...))
   end
   return false
@@ -258,7 +262,7 @@ end
 ---                       in the pipeline as arguments.
 function Iter.each(self, f)
   local function fn(...)
-    if select('#', ...) > 0 then
+    if select(1, ...) ~= nil then
       f(...)
       return true
     end
@@ -740,6 +744,12 @@ end
 ---@param last number
 ---@return Iter
 function Iter.slice(self, first, last) -- luacheck: no unused args
+  error('slice() requires a list-like table')
+  return self
+end
+
+---@private
+function ListIter.slice(self, first, last)
   return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1))
 end
 
@@ -912,6 +922,8 @@ function Iter.new(src, ...)
 
     --- 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 ...
diff --git a/test/functional/lua/iter_spec.lua b/test/functional/lua/iter_spec.lua
index 3b603c9911..ffa28e7b11 100644
--- a/test/functional/lua/iter_spec.lua
+++ b/test/functional/lua/iter_spec.lua
@@ -154,6 +154,9 @@ describe('vim.iter', function()
     eq({1, 2}, vim.iter(t):slice(1, 2):totable())
     eq({10}, vim.iter(t):slice(10, 10):totable())
     eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
+
+    local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+    matches('slice%(%) requires a list%-like table', pcall_err(it.slice, it, 1, 3))
   end)
 
   it('nth()', function()
@@ -396,39 +399,4 @@ describe('vim.iter', function()
       { item_3 = 'test' },
     }, output)
   end)
-
-  it('handles nil values', function()
-    local t = {1, 2, 3, 4, 5}
-    do
-      local it = vim.iter(t):enumerate():map(function(i, v)
-        if i % 2 == 0 then
-          return nil, v*v
-        end
-        return v, nil
-      end)
-      eq({
-        { [1] = 1 },
-        { [2] = 4 },
-        { [1] = 3 },
-        { [2] = 16 },
-        { [1] = 5 },
-      }, it:totable())
-    end
-
-    do
-      local it = vim.iter(ipairs(t)):map(function(i, v)
-        if i % 2 == 0 then
-          return nil, v*v
-        end
-        return v, nil
-      end)
-      eq({
-        { [1] = 1 },
-        { [2] = 4 },
-        { [1] = 3 },
-        { [2] = 16 },
-        { [1] = 5 },
-      }, it:totable())
-    end
-  end)
 end)