aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-07-10 08:07:16 +0800
committerGitHub <noreply@github.com>2024-07-10 08:07:16 +0800
commit545aafbeb80eb52c182ce139800489b392a12d0d (patch)
tree580a4c7a0f42bddae3b4577054b8758906d4d631
parentf3c7fb9db176f32606e83eb47cc7549300191d2f (diff)
downloadrneovim-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.txt31
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua23
-rwxr-xr-xscripts/gen_eval_files.lua16
-rw-r--r--src/nvim/eval.lua27
-rw-r--r--src/nvim/eval/funcs.c27
-rw-r--r--src/nvim/eval/userfunc.c38
-rw-r--r--test/old/testdir/test_getvar.vim10
-rw-r--r--test/old/testdir/test_partial.vim22
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()