From e0bdc9b89c336a15e77639eef1c9087808292f36 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 21 May 2021 22:15:31 +0100 Subject: fix(eval): cherry-pick emsg changes from v8.1.0736 v8.1.0736 made some changes for making some emsgs more specific. Includes the change for Test_lambda_fails() in test_lambda.vim. Adjust relevant functionaltests to expect the new emsgs. This patch has been fully ported in my Blob port PR, but it hasn't been merged yet, so just use what we need from it for now. Required for v8.1.1821. --- src/nvim/eval.c | 12 +++++++++--- src/nvim/testdir/test_lambda.vim | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 789936fdff..5634eafdf2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3162,6 +3162,8 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) { int ret; char_u *p; + const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; p = skipwhite(arg); ret = eval1(&p, rettv, evaluate); @@ -3171,8 +3173,10 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) } // Report the invalid expression unless the expression evaluation has // been cancelled due to an aborting error, an interrupt, or an - // exception. - if (!aborting()) { + // exception, or we already gave a more specific error. + // Also check called_emsg for when using assert_fails(). + if (!aborting() && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { emsgf(_(e_invexpr2), arg); } ret = FAIL; @@ -9275,6 +9279,7 @@ void ex_echo(exarg_T *eap) bool atstart = true; bool need_clear = true; const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; if (eap->skip) ++emsg_skip; @@ -9289,7 +9294,8 @@ void ex_echo(exarg_T *eap) // Report the invalid expression unless the expression evaluation // has been cancelled due to an aborting error, an interrupt, or an // exception. - if (!aborting() && did_emsg == did_emsg_before) { + if (!aborting() && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { EMSG2(_(e_invexpr2), p); } need_clr_eos = false; diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index f026c8a55f..63bb4ae1ef 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -61,7 +61,7 @@ endfunction function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) - call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:') + call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:') call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') endfunc -- cgit From 1918638c673e5c081bf063617ae69facd82c30d9 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sat, 7 Aug 2021 21:17:13 +0100 Subject: test(oldtest): cherry-pick needed test functions from v8.1.1336 Cherry-pick Test_call() and Test_cindent_func(). Required for v8.1.1911 and v8.1.1915. --- src/nvim/testdir/test_cindent.vim | 10 ++++++++++ src/nvim/testdir/test_functions.vim | 12 ++++++++++++ 2 files changed, 22 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index b6c2d1467e..c8b7d90fbb 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -118,6 +118,16 @@ b = something(); bw! endfunc +func Test_cindent_func() + new + setlocal cindent + call setline(1, ['int main(void)', '{', 'return 0;', '}']) + call assert_equal(cindent(0), -1) + call assert_equal(cindent(3), &sw) + call assert_equal(cindent(line('$')+1), -1) + bwipe! +endfunc + " this was going beyond the end of the line. func Test_cindent_case() new diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 48f97be96b..612b72cb62 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1473,6 +1473,18 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc +func Test_call() + call assert_equal(3, call('len', [123])) + call assert_fails("call call('len', 123)", 'E714:') + call assert_equal(0, call('', [])) + + function Mylen() dict + return len(self.data) + endfunction + let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} + call assert_fails("call call('Mylen', [], 0)", 'E715:') +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') -- cgit From 60dd76c7e29e75dee641f93483eb6e5c2afb28e4 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sat, 7 Aug 2021 17:33:07 +0100 Subject: vim-patch:8.1.1638: running tests leaves some files behind Problem: Running tests leaves some files behind. Solution: Delete the files. (Ozaki Kiichi, closes vim/vim#4617) https://github.com/vim/vim/commit/3940ec6d41a07f9abbfba7d4db6b49d3d8b43a9a Required for v8.1.1888. N/A patches for version.c: vim-patch:8.1.1912: more functions can be used as methods Problem: More functions can be used as methods. Solution: Make channel and job functions usable as a method. vim/vim@570497a --- src/nvim/testdir/test_functions.vim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 612b72cb62..2493cc2e27 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1413,10 +1413,10 @@ func Test_bufadd_bufload() call assert_equal([''], getbufline(buf, 1, '$')) let curbuf = bufnr('') - call writefile(['some', 'text'], 'otherName') - let buf = bufadd('otherName') + call writefile(['some', 'text'], 'XotherName') + let buf = bufadd('XotherName') call assert_notequal(0, buf) - call assert_equal(1, bufexists('otherName')) + call assert_equal(1, bufexists('XotherName')) call assert_equal(0, getbufvar(buf, '&buflisted')) call assert_equal(0, bufloaded(buf)) call bufload(buf) @@ -1439,8 +1439,9 @@ func Test_bufadd_bufload() call assert_equal(0, bufexists(buf2)) bwipe someName - bwipe otherName + bwipe XotherName call assert_equal(0, bufexists('someName')) + call delete('XotherName') endfunc func Test_readdir() -- cgit From 4042ae5a2bc4bbca608ebb196a3d54a78d6c100c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 21 May 2021 15:12:35 +0100 Subject: vim-patch:8.1.1800: function call functions have too many arguments Problem: Function call functions have too many arguments. Solution: Pass values in a funcexe_T struct. https://github.com/vim/vim/commit/c6538bcc1cdd1fb83732f22fdc69bd9bb66f968a Use FUNCEXE_INIT to initialize funcexe_T instances. call_callback() and other Vim listener related stuff is N/A. --- src/nvim/api/vim.c | 9 ++++-- src/nvim/eval.c | 70 ++++++++++++++++++++++-------------------- src/nvim/eval/funcs.c | 10 +++--- src/nvim/eval/userfunc.c | 80 +++++++++++++++++++++++------------------------- src/nvim/eval/userfunc.h | 23 ++++++++++++++ src/nvim/lua/executor.c | 10 +++--- src/nvim/normal.c | 12 ++++---- src/nvim/regexp.c | 14 ++++----- 8 files changed, 126 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 400dbb126c..ec13bdd0e2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -614,12 +614,15 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) recursive++; try_start(); typval_T rettv; - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.selfdict = self; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (see above) to capture abort-causing non-exception errors. (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, - vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, self); + vim_args, &funcexe); if (!try_end(err)) { rv = vim_to_object(&rettv); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5634eafdf2..5a6dbf7ff4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -737,15 +737,15 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; if (expr->v_type == VAR_FUNC) { const char_u *const s = expr->vval.v_string; if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, -1, rettv, argc, argv, NULL, - 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + funcexe.evaluate = true; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } } else if (expr->v_type == VAR_PARTIAL) { @@ -754,8 +754,9 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, -1, rettv, argc, argv, NULL, - 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + funcexe.evaluate = true; + funcexe.partial = partial; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } } else { @@ -1051,7 +1052,6 @@ int call_vim_function( ) FUNC_ATTR_NONNULL_ALL { - int doesrange; int ret; int len = (int)STRLEN(func); partial_T *pt = NULL; @@ -1067,9 +1067,12 @@ int call_vim_function( } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, len, rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, pt, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = pt; + ret = call_func(func, len, rettv, argc, argv, &funcexe); fail: if (ret == FAIL) { @@ -3988,9 +3991,12 @@ static int eval7( s = xmemdupz(s, len); // Invoke the function. - ret = get_func_tv(s, len, rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, partial, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = evaluate; + funcexe.partial = partial; + ret = get_func_tv(s, len, rettv, arg, &funcexe); xfree(s); @@ -7260,10 +7266,12 @@ bool callback_call(Callback *const callback, const int argcount_in, abort(); } - int dummy; - return call_func(name, -1, rettv, argcount_in, argvars_in, - NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, partial, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = partial; + return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe); } static bool set_ref_in_callback(Callback *callback, int copyID, @@ -8426,7 +8434,6 @@ handle_subscript( int ret = OK; dict_T *selfdict = NULL; const char_u *s; - int len; typval_T functv; int slen = 0; bool lua = false; @@ -8474,9 +8481,14 @@ handle_subscript( } else { s = (char_u *)""; } - ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, pt, selfdict); + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = evaluate; + funcexe.partial = pt; + funcexe.selfdict = selfdict; + ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg, &funcexe); // Clear the funcref afterwards, so that deleting it while // evaluating the arguments is possible (see test55). @@ -10416,19 +10428,11 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; tv_list_ref(arguments); - int dummy; - (void)call_func((const char_u *)func, - name_len, - &rettv, - 2, - argvars, - NULL, - curwin->w_cursor.lnum, - curwin->w_cursor.lnum, - &dummy, - true, - NULL, - NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + (void)call_func((const char_u *)func, name_len, &rettv, 2, argvars, &funcexe); tv_list_unref(arguments); // Restore caller scope information diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 4e409cca50..363640adc5 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9420,7 +9420,6 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) int res; typval_T rettv; typval_T argv[3]; - int dummy; const char *func_name; partial_T *partial = sortinfo->item_compare_partial; @@ -9444,10 +9443,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - res = call_func((const char_u *)func_name, - -1, - &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, - partial, sortinfo->item_compare_selfdict); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = sortinfo->item_compare_selfdict; + res = call_func((const char_u *)func_name, -1, &rettv, 2, argv, &funcexe); tv_clear(&argv[0]); tv_clear(&argv[1]); diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index deddec413b..516f7a1d53 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -414,12 +414,7 @@ get_func_tv( int len, // length of "name" or -1 to use strlen() typval_T *rettv, char_u **arg, // argument, pointing to the '(' - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // return: function handled range - int evaluate, - partial_T *partial, // for extra arguments - dict_T *selfdict // Dictionary for "self" + funcexe_T *funcexe // various values ) { char_u *argp; @@ -431,12 +426,13 @@ get_func_tv( * Get the arguments. */ argp = *arg; - while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + while (argcount < MAX_FUNC_ARGS + - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) { argp = skipwhite(argp + 1); // skip the '(' or ',' if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { ret = FAIL; break; } @@ -463,9 +459,7 @@ get_func_tv( ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; } } - ret = call_func(name, len, rettv, argcount, argvars, NULL, - firstline, lastline, doesrange, evaluate, - partial, selfdict); + ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; } else if (!aborting()) { @@ -1367,7 +1361,6 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, { typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; - int dummy; int r = 0; TV_LIST_ITER(args->vval.v_list, item, { @@ -1380,9 +1373,13 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); }); - r = call_func(name, -1, rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = selfdict; + r = call_func(name, -1, rettv, argc, argv, &funcexe); func_call_skip_call: // Free the arguments. @@ -1425,10 +1422,6 @@ static void user_func_error(int error, const char_u *name) /// Call a function with its resolved parameters /// -/// "argv_func", when not NULL, can be used to fill in arguments only when the -/// invoked function uses them. It is called like this: -/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) -/// /// @return FAIL if function cannot be called, else OK (even if an error /// occurred while executing the function! Set `msg_list` to capture /// the error, see do_cmdline()). @@ -1440,15 +1433,9 @@ call_func( int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! - ArgvFunc argv_func, // function to fill in argvars - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // [out] function handled range - bool evaluate, - partial_T *partial, // optional, can be NULL - dict_T *selfdict_in // Dictionary for "self" + funcexe_T *funcexe // more arguments ) - FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6) { int ret = FAIL; int error = ERROR_NONE; @@ -1459,9 +1446,10 @@ call_func( char_u *name = NULL; int argcount = argcount_in; typval_T *argvars = argvars_in; - dict_T *selfdict = selfdict_in; + dict_T *selfdict = funcexe->selfdict; typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL int argv_clear = 0; + partial_T *partial = funcexe->partial; // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) // even when call_func() returns FAIL. @@ -1480,14 +1468,15 @@ call_func( fname = fname_trans_sid(name, fname_buf, &tofree, &error); } - *doesrange = false; + if (funcexe->doesrange != NULL) { + *funcexe->doesrange = false; + } if (partial != NULL) { // When the function has a partial with a dict and there is a dict // argument, use the dict argument. That is backwards compatible. // When the dict was bound explicitly use the one from the partial. - if (partial->pt_dict != NULL - && (selfdict_in == NULL || !partial->pt_auto)) { + if (partial->pt_dict != NULL && (selfdict == NULL || !partial->pt_auto)) { selfdict = partial->pt_dict; } if (error == ERROR_NONE && partial->pt_argc > 0) { @@ -1506,7 +1495,7 @@ call_func( } } - if (error == ERROR_NONE && evaluate) { + if (error == ERROR_NONE && funcexe->evaluate) { char_u *rfname = fname; // Ignore "g:" before a function name. @@ -1549,13 +1538,13 @@ call_func( cfunc_T cb = fp->uf_cb; error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state); } else if (fp != NULL) { - if (argv_func != NULL) { + if (funcexe->argv_func != NULL) { // postponed filling in the arguments, do it now - argcount = argv_func(argcount, argvars, argv_clear, - fp->uf_args.ga_len); + argcount = funcexe->argv_func(argcount, argvars, argv_clear, + fp->uf_args.ga_len); } - if (fp->uf_flags & FC_RANGE) { - *doesrange = true; + if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { + *funcexe->doesrange = true; } if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { error = ERROR_TOOFEW; @@ -1565,7 +1554,8 @@ call_func( error = ERROR_DICT; } else { // Call the user function. - call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + call_user_func(fp, argcount, argvars, rettv, funcexe->firstline, + funcexe->lastline, (fp->uf_flags & FC_DICT) ? selfdict : NULL); error = ERROR_NONE; } @@ -2901,7 +2891,7 @@ void ex_call(exarg_T *eap) int len; typval_T rettv; linenr_T lnum; - int doesrange; + bool doesrange; bool failed = false; funcdict_T fudi; partial_T *partial = NULL; @@ -2965,9 +2955,15 @@ void ex_call(exarg_T *eap) curwin->w_cursor.coladd = 0; } arg = startarg; - if (get_func_tv(name, -1, &rettv, &arg, - eap->line1, eap->line2, &doesrange, - true, partial, fudi.fd_dict) == FAIL) { + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = eap->line1; + funcexe.lastline = eap->line2; + funcexe.doesrange = &doesrange; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = fudi.fd_dict; + if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { failed = true; break; } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index e8ad0bf1da..9af35e0411 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -30,9 +30,32 @@ typedef enum { ERROR_DELETED, } FnameTransError; +/// Used in funcexe_T. Returns the new argcount. typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip, int called_func_argcount); +/// Structure passed between functions dealing with function call execution. +typedef struct { + ArgvFunc argv_func; ///< when not NULL, can be used to fill in arguments only + ///< when the invoked function uses them + linenr_T firstline; ///< first line of range + linenr_T lastline; ///< last line of range + bool *doesrange; ///< [out] if not NULL: function handled range + bool evaluate; ///< actually evaluate expressions + partial_T *partial; ///< for extra arguments + dict_T *selfdict; ///< Dictionary for "self" +} funcexe_T; + +#define FUNCEXE_INIT (funcexe_T) { \ + .argv_func = NULL, \ + .firstline = 0, \ + .lastline = 0, \ + .doesrange = NULL, \ + .evaluate = false, \ + .partial = NULL, \ + .selfdict = NULL, \ +} + #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5799c3ee98..cb3cb6e73e 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -780,13 +780,13 @@ int nlua_call(lua_State *lstate) try_start(); typval_T rettv; - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (TRY_WRAP) to capture abort-causing non-exception errors. - (void)call_func(name, (int)name_len, &rettv, nargs, - vim_args, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, NULL); + (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe); if (!try_end(&err)) { nlua_push_typval(lstate, &rettv, false); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2a530db934..e42ec92a4c 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2531,12 +2531,12 @@ do_mouse ( } }; typval_T rettv; - int doesrange; - (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, - -1, - &rettv, ARRAY_SIZE(argv), argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, -1, + &rettv, ARRAY_SIZE(argv), argv, &funcexe); tv_clear(&rettv); break; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 6379174938..98a46cf781 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6726,26 +6726,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, if (expr != NULL) { typval_T argv[2]; - int dummy; typval_T rettv; staticList10_T matchList = TV_LIST_STATIC10_INIT; - rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.argv_func = fill_submatch_list; + funcexe.evaluate = true; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; - call_func(s, -1, &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, NULL, NULL); + call_func(s, -1, &rettv, 1, argv, &funcexe); } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; s = partial_name(partial); - call_func(s, -1, &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, partial, NULL); + funcexe.partial = partial; + call_func(s, -1, &rettv, 1, argv, &funcexe); } if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. -- cgit From e6be6c307a832d661d2a6269ad2d322e4bf5e9cc Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Thu, 5 Aug 2021 19:46:42 +0100 Subject: vim-patch:8.1.1803: all builtin functions are global Problem: All builtin functions are global. Solution: Add the method call operator ->. Implemented for a limited number of functions. https://github.com/vim/vim/commit/ac92e25a33c37ec5becbfffeccda136c73b761ac - Note that to *exactly* port hunk @@ -7376,18 +7444,19 from handle_subscript(), we need the :scriptversion patches (I have an open PR for those, but this patch works fine without them anyway). - Port call_internal_func() from v7.4.2058. - Adjust some error messages in tests, as they rely on the Blob patches. - Add a modeline to test_method.vim. Ignore the global_functions and base_method tables and prefer the current GPerf implementation. Instead, add an extra base_arg field to VimLFuncDef that holds the number of the argument to use as the base (1-indexed, so that 0 may be used to refer to functions that cannot be used as methods). This also means we support using any argument as a base from the get-go, rather than just the first (Vim includes this ability in future patches, however). To mark a function as usable as a method, use the "base" key as described in eval.lua. --- src/nvim/eval.c | 82 ++++++++++++++++++++++++++++++++++++---- src/nvim/eval.lua | 51 +++++++++++++------------ src/nvim/eval/funcs.c | 44 +++++++++++++++++++++ src/nvim/eval/funcs.h | 1 + src/nvim/eval/userfunc.c | 24 +++++------- src/nvim/eval/userfunc.h | 2 + src/nvim/generators/gen_eval.lua | 7 ++-- src/nvim/globals.h | 1 + src/nvim/testdir/test_method.vim | 68 +++++++++++++++++++++++++++++++++ 9 files changed, 231 insertions(+), 49 deletions(-) create mode 100644 src/nvim/testdir/test_method.vim (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5a6dbf7ff4..39e8686580 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3809,6 +3809,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) // + in front unary plus (ignored) // trailing [] subscript in String or List // trailing .name entry in Dictionary +// trailing ->name() method call // // "arg" must point to the first non-white of the expression. // "arg" is advanced to the next non-white after the recognized expression. @@ -4080,6 +4081,63 @@ static int eval7( return ret; } +/// Evaluate "->method()". +/// @note "*arg" points to the '-'. +/// @return FAIL or OK. "*arg" is advanced to after the ')'. +static int eval_method(char_u **const arg, typval_T *const rettv, + const bool evaluate, const bool verbose) + FUNC_ATTR_NONNULL_ALL +{ + // Skip over the ->. + *arg += 2; + + // Locate the method name. + const char_u *const name = *arg; + size_t len; + for (len = 0; ASCII_ISALNUM(name[len]) || name[len] == '_'; len++) { + } + + if (len == 0) { + if (verbose) { + EMSG(_("E260: Missing name after ->")); + } + return FAIL; + } + + // Check for the "(". Skip over white space after it. + if (name[len] != '(') { + if (verbose) { + EMSG2(_(e_missingparen), name); + } + return FAIL; + } + *arg += len; + + typval_T base = *rettv; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = evaluate; + funcexe.basetv = &base; + rettv->v_type = VAR_UNKNOWN; + int ret = get_func_tv(name, len, rettv, arg, &funcexe); + + // Clear the funcref afterwards, so that deleting it while + // evaluating the arguments is possible (see test55). + if (evaluate) { + tv_clear(&base); + } + + // Stop the expression evaluation when immediately aborting on + // error, or when an interrupt occurred or an exception was thrown + // but not caught. + if (aborting()) { + if (ret == OK) { + tv_clear(rettv); + } + ret = FAIL; + } + return ret; +} + // TODO(ZyX-I): move to eval/expressions /* @@ -8420,9 +8478,13 @@ int check_luafunc_name(const char *str, bool paren) } } -/// Handle expr[expr], expr[expr:expr] subscript and .name lookup. -/// Also handle function call with Funcref variable: func(expr) -/// Can all be combined: dict.func(expr)[idx]['func'](expr) +/// Handle: +/// - expr[expr], expr[expr:expr] subscript +/// - ".name" lookup +/// - function call with Funcref variable: func(expr) +/// - method call: var->method() +/// +/// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len() int handle_subscript( const char **const arg, @@ -8456,12 +8518,11 @@ handle_subscript( } } - while (ret == OK - && (**arg == '[' - || (**arg == '.' && rettv->v_type == VAR_DICT) - || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) - && !ascii_iswhite(*(*arg - 1))) { + && (((**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT) + || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) + && !ascii_iswhite(*(*arg - 1))) + || (**arg == '-' && (*arg)[1] == '>'))) { if (**arg == '(') { partial_T *pt = NULL; // need to copy the funcref so that we can clear rettv @@ -8507,6 +8568,11 @@ handle_subscript( } tv_dict_unref(selfdict); selfdict = NULL; + } else if (**arg == '-') { + if (eval_method((char_u **)arg, rettv, evaluate, verbose) == FAIL) { + tv_clear(rettv); + ret = FAIL; + } } else { // **arg == '[' || **arg == '.' tv_dict_unref(selfdict); if (rettv->v_type == VAR_DICT) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index eb20cd1bc8..f94ecffed9 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5,6 +5,9 @@ -- args Number of arguments, list with maximum and minimum number of arguments -- or list with a minimum number of arguments only. Defaults to zero -- arguments. +-- base For methods: the argument to use as the base argument (1-indexed): +-- base->method() +-- Defaults to zero (function cannot be used as a method). -- func Name of the C function which implements the VimL function. Defaults to -- `f_{funcname}`. @@ -16,7 +19,7 @@ return { funcs={ abs={args=1}, acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc - add={args=2}, + add={args=2, base=1}, ['and']={args=2}, api_info={}, append={args=2}, @@ -73,10 +76,10 @@ return { complete_check={}, complete_info={args={0, 1}}, confirm={args={1, 4}}, - copy={args=1}, + copy={args=1, base=1}, cos={args=1, func="float_op_wrapper", data="&cos"}, cosh={args=1, func="float_op_wrapper", data="&cosh"}, - count={args={2, 4}}, + count={args={2, 4}, base=1}, cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, ctxpop={}, @@ -93,7 +96,7 @@ return { did_filetype={}, diff_filler={args=1}, diff_hlID={args=2}, - empty={args=1}, + empty={args=1, base=1}, environ={}, escape={args=2}, eval={args=1}, @@ -105,12 +108,12 @@ return { exp={args=1, func="float_op_wrapper", data="&exp"}, expand={args={1, 3}}, expandcmd={args=1}, - extend={args={2, 3}}, + extend={args={2, 3}, base=1}, feedkeys={args={1, 2}}, file_readable={args=1, func='f_filereadable'}, -- obsolete filereadable={args=1}, filewritable={args=1}, - filter={args=2}, + filter={args=2, base=1}, finddir={args={1, 3}}, findfile={args={1, 3}}, flatten={args={1, 2}}, @@ -128,7 +131,7 @@ return { funcref={args={1, 3}}, ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, - get={args={2, 3}}, + get={args={2, 3}, base=1}, getbufinfo={args={0, 1}}, getbufline={args={2, 3}}, getbufvar={args={2, 3}}, @@ -187,14 +190,14 @@ return { hostname={}, iconv={args=3}, indent={args=1}, - index={args={2, 4}}, + index={args={2, 4}, base=1}, input={args={1, 3}}, inputdialog={args={1, 3}}, inputlist={args=1}, inputrestore={}, inputsave={}, inputsecret={args={1, 2}}, - insert={args={2, 3}}, + insert={args={2, 3}, base=1}, interrupt={args=0}, invert={args=1}, isdirectory={args=1}, @@ -202,7 +205,7 @@ return { islocked={args=1}, isnan={args=1}, id={args=1}, - items={args=1}, + items={args=1, base=1}, jobclose={args={1, 2}, func="f_chanclose"}, jobpid={args=1}, jobresize={args=3}, @@ -210,12 +213,12 @@ return { jobstart={args={1, 2}}, jobstop={args=1}, jobwait={args={1, 2}}, - join={args={1, 2}}, + join={args={1, 2}, base=1}, json_decode={args=1}, json_encode={args=1}, - keys={args=1}, + keys={args=1, base=1}, last_buffer_nr={}, -- obsolete - len={args=1}, + len={args=1, base=1}, libcall={args=3}, libcallnr={args=3}, line={args={1, 2}}, @@ -226,7 +229,7 @@ return { log={args=1, func="float_op_wrapper", data="&log"}, log10={args=1, func="float_op_wrapper", data="&log10"}, luaeval={args={1, 2}}, - map={args=2}, + map={args=2, base=1}, maparg={args={1, 4}}, mapcheck={args={1, 3}}, match={args={2, 4}}, @@ -238,9 +241,9 @@ return { matchlist={args={2, 4}}, matchstr={args={2, 4}}, matchstrpos={args={2,4}}, - max={args=1}, + max={args=1, base=1}, menu_get={args={1, 2}}, - min={args=1}, + min={args=1, base=1}, mkdir={args={1, 3}}, mode={args={0, 1}}, msgpackdump={args=1}, @@ -270,11 +273,11 @@ return { reltime={args={0, 2}}, reltimefloat={args=1}, reltimestr={args=1}, - remove={args={2, 3}}, + remove={args={2, 3}, base=1}, rename={args=2}, - ['repeat']={args=2}, + ['repeat']={args=2, base=1}, resolve={args=1}, - reverse={args=1}, + reverse={args=1, base=1}, round={args=1, func="float_op_wrapper", data="&round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, @@ -327,7 +330,7 @@ return { sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, sockconnect={args={2,3}}, - sort={args={1, 3}}, + sort={args={1, 3}, base=1}, soundfold={args=1}, stdioopen={args=1}, spellbadword={args={0, 1}}, @@ -344,7 +347,7 @@ return { strftime={args={1, 2}}, strgetchar={args={2, 2}}, stridx={args={2, 3}}, - string={args=1}, + string={args=1, base=1}, strlen={args=1}, strpart={args={2, 4}}, strptime={args=2}, @@ -383,11 +386,11 @@ return { tr={args=3}, trim={args={1,3}}, trunc={args=1, func="float_op_wrapper", data="&trunc"}, - type={args=1}, + type={args=1, base=1}, undofile={args=1}, undotree={}, - uniq={args={1, 3}}, - values={args=1}, + uniq={args={1, 3}, base=1}, + values={args=1, base=1}, virtcol={args=1}, visualmode={args={0, 1}}, wait={args={2,3}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 363640adc5..787d5aaf78 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -175,6 +175,50 @@ const VimLFuncDef *find_internal_func(const char *const name) return find_internal_func_gperf(name, len); } +int call_internal_func(const char_u *const fname, const int argcount, + typval_T *const argvars, typval_T *const rettv) + FUNC_ATTR_NONNULL_ALL +{ + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef == NULL) { + return ERROR_UNKNOWN; + } else if (argcount < fdef->min_argc) { + return ERROR_TOOFEW; + } else if (argcount > fdef->max_argc) { + return ERROR_TOOMANY; + } + argvars[argcount].v_type = VAR_UNKNOWN; + fdef->func(argvars, rettv, fdef->data); + return ERROR_NONE; +} + +/// Invoke a method for base->method(). +int call_internal_method(const char_u *const fname, const int argcount, + typval_T *const argvars, typval_T *const rettv, + typval_T *const basetv) + FUNC_ATTR_NONNULL_ALL +{ + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef == NULL || fdef->base_arg == 0) { + return ERROR_UNKNOWN; + } else if (argcount + 1 < fdef->min_argc) { + return ERROR_TOOFEW; + } else if (argcount + 1 > fdef->max_argc) { + return ERROR_TOOMANY; + } + + typval_T argv[MAX_FUNC_ARGS + 1]; + const ptrdiff_t base_index = fdef->base_arg - 1; + memcpy(argv, argvars, base_index * sizeof(typval_T)); + argv[base_index] = *basetv; + memcpy(argv + base_index + 1, argvars + base_index, + (argcount - base_index) * sizeof(typval_T)); + argv[argcount + 1].v_type = VAR_UNKNOWN; + + fdef->func(argv, rettv, fdef->data); + return ERROR_NONE; +} + /* * Return TRUE for a non-zero Number and a non-empty String. */ diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index a343290734..3ad0b8282a 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -14,6 +14,7 @@ typedef struct fst { char *name; ///< Name of the function. uint8_t min_argc; ///< Minimal number of arguments. uint8_t max_argc; ///< Maximal number of arguments. + uint8_t base_arg; ///< Method base arg # (1-indexed), or 0 if not a method. VimLFunc func; ///< Function implementation. FunPtr data; ///< Userdata for function implementation. } VimLFuncDef; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 516f7a1d53..ec43987b07 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1514,7 +1514,10 @@ call_func( } } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // User defined function. - if (fp == NULL) { + if (funcexe->basetv != NULL) { + // TODO(seandewar): support User function: base->Method() + fp = NULL; + } else if (fp == NULL) { fp = find_func(rfname); } @@ -1560,20 +1563,13 @@ call_func( error = ERROR_NONE; } } + } else if (funcexe->basetv != NULL) { + // Find the method name in the table, call its implementation. + error = call_internal_method(fname, argcount, argvars, rettv, + funcexe->basetv); } else { // Find the function name in the table, call its implementation. - const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef != NULL) { - if (argcount < fdef->min_argc) { - error = ERROR_TOOFEW; - } else if (argcount > fdef->max_argc) { - error = ERROR_TOOMANY; - } else { - argvars[argcount].v_type = VAR_UNKNOWN; - fdef->func(argvars, rettv, fdef->data); - error = ERROR_NONE; - } - } + error = call_internal_func(fname, argcount, argvars, rettv); } /* * The function call (or "FuncUndefined" autocommand sequence) might @@ -2937,7 +2933,7 @@ void ex_call(exarg_T *eap) rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. if (*startarg != '(') { - EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + EMSG2(_(e_missingparen), eap->arg); goto end; } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index 9af35e0411..513473449a 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -44,6 +44,7 @@ typedef struct { bool evaluate; ///< actually evaluate expressions partial_T *partial; ///< for extra arguments dict_T *selfdict; ///< Dictionary for "self" + typval_T *basetv; ///< base for base->method() } funcexe_T; #define FUNCEXE_INIT (funcexe_T) { \ @@ -54,6 +55,7 @@ typedef struct { .evaluate = false, \ .partial = NULL, \ .selfdict = NULL, \ + .basetv = NULL, \ } #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 679895421a..aa2274e0a2 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -42,7 +42,7 @@ gperfpipe:write([[ %language=ANSI-C %global-table %readonly-tables -%define initializer-suffix ,0,0,NULL,NULL +%define initializer-suffix ,0,0,0,NULL,NULL %define word-array-name functions %define hash-function-name hash_internal_func_gperf %define lookup-function-name find_internal_func_gperf @@ -59,9 +59,10 @@ for name, def in pairs(funcs) do elseif #args == 1 then args[2] = 'MAX_FUNC_ARGS' end + local base = def.base or 0 local func = def.func or ('f_' .. name) local data = def.data or "NULL" - gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n') - :format(name, args[1], args[2], func, data)) + gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n') + :format(name, args[1], args[2], base, func, data)) end gperfpipe:close() diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 96acca4ac7..2a72dbcd09 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -972,6 +972,7 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using not in a script context")); +EXTERN char_u e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim new file mode 100644 index 0000000000..1c0de01ac5 --- /dev/null +++ b/src/nvim/testdir/test_method.vim @@ -0,0 +1,68 @@ +" Tests for ->method() + +func Test_list() + let l = [1, 2, 3] + call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4)) + call assert_equal(l, l->copy()) + call assert_equal(1, l->count(2)) + call assert_false(l->empty()) + call assert_true([]->empty()) + call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5])) + call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2')) + call assert_equal(2, l->get(1)) + call assert_equal(1, l->index(2)) + call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) + call assert_fails('let x = l->items()', 'E715:') + call assert_equal('1 2 3', l->join()) + call assert_fails('let x = l->keys()', 'E715:') + call assert_equal(3, l->len()) + call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) + call assert_equal(3, l->max()) + call assert_equal(1, l->min()) + call assert_equal(2, [1, 2, 3]->remove(1)) + call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2)) + call assert_equal([3, 2, 1], [1, 2, 3]->reverse()) + call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort()) + call assert_equal('[1, 2, 3]', l->string()) + call assert_equal(v:t_list, l->type()) + call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) + call assert_fails('let x = l->values()', 'E715:') +endfunc + +func Test_dict() + let d = #{one: 1, two: 2, three: 3} + + call assert_equal(d, d->copy()) + call assert_equal(1, d->count(2)) + call assert_false(d->empty()) + call assert_true({}->empty()) + call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4})) + call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4')) + call assert_equal(2, d->get('two')) + " Nvim doesn't support Blobs yet; expect a different emsg + " call assert_fails("let x = d->index(2)", 'E897:') + " call assert_fails("let x = d->insert(0)", 'E899:') + call assert_fails("let x = d->index(2)", 'E714:') + call assert_fails("let x = d->insert(0)", 'E686:') + call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items()) + call assert_fails("let x = d->join()", 'E714:') + call assert_equal(['one', 'two', 'three'], d->keys()) + call assert_equal(3, d->len()) + call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1')) + call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1')) + call assert_equal(3, d->max()) + call assert_equal(1, d->min()) + call assert_equal(2, d->remove("two")) + let d.two = 2 + call assert_fails('let x = d->repeat(2)', 'E731:') + " Nvim doesn't support Blobs yet; expect a different emsg + " call assert_fails('let x = d->reverse()', 'E899:') + call assert_fails('let x = d->reverse()', 'E686:') + call assert_fails('let x = d->sort()', 'E686:') + call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string()) + call assert_equal(v:t_dict, d->type()) + call assert_fails('let x = d->uniq()', 'E686:') + call assert_equal([1, 2, 3], d->values()) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From 003c8acc8a9863932430bfb51bee8403b964c19b Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 6 Aug 2021 17:09:47 +0100 Subject: vim-patch:8.1.1807: more functions can be used as a method Problem: More functions can be used as a method. Solution: Add append(), appendbufline(), assert_equal(), etc. Also add the :eval command. https://github.com/vim/vim/commit/25e42231d3ee27feec2568fa4be2aa2bfba82ae5 :eval is already ported. --- src/nvim/eval.lua | 16 ++++++++++------ src/nvim/eval/funcs.c | 5 +++-- src/nvim/eval/funcs.h | 6 +++++- src/nvim/generators/gen_eval.lua | 4 ++-- src/nvim/testdir/test_method.vim | 25 ++++++++++++++++++++++--- 5 files changed, 42 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index f94ecffed9..506368a3b2 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -7,7 +7,7 @@ -- arguments. -- base For methods: the argument to use as the base argument (1-indexed): -- base->method() --- Defaults to zero (function cannot be used as a method). +-- Defaults to BASE_NONE (function cannot be used as a method). -- func Name of the C function which implements the VimL function. Defaults to -- `f_{funcname}`. @@ -15,6 +15,10 @@ local varargs = function(nr) return {nr} end +-- Usable with the base key: use the last function argument as the method base. +-- Value is from funcs.h file. "BASE_" prefix is omitted. +local LAST = "BASE_LAST" + return { funcs={ abs={args=1}, @@ -22,15 +26,15 @@ return { add={args=2, base=1}, ['and']={args=2}, api_info={}, - append={args=2}, - appendbufline={args=3}, + append={args=2, base=LAST}, + appendbufline={args=3, base=LAST}, argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1}}, - assert_equal={args={2, 3}}, + assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, assert_fails={args={1, 3}}, @@ -38,7 +42,7 @@ return { assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, assert_nobeep={args={1}}, - assert_notequal={args={2, 3}}, + assert_notequal={args={2, 3}, base=2}, assert_notmatch={args={2, 3}}, assert_report={args=1}, assert_true={args={1, 2}}, @@ -99,7 +103,7 @@ return { empty={args=1, base=1}, environ={}, escape={args=2}, - eval={args=1}, + eval={args=1, base=1}, eventhandler={}, executable={args=1}, execute={args={1, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 787d5aaf78..e090f3b37f 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -199,7 +199,7 @@ int call_internal_method(const char_u *const fname, const int argcount, FUNC_ATTR_NONNULL_ALL { const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef == NULL || fdef->base_arg == 0) { + if (fdef == NULL || fdef->base_arg == BASE_NONE) { return ERROR_UNKNOWN; } else if (argcount + 1 < fdef->min_argc) { return ERROR_TOOFEW; @@ -208,7 +208,8 @@ int call_internal_method(const char_u *const fname, const int argcount, } typval_T argv[MAX_FUNC_ARGS + 1]; - const ptrdiff_t base_index = fdef->base_arg - 1; + const ptrdiff_t base_index + = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1; memcpy(argv, argvars, base_index * sizeof(typval_T)); argv[base_index] = *basetv; memcpy(argv + base_index + 1, argvars + base_index, diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 3ad0b8282a..c6a0cb959e 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -9,12 +9,16 @@ typedef void (*FunPtr)(void); /// Prototype of C function that implements VimL function typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); +/// Special flags for base_arg @see VimLFuncDef +#define BASE_NONE 0 ///< Not a method (no base argument). +#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base. + /// Structure holding VimL function definition typedef struct fst { char *name; ///< Name of the function. uint8_t min_argc; ///< Minimal number of arguments. uint8_t max_argc; ///< Maximal number of arguments. - uint8_t base_arg; ///< Method base arg # (1-indexed), or 0 if not a method. + uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. VimLFunc func; ///< Function implementation. FunPtr data; ///< Userdata for function implementation. } VimLFuncDef; diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index aa2274e0a2..945fa5099f 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -42,7 +42,7 @@ gperfpipe:write([[ %language=ANSI-C %global-table %readonly-tables -%define initializer-suffix ,0,0,0,NULL,NULL +%define initializer-suffix ,0,0,BASE_NONE,NULL,NULL %define word-array-name functions %define hash-function-name hash_internal_func_gperf %define lookup-function-name find_internal_func_gperf @@ -59,7 +59,7 @@ for name, def in pairs(funcs) do elseif #args == 1 then args[2] = 'MAX_FUNC_ARGS' end - local base = def.base or 0 + local base = def.base or "BASE_NONE" local func = def.func or ('f_' .. name) local data = def.data or "NULL" gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n') diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim index 1c0de01ac5..43ed830aba 100644 --- a/src/nvim/testdir/test_method.vim +++ b/src/nvim/testdir/test_method.vim @@ -3,18 +3,23 @@ func Test_list() let l = [1, 2, 3] call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4)) + eval l->assert_equal(l) + eval l->assert_equal(l, 'wrong') + eval l->assert_notequal([3, 2, 1]) + eval l->assert_notequal([3, 2, 1], 'wrong') call assert_equal(l, l->copy()) call assert_equal(1, l->count(2)) call assert_false(l->empty()) call assert_true([]->empty()) + call assert_equal(579, ['123', '+', '456']->join()->eval()) call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5])) call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2')) call assert_equal(2, l->get(1)) call assert_equal(1, l->index(2)) call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) - call assert_fails('let x = l->items()', 'E715:') + call assert_fails('eval l->items()', 'E715:') call assert_equal('1 2 3', l->join()) - call assert_fails('let x = l->keys()', 'E715:') + call assert_fails('eval l->keys()', 'E715:') call assert_equal(3, l->len()) call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) call assert_equal(3, l->max()) @@ -26,7 +31,7 @@ func Test_list() call assert_equal('[1, 2, 3]', l->string()) call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) - call assert_fails('let x = l->values()', 'E715:') + call assert_fails('eval l->values()', 'E715:') endfunc func Test_dict() @@ -65,4 +70,18 @@ func Test_dict() call assert_equal([1, 2, 3], d->values()) endfunc +func Test_append() + new + eval ['one', 'two', 'three']->append(1) + call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) + + %del + let bnr = bufnr('') + wincmd w + eval ['one', 'two', 'three']->appendbufline(bnr, 1) + call assert_equal(['', 'one', 'two', 'three'], getbufline(bnr, 1, '$')) + + exe 'bwipe! ' .. bnr +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From aa2dc8b7b4e0e86c9102f2df6b670317c5693657 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 6 Aug 2021 18:10:30 +0100 Subject: vim-patch:8.1.1809: more functions can be used as a method Problem: More functions can be used as a method. Solution: Add has_key(), split(), str2list(), etc. https://github.com/vim/vim/commit/a74e4946de074d2916e3d6004f7fa1810d12dda9 --- src/nvim/eval.lua | 22 +++++++++++----------- src/nvim/testdir/test_diffmode.vim | 12 ++++++------ src/nvim/testdir/test_method.vim | 11 +++++++++++ src/nvim/testdir/test_syntax.vim | 4 ++-- src/nvim/testdir/test_system.vim | 4 ++-- 5 files changed, 32 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 506368a3b2..50b6eaadfd 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -180,7 +180,7 @@ return { glob2regpat={args=1}, globpath={args={2, 5}}, has={args=1}, - has_key={args=2}, + has_key={args=2, base=1}, haslocaldir={args={0,2}}, hasmapto={args={1, 3}}, highlightID={args=1, func='f_hlID'}, -- obsolete @@ -339,11 +339,11 @@ return { stdioopen={args=1}, spellbadword={args={0, 1}}, spellsuggest={args={1, 3}}, - split={args={1, 3}}, + split={args={1, 3}, base=1}, sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, stdpath={args=1}, str2float={args=1}, - str2list={args={1, 2}}, + str2list={args={1, 2}, base=1}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, strchars={args={1,2}}, @@ -352,23 +352,23 @@ return { strgetchar={args={2, 2}}, stridx={args={2, 3}}, string={args=1, base=1}, - strlen={args=1}, + strlen={args=1, base=1}, strpart={args={2, 4}}, strptime={args=2}, strridx={args={2, 3}}, - strtrans={args=1}, - strwidth={args=1}, + strtrans={args=1, base=1}, + strwidth={args=1, base=1}, submatch={args={1, 2}}, - substitute={args=4}, + substitute={args=4, base=1}, swapinfo={args={1}}, swapname={args={1}}, synID={args=3}, - synIDattr={args={2, 3}}, - synIDtrans={args=1}, + synIDattr={args={2, 3}, base=1}, + synIDtrans={args=1, base=1}, synconcealed={args=2}, synstack={args=2}, - system={args={1, 2}}, - systemlist={args={1, 3}}, + system={args={1, 2}, base=1}, + systemlist={args={1, 3}, base=1}, tabpagebuflist={args={0, 1}}, tabpagenr={args={0, 1}}, tabpagewinnr={args={1, 2}}, diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8592f48af7..1552bc4f78 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -741,16 +741,16 @@ func Test_diff_hlID() diffthis redraw - call assert_equal(synIDattr(diff_hlID(-1, 1), "name"), "") + call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("") call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) - call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") + call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange") call assert_equal(diff_hlID(1, 2), hlID("DiffText")) - call assert_equal(synIDattr(diff_hlID(1, 2), "name"), "DiffText") - call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") + call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) - call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "DiffAdd") - call assert_equal(synIDattr(diff_hlID(4, 1), "name"), "") + call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") + call diff_hlID(4, 1)->synIDattr("name")->assert_equal("") wincmd w call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim index 43ed830aba..ef87773924 100644 --- a/src/nvim/testdir/test_method.vim +++ b/src/nvim/testdir/test_method.vim @@ -49,6 +49,7 @@ func Test_dict() " call assert_fails("let x = d->insert(0)", 'E899:') call assert_fails("let x = d->index(2)", 'E714:') call assert_fails("let x = d->insert(0)", 'E686:') + call assert_true(d->has_key('two')) call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items()) call assert_fails("let x = d->join()", 'E714:') call assert_equal(['one', 'two', 'three'], d->keys()) @@ -70,6 +71,16 @@ func Test_dict() call assert_equal([1, 2, 3], d->values()) endfunc +func Test_string() + call assert_equal(['1', '2', '3'], '1 2 3'->split()) + call assert_equal([1, 2, 3], '1 2 3'->split()->map({i, v -> str2nr(v)})) + call assert_equal([65, 66, 67], 'ABC'->str2list()) + call assert_equal(3, 'ABC'->strlen()) + call assert_equal('a^Mb^[c', "a\rb\ec"->strtrans()) + call assert_equal(4, "aあb"->strwidth()) + call assert_equal('axc', 'abc'->substitute('b', 'x', '')) +endfunc + func Test_append() new eval ['one', 'two', 'three']->append(1) diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 875e23894f..2344bac498 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -546,8 +546,8 @@ func Test_synstack_synIDtrans() call assert_equal([], synstack(1, 1)) norm f/ - call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) - call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + eval synstack(line("."), col("."))->map('synIDattr(v:val, "name")')->assert_equal(['cComment', 'cCommentStart']) + eval synstack(line("."), col("."))->map('synIDattr(synIDtrans(v:val), "name")')->assert_equal(['Comment', 'Comment']) norm fA call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 6bbe714d19..7b8ee778cc 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -7,10 +7,10 @@ func Test_System() if !executable('echo') || !executable('cat') || !executable('wc') return endif - let out = system('echo 123') + let out = 'echo 123'->system() call assert_equal("123\n", out) - let out = systemlist('echo 123') + let out = 'echo 123'->systemlist() if &shell =~# 'cmd.exe$' call assert_equal(["123\r"], out) else -- cgit From 8d1ca37d1f413b08f5ff9ceb502fa35ffd6034c7 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Fri, 6 Aug 2021 18:31:27 +0100 Subject: vim-patch:8.1.1816: cannot use a user defined function as a method Problem: Cannot use a user defined function as a method. Solution: Pass the base as the first argument to the user defined function after "->". (partly by FUJIWARA Takuya) https://github.com/vim/vim/commit/fcfe1a9b8950b8b211ab3b24d84b17c6847ea43f --- src/nvim/eval.c | 6 ++++-- src/nvim/eval/userfunc.c | 23 ++++++++++++++++------- src/nvim/testdir/sautest/autoload/foo.vim | 4 ++++ src/nvim/testdir/test_autoload.vim | 2 ++ src/nvim/testdir/test_user_func.vim | 18 +++++++++++++++++- 5 files changed, 43 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 39e8686580..021a9a75b8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4031,7 +4031,7 @@ static int eval7( *arg = skipwhite(*arg); // Handle following '[', '(' and '.' for expr[expr], expr.name, - // expr(expr). + // expr(expr), expr->name(expr) if (ret == OK) { ret = handle_subscript((const char **)arg, rettv, evaluate, true); } @@ -4094,7 +4094,7 @@ static int eval_method(char_u **const arg, typval_T *const rettv, // Locate the method name. const char_u *const name = *arg; size_t len; - for (len = 0; ASCII_ISALNUM(name[len]) || name[len] == '_'; len++) { + for (len = 0; eval_isnamec(name[len]); len++) { } if (len == 0) { @@ -4113,6 +4113,8 @@ static int eval_method(char_u **const arg, typval_T *const rettv, } *arg += len; + // TODO(seandewar): if "name" is a function reference, resolve it. + typval_T base = *rettv; funcexe_T funcexe = FUNCEXE_INIT; funcexe.evaluate = evaluate; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ec43987b07..3c0c0091f2 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1447,7 +1447,8 @@ call_func( int argcount = argcount_in; typval_T *argvars = argvars_in; dict_T *selfdict = funcexe->selfdict; - typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" or + // "funcexe->basetv" is not NULL int argv_clear = 0; partial_T *partial = funcexe->partial; @@ -1514,10 +1515,7 @@ call_func( } } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // User defined function. - if (funcexe->basetv != NULL) { - // TODO(seandewar): support User function: base->Method() - fp = NULL; - } else if (fp == NULL) { + if (fp == NULL) { fp = find_func(rfname); } @@ -1546,6 +1544,16 @@ call_func( argcount = funcexe->argv_func(argcount, argvars, argv_clear, fp->uf_args.ga_len); } + + if (funcexe->basetv != NULL) { + // Method call: base->Method() + memmove(&argv[1], argvars, sizeof(typval_T) * argcount); + argv[0] = *funcexe->basetv; + argcount++; + } else { + memcpy(argv, argvars, sizeof(typval_T) * argcount); + } + if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { *funcexe->doesrange = true; } @@ -1557,14 +1565,15 @@ call_func( error = ERROR_DICT; } els