Repository: nvim-lua/plenary.nvim
Pull request #170 (2021-06-18 14:02:27)

Fixes for async2
```
- Allow wrapped functions to be called with less arguments then they
  were defined with.

- Reimplement void()
```

There were the changes require to get gitsigns working with the new lib.

lewis6991: @oberblastmeister can you take a look at this? I've probably misunderstood how the lib is supposed to be used but I am at least able to get gitsigns to work with this.
oberblastmeister: This pr also brought my attention to this, https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/async/async.lua#L79, which I think is wrong by me and should only be `== argc`.
oberblastmeister: Looks good, some tests would be nice
lewis6991: Sure, no problem. I just wanted an initial review to make sure this was on the right track.
oberblastmeister: could you also add a test that makes sure everything works when you pass in nil and when you ignore some arguments
oberblastmeister: Thanks for the fixes

Patch:
diff --git a/lua/plenary/async/async.lua b/lua/plenary/async/async.lua
index 490dfad..dbec0b8 100644
--- a/lua/plenary/async/async.lua
+++ b/lua/plenary/async/async.lua
@@ -19,9 +19,10 @@ local function callback_or_next(step, thread, callback, ...)
     callback(select(2, ...))
   else
     local returned_function = f.second(...)
+    local nargs = f.third(...)
     assert(type(returned_function) == "function", "type error :: expected func")
-    local stat, msg = pcall(returned_function, vararg.rotate(step, select(3, ...)))
-    if not stat then
+    local rstat, msg = pcall(returned_function, vararg.rotate(nargs, step, select(4, ...)))
+    if not rstat then
       error(('Failed to call leaf async function: %s'):format(msg))
     end
   end
@@ -30,7 +31,7 @@ end
 ---Executes a future with a callback when it is done
 ---@param async_function Future: the future to execute
 ---@param callback function: the callback to call when done
-local execute = function(async_function, callback)
+local execute = function(async_function, callback, ...)
   assert(type(async_function) == "function", "type error :: expected func")
 
   local thread = co.create(async_function)
@@ -40,7 +41,7 @@ local execute = function(async_function, callback)
     callback_or_next(step, thread, callback, co.resume(thread, ...))
   end
 
-  step()
+  step(...)
 end
 
 local add_leaf_function
@@ -50,14 +51,18 @@ do
     __mode = "k",
   })
 
-  add_leaf_function = function(async_func)
+  add_leaf_function = function(async_func, argc)
     assert(_PlenaryLeafTable[async_func] == nil, "Async function should not already be in the table")
-    _PlenaryLeafTable[async_func] = true
+    _PlenaryLeafTable[async_func] = argc
   end
 
   function M.is_leaf_function(async_func)
     return _PlenaryLeafTable[async_func] ~= nil
   end
+
+  function M.get_leaf_function_argc(async_func)
+    return _PlenaryLeafTable[async_func]
+  end
 end
 
 ---Creates an async function with a callback style function.
@@ -76,18 +81,14 @@ M.wrap = function(func, argc)
   local function leaf(...)
     local nargs = select('#', ...)
 
-    if not (nargs == argc - 1 or nargs == argc) then
-      print(('Expected %s or %s number of arguments, got %s'):format(argc - 1, argc, nargs))
-    end
-
     if nargs == argc then
       return func(...)
     else
-      return co.yield(func, ...)
+      return co.yield(func, argc, ...)
     end
   end
 
-  add_leaf_function(leaf)
+  add_leaf_function(leaf, argc)
 
   return leaf
 end
@@ -104,9 +105,14 @@ M.run = function(async_function, callback)
   end
 end
 
----this needs to be fixed
-M.void = function(async_fun)
-  return co.wrap(async_fun)
+---Use this to create a function which executes in an async context but
+---called from a non-async context. Inherently this cannot return anything
+---since it is non-blocking
+---@param func function
+M.void = function(func)
+  return function(...)
+    execute(func, nil, ...)
+  end
 end
 
 return M
diff --git a/lua/plenary/async/util.lua b/lua/plenary/async/util.lua
index 684e0c1..bbb8e5c 100644
--- a/lua/plenary/async/util.lua
+++ b/lua/plenary/async/util.lua
@@ -105,9 +105,10 @@ M.run_all = function(async_fns, callback)
 end
 
 function M.apcall(async_fn, ...)
-  if a.is_leaf_function(async_fn) then
+  local nargs = a.get_leaf_function_argc(async_fn)
+  if nargs then
     local tx, rx = channel.oneshot()
-    local stat, ret = pcall(async_fn, vararg.rotate(tx, ...))
+    local stat, ret = pcall(async_fn, vararg.rotate(nargs, tx, ...))
     if not stat then
       return stat, ret
     else
diff --git a/lua/plenary/vararg/rotate.lua b/lua/plenary/vararg/rotate.lua
index d840128..784dec6 100644
--- a/lua/plenary/vararg/rotate.lua
+++ b/lua/plenary/vararg/rotate.lua
@@ -66,10 +66,6 @@ local function rotate_n(first, ...)
     return tbl.unpack(args)
 end
 
-local function rotate(...)
-    local nargs = select('#', ...)
-
-    return (rotate_lookup[nargs] or rotate_n)(...)
-end
+local function rotate(nargs, ...) return (rotate_lookup[nargs] or rotate_n)(...) end
 
 return rotate
diff --git a/scripts/vararg/rotate.lua b/scripts/vararg/rotate.lua
index 43066a9..1e1719c 100644
--- a/scripts/vararg/rotate.lua
+++ b/scripts/vararg/rotate.lua
@@ -19,9 +19,7 @@ local function rotate_n(first, ...)
   return tbl.unpack(args)
 end
 
-local function rotate(...)
-  local nargs = select('#', ...)
-
+local function rotate(nargs, ...)
   return (rotate_lookup[nargs] or rotate_n)(...)
 end
 


Test patch:
diff --git a/tests/plenary/async/async_spec.lua b/tests/plenary/async/async_spec.lua
new file mode 100644
index 0000000..104083f
--- /dev/null
+++ b/tests/plenary/async/async_spec.lua
@@ -0,0 +1,54 @@
+require('plenary.async').tests.add_to_env()
+
+describe('async', function()
+  a.it('void functions can call wrapped functions', function()
+    local stat = 0
+    local saved_arg
+
+    local wrapped = a.wrap(function(inc, callback)
+      stat = stat + inc
+      callback()
+    end, 2)
+
+    local voided = a.void(function(arg)
+      wrapped(1)
+      wrapped(2)
+      wrapped(3)
+      stat = stat + 1
+      saved_arg = arg
+    end)
+
+    voided('hello')
+
+    assert(stat == 7)
+    assert(saved_arg == 'hello')
+  end)
+
+  a.it('void functions can call wrapped functions with ignored arguments', function()
+    local stat = 0
+    local saved_arg
+
+    local wrapped = a.wrap(function(inc, nil1, nil2, callback)
+      assert(type(inc) == 'number')
+      assert(nil1 == nil)
+      assert(nil2 == nil)
+      assert(type(callback) == 'function')
+      stat = stat + inc
+      callback()
+    end, 4)
+
+    local voided = a.void(function(arg)
+      wrapped(1)
+      wrapped(2, nil)
+      wrapped(3, nil, nil)
+      stat = stat + 1
+      saved_arg = arg
+    end)
+
+    voided('hello')
+
+    assert(stat == 7)
+    assert(saved_arg == 'hello')
+  end)
+
+end)