diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-07-10 08:07:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-10 08:07:16 +0800 |
commit | 545aafbeb80eb52c182ce139800489b392a12d0d (patch) | |
tree | 580a4c7a0f42bddae3b4577054b8758906d4d631 | |
parent | f3c7fb9db176f32606e83eb47cc7549300191d2f (diff) | |
download | rneovim-545aafbeb80eb52c182ce139800489b392a12d0d.tar.gz rneovim-545aafbeb80eb52c182ce139800489b392a12d0d.tar.bz2 rneovim-545aafbeb80eb52c182ce139800489b392a12d0d.zip |
vim-patch:9.1.0547: No way to get the arity of a Vim function (#29638)
Problem: No way to get the arity of a Vim function
(Austin Ziegler)
Solution: Enhance get() Vim script function to return the function
argument info using get(func, "arity") (LemonBoy)
fixes: vim/vim#15097
closes: vim/vim#15109
https://github.com/vim/vim/commit/48b7d05a4f88c4326bd5d7a73a523f2d953b3e51
Co-authored-by: LemonBoy <thatlemon@gmail.com>
-rw-r--r-- | runtime/doc/builtin.txt | 31 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 23 | ||||
-rwxr-xr-x | scripts/gen_eval_files.lua | 16 | ||||
-rw-r--r-- | src/nvim/eval.lua | 27 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 27 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 38 | ||||
-rw-r--r-- | test/old/testdir/test_getvar.vim | 10 | ||||
-rw-r--r-- | test/old/testdir/test_partial.vim | 22 |
8 files changed, 168 insertions, 26 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 648598fb3f..334531cec7 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -2028,17 +2028,17 @@ garbagecollect([{atexit}]) *garbagecollect()* it's safe to perform. This is when waiting for the user to type a character. -get({list}, {idx} [, {default}]) *get()* +get({list}, {idx} [, {default}]) *get()* *get()-list* Get item {idx} from |List| {list}. When this item is not available return {default}. Return zero when {default} is omitted. -get({blob}, {idx} [, {default}]) +get({blob}, {idx} [, {default}]) *get()-blob* Get byte {idx} from |Blob| {blob}. When this byte is not available return {default}. Return -1 when {default} is omitted. -get({dict}, {key} [, {default}]) +get({dict}, {key} [, {default}]) *get()-dict* Get item with key {key} from |Dictionary| {dict}. When this item is not available return {default}. Return zero when {default} is omitted. Useful example: >vim @@ -2046,13 +2046,26 @@ get({dict}, {key} [, {default}]) < This gets the value of g:var_name if it exists, and uses "default" when it does not exist. -get({func}, {what}) - Get item {what} from Funcref {func}. Possible values for +get({func}, {what}) *get()-func* + Get item {what} from |Funcref| {func}. Possible values for {what} are: - "name" The function name - "func" The function - "dict" The dictionary - "args" The list with arguments + "name" The function name + "func" The function + "dict" The dictionary + "args" The list with arguments + "arity" A dictionary with information about the number of + arguments accepted by the function (minus the + {arglist}) with the following fields: + required the number of positional arguments + optional the number of optional arguments, + in addition to the required ones + varargs |TRUE| if the function accepts a + variable number of arguments |...| + + Note: There is no error, if the {arglist} of + the Funcref contains more arguments than the + Funcref expects, it's not validated. + Returns zero on error. getbufinfo([{buf}]) *getbufinfo()* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 34a3addb57..d9bd683bbb 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2523,12 +2523,25 @@ function vim.fn.get(blob, idx, default) end --- @return any function vim.fn.get(dict, key, default) end ---- Get item {what} from Funcref {func}. Possible values for +--- Get item {what} from |Funcref| {func}. Possible values for --- {what} are: ---- "name" The function name ---- "func" The function ---- "dict" The dictionary ---- "args" The list with arguments +--- "name" The function name +--- "func" The function +--- "dict" The dictionary +--- "args" The list with arguments +--- "arity" A dictionary with information about the number of +--- arguments accepted by the function (minus the +--- {arglist}) with the following fields: +--- required the number of positional arguments +--- optional the number of optional arguments, +--- in addition to the required ones +--- varargs |TRUE| if the function accepts a +--- variable number of arguments |...| +--- +--- Note: There is no error, if the {arglist} of +--- the Funcref contains more arguments than the +--- Funcref expects, it's not validated. +--- --- Returns zero on error. --- --- @param func function diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 76092f8b39..b490e7b480 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -432,14 +432,15 @@ local function render_eval_meta(f, fun, write) end --- @param name string +--- @param name_tag boolean --- @param fun vim.EvalFn --- @param write fun(line: string) -local function render_sig_and_tag(name, fun, write) +local function render_sig_and_tag(name, name_tag, fun, write) if not fun.signature then return end - local tags = { '*' .. name .. '()*' } + local tags = name_tag and { '*' .. name .. '()*' } or {} if fun.tags then for _, t in ipairs(fun.tags) do @@ -447,6 +448,11 @@ local function render_sig_and_tag(name, fun, write) end end + if #tags == 0 then + write(fun.signature) + return + end + local tag = table.concat(tags, ' ') local siglen = #fun.signature local conceal_offset = 2 * (#tags - 1) @@ -472,11 +478,7 @@ local function render_eval_doc(f, fun, write) return end - if f:find('__%d+$') then - write(fun.signature) - else - render_sig_and_tag(fun.name or f, fun, write) - end + render_sig_and_tag(fun.name or f, not f:find('__%d+$'), fun, write) if not fun.desc then return diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 0e5503b829..72a3246da5 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3141,6 +3141,7 @@ M.funcs = { name = 'get', params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } }, signature = 'get({list}, {idx} [, {default}])', + tags = { 'get()-list' }, }, get__1 = { args = { 2, 3 }, @@ -3153,6 +3154,7 @@ M.funcs = { name = 'get', params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } }, signature = 'get({blob}, {idx} [, {default}])', + tags = { 'get()-blob' }, }, get__2 = { args = { 2, 3 }, @@ -3168,23 +3170,38 @@ M.funcs = { name = 'get', params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } }, signature = 'get({dict}, {key} [, {default}])', + tags = { 'get()-dict' }, }, get__3 = { args = { 2, 3 }, base = 1, desc = [=[ - Get item {what} from Funcref {func}. Possible values for + Get item {what} from |Funcref| {func}. Possible values for {what} are: - "name" The function name - "func" The function - "dict" The dictionary - "args" The list with arguments + "name" The function name + "func" The function + "dict" The dictionary + "args" The list with arguments + "arity" A dictionary with information about the number of + arguments accepted by the function (minus the + {arglist}) with the following fields: + required the number of positional arguments + optional the number of optional arguments, + in addition to the required ones + varargs |TRUE| if the function accepts a + variable number of arguments |...| + + Note: There is no error, if the {arglist} of + the Funcref contains more arguments than the + Funcref expects, it's not validated. + Returns zero on error. ]=], name = 'get', params = { { 'func', 'function' }, { 'what', 'string' } }, returns = 'any', signature = 'get({func}, {what})', + tags = { 'get()-func' }, }, getbufinfo = { args = { 0, 1 }, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c319bd8214..22e7f383a5 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2372,6 +2372,33 @@ static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } + } else if (strcmp(what, "arity") == 0) { + int required = 0; + int optional = 0; + bool varargs = false; + const char *name = partial_name(pt); + + get_func_arity(name, &required, &optional, &varargs); + + rettv->v_type = VAR_DICT; + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + // Take into account the arguments of the partial, if any. + // Note that it is possible to supply more arguments than the function + // accepts. + if (pt->pt_argc >= required + optional) { + required = optional = 0; + } else if (pt->pt_argc > required) { + optional -= pt->pt_argc - required; + required = 0; + } else { + required -= pt->pt_argc; + } + + tv_dict_add_nr(dict, S_LEN("required"), required); + tv_dict_add_nr(dict, S_LEN("optional"), optional); + tv_dict_add_bool(dict, S_LEN("varargs"), varargs); } else { semsg(_(e_invarg2), what); } diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 122a1ac8ab..42391ecec7 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -642,6 +642,44 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char return fname; } +int get_func_arity(const char *name, int *required, int *optional, bool *varargs) +{ + int argcount = 0; + int min_argcount = 0; + + const EvalFuncDef *fdef = find_internal_func(name); + if (fdef != NULL) { + argcount = fdef->max_argc; + min_argcount = fdef->min_argc; + *varargs = false; + } else { + char fname_buf[FLEN_FIXED + 1]; + char *tofree = NULL; + int error = FCERR_NONE; + + // May need to translate <SNR>123_ to K_SNR. + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); + ufunc_T *ufunc = NULL; + if (error == FCERR_NONE) { + ufunc = find_func(fname); + } + xfree(tofree); + + if (ufunc == NULL) { + return FAIL; + } + + argcount = ufunc->uf_args.ga_len; + min_argcount = ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len; + *varargs = ufunc->uf_varargs; + } + + *required = min_argcount; + *optional = argcount - min_argcount; + + return OK; +} + /// Find a function by name, return pointer to it in ufuncs. /// /// @return NULL for unknown function. diff --git a/test/old/testdir/test_getvar.vim b/test/old/testdir/test_getvar.vim index 1e29f49fb4..56f737ab9c 100644 --- a/test/old/testdir/test_getvar.vim +++ b/test/old/testdir/test_getvar.vim @@ -142,21 +142,31 @@ func Test_get_func() let l:F = function('tr') call assert_equal('tr', get(l:F, 'name')) call assert_equal(l:F, get(l:F, 'func')) + call assert_equal({'required': 3, 'optional': 0, 'varargs': v:false}, + \ get(l:F, 'arity')) let Fb_func = function('s:FooBar') call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(Fb_func, 'arity')) let Fb_ref = funcref('s:FooBar') call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(Fb_ref, 'arity')) call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'})) call assert_equal(0, get(l:F, 'dict')) call assert_equal([], get(l:F, 'args')) + " Nvim doesn't have null functions " let NF = test_null_function() " call assert_equal('', get(NF, 'name')) " call assert_equal(NF, get(NF, 'func')) " call assert_equal(0, get(NF, 'dict')) " call assert_equal([], get(NF, 'args')) + " call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, get(NF, 'arity')) endfunc " get({partial}, {what} [, {default}]) - in test_partial.vim + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_partial.vim b/test/old/testdir/test_partial.vim index 8b62e4a0e5..d049cc9e4b 100644 --- a/test/old/testdir/test_partial.vim +++ b/test/old/testdir/test_partial.vim @@ -284,6 +284,11 @@ func Test_auto_partial_rebind() endfunc func Test_get_partial_items() + func s:Qux(x, y, z=3, w=1, ...) + endfunc + func s:Qux1(x, y) + endfunc + let dict = {'name': 'hello'} let args = ["foo", "bar"] let Func = function('MyDictFunc') @@ -304,6 +309,23 @@ func Test_get_partial_items() let dict = {'partial has': 'no dict'} call assert_equal(dict, get(P, 'dict', dict)) call assert_equal(0, get(l:P, 'dict')) + + call assert_equal({'required': 2, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', []), 'arity')) + call assert_equal({'required': 1, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', [1]), 'arity')) + call assert_equal({'required': 0, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2]), 'arity')) + call assert_equal({'required': 0, 'optional': 1, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2, 3]), 'arity')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2, 3, 4]), 'arity')) + " More args than expected is not an error + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(funcref('s:Qux1', [1, 2, 3, 4]), 'arity')) + + delfunc s:Qux + delfunc s:Qux1 endfunc func Test_compare_partials() |