patch 9.1.0547: No way to get the arity of a Vim function

Commit: 
https://github.com/vim/vim/commit/48b7d05a4f88c4326bd5d7a73a523f2d953b3e51
Author: LemonBoy <thatle...@gmail.com>
Date:   Tue Jul 9 18:24:59 2024 +0200

    patch 9.1.0547: No way to get the arity of a Vim function
    
    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: #15097
    closes: #15109
    
    Signed-off-by: LemonBoy <thatle...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 238b7e43d..635b7b221 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*  For Vim version 9.1.  Last change: 2024 Jun 23
+*builtin.txt*  For Vim version 9.1.  Last change: 2024 Jul 09
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -3574,7 +3574,7 @@ garbagecollect([{atexit}])                                
*garbagecollect()*
                Return type: |String|
 
 
-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.
@@ -3583,7 +3583,7 @@ get({list}, {idx} [, {default}])                  *get()*
 <
                Return type: any, depending on {list}
 
-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.
@@ -3592,7 +3592,7 @@ get({blob}, {idx} [, {default}])
 <
                Return type: |Number|
 
-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: >
@@ -3604,18 +3604,32 @@ get({dict}, {key} [, {default}])
 <
                Return type: any, depending on {dict}
 
-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.
+
                Preferably used as a |method|: >
                        myfunc->get(what)
 <
-               Return type: any, depending on {func}
+               Return type: any, depending on {func} and {what}
 
                                                        *getbufinfo()*
 getbufinfo([{buf}])
diff --git a/runtime/doc/tags b/runtime/doc/tags
index e72f73927..be71710bc 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -7783,6 +7783,10 @@ gdb-version      terminal.txt    /*gdb-version*
 ge     motion.txt      /*ge*
 gender-neutral helphelp.txt    /*gender-neutral*
 get()  builtin.txt     /*get()*
+get()-blob     builtin.txt     /*get()-blob*
+get()-dict     builtin.txt     /*get()-dict*
+get()-func     builtin.txt     /*get()-func*
+get()-list     builtin.txt     /*get()-list*
 get-ms-debuggers       debug.txt       /*get-ms-debuggers*
 getbufinfo()   builtin.txt     /*getbufinfo()*
 getbufline()   builtin.txt     /*getbufline()*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 92792b9cd..43de2692f 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Jul 06
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Jul 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41577,6 +41577,8 @@ Changed~
 - 'nrformat' accepts the new "blank" suboption, to determine a signed or
   unsigned number based on whitespace in front of a minus sign.
 - allow to specify a priority when defining a new sign |:sign-define|
+- provide information about function arguments using the get(func, "arity")
+  function |get()-func|
 
                                                        *added-9.2*
 Added ~
diff --git a/src/evalfunc.c b/src/evalfunc.c
index c9480f9f6..5e3122dd9 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -5134,6 +5134,36 @@ f_get(typval_T *argvars, typval_T *rettv)
                        list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
                }
            }
+           else if (STRCMP(what, "arity") == 0)
+           {
+               int required = 0, optional = 0, varargs = FALSE;
+               char_u *name = partial_name(pt);
+
+               get_func_arity(name, &required, &optional, &varargs);
+
+               rettv->v_type = VAR_DICT;
+               if (rettv_dict_alloc(rettv) == OK)
+               {
+                   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;
+
+                   dict_add_number(dict, "required", required);
+                   dict_add_number(dict, "optional", optional);
+                   dict_add_bool(dict, "varargs", varargs);
+               }
+           }
            else
                semsg(_(e_invalid_argument_str), what);
 
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index 9bb461663..ce5d257ca 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -95,4 +95,5 @@ int set_ref_in_call_stack(int copyID);
 int set_ref_in_functions(int copyID);
 int set_ref_in_func_args(int copyID);
 int set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID);
+int get_func_arity(char_u *name, int *required, int *optional, int *varargs);
 /* vim: set ft=c : */
diff --git a/src/testdir/test_getvar.vim b/src/testdir/test_getvar.vim
index 2065186a5..6efb192eb 100644
--- a/src/testdir/test_getvar.vim
+++ b/src/testdir/test_getvar.vim
@@ -142,20 +142,28 @@ 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'))
+
   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
diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim
index b5a58f6e5..acc8b73c8 100644
--- a/src/testdir/test_partial.vim
+++ b/src/testdir/test_partial.vim
@@ -311,6 +311,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')
@@ -331,6 +336,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()
diff --git a/src/userfunc.c b/src/userfunc.c
index 7536234b8..e44397d81 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5503,6 +5503,47 @@ ex_function(exarg_T *eap)
     ga_clear_strings(&lines_to_free);
 }
 
+    int
+get_func_arity(char_u *name, int *required, int *optional, int *varargs)
+{
+    ufunc_T    *ufunc = NULL;
+    int                argcount = 0;
+    int                min_argcount = 0;
+    int                idx;
+
+    idx = find_internal_func(name);
+    if (idx >= 0)
+    {
+       internal_func_get_argcount(idx, &argcount, &min_argcount);
+       *varargs = FALSE;
+    }
+    else
+    {
+       char_u  fname_buf[FLEN_FIXED + 1];
+       char_u  *tofree = NULL;
+       funcerror_T error = FCERR_NONE;
+       char_u  *fname;
+
+       // May need to translate <SNR>123_ to K_SNR.
+       fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+       if (error == FCERR_NONE)
+           ufunc = find_func(fname, FALSE);
+       vim_free(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 = has_varargs(ufunc);
+    }
+
+    *required = min_argcount;
+    *optional = argcount - min_argcount;
+
+    return OK;
+}
+
 /*
  * Find a function by name, including "<lambda>123".
  * Check for "profile" and "debug" arguments and set"compile_type".
diff --git a/src/version.c b/src/version.c
index 53bafd37d..ce6b8d60a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    547,
 /**/
     546,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1sRDxj-00DGS1-MI%40256bit.org.

Raspunde prin e-mail lui