mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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
48b7d05a4f
Co-authored-by: LemonBoy <thatlemon@gmail.com>
This commit is contained in:
parent
f3c7fb9db1
commit
545aafbeb8
31
runtime/doc/builtin.txt
generated
31
runtime/doc/builtin.txt
generated
@ -2028,17 +2028,17 @@ garbagecollect([{atexit}]) *garbagecollect()*
|
|||||||
it's safe to perform. This is when waiting for the user to
|
it's safe to perform. This is when waiting for the user to
|
||||||
type a character.
|
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
|
Get item {idx} from |List| {list}. When this item is not
|
||||||
available return {default}. Return zero when {default} is
|
available return {default}. Return zero when {default} is
|
||||||
omitted.
|
omitted.
|
||||||
|
|
||||||
get({blob}, {idx} [, {default}])
|
get({blob}, {idx} [, {default}]) *get()-blob*
|
||||||
Get byte {idx} from |Blob| {blob}. When this byte is not
|
Get byte {idx} from |Blob| {blob}. When this byte is not
|
||||||
available return {default}. Return -1 when {default} is
|
available return {default}. Return -1 when {default} is
|
||||||
omitted.
|
omitted.
|
||||||
|
|
||||||
get({dict}, {key} [, {default}])
|
get({dict}, {key} [, {default}]) *get()-dict*
|
||||||
Get item with key {key} from |Dictionary| {dict}. When this
|
Get item with key {key} from |Dictionary| {dict}. When this
|
||||||
item is not available return {default}. Return zero when
|
item is not available return {default}. Return zero when
|
||||||
{default} is omitted. Useful example: >vim
|
{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
|
< This gets the value of g:var_name if it exists, and uses
|
||||||
"default" when it does not exist.
|
"default" when it does not exist.
|
||||||
|
|
||||||
get({func}, {what})
|
get({func}, {what}) *get()-func*
|
||||||
Get item {what} from Funcref {func}. Possible values for
|
Get item {what} from |Funcref| {func}. Possible values for
|
||||||
{what} are:
|
{what} are:
|
||||||
"name" The function name
|
"name" The function name
|
||||||
"func" The function
|
"func" The function
|
||||||
"dict" The dictionary
|
"dict" The dictionary
|
||||||
"args" The list with arguments
|
"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.
|
Returns zero on error.
|
||||||
|
|
||||||
getbufinfo([{buf}]) *getbufinfo()*
|
getbufinfo([{buf}]) *getbufinfo()*
|
||||||
|
23
runtime/lua/vim/_meta/vimfn.lua
generated
23
runtime/lua/vim/_meta/vimfn.lua
generated
@ -2523,12 +2523,25 @@ function vim.fn.get(blob, idx, default) end
|
|||||||
--- @return any
|
--- @return any
|
||||||
function vim.fn.get(dict, key, default) end
|
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:
|
--- {what} are:
|
||||||
--- "name" The function name
|
--- "name" The function name
|
||||||
--- "func" The function
|
--- "func" The function
|
||||||
--- "dict" The dictionary
|
--- "dict" The dictionary
|
||||||
--- "args" The list with arguments
|
--- "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.
|
--- Returns zero on error.
|
||||||
---
|
---
|
||||||
--- @param func function
|
--- @param func function
|
||||||
|
@ -432,14 +432,15 @@ local function render_eval_meta(f, fun, write)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param name string
|
--- @param name string
|
||||||
|
--- @param name_tag boolean
|
||||||
--- @param fun vim.EvalFn
|
--- @param fun vim.EvalFn
|
||||||
--- @param write fun(line: string)
|
--- @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
|
if not fun.signature then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local tags = { '*' .. name .. '()*' }
|
local tags = name_tag and { '*' .. name .. '()*' } or {}
|
||||||
|
|
||||||
if fun.tags then
|
if fun.tags then
|
||||||
for _, t in ipairs(fun.tags) do
|
for _, t in ipairs(fun.tags) do
|
||||||
@ -447,6 +448,11 @@ local function render_sig_and_tag(name, fun, write)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #tags == 0 then
|
||||||
|
write(fun.signature)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local tag = table.concat(tags, ' ')
|
local tag = table.concat(tags, ' ')
|
||||||
local siglen = #fun.signature
|
local siglen = #fun.signature
|
||||||
local conceal_offset = 2 * (#tags - 1)
|
local conceal_offset = 2 * (#tags - 1)
|
||||||
@ -472,11 +478,7 @@ local function render_eval_doc(f, fun, write)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if f:find('__%d+$') then
|
render_sig_and_tag(fun.name or f, not f:find('__%d+$'), fun, write)
|
||||||
write(fun.signature)
|
|
||||||
else
|
|
||||||
render_sig_and_tag(fun.name or f, fun, write)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not fun.desc then
|
if not fun.desc then
|
||||||
return
|
return
|
||||||
|
@ -3141,6 +3141,7 @@ M.funcs = {
|
|||||||
name = 'get',
|
name = 'get',
|
||||||
params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } },
|
params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } },
|
||||||
signature = 'get({list}, {idx} [, {default}])',
|
signature = 'get({list}, {idx} [, {default}])',
|
||||||
|
tags = { 'get()-list' },
|
||||||
},
|
},
|
||||||
get__1 = {
|
get__1 = {
|
||||||
args = { 2, 3 },
|
args = { 2, 3 },
|
||||||
@ -3153,6 +3154,7 @@ M.funcs = {
|
|||||||
name = 'get',
|
name = 'get',
|
||||||
params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } },
|
params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } },
|
||||||
signature = 'get({blob}, {idx} [, {default}])',
|
signature = 'get({blob}, {idx} [, {default}])',
|
||||||
|
tags = { 'get()-blob' },
|
||||||
},
|
},
|
||||||
get__2 = {
|
get__2 = {
|
||||||
args = { 2, 3 },
|
args = { 2, 3 },
|
||||||
@ -3168,23 +3170,38 @@ M.funcs = {
|
|||||||
name = 'get',
|
name = 'get',
|
||||||
params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } },
|
params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } },
|
||||||
signature = 'get({dict}, {key} [, {default}])',
|
signature = 'get({dict}, {key} [, {default}])',
|
||||||
|
tags = { 'get()-dict' },
|
||||||
},
|
},
|
||||||
get__3 = {
|
get__3 = {
|
||||||
args = { 2, 3 },
|
args = { 2, 3 },
|
||||||
base = 1,
|
base = 1,
|
||||||
desc = [=[
|
desc = [=[
|
||||||
Get item {what} from Funcref {func}. Possible values for
|
Get item {what} from |Funcref| {func}. Possible values for
|
||||||
{what} are:
|
{what} are:
|
||||||
"name" The function name
|
"name" The function name
|
||||||
"func" The function
|
"func" The function
|
||||||
"dict" The dictionary
|
"dict" The dictionary
|
||||||
"args" The list with arguments
|
"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.
|
Returns zero on error.
|
||||||
]=],
|
]=],
|
||||||
name = 'get',
|
name = 'get',
|
||||||
params = { { 'func', 'function' }, { 'what', 'string' } },
|
params = { { 'func', 'function' }, { 'what', 'string' } },
|
||||||
returns = 'any',
|
returns = 'any',
|
||||||
signature = 'get({func}, {what})',
|
signature = 'get({func}, {what})',
|
||||||
|
tags = { 'get()-func' },
|
||||||
},
|
},
|
||||||
getbufinfo = {
|
getbufinfo = {
|
||||||
args = { 0, 1 },
|
args = { 0, 1 },
|
||||||
|
@ -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++) {
|
for (int i = 0; i < pt->pt_argc; i++) {
|
||||||
tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[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 {
|
} else {
|
||||||
semsg(_(e_invarg2), what);
|
semsg(_(e_invarg2), what);
|
||||||
}
|
}
|
||||||
|
@ -642,6 +642,44 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char
|
|||||||
return fname;
|
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.
|
/// Find a function by name, return pointer to it in ufuncs.
|
||||||
///
|
///
|
||||||
/// @return NULL for unknown function.
|
/// @return NULL for unknown function.
|
||||||
|
@ -142,21 +142,31 @@ func Test_get_func()
|
|||||||
let l:F = function('tr')
|
let l:F = function('tr')
|
||||||
call assert_equal('tr', get(l:F, 'name'))
|
call assert_equal('tr', get(l:F, 'name'))
|
||||||
call assert_equal(l:F, get(l:F, 'func'))
|
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')
|
let Fb_func = function('s:FooBar')
|
||||||
call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name'))
|
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')
|
let Fb_ref = funcref('s:FooBar')
|
||||||
call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name'))
|
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({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
|
||||||
call assert_equal(0, get(l:F, 'dict'))
|
call assert_equal(0, get(l:F, 'dict'))
|
||||||
call assert_equal([], get(l:F, 'args'))
|
call assert_equal([], get(l:F, 'args'))
|
||||||
|
|
||||||
" Nvim doesn't have null functions
|
" Nvim doesn't have null functions
|
||||||
" let NF = test_null_function()
|
" let NF = test_null_function()
|
||||||
" call assert_equal('', get(NF, 'name'))
|
" call assert_equal('', get(NF, 'name'))
|
||||||
" call assert_equal(NF, get(NF, 'func'))
|
" call assert_equal(NF, get(NF, 'func'))
|
||||||
" call assert_equal(0, get(NF, 'dict'))
|
" call assert_equal(0, get(NF, 'dict'))
|
||||||
" call assert_equal([], get(NF, 'args'))
|
" call assert_equal([], get(NF, 'args'))
|
||||||
|
" call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, get(NF, 'arity'))
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
" get({partial}, {what} [, {default}]) - in test_partial.vim
|
" get({partial}, {what} [, {default}]) - in test_partial.vim
|
||||||
|
|
||||||
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
@ -284,6 +284,11 @@ func Test_auto_partial_rebind()
|
|||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_get_partial_items()
|
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 dict = {'name': 'hello'}
|
||||||
let args = ["foo", "bar"]
|
let args = ["foo", "bar"]
|
||||||
let Func = function('MyDictFunc')
|
let Func = function('MyDictFunc')
|
||||||
@ -304,6 +309,23 @@ func Test_get_partial_items()
|
|||||||
let dict = {'partial has': 'no dict'}
|
let dict = {'partial has': 'no dict'}
|
||||||
call assert_equal(dict, get(P, 'dict', dict))
|
call assert_equal(dict, get(P, 'dict', dict))
|
||||||
call assert_equal(0, get(l:P, '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
|
endfunc
|
||||||
|
|
||||||
func Test_compare_partials()
|
func Test_compare_partials()
|
||||||
|
Loading…
Reference in New Issue
Block a user