diff options
author | bfredl <bjorn.linse@gmail.com> | 2024-02-11 15:46:14 +0100 |
---|---|---|
committer | bfredl <bjorn.linse@gmail.com> | 2024-02-13 11:54:44 +0100 |
commit | 0353dd3029f9ce31c3894530385443a90f6677ee (patch) | |
tree | fa288427461ee2c1ce1c271d01a760977a161bf5 | |
parent | 89135cff030b06f60cd596a9ae81cd9583987517 (diff) | |
download | rneovim-0353dd3029f9ce31c3894530385443a90f6677ee.tar.gz rneovim-0353dd3029f9ce31c3894530385443a90f6677ee.tar.bz2 rneovim-0353dd3029f9ce31c3894530385443a90f6677ee.zip |
refactor(lua): use Arena when converting from lua stack to API args
and for return value of nlua_exec/nlua_call_ref, as this uses
the same family of functions.
NB: the handling of luaref:s is a bit of a mess.
add api_luarefs_free_XX functions as a stop-gap as refactoring
luarefs is a can of worms for another PR:s.
as a minor feature/bug-fix, nvim_buf_call and nvim_win_call now preserves
arbitrary return values.
33 files changed, 318 insertions, 230 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 95bcb31db9..85b3dffb4b 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2183,8 +2183,7 @@ nvim_buf_call({buffer}, {fun}) *nvim_buf_call()* only) Return: ~ - Return value of function. NB: will deepcopy Lua values currently, use - upvalues to send Lua references in and out. + Return value of function. nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()* Unmaps a buffer-local |mapping| for the given mode. @@ -2879,8 +2878,7 @@ nvim_win_call({window}, {fun}) *nvim_win_call()* only) Return: ~ - Return value of function. NB: will deepcopy Lua values currently, use - upvalues to send Lua references in and out. + Return value of function. See also: ~ • |win_execute()| diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 573a9f43b8..9891926632 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -422,6 +422,8 @@ The following changes to existing APIs or features add new behavior. • |--startuptime| reports the startup times for both processes (TUI + server) as separate sections. +• |nvim_buf_call()| and |nvim_win_call()| now preserves any return value (NB: not multiple return values) + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 78628c4154..4f67f682f1 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -431,7 +431,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc }); cb.type = kCallbackLua; - cb.data.luaref = api_new_luaref(callback->data.luaref); + cb.data.luaref = callback->data.luaref; + callback->data.luaref = LUA_NOREF; break; case kObjectTypeString: cb.type = kCallbackFuncref; diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 183483b329..993e290b2d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1227,8 +1227,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// @param fun Function to call inside the buffer (currently Lua callable /// only) /// @param[out] err Error details, if any -/// @return Return value of function. NB: will deepcopy Lua values -/// currently, use upvalues to send Lua references in and out. +/// @return Return value of function. Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY @@ -1242,7 +1241,7 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) aucmd_prepbuf(&aco, buf); Array args = ARRAY_DICT_INIT; - Object res = nlua_call_ref(fun, NULL, args, true, err); + Object res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); aucmd_restbuf(&aco); try_end(err); @@ -1419,7 +1418,7 @@ static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, } else { String str = STRING_INIT; if (len > 0) { - str = arena_string(arena, cbuf_as_string((char *)s, len)); + str = CBUF_TO_ARENA_STR(arena, s, len); if (replace_nl) { // Vim represents NULs as NLs, but this may confuse clients. strchrsub(str.data, '\n', '\0'); diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index bafc45e543..0ac3d42231 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -99,7 +99,7 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err) FUNC_API_SINCE(10) FUNC_API_FAST { - Dict(cmd) result = { 0 }; + Dict(cmd) result = KEYDICT_INIT; // Parse command line exarg_T ea; @@ -514,7 +514,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data); if (HAS_KEY(cmd, cmd, magic)) { - Dict(cmd_magic) magic[1] = { 0 }; + Dict(cmd_magic) magic[1] = KEYDICT_INIT; if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, err)) { goto end; } @@ -532,13 +532,13 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } if (HAS_KEY(cmd, cmd, mods)) { - Dict(cmd_mods) mods[1] = { 0 }; + Dict(cmd_mods) mods[1] = KEYDICT_INIT; if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) { goto end; } if (HAS_KEY(mods, cmd_mods, filter)) { - Dict(cmd_mods_filter) filter[1] = { 0 }; + Dict(cmd_mods_filter) filter[1] = KEYDICT_INIT; if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field, mods->filter, err)) { @@ -1103,7 +1103,8 @@ void create_user_command(uint64_t channel_id, String name, Object command, Dict( if (opts->complete.type == kObjectTypeLuaRef) { context = EXPAND_USER_LUA; - compl_luaref = api_new_luaref(opts->complete.data.luaref); + compl_luaref = opts->complete.data.luaref; + opts->complete.data.luaref = LUA_NOREF; } else if (opts->complete.type == kObjectTypeString) { VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data, (int)opts->complete.data.string.size, &context, &argt, @@ -1123,7 +1124,8 @@ void create_user_command(uint64_t channel_id, String name, Object command, Dict( }); argt |= EX_PREVIEW; - preview_luaref = api_new_luaref(opts->preview.data.luaref); + preview_luaref = opts->preview.data.luaref; + opts->preview.data.luaref = LUA_NOREF; } switch (command.type) { diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index dccaeb6922..c9a6036b8f 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -51,12 +51,12 @@ String nvim_command_output(uint64_t channel_id, String command, Error *err) /// @deprecated Use nvim_exec_lua() instead. /// @see nvim_exec_lua -Object nvim_execute_lua(String code, Array args, Error *err) +Object nvim_execute_lua(String code, Array args, Arena *arena, Error *err) FUNC_API_SINCE(3) FUNC_API_DEPRECATED_SINCE(7) FUNC_API_REMOTE_ONLY { - return nlua_exec(code, args, err); + return nlua_exec(code, args, kRetObject, arena, err); } /// Gets the buffer number diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8b45af7c71..7bf0d87603 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -576,7 +576,7 @@ String arena_string(Arena *arena, String str) if (str.size) { return cbuf_as_string(arena_memdupz(arena, str.data, str.size), str.size); } else { - return (String)STRING_INIT; + return (String){ .data = arena ? "" : xstrdup(""), .size = 0 }; } } @@ -1062,24 +1062,56 @@ Dictionary api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, return rv; } -void api_free_keydict(void *dict, KeySetLink *table) +void api_luarefs_free_object(Object value) +{ + // TODO(bfredl): this is more complicated than it needs to be. + // we should be able to lock down more specifically where luarefs can be + switch (value.type) { + case kObjectTypeLuaRef: + api_free_luaref(value.data.luaref); + break; + + case kObjectTypeArray: + api_luarefs_free_array(value.data.array); + break; + + case kObjectTypeDictionary: + api_luarefs_free_dict(value.data.dictionary); + break; + + default: + break; + } +} + +void api_luarefs_free_keydict(void *dict, KeySetLink *table) { for (size_t i = 0; table[i].str; i++) { char *mem = ((char *)dict + table[i].ptr_off); if (table[i].type == kObjectTypeNil) { - api_free_object(*(Object *)mem); - } else if (table[i].type == kObjectTypeString) { - api_free_string(*(String *)mem); - } else if (table[i].type == kObjectTypeArray) { - api_free_array(*(Array *)mem); - } else if (table[i].type == kObjectTypeDictionary) { - api_free_dictionary(*(Dictionary *)mem); + api_luarefs_free_object(*(Object *)mem); } else if (table[i].type == kObjectTypeLuaRef) { api_free_luaref(*(LuaRef *)mem); + } else if (table[i].type == kObjectTypeDictionary) { + api_luarefs_free_dict(*(Dictionary *)mem); } } } +void api_luarefs_free_array(Array value) +{ + for (size_t i = 0; i < value.size; i++) { + api_luarefs_free_object(value.items[i]); + } +} + +void api_luarefs_free_dict(Dictionary value) +{ + for (size_t i = 0; i < value.size; i++) { + api_luarefs_free_object(value.items[i].value); + } +} + /// Set a named mark /// buffer and mark name must be validated already /// @param buffer Buffer to set the mark on diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 9ee812f45c..11abb8f801 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -34,6 +34,7 @@ #define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s)) #define CSTR_TO_ARENA_STR(arena, s) arena_string(arena, cstr_as_string(s)) #define CSTR_TO_ARENA_OBJ(arena, s) STRING_OBJ(CSTR_TO_ARENA_STR(arena, s)) +#define CBUF_TO_ARENA_STR(arena, s, len) arena_string(arena, cbuf_as_string((char *)(s), len)) #define BUFFER_OBJ(s) ((Object) { \ .type = kObjectTypeBuffer, \ @@ -119,6 +120,8 @@ #define api_init_array = ARRAY_DICT_INIT #define api_init_dictionary = ARRAY_DICT_INIT +#define KEYDICT_INIT { 0 } + #define api_free_boolean(value) #define api_free_integer(value) #define api_free_float(value) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 78c2561bbd..769537ac98 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -496,11 +496,12 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool /// or executing the Lua code. /// /// @return Return value of Lua code if present or NIL. -Object nvim_exec_lua(String code, Array args, Error *err) +Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err) FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY { - return nlua_exec(code, args, err); + // TODO(bfredl): convert directly from msgpack to lua and then back again + return nlua_exec(code, args, kRetObject, arena, err); } /// Notify the user with a message @@ -512,7 +513,7 @@ Object nvim_exec_lua(String code, Array args, Error *err) /// @param log_level The log level /// @param opts Reserved for future use. /// @param[out] err Error details, if any -Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) +Object nvim_notify(String msg, Integer log_level, Dictionary opts, Arena *arena, Error *err) FUNC_API_SINCE(7) { MAXSIZE_TEMP_ARRAY(args, 3); @@ -520,7 +521,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) ADD_C(args, INTEGER_OBJ(log_level)); ADD_C(args, DICTIONARY_OBJ(opts)); - return NLUA_EXEC_STATIC("return vim.notify(...)", args, err); + return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); } /// Calculates the number of display cells occupied by `text`. @@ -603,7 +604,8 @@ String nvim__get_lib_dir(void) /// @param all whether to return all matches or only the first /// @param opts is_lua: only search Lua subdirs /// @return list of absolute paths to the found files -ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err) +ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Arena *arena, + Error *err) FUNC_API_SINCE(8) FUNC_API_FAST { @@ -613,7 +615,7 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E return (Array)ARRAY_DICT_INIT; } - ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all); + ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all, arena); if (opts->do_source) { for (size_t i = 0; i < res.size; i++) { @@ -1068,7 +1070,7 @@ static void term_write(const char *buf, size_t size, void *data) ADD_C(args, BUFFER_OBJ(terminal_buf(chan->term))); ADD_C(args, STRING_OBJ(((String){ .data = (char *)buf, .size = size }))); textlock++; - nlua_call_ref(cb, "input", args, false, NULL); + nlua_call_ref(cb, "input", args, kRetNilBool, NULL, NULL); textlock--; } @@ -1189,7 +1191,7 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// @return /// - true: Client may continue pasting. /// - false: Client must cancel the paste. -Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) +Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error *err) FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN { @@ -1199,19 +1201,18 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, { return false; }); - Array args = ARRAY_DICT_INIT; - Object rv = OBJECT_INIT; + Array lines = ARRAY_DICT_INIT; if (phase == -1 || phase == 1) { // Start of paste-stream. draining = false; } else if (draining) { // Skip remaining chunks. Report error only once per "stream". goto theend; } - Array lines = string_to_array(data, crlf); - ADD(args, ARRAY_OBJ(lines)); - ADD(args, INTEGER_OBJ(phase)); - rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, - err); + lines = string_to_array(data, crlf); + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, ARRAY_OBJ(lines)); + ADD_C(args, INTEGER_OBJ(phase)); + Object rv = NLUA_EXEC_STATIC("return vim.paste(...)", args, kRetNilBool, arena, err); if (ERROR_SET(err)) { draining = true; goto theend; @@ -1238,8 +1239,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) AppendCharToRedobuff(ESC); // Dot-repeat. } theend: - api_free_object(rv); - api_free_array(args); + api_free_array(lines); if (cancel || phase == -1 || phase == 3) { // End of paste-stream. draining = false; } @@ -1875,7 +1875,7 @@ Array nvim_list_uis(Arena *arena) /// Gets the immediate children of process `pid`. /// /// @return Array of child process ids, empty if process not found. -Array nvim_get_proc_children(Integer pid, Error *err) +Array nvim_get_proc_children(Integer pid, Arena *arena, Error *err) FUNC_API_SINCE(4) { Array rvobj = ARRAY_DICT_INIT; @@ -1892,7 +1892,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) DLOG("fallback to vim._os_proc_children()"); MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - Object o = NLUA_EXEC_STATIC("return vim._os_proc_children(...)", a, err); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_children(...)", a, kRetObject, arena, err); if (o.type == kObjectTypeArray) { rvobj = o.data.array; } else if (!ERROR_SET(err)) { @@ -1900,11 +1900,11 @@ Array nvim_get_proc_children(Integer pid, Error *err) "Failed to get process children. pid=%" PRId64 " error=%d", pid, rv); } - goto end; - } - - for (size_t i = 0; i < proc_count; i++) { - ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } else { + rvobj = arena_array(arena, proc_count); + for (size_t i = 0; i < proc_count; i++) { + ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } } end: @@ -1915,19 +1915,17 @@ end: /// Gets info describing process `pid`. /// /// @return Map of process properties, or NIL if process not found. -Object nvim_get_proc(Integer pid, Error *err) +Object nvim_get_proc(Integer pid, Arena *arena, Error *err) FUNC_API_SINCE(4) { - Object rvobj = OBJECT_INIT; - rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; - rvobj.type = kObjectTypeDictionary; + Object rvobj = NIL; VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, { return NIL; }); #ifdef MSWIN - rvobj.data.dictionary = os_proc_info((int)pid); + rvobj = DICTIONARY_OBJ(os_proc_info((int)pid)); if (rvobj.data.dictionary.size == 0) { // Process not found. return NIL; } @@ -1935,11 +1933,11 @@ Object nvim_get_proc(Integer pid, Error *err) // Cross-platform process info APIs are miserable, so use `ps` instead. MAXSIZE_TEMP_ARRAY(a, 1); ADD(a, INTEGER_OBJ(pid)); - Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, err); + Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, kRetObject, arena, err); if (o.type == kObjectTypeArray && o.data.array.size == 0) { return NIL; // Process not found. } else if (o.type == kObjectTypeDictionary) { - rvobj.data.dictionary = o.data.dictionary; + rvobj = o; } else if (!ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to get process info. pid=%" PRId64, pid); diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 52372b838e..e76db82c61 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -600,7 +600,7 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) /// Keep in sync with WinSplit in buffer_defs.h static const char *const win_split_str[] = { "left", "right", "above", "below" }; - Dict(win_config) rv = { 0 }; + Dict(win_config) rv = KEYDICT_INIT; win_T *wp = find_window_by_handle(window, err); if (!wp) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 4ac7e47832..93c9dfa049 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -421,8 +421,7 @@ void nvim_win_close(Window window, Boolean force, Error *err) /// @param fun Function to call inside the window (currently Lua callable /// only) /// @param[out] err Error details, if any -/// @return Return value of function. NB: will deepcopy Lua values -/// currently, use upvalues to send Lua references in and out. +/// @return Return value of function. Object nvim_win_call(Window window, LuaRef fun, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY @@ -438,7 +437,7 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) win_execute_T win_execute_args; if (win_execute_before(&win_execute_args, win, tabpage)) { Array args = ARRAY_DICT_INIT; - res = nlua_call_ref(fun, NULL, args, true, err); + res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); } win_execute_after(&win_execute_args); try_end(err); diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 1edc60f230..3b747e0920 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2002,15 +2002,15 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) { Callback callback = ac->exec.callable.cb; if (callback.type == kCallbackLua) { - Dictionary data = ARRAY_DICT_INIT; - PUT(data, "id", INTEGER_OBJ(ac->id)); - PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event))); - PUT(data, "match", CSTR_TO_OBJ(autocmd_match)); - PUT(data, "file", CSTR_TO_OBJ(autocmd_fname)); - PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); + MAXSIZE_TEMP_DICT(data, 7); + PUT_C(data, "id", INTEGER_OBJ(ac->id)); + PUT_C(data, "event", CSTR_AS_OBJ(event_nr2name(apc->event))); + PUT_C(data, "match", CSTR_AS_OBJ(autocmd_match)); + PUT_C(data, "file", CSTR_AS_OBJ(autocmd_fname)); + PUT_C(data, "buf", INTEGER_OBJ(autocmd_bufnr)); if (apc->data) { - PUT(data, "data", copy_object(*apc->data, NULL)); + PUT_C(data, "data", *apc->data); } int group = ac->pat->group; @@ -2023,21 +2023,15 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) // omit group in these cases break; default: - PUT(data, "group", INTEGER_OBJ(group)); + PUT_C(data, "group", INTEGER_OBJ(group)); break; } MAXSIZE_TEMP_ARRAY(args, 1); ADD_C(args, DICTIONARY_OBJ(data)); - Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL); - bool ret = false; - if (result.type == kObjectTypeBoolean) { - ret = result.data.boolean; - } - api_free_dictionary(data); - api_free_object(result); - return ret; + Object result = nlua_call_ref(callback.data.luaref, NULL, args, kRetNilBool, NULL, NULL); + return LUARET_TRUTHY(result); } else { typval_T argsin = TV_INITIAL_VALUE; typval_T rettv = TV_INITIAL_VALUE; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 1a02ac78d7..e725678937 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -179,7 +179,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload) ADD_C(args, BUFFER_OBJ(buf->handle)); TEXTLOCK_WRAP({ - nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL); + nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL, NULL); }); } @@ -295,10 +295,10 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, Object res; TEXTLOCK_WRAP({ - res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL); + res = nlua_call_ref(cb.on_lines, "lines", args, kRetNilBool, NULL, NULL); }); - if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + if (LUARET_TRUTHY(res)) { buffer_update_callbacks_free(cb); keep = false; } @@ -345,10 +345,10 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun Object res; TEXTLOCK_WRAP({ - res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL); + res = nlua_call_ref(cb.on_bytes, "bytes", args, kRetNilBool, NULL, NULL); }); - if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + if (LUARET_TRUTHY(res)) { buffer_update_callbacks_free(cb); keep = false; } @@ -381,10 +381,10 @@ void buf_updates_changedtick(buf_T *buf) Object res; TEXTLOCK_WRAP({ - res = nlua_call_ref(cb.on_changedtick, "changedtick", args, false, NULL); + res = nlua_call_ref(cb.on_changedtick, "changedtick", args, kRetNilBool, NULL, NULL); }); - if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + if (LUARET_TRUTHY(res)) { buffer_update_callbacks_free(cb); keep = false; } diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index c5f1bfeb87..f172646edf 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -2591,7 +2591,7 @@ static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) if (last_gen != get_cmdline_last_prompt_id() || last_gen == 0) { Array a = ARRAY_DICT_INIT; Error err = ERROR_INIT; - Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err); + Object res = NLUA_EXEC_STATIC("return vim.health._complete()", a, kRetObject, NULL, &err); api_clear_error(&err); api_free_object(names); names = res; diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index cd55219bf6..2417c14f7f 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -48,7 +48,7 @@ static bool decor_provider_invoke(int provider_idx, const char *name, LuaRef ref textlock++; provider_active = true; - Object ret = nlua_call_ref(ref, name, args, true, &err); + Object ret = nlua_call_ref(ref, name, args, kRetNilBool, NULL, &err); provider_active = false; textlock--; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3647bde952..a4da582dca 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6143,8 +6143,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co break; case kCallbackLua: - rv = nlua_call_ref(callback->data.luaref, NULL, args, false, NULL); - return (rv.type == kObjectTypeBoolean && rv.data.boolean == true); + rv = nlua_call_ref(callback->data.luaref, NULL, args, kRetNilBool, NULL, NULL); + return LUARET_TRUTHY(rv); case kCallbackNone: return false; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e4d2a219d9..726b9ce758 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3278,13 +3278,11 @@ static bool has_wsl(void) static TriState has_wsl = kNone; if (has_wsl == kNone) { Error err = ERROR_INIT; - Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.uv.os_uname()['release']:lower()" - ":match('microsoft') and true or false"), - (Array)ARRAY_DICT_INIT, &err); + Object o = NLUA_EXEC_STATIC("return vim.uv.os_uname()['release']:lower()" + ":match('microsoft')", + (Array)ARRAY_DICT_INIT, kRetNilBool, NULL, &err); assert(!ERROR_SET(&err)); - assert(o.type == kObjectTypeBoolean); - has_wsl = o.data.boolean ? kTrue : kFalse; - api_free_object(o); + has_wsl = LUARET_TRUTHY(o) ? kTrue : kFalse; } return has_wsl == kTrue; } @@ -6963,6 +6961,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ArenaMem res_mem = NULL; Object result = rpc_send_call(chan_id, method, args, &res_mem, &err); + api_free_array(args); if (l_provider_call_nesting) { current_sctx = save_current_sctx; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 12e746d49e..2913f6d4e9 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7445,7 +7445,7 @@ static void ex_checkhealth(exarg_T *eap) ADD_C(args, STRING_OBJ(((String){ .data = mods, .size = mods_len }))); ADD_C(args, CSTR_AS_OBJ(eap->arg)); - NLUA_EXEC_STATIC("return vim.health._check(...)", args, &err); + NLUA_EXEC_STATIC("vim.health._check(...)", args, kRetNilBool, NULL, &err); if (!ERROR_SET(&err)) { return; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 04fe43b712..c16ea29a01 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -333,9 +333,6 @@ KeySetLink *KeyDict_]] .. k.name .. [[_get_field(const char *str, size_t len) } ]]) - keysets_defs:write( - '#define api_free_keydict_' .. k.name .. '(x) api_free_keydict(x, ' .. k.name .. '_table)\n' - ) end local function real_type(type) @@ -687,6 +684,7 @@ local function process_function(fn) static int %s(lua_State *lstate) { Error err = ERROR_INIT; + Arena arena = ARENA_EMPTY; char *err_param = 0; if (lua_gettop(lstate) != %i) { api_set_error(&err, kErrorTypeValidation, "Expected %i argument%s"); @@ -736,18 +734,24 @@ local function process_function(fn) local param = fn.parameters[j] local cparam = string.format('arg%u', j) local param_type = real_type(param[1]) - local lc_param_type = real_type(param[1]):lower() local extra = param_type == 'Dictionary' and 'false, ' or '' - if param[1] == 'Object' or param[1] == 'DictionaryOf(LuaRef)' then + local arg_free_code = '' + if param[1] == 'Object' then + extra = 'true, ' + arg_free_code = 'api_luarefs_free_object(' .. cparam .. ');' + elseif param[1] == 'DictionaryOf(LuaRef)' then extra = 'true, ' + arg_free_code = 'api_luarefs_free_dict(' .. cparam .. ');' + elseif param[1] == 'LuaRef' then + arg_free_code = 'api_free_luaref(' .. cparam .. ');' end local errshift = 0 local seterr = '' if string.match(param_type, '^KeyDict_') then write_shifted_output( [[ - %s %s = { 0 }; - nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err); + %s %s = KEYDICT_INIT; + nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &arena, &err); ]], param_type, cparam, @@ -756,10 +760,15 @@ local function process_function(fn) ) cparam = '&' .. cparam errshift = 1 -- free incomplete dict on error + arg_free_code = 'api_luarefs_free_keydict(' + .. cparam + .. ', ' + .. string.sub(param_type, 9) + .. '_table);' else write_shifted_output( [[ - const %s %s = nlua_pop_%s(lstate, %s&err);]], + const %s %s = nlua_pop_%s(lstate, %s&arena, &err);]], param[1], cparam, param_type, @@ -776,7 +785,7 @@ local function process_function(fn) } ]], #fn.parameters - j + errshift) - free_code[#free_code + 1] = ('api_free_%s(%s);'):format(lc_param_type, cparam) + free_code[#free_code + 1] = arg_free_code cparams = cparam .. ', ' .. cparams end if fn.receives_channel_id then @@ -784,7 +793,6 @@ local function process_function(fn) end if fn.arena_return then cparams = cparams .. '&arena, ' - write_shifted_output(' Arena arena = ARENA_EMPTY;\n') end if fn.has_lua_imp then @@ -809,6 +817,7 @@ local function process_function(fn) local err_throw_code = [[ exit_0: + arena_mem_free(arena_finish(&arena)); if (ERROR_SET(&err)) { luaL_where(lstate, 1); if (err_param) { @@ -829,10 +838,8 @@ exit_0: else return_type = fn.return_type end - local free_retval - if fn.arena_return then - free_retval = ' arena_mem_free(arena_finish(&arena));' - else + local free_retval = '' + if not fn.arena_return then free_retval = ' api_free_' .. return_type:lower() .. '(ret);' end write_shifted_output(' %s ret = %s(%s);\n', fn.return_type, fn.name, cparams) @@ -858,6 +865,7 @@ exit_0: write_shifted_output(' nlua_push_%s(lstate, ret, %s);\n', return_type, tostring(special)) end + -- NOTE: we currently assume err_throw needs nothing from arena write_shifted_output( [[ diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index d3411850fd..a010687001 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3027,7 +3027,7 @@ bool map_execute_lua(bool may_repeat) Error err = ERROR_INIT; Array args = ARRAY_DICT_INIT; - nlua_call_ref(ref, NULL, args, false, &err); + nlua_call_ref(ref, NULL, args, kRetNilBool, NULL, &err); if (err.type != kErrorTypeNone) { semsg_multiline("E5108: %s", err.msg); api_clear_error(&err); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index e74ae817d0..5723ea1198 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -212,7 +212,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) Error err = ERROR_INIT; recursive++; - Object ret = nlua_call_ref(p->hl_def, "hl_def", args, true, &err); + Object ret = nlua_call_ref(p->hl_def, "hl_def", args, kRetObject, NULL, &err); recursive--; // TODO(bfredl): or "inherit", combine with global value? @@ -221,7 +221,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) HlAttrs attrs = HLATTRS_INIT; if (ret.type == kObjectTypeDictionary) { fallback = false; - Dict(highlight) dict = { 0 }; + Dict(highlight) dict = KEYDICT_INIT; if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, ret.data.dictionary, &err)) { attrs = dict2hlattrs(&dict, true, &it.link_id, &err); @@ -1086,7 +1086,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e // Handle cterm attrs if (dict->cterm.type == kObjectTypeDictionary) { - Dict(highlight_cterm) cterm[1] = { 0 }; + Dict(highlight_cterm) cterm[1] = KEYDICT_INIT; if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field, dict->cterm.data.dictionary, err)) { return hlattrs; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 3cf5b0e94f..423d2fa775 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -795,8 +795,8 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) /// Convert lua value to string /// /// Always pops one value from the stack. -String nlua_pop_String(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +String nlua_pop_String(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) != LUA_TSTRING) { lua_pop(lstate, 1); @@ -807,7 +807,10 @@ String nlua_pop_String(lua_State *lstate, Error *err) ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); assert(ret.data != NULL); - ret.data = xmemdupz(ret.data, ret.size); + // TODO(bfredl): it would be "nice" to just use the memory of the lua string + // directly, although ensuring the lifetime of such strings is a bit tricky + // (an API call could invoke nested lua, which triggers GC, and kaboom?) + ret.data = arena_memdupz(arena, ret.data, ret.size); lua_pop(lstate, 1); return ret; @@ -816,8 +819,8 @@ String nlua_pop_String(lua_State *lstate, Error *err) /// Convert lua value to integer /// /// Always pops one value from the stack. -Integer nlua_pop_Integer(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Integer nlua_pop_Integer(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) != LUA_TNUMBER) { lua_pop(lstate, 1); @@ -840,8 +843,8 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err) /// thus `err` is never set as any lua value can be co-erced into a lua bool /// /// Always pops one value from the stack. -Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Boolean nlua_pop_Boolean(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { const Boolean ret = lua_toboolean(lstate, -1); lua_pop(lstate, 1); @@ -915,7 +918,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons /// Convert lua table to float /// /// Always pops one value from the stack. -Float nlua_pop_Float(lua_State *lstate, Error *err) +Float nlua_pop_Float(lua_State *lstate, Arena *arena, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) == LUA_TNUMBER) { @@ -939,29 +942,29 @@ Float nlua_pop_Float(lua_State *lstate, Error *err) /// @param[in] table_props nlua_traverse_table() output. /// @param[out] err Location where error will be saved. static Array nlua_pop_Array_unchecked(lua_State *const lstate, const LuaTableProps table_props, - Error *const err) + Arena *arena, Error *const err) { - Array ret = { .size = table_props.maxidx, .items = NULL }; + Array ret = arena_array(arena, table_props.maxidx); - if (ret.size == 0) { + if (table_props.maxidx == 0) { lua_pop(lstate, 1); return ret; } - ret.items = xcalloc(ret.size, sizeof(*ret.items)); - for (size_t i = 1; i <= ret.size; i++) { + for (size_t i = 1; i <= table_props.maxidx; i++) { Object val; lua_rawgeti(lstate, -1, (int)i); - val = nlua_pop_Object(lstate, false, err); + val = nlua_pop_Object(lstate, false, arena, err); if (ERROR_SET(err)) { - ret.size = i - 1; lua_pop(lstate, 1); - api_free_array(ret); + if (!arena) { + api_free_array(ret); + } return (Array) { .size = 0, .items = NULL }; } - ret.items[i - 1] = val; + ADD_C(ret, val); } lua_pop(lstate, 1); @@ -971,15 +974,14 @@ static Array nlua_pop_Array_unchecked(lua_State *const lstate, const LuaTablePro /// Convert lua table to array /// /// Always pops one value from the stack. -Array nlua_pop_Array(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Array nlua_pop_Array(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { - const LuaTableProps table_props = nlua_check_type(lstate, err, - kObjectTypeArray); + const LuaTableProps table_props = nlua_check_type(lstate, err, kObjectTypeArray); if (table_props.type != kObjectTypeArray) { return (Array) { .size = 0, .items = NULL }; } - return nlua_pop_Array_unchecked(lstate, table_props, err); + return nlua_pop_Array_unchecked(lstate, table_props, arena, err); } /// Convert lua table to dictionary @@ -991,30 +993,30 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) /// @param[in] table_props nlua_traverse_table() output. /// @param[out] err Location where error will be saved. static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTableProps table_props, - bool ref, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + bool ref, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 5) FUNC_ATTR_WARN_UNUSED_RESULT { - Dictionary ret = { .size = table_props.string_keys_num, .items = NULL }; + Dictionary ret = arena_dict(arena, table_props.string_keys_num); - if (ret.size == 0) { + if (table_props.string_keys_num == 0) { lua_pop(lstate, 1); return ret; } - ret.items = xcalloc(ret.size, sizeof(*ret.items)); lua_pushnil(lstate); - for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) { + for (size_t i = 0; lua_next(lstate, -2) && i < table_props.string_keys_num;) { // stack: dict, key, value if (lua_type(lstate, -2) == LUA_TSTRING) { lua_pushvalue(lstate, -2); // stack: dict, key, value, key - ret.items[i].key = nlua_pop_String(lstate, err); + String key = nlua_pop_String(lstate, arena, err); // stack: dict, key, value if (!ERROR_SET(err)) { - ret.items[i].value = nlua_pop_Object(lstate, ref, err); + Object value = nlua_pop_Object(lstate, ref, arena, err); + kv_push_c(ret, ((KeyValuePair) { .key = key, .value = value })); // stack: dict, key } else { lua_pop(lstate, 1); @@ -1022,8 +1024,9 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl } if (ERROR_SET(err)) { - ret.size = i; - api_free_dictionary(ret); + if (!arena) { + api_free_dictionary(ret); + } lua_pop(lstate, 2); // stack: return (Dictionary) { .size = 0, .items = NULL }; @@ -1042,8 +1045,8 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl /// Convert lua table to dictionary /// /// Always pops one value from the stack. -Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 4) FUNC_ATTR_WARN_UNUSED_RESULT { const LuaTableProps table_props = nlua_check_type(lstate, err, kObjectTypeDictionary); @@ -1052,7 +1055,7 @@ Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Error *err) return (Dictionary) { .size = 0, .items = NULL }; } - return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, err); + return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, arena, err); } /// Helper structure for nlua_pop_Object @@ -1064,7 +1067,8 @@ typedef struct { /// Convert lua table to object /// /// Always pops one value from the stack. -Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) +Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *const err) + FUNC_ATTR_NONNULL_ARG(1, 4) FUNC_ATTR_WARN_UNUSED_RESULT { Object ret = NIL; const int initial_size = lua_gettop(lstate); @@ -1099,10 +1103,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) size_t len; const char *s = lua_tolstring(lstate, -2, &len); const size_t idx = cur.obj->data.dictionary.size++; - cur.obj->data.dictionary.items[idx].key = (String) { - .data = xmemdupz(s, len), - .size = len, - }; + cur.obj->data.dictionary.items[idx].key = CBUF_TO_ARENA_STR(arena, s, len); kvi_push(stack, cur); cur = (ObjPopStackItem){ .obj = &cur.obj->data.dictionary.items[idx].value }; } else { @@ -1133,7 +1134,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(lstate, -1, &len); - *cur.obj = STRING_OBJ(((String) { .data = xmemdupz(s, len), .size = len })); + *cur.obj = STRING_OBJ(CBUF_TO_ARENA_STR(arena, s, len)); break; } case LUA_TNUMBER: { @@ -1151,23 +1152,17 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) switch (table_props.type) { case kObjectTypeArray: - *cur.obj = ARRAY_OBJ(((Array) { .items = NULL, .size = 0, .capacity = 0 })); + *cur.obj = ARRAY_OBJ(((Array)ARRAY_DICT_INIT)); if (table_props.maxidx != 0) { - cur.obj->data.array.items = - xcalloc(table_props.maxidx, - sizeof(cur.obj->data.array.items[0])); - cur.obj->data.array.capacity = table_props.maxidx; + cur.obj->data.array = arena_array(arena, table_props.maxidx); cur.container = true; kvi_push(stack, cur); } break; case kObjectTypeDictionary: - *cur.obj = DICTIONARY_OBJ(((Dictionary) { .items = NULL, .size = 0, .capacity = 0 })); + *cur.obj = DICTIONARY_OBJ(((Dictionary)ARRAY_DICT_INIT)); if (table_props.string_keys_num != 0) { - cur.obj->data.dictionary.items = - xcalloc(table_props.string_keys_num, - sizeof(cur.obj->data.dictionary.items[0])); - cur.obj->data.dictionary.capacity = table_props.string_keys_num; + cur.obj->data.dictionary = arena_dict(arena, table_props.string_keys_num); cur.container = true; kvi_push(stack, cur); lua_pushnil(lstate); @@ -1219,7 +1214,9 @@ type_error: } kvi_destroy(stack); if (ERROR_SET(err)) { - api_free_object(ret); + if (!arena) { + api_free_object(ret); + } ret = NIL; lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); } @@ -1227,14 +1224,14 @@ type_error: return ret; } -LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) +LuaRef nlua_pop_LuaRef(lua_State *const lstate, Arena *arena, Error *err) { LuaRef rv = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return rv; } -handle_T nlua_pop_handle(lua_State *lstate, Error *err) +handle_T nlua_pop_handle(lua_State *lstate, Arena *arena, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { handle_T ret; @@ -1296,7 +1293,8 @@ void nlua_init_types(lua_State *const lstate) } // lua specific variant of api_dict_to_keydict -void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Error *err) +void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Arena *arena, + Error *err) { if (!lua_istable(L, -1)) { api_set_error(err, kErrorTypeValidation, "Expected Lua table"); @@ -1323,7 +1321,7 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_ char *mem = ((char *)retval + field->ptr_off); if (field->type == kObjectTypeNil) { - *(Object *)mem = nlua_pop_Object(L, true, err); + *(Object *)mem = nlua_pop_Object(L, true, arena, err); } else if (field->type == kObjectTypeInteger) { if (field->is_hlgroup && lua_type(L, -1) == LUA_TSTRING) { size_t name_len; @@ -1331,23 +1329,23 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_ lua_pop(L, 1); *(Integer *)mem = name_len > 0 ? syn_check_group(name, name_len) : 0; } else { - *(Integer *)mem = nlua_pop_Integer(L, err); + *(Integer *)mem = nlua_pop_Integer(L, arena, err); } } else if (field->type == kObjectTypeBoolean) { *(Boolean *)mem = nlua_pop_Boolean_strict(L, err); } else if (field->type == kObjectTypeString) { - *(String *)mem = nlua_pop_String(L, err); + *(String *)mem = nlua_pop_String(L, arena, err); } else if (field->type == kObjectTypeFloat) { - *(Float *)mem = nlua_pop_Float(L, err); + *(Float *)mem = nlua_pop_Float(L, arena, err); } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow || field->type == kObjectTypeTabpage) { - *(handle_T *)mem = nlua_pop_handle(L, err); + *(handle_T *)mem = nlua_pop_handle(L, arena, err); } else if (field->type == kObjectTypeArray) { - *(Array *)mem = nlua_pop_Array(L, err); + *(Array *)mem = nlua_pop_Array(L, arena, err); } else if (field->type == kObjectTypeDictionary) { - *(Dictionary *)mem = nlua_pop_Dictionary(L, false, err); + *(Dictionary *)mem = nlua_pop_Dictionary(L, false, arena, err); } else if (field->type == kObjectTypeLuaRef) { - *(LuaRef *)mem = nlua_pop_LuaRef(L, err); + *(LuaRef *)mem = nlua_pop_LuaRef(L, arena, err); } else { abort(); } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 85d614fe0d..be55bde202 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -311,7 +311,10 @@ static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) lua_pop(lstate, 1); Error err = ERROR_INIT; - const Array pat = nlua_pop_Array(lstate, &err); + // TODO(bfredl): we could use an arena here for both "pat" and "ret", but then + // we need a path to not use the freelist but a private block local to the thread. + // We do not want mutex contentionery for the main arena freelist. + const Array pat = nlua_pop_Array(lstate, NULL, &err); if (ERROR_SET(&err)) { luaL_where(lstate, 1); lua_pushstring(lstate, err.msg); @@ -1242,13 +1245,13 @@ static int nlua_rpc(lua_State *lstate, bool request) const char *name = luaL_checklstring(lstate, 2, &name_len); int nargs = lua_gettop(lstate) - 2; Error err = ERROR_INIT; - Array args = ARRAY_DICT_INIT; + Arena arena = ARENA_EMPTY; + Array args = arena_array(&arena, (size_t)nargs); for (int i = 0; i < nargs; i++) { lua_pushvalue(lstate, i + 3); - ADD(args, nlua_pop_Object(lstate, false, &err)); + ADD(args, nlua_pop_Object(lstate, false, &arena, &err)); if (ERROR_SET(&err)) { - api_free_array(args); goto check_err; } } @@ -1265,10 +1268,11 @@ static int nlua_rpc(lua_State *lstate, bool request) api_set_error(&err, kErrorTypeValidation, "Invalid channel: %" PRIu64, chan_id); } - api_free_array(args); // TODO(bfredl): no } check_err: + arena_mem_free(arena_finish(&arena)); + if (ERROR_SET(&err)) { lua_pushstring(lstate, err.msg); api_clear_error(&err); @@ -1541,10 +1545,12 @@ int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typ /// /// @param[in] str String to execute. /// @param[in] args array of ... args +/// @param[in] mode Whether and how the the return value should be converted to Object +/// @param[in] arena can be NULL, then nested allocations are used /// @param[out] err Location where error will be saved. /// /// @return Return value of the execution. -Object nlua_exec(const String str, const Array args, Error *err) +Object nlua_exec(const String str, const Array args, LuaRetMode mode, Arena *arena, Error *err) { lua_State *const lstate = global_lstate; @@ -1568,7 +1574,7 @@ Object nlua_exec(const String str, const Array args, Error *err) return NIL; } - return nlua_pop_Object(lstate, false, err); + return nlua_call_pop_retval(lstate, mode, arena, err); } bool nlua_ref_is_function(LuaRef ref) @@ -1589,12 +1595,12 @@ bool nlua_ref_is_function(LuaRef ref) /// @param ref the reference to call (not consumed) /// @param name if non-NULL, sent to callback as first arg /// if NULL, only args are used -/// @param retval if true, convert return value to Object -/// if false, only check if return value is truthy +/// @param mode Whether and how the the return value should be converted to Object +/// @param arena can be NULL, then nested allocations are used /// @param err Error details, if any (if NULL, errors are echoed) -/// @return Return value of function, if retval was set. Otherwise -/// BOOLEAN_OBJ(true) or NIL. -Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err) +/// @return Return value of function, as per mode +Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, + Error *err) { lua_State *const lstate = global_lstate; nlua_pushref(lstate, ref); @@ -1620,18 +1626,34 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro return NIL; } - if (retval) { - Error dummy = ERROR_INIT; - if (err == NULL) { - err = &dummy; - } - return nlua_pop_Object(lstate, false, err); - } else { - bool value = lua_toboolean(lstate, -1); + return nlua_call_pop_retval(lstate, mode, arena, err); +} + +static Object nlua_call_pop_retval(lua_State *lstate, LuaRetMode mode, Arena *arena, Error *err) +{ + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 1); + return NIL; + } + Error dummy = ERROR_INIT; + + switch (mode) { + case kRetNilBool: { + bool bool_value = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + return BOOLEAN_OBJ(bool_value); + } + case kRetLuaref: { + LuaRef ref = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); - return value ? BOOLEAN_OBJ(true) : NIL; + return LUAREF_OBJ(ref); + } + case kRetObject: + return nlua_pop_Object(lstate, false, arena, err ? err : &dummy); } + UNREACHABLE; } /// check if the current execution context is safe for calling deferred API @@ -1930,13 +1952,14 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) *num_results = 0; *results = NULL; - int prefix_len = (int)nlua_pop_Integer(lstate, &err); + Arena arena = ARENA_EMPTY; + int prefix_len = (int)nlua_pop_Integer(lstate, &arena, &err); if (ERROR_SET(&err)) { ret = FAIL; goto cleanup; } - Array completions = nlua_pop_Array(lstate, &err); + Array completions = nlua_pop_Array(lstate, &arena, &err); if (ERROR_SET(&err)) { ret = FAIL; goto cleanup_array; @@ -1960,7 +1983,7 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) *num_results = result_array.ga_len; cleanup_array: - api_free_array(completions); + arena_mem_free(arena_finish(&arena)); cleanup: @@ -2354,13 +2377,10 @@ bool nlua_func_exists(const char *lua_funcname) vim_snprintf(str, length, "return %s", lua_funcname); ADD_C(args, CSTR_AS_OBJ(str)); Error err = ERROR_INIT; - Object result = NLUA_EXEC_STATIC("return type(loadstring(...)()) == 'function'", args, &err); + Object result = NLUA_EXEC_STATIC("return type(loadstring(...)()) == 'function'", args, + kRetNilBool, NULL, &err); xfree(str); api_clear_error(&err); - if (result.type != kObjectTypeBoolean) { - api_free_object(result); - return false; - } - return result.data.boolean; + return LUARET_TRUTHY(result); } diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 0b4623cbd3..ebcd62122f 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -24,7 +24,8 @@ typedef struct { #endif } nlua_ref_state_t; -#define NLUA_EXEC_STATIC(cstr, arg, err) nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, err) +#define NLUA_EXEC_STATIC(cstr, arg, mode, arena, err) \ + nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, mode, arena, err) #define NLUA_CLEAR_REF(x) \ do { \ @@ -35,6 +36,16 @@ typedef struct { } \ } while (0) +typedef enum { + kRetObject, ///< any object, but doesn't preserve nested luarefs + kRetNilBool, ///< NIL preserved as such, other values return their booleanness + ///< Should also be used when return value is ignored, as it is allocation-free + kRetLuaref, ///< return value becomes a single Luaref, regardless of type (except NIL) +} LuaRetMode; + +/// To use with kRetNilBool for quick thuthyness check +#define LUARET_TRUTHY(res) ((res).type == kObjectTypeBoolean && (res).data.boolean == true) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index 5285c1187d..f5a5949ab6 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -209,7 +209,8 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char * static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params, int64_t *linematch, Error *err) { - const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); + // TODO: this is very much a keydict.. + const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, NULL, err); NluaXdiffMode mode = kNluaXdiffModeUnified; diff --git a/src/nvim/main.c b/src/nvim/main.c index f2893dc9e3..d712a45b81 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -940,20 +940,20 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, } Array args = ARRAY_DICT_INIT; + kv_resize(args, (size_t)(argc - remote_args)); for (int t_argc = remote_args; t_argc < argc; t_argc++) { - String arg_s = cstr_to_string(argv[t_argc]); - ADD(args, STRING_OBJ(arg_s)); + ADD_C(args, CSTR_AS_OBJ(argv[t_argc])); } Error err = ERROR_INIT; - Array a = ARRAY_DICT_INIT; + MAXSIZE_TEMP_ARRAY(a, 4); ADD(a, INTEGER_OBJ((int)chan)); - ADD(a, CSTR_TO_OBJ(server_addr)); - ADD(a, CSTR_TO_OBJ(connect_error)); + ADD(a, CSTR_AS_OBJ(server_addr)); + ADD(a, CSTR_AS_OBJ(connect_error)); ADD(a, ARRAY_OBJ(args)); String s = STATIC_CSTR_AS_STRING("return vim._cs_remote(...)"); - Object o = nlua_exec(s, a, &err); - api_free_array(a); + Object o = nlua_exec(s, a, kRetObject, NULL, &err); + kv_destroy(args); if (ERROR_SET(&err)) { fprintf(stderr, "%s\n", err.msg); os_exit(2); @@ -2085,7 +2085,7 @@ static void do_exrc_initialization(void) str = nlua_read_secure(VIMRC_LUA_FILE); if (str != NULL) { Error err = ERROR_INIT; - nlua_exec(cstr_as_string(str), (Array)ARRAY_DICT_INIT, &err); + nlua_exec(cstr_as_string(str), (Array)ARRAY_DICT_INIT, kRetNilBool, NULL, &err); xfree(str); if (ERROR_SET(&err)) { semsg("Error detected while processing %s:", VIMRC_LUA_FILE); diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index cb7b8aab33..59022e8be6 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -1644,7 +1644,7 @@ char *eval_map_expr(mapblock_T *mp, int c) if (mp->m_luaref != LUA_NOREF) { Error err = ERROR_INIT; Array args = ARRAY_DICT_INIT; - Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err); + Object ret = nlua_call_ref(mp->m_luaref, NULL, args, kRetObject, NULL, &err); if (ret.type == kObjectTypeString) { p = string_to_cstr(ret.data.string); } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 36fa7e77fc..2224403fa2 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -192,7 +192,6 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem if (!(channel = find_rpc_channel(id))) { api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id); - api_free_array(args); return NIL; } @@ -201,7 +200,6 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem uint32_t request_id = rpc->next_request_id++; // Send the msgpack-rpc request send_request(channel, request_id, method_name, args); - api_free_array(args); // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL, NULL }; diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 98e9d6c9e6..44fcc29ad1 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -592,13 +592,13 @@ Array runtime_inspect(void) return rv; } -ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) +ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all, Arena *arena) { int ref; RuntimeSearchPath path = runtime_search_path_get_cached(&ref); static char buf[MAXPATHL]; - ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf); + ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf, arena); runtime_search_path_unref(path, &ref); return rv; @@ -610,15 +610,16 @@ ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all) uv_mutex_lock(&runtime_search_path_mutex); static char buf[MAXPATHL]; ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread, - buf, sizeof buf); + buf, sizeof buf, NULL); uv_mutex_unlock(&runtime_search_path_mutex); return rv; } static ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, - RuntimeSearchPath path, char *buf, size_t buf_len) + RuntimeSearchPath path, char *buf, size_t buf_len, + Arena *arena) { - ArrayOf(String) rv = ARRAY_DICT_INIT; + ArrayOf(String) rv = arena_array(arena, kv_size(path) * pat.size); for (size_t i = 0; i < kv_size(path); i++) { SearchPathItem *item = &kv_A(path, i); if (lua) { @@ -638,7 +639,7 @@ static ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, item->path, pat_item.data.string.data); if (size < buf_len) { if (os_file_is_readable(buf)) { - ADD(rv, CSTR_TO_OBJ(buf)); + ADD_C(rv, CSTR_TO_ARENA_OBJ(arena, buf)); if (!all) { goto done; } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index debd2fe511..3708a87ddf 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -710,8 +710,8 @@ void ui_call_event(char *name, Array args) bool handled = false; map_foreach_value(&ui_event_cbs, event_cb, { Error err = ERROR_INIT; - Object res = nlua_call_ref(event_cb->cb, name, args, false, &err); - if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + Object res = nlua_call_ref(event_cb->cb, name, args, kRetNilBool, NULL, &err); + if (LUARET_TRUTHY(res)) { handled = true; } if (ERROR_SET(&err)) { diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index d4dec7db83..2bb5ee16b3 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -170,7 +170,7 @@ Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Er static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) { Error err = ERROR_INIT; - Dict(highlight) dict = { 0 }; + Dict(highlight) dict = KEYDICT_INIT; if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) { // TODO(bfredl): log "err" return HLATTRS_INIT; diff --git a/src/nvim/version.c b/src/nvim/version.c index 22101531ef..8568f67aa1 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2681,9 +2681,9 @@ void list_in_columns(char **items, int size, int current) void list_lua_version(void) { - char *code = "return ((jit and jit.version) and jit.version or _VERSION)"; Error err = ERROR_INIT; - Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err); + Object ret = NLUA_EXEC_STATIC("return ((jit and jit.version) and jit.version or _VERSION)", + (Array)ARRAY_DICT_INIT, kRetObject, NULL, &err); assert(!ERROR_SET(&err)); assert(ret.type == kObjectTypeString); msg(ret.data.string.data, 0); diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index c7490756d4..a262d239e8 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3543,6 +3543,18 @@ describe('lua stdlib', function() ]]) ) end) + + it('can return values by reference', function() + eq( + { 4, 7 }, + exec_lua [[ + local val = {4, 10} + local ref = vim.api.nvim_buf_call(0, function() return val end) + ref[2] = 7 + return val + ]] + ) + end) end) describe('vim.api.nvim_win_call', function() @@ -3640,6 +3652,18 @@ describe('lua stdlib', function() | ]] end) + + it('can return values by reference', function() + eq( + { 7, 10 }, + exec_lua [[ + local val = {4, 10} + local ref = vim.api.nvim_win_call(0, function() return val end) + ref[1] = 7 + return val + ]] + ) + end) end) describe('vim.iconv', function() |