diff --git a/src/gen/gen_api_dispatch.lua b/src/gen/gen_api_dispatch.lua index ec491b13c2..c78eb94176 100644 --- a/src/gen/gen_api_dispatch.lua +++ b/src/gen/gen_api_dispatch.lua @@ -708,6 +708,9 @@ output:write('\n') --- @type {binding: string, api:string}[] local lua_c_functions = {} +--- Functions which use kRetMultiStack. +local lua_retstack = { nvim_buf_call = true, nvim_win_call = true } + --- Generates C code to bridge RPC API <=> Lua. --- --- Inspect the result here: @@ -844,6 +847,9 @@ local function process_function(fn) end write_shifted_output(' ENTER_LUA_ACTIVE_STATE(lstate);\n') + if fn.has_lua_imp and not lua_retstack[fn.name] then + write_shifted_output(' const int pretop = lua_gettop(lstate); (void)pretop;\n') + end local free_at_exit_code = '' for i = 1, #free_code do local rev_i = #free_code - i + 1 @@ -882,8 +888,14 @@ exit_0: local ret_type = real_type(fn.return_type) local ret_mode = (ret_type == 'Object') and '&' or '' if fn.has_lua_imp then - -- it is up to the function to push return values - write_shifted_output(' (void)ret;') + -- Most has_lua_imp functions are expected to produce a single retval (e.g. + -- nvim_buf_get_lines, but not kRetMultiStack callers such as nvim_buf_call). #39851 + if not lua_retstack[fn.name] then + write_shifted_output( + ' assert((ERROR_SET(&err) || lua_gettop(lstate) == pretop + 1) && "has_lua_imp function must push exactly one return value");\n' + ) + end + write_shifted_output(' (void)ret;\n') elseif ret_type:match('^KeyDict_') then write_shifted_output(' nlua_push_keydict(lstate, &ret, %s_table);\n', return_type:sub(9)) else diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1ab51d7386..4e017d23be 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1808,6 +1808,9 @@ static int mode_ret(LuaRetMode mode) Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, Error *err) { + // Use active_lstate (set by ENTER_LUA_ACTIVE_STATE in gen_api_dispatch.lua) so callbacks run on + // the caller's Lua thread. This matters for coroutines: with global_lstate, return values pushed + // by kRetMultiStack would land on the main thread's stack, instead of coroutine stack. lua_State *const lstate = active_lstate; int top = lua_gettop(lstate); nlua_pushref(lstate, ref);