diff options
| author | Barrett Ruth <62671086+barrettruth@users.noreply.github.com> | 2026-07-13 09:28:24 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-13 10:28:24 -0400 |
| commit | 25439dad7e2ba21ff2f78759c68be888a217342c (patch) | |
| tree | 537cc1ed5794be92e990b066fb3eac183fe83eda /src/gen/gen_api_dispatch.lua | |
| parent | e18820007f00541cb95b7ac188666486aaa0a8d9 (diff) | |
| download | rneovim-25439dad7e2ba21ff2f78759c68be888a217342c.tar.gz rneovim-25439dad7e2ba21ff2f78759c68be888a217342c.tar.bz2 rneovim-25439dad7e2ba21ff2f78759c68be888a217342c.zip | |
feat(api): relax contract, `opts` + rest params are optional #40581
Problem:
|api-contract| allows adding an optional trailing `opts` parameter, but
the dispatch generator still requires every parameter, so `opts` cannot
be omitted.
Solution:
Treat a trailing `Dict(...) *opts` parameter as optional in the generated
RPC and Lua dispatch wrappers.
Co-authored-by: Judit Novak <judit.novak@gmail.com>
Diffstat (limited to 'src/gen/gen_api_dispatch.lua')
| -rw-r--r-- | src/gen/gen_api_dispatch.lua | 153 |
1 files changed, 131 insertions, 22 deletions
diff --git a/src/gen/gen_api_dispatch.lua b/src/gen/gen_api_dispatch.lua index d7b4dde1b2..9a224d3077 100644 --- a/src/gen/gen_api_dispatch.lua +++ b/src/gen/gen_api_dispatch.lua @@ -59,6 +59,7 @@ end --- @field can_fail? true --- @field has_lua_imp? true --- @field receives_arena? true +--- @field has_opts? true --- @field impl_name? string --- @field remote? boolean --- @field lua? boolean @@ -116,6 +117,13 @@ local function add_function(fn) fn.receives_arena = true fn.parameters[#fn.parameters] = nil end + if + #fn.parameters ~= 0 + and fn.parameters[#fn.parameters][2] == 'opts' + and real_type(fn.parameters[#fn.parameters][1]):match('^KeyDict_') + then + fn.has_opts = true + end end end @@ -431,19 +439,50 @@ for i = 1, #functions do local param = fn.parameters[j] local rt = real_type(param[1]) local converted = 'arg_' .. j - output:write('\n ' .. rt .. ' ' .. converted .. ';') + if fn.has_opts and j == #fn.parameters then + output:write('\n ' .. rt .. ' ' .. converted .. ' = { 0 };') + else + output:write('\n ' .. rt .. ' ' .. converted .. ';') + end end output:write('\n') if not fn.receives_array_args then - output:write('\n if (args.size != ' .. #fn.parameters .. ') {') - output:write( - '\n api_set_error(error, kErrorTypeException, \ + if fn.has_opts then + local minargs = #fn.parameters - 1 + if minargs > 0 then + output:write( + '\n if (args.size < ' .. minargs .. ' || args.size > ' .. #fn.parameters .. ') {' + ) + output:write( + '\n api_set_error(error, kErrorTypeException, \ "Wrong number of arguments: expecting ' - .. #fn.parameters - .. ' but got %zu", args.size);' - ) - output:write('\n goto cleanup;') - output:write('\n }\n') + .. minargs + .. ' to ' + .. #fn.parameters + .. ' but got %zu", args.size);' + ) + else + output:write('\n if (args.size > ' .. #fn.parameters .. ') {') + output:write( + '\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting at most ' + .. #fn.parameters + .. ' but got %zu", args.size);' + ) + end + output:write('\n goto cleanup;') + output:write('\n }\n') + else + output:write('\n if (args.size != ' .. #fn.parameters .. ') {') + output:write( + '\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting ' + .. #fn.parameters + .. ' but got %zu", args.size);' + ) + output:write('\n goto cleanup;') + output:write('\n }\n') + end end -- Validation/conversion for each argument @@ -456,6 +495,10 @@ for i = 1, #functions do output:write('\n ' .. converted .. ' = args.items[' .. (j - 1) .. '];\n') elseif rt:match('^KeyDict_') then converted = '&' .. converted + local optional = fn.has_opts and j == #fn.parameters + if optional then + output:write('\n if (args.size == ' .. j .. ') {') + end output:write('\n if (args.items[' .. (j - 1) .. '].type == kObjectTypeDict) {') --luacheck: ignore 631 output:write('\n memset(' .. converted .. ', 0, sizeof(*' .. converted .. '));') -- TODO: neeeee output:write( @@ -491,6 +534,9 @@ for i = 1, #functions do ) output:write('\n goto cleanup;') output:write('\n }\n') + if optional then + output:write('\n }\n') + end else if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') then -- Buffer, Window, and Tabpage have a specific type, but are stored in integer @@ -730,16 +776,50 @@ local function process_function(fn) Error err = ERROR_INIT; Arena arena = ARENA_EMPTY; char *err_param = 0; + ]], + lua_c_function_name + ) + if fn.has_opts then + local minargs = #fn.parameters - 1 + if minargs > 0 then + write_shifted_output( + [[ + if (lua_gettop(lstate) < %i || lua_gettop(lstate) > %i) { + api_set_error(&err, kErrorTypeValidation, "Expected %i to %i arguments"); + goto exit_0; + } + ]], + minargs, + #fn.parameters, + minargs, + #fn.parameters + ) + else + write_shifted_output( + [[ + if (lua_gettop(lstate) > %i) { + api_set_error(&err, kErrorTypeValidation, "Expected at most %i argument%s"); + goto exit_0; + } + ]], + #fn.parameters, + #fn.parameters, + (#fn.parameters == 1) and '' or 's' + ) + end + else + write_shifted_output( + [[ if (lua_gettop(lstate) != %i) { api_set_error(&err, kErrorTypeValidation, "Expected %i argument%s"); goto exit_0; } ]], - lua_c_function_name, - #fn.parameters, - #fn.parameters, - (#fn.parameters == 1) and '' or 's' - ) + #fn.parameters, + #fn.parameters, + (#fn.parameters == 1) and '' or 's' + ) + end lua_c_functions[#lua_c_functions + 1] = { binding = lua_c_function_name, api = fn.name, @@ -784,17 +864,33 @@ local function process_function(fn) end local errshift = 0 local seterr = '' + local optional = fn.has_opts and j == #fn.parameters if param_type:match('^KeyDict_') then - write_shifted_output( - [[ + if optional then + write_shifted_output( + [[ + %s %s = KEYDICT_INIT; + if (lua_gettop(lstate) == %d) { + nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &arena, &err); + ]], + param_type, + cparam, + #fn.parameters, + cparam, + param_type + ) + else + write_shifted_output( + [[ %s %s = KEYDICT_INIT; nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &arena, &err); ]], - param_type, - cparam, - cparam, - param_type - ) + param_type, + cparam, + cparam, + param_type + ) + end cparam = '&' .. cparam errshift = 1 -- free incomplete dict on error arg_free_code = ' api_luarefs_free_keydict(' @@ -814,7 +910,19 @@ local function process_function(fn) seterr = '\n err_param = "' .. param[2] .. '";' end - write_shifted_output([[ + if optional then + write_shifted_output( + [[ + if (ERROR_SET(&err)) { + goto exit_%u; + } + } + + ]], + #fn.parameters - j + errshift + ) + else + write_shifted_output([[ if (ERROR_SET(&err)) {]] .. seterr .. [[ @@ -822,6 +930,7 @@ local function process_function(fn) } ]], #fn.parameters - j + errshift) + end free_code[#free_code + 1] = arg_free_code cparams = cparam .. ', ' .. cparams end |