From 32b3f820107139d7edf0c592bb06f090c3eb6c51 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 6 Jan 2021 21:59:39 +0100 Subject: [PATCH 01/22] patch 8.2.2306: Vim9: when using function reference type is not checked Problem: Vim9: when using function reference type is not checked. Solution: When using a function reference lookup the type and check the argument types. (issue #7629) --- src/eval.c | 20 +++--- src/evalfunc.c | 3 +- src/evalvars.c | 10 ++- src/proto/userfunc.pro | 4 +- src/proto/vim9type.pro | 1 + src/structs.h | 1 + src/testdir/test_vim9_func.vim | 16 +++++ src/userfunc.c | 107 ++++++++++++++++++++++----------- src/version.c | 2 + src/vim9compile.c | 4 +- src/vim9execute.c | 2 +- src/vim9type.c | 40 ++++++++++++ 12 files changed, 155 insertions(+), 55 deletions(-) diff --git a/src/eval.c b/src/eval.c index 02cfd7d7dd..373c02753c 100644 --- a/src/eval.c +++ b/src/eval.c @@ -721,8 +721,10 @@ call_func_retlist( #ifdef FEAT_FOLDING /* - * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding - * it in "*cp". Doesn't give error messages. + * Evaluate "arg", which is 'foldexpr'. + * Note: caller must set "curwin" to match "arg". + * Returns the foldlevel, and any character preceding it in "*cp". Doesn't + * give error messages. */ int eval_foldexpr(char_u *arg, int *cp) @@ -809,6 +811,7 @@ get_lval( int len; hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; + int writing; // Clear everything in "lp". CLEAR_POINTER(lp); @@ -882,10 +885,10 @@ get_lval( cc = *p; *p = NUL; - // Only pass &ht when we would write to the variable, it prevents autoload - // as well. - v = find_var(lp->ll_name, (flags & GLV_READ_ONLY) ? NULL : &ht, - flags & GLV_NO_AUTOLOAD); + // When we would write to the variable pass &ht and prevent autoload. + writing = !(flags & GLV_READ_ONLY); + v = find_var(lp->ll_name, writing ? &ht : NULL, + (flags & GLV_NO_AUTOLOAD) || writing); if (v == NULL && !quiet) semsg(_(e_undefined_variable_str), lp->ll_name); *p = cc; @@ -1972,13 +1975,15 @@ eval_func( int len = name_len; partial_T *partial; int ret = OK; + type_T *type = NULL; if (!evaluate) check_vars(s, len); // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, &partial, !evaluate); + s = deref_func_name(s, &len, &partial, + in_vim9script() ? &type : NULL, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. @@ -1996,6 +2001,7 @@ eval_func( funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; + funcexe.check_type = type; ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); } vim_free(s); diff --git a/src/evalfunc.c b/src/evalfunc.c index 07f1da9bb6..ed9f06fed4 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3497,7 +3497,8 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) { name = s; trans_name = trans_function_name(&name, &is_global, FALSE, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, + NULL, NULL, NULL); if (*name != NUL) s = NULL; } diff --git a/src/evalvars.c b/src/evalvars.c index 8aac7667d4..742c5a34f8 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2639,8 +2639,7 @@ check_vars(char_u *name, int len) * Find variable "name" in the list of variables. * Return a pointer to it if found, NULL if not found. * Careful: "a:0" variables don't have a name. - * When "htp" is not NULL we are writing to the variable, set "htp" to the - * hashtab_T used. + * When "htp" is not NULL set "htp" to the hashtab_T used. */ dictitem_T * find_var(char_u *name, hashtab_T **htp, int no_autoload) @@ -2654,12 +2653,12 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload) *htp = ht; if (ht == NULL) return NULL; - ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) return ret; // Search in parent scope for lambda - ret = find_var_in_scoped_ht(name, no_autoload || htp != NULL); + ret = find_var_in_scoped_ht(name, no_autoload); if (ret != NULL) return ret; @@ -2669,8 +2668,7 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload) ht = get_script_local_ht(); if (ht != NULL) { - ret = find_var_in_ht(ht, *name, varname, - no_autoload || htp != NULL); + ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) { if (htp != NULL) diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index f3bd2ae731..08dedae0a6 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -4,7 +4,7 @@ hashtab_T *func_tbl_get(void); char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); -char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); +char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, type_T **type, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); @@ -31,7 +31,7 @@ int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, void user_func_error(int error, char_u *name); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *printable_func_name(ufunc_T *fp); -char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); +char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type); char_u *untrans_function_name(char_u *name); void list_functions(regmatch_T *regmatch); ufunc_T *define_function(exarg_T *eap, char_u *name_arg); diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro index ae947e699f..bc306687f8 100644 --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -16,6 +16,7 @@ void type_mismatch(type_T *expected, type_T *actual); void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); int check_type(type_T *expected, type_T *actual, int give_msg, int argidx); int check_arg_type(type_T *expected, type_T *actual, int argidx); +int check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name); char_u *skip_type(char_u *start, int optional); type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error); int equal_type(type_T *type1, type_T *type2); diff --git a/src/structs.h b/src/structs.h index c016f19975..3324b067ae 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1944,6 +1944,7 @@ typedef struct { partial_T *partial; // for extra arguments dict_T *selfdict; // Dictionary for "self" typval_T *basetv; // base for base->method() + type_T *check_type; // type from funcref or NULL } funcexe_T; /* diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index e1d4853411..c26f2af5bc 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -579,6 +579,22 @@ def Test_call_funcref_wrong_args() CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:') CheckScriptFailure(head + ["funcMap['func']('str', 123, [1], 4)"] + tail, 'E118:') + + var lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + echo Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) + + lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + call Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) enddef def Test_call_lambda_args() diff --git a/src/userfunc.c b/src/userfunc.c index 38930a2169..4c59bb516e 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -727,47 +727,68 @@ errret: * name it contains, otherwise return "name". * If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set * "partialp". + * If "type" is not NULL and a Vim9 script-local variable is found look up the + * type of the variable. */ char_u * -deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload) +deref_func_name( + char_u *name, + int *lenp, + partial_T **partialp, + type_T **type, + int no_autoload) { dictitem_T *v; int cc; - char_u *s; + char_u *s = NULL; + hashtab_T *ht; if (partialp != NULL) *partialp = NULL; cc = name[*lenp]; name[*lenp] = NUL; - v = find_var(name, NULL, no_autoload); + v = find_var(name, &ht, no_autoload); name[*lenp] = cc; - if (v != NULL && v->di_tv.v_type == VAR_FUNC) + if (v != NULL) { - if (v->di_tv.vval.v_string == NULL) + if (v->di_tv.v_type == VAR_FUNC) { - *lenp = 0; - return (char_u *)""; // just in case + if (v->di_tv.vval.v_string == NULL) + { + *lenp = 0; + return (char_u *)""; // just in case + } + s = v->di_tv.vval.v_string; + *lenp = (int)STRLEN(s); } - s = v->di_tv.vval.v_string; - *lenp = (int)STRLEN(s); - return s; - } - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) - { - partial_T *pt = v->di_tv.vval.v_partial; - - if (pt == NULL) + if (v->di_tv.v_type == VAR_PARTIAL) { - *lenp = 0; - return (char_u *)""; // just in case + partial_T *pt = v->di_tv.vval.v_partial; + + if (pt == NULL) + { + *lenp = 0; + return (char_u *)""; // just in case + } + if (partialp != NULL) + *partialp = pt; + s = partial_name(pt); + *lenp = (int)STRLEN(s); + } + + if (s != NULL) + { + if (type != NULL && ht == get_script_local_ht()) + { + svar_T *sv = find_typval_in_script(&v->di_tv); + + if (sv != NULL) + *type = sv->sv_type; + } + return s; } - if (partialp != NULL) - *partialp = pt; - s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; } return name; @@ -2387,6 +2408,14 @@ call_func( } } + if (error == FCERR_NONE && funcexe->check_type != NULL && funcexe->evaluate) + { + // Check that the argument types are OK for the types of the funcref. + if (check_argument_types(funcexe->check_type, argvars, argcount, + name) == FAIL) + error = FCERR_OTHER; + } + if (error == FCERR_NONE && funcexe->evaluate) { char_u *rfname = fname; @@ -2629,7 +2658,8 @@ trans_function_name( int skip, // only find the end, don't evaluate int flags, funcdict_T *fdp, // return: info about dictionary used - partial_T **partial) // return: partial of a FuncRef + partial_T **partial, // return: partial of a FuncRef + type_T **type) // return: type of funcref if not NULL { char_u *name = NULL; char_u *start; @@ -2733,7 +2763,7 @@ trans_function_name( if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, + name = deref_func_name(lv.ll_exp_name, &len, partial, type, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) name = NULL; @@ -2741,7 +2771,8 @@ trans_function_name( else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name(*pp, &len, partial, type, + flags & TFN_NO_AUTOLOAD); if (name == *pp) name = NULL; } @@ -3064,7 +3095,7 @@ define_function(exarg_T *eap, char_u *name_arg) else { name = trans_function_name(&p, &is_global, eap->skip, - TFN_NO_AUTOLOAD, &fudi, NULL); + TFN_NO_AUTOLOAD, &fudi, NULL, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { @@ -3479,7 +3510,8 @@ define_function(exarg_T *eap, char_u *name_arg) if (*p == '!') p = skipwhite(p + 1); p += eval_fname_script(p); - vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, NULL)); + vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, + NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) @@ -3616,7 +3648,7 @@ define_function(exarg_T *eap, char_u *name_arg) { hashtab_T *ht; - v = find_var(name, &ht, FALSE); + v = find_var(name, &ht, TRUE); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), @@ -4007,7 +4039,7 @@ function_exists(char_u *name, int no_deref) flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) flag |= TFN_NO_DEREF; - p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL); + p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL, NULL); nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and @@ -4027,7 +4059,7 @@ get_expanded_name(char_u *name, int check) int is_global = FALSE; p = trans_function_name(&nm, &is_global, FALSE, - TFN_INT|TFN_QUIET, NULL, NULL); + TFN_INT|TFN_QUIET, NULL, NULL, NULL); if (p != NULL && *nm == NUL && (!check || translated_function_exists(p, is_global))) @@ -4097,7 +4129,8 @@ ex_delfunction(exarg_T *eap) int is_global = FALSE; p = eap->arg; - name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, NULL); + name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, + NULL, NULL); vim_free(fudi.fd_newkey); if (name == NULL) { @@ -4328,6 +4361,7 @@ ex_call(exarg_T *eap) funcdict_T fudi; partial_T *partial = NULL; evalarg_T evalarg; + type_T *type = NULL; fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) @@ -4343,8 +4377,8 @@ ex_call(exarg_T *eap) return; } - tofree = trans_function_name(&arg, NULL, eap->skip, - TFN_INT, &fudi, &partial); + tofree = trans_function_name(&arg, NULL, eap->skip, TFN_INT, + &fudi, &partial, in_vim9script() ? &type : NULL); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. @@ -4363,8 +4397,8 @@ ex_call(exarg_T *eap) // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, - partial != NULL ? NULL : &partial, FALSE); + name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, + in_vim9script() && type == NULL ? &type : NULL, FALSE); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. @@ -4416,6 +4450,7 @@ ex_call(exarg_T *eap) funcexe.evaluate = !eap->skip; funcexe.partial = partial; funcexe.selfdict = fudi.fd_dict; + funcexe.check_type = type; if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) { failed = TRUE; diff --git a/src/version.c b/src/version.c index 0e711ed4c8..1945a4a30b 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2306, /**/ 2305, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 9810f410ad..c3aab20d3d 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1790,9 +1790,9 @@ generate_PCALL( stack->ga_len + offset]; type_T *expected; - if (varargs && i >= type->tt_min_argcount - 1) + if (varargs && i >= type->tt_argcount - 1) expected = type->tt_args[ - type->tt_min_argcount - 1]->tt_member; + type->tt_argcount - 1]->tt_member; else expected = type->tt_args[i]; if (need_type(actual, expected, offset, diff --git a/src/vim9execute.c b/src/vim9execute.c index b6297d7eba..1376148228 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3423,7 +3423,7 @@ ex_disassemble(exarg_T *eap) } else fname = trans_function_name(&arg, &is_global, FALSE, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL); + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL); if (fname == NULL) { semsg(_(e_invarg2), eap->arg); diff --git a/src/vim9type.c b/src/vim9type.c index 2344874a33..c65f747483 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -527,6 +527,46 @@ check_arg_type(type_T *expected, type_T *actual, int argidx) return check_type(expected, actual, TRUE, argidx); } +/* + * Check that the arguments of "type" match "argvars[argcount]". + * Return OK/FAIL. + */ + int +check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name) +{ + int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0; + int i; + + if (type->tt_type != VAR_FUNC && type->tt_type != VAR_PARTIAL) + return OK; // just in case + if (argcount < type->tt_min_argcount - varargs) + { + semsg(_(e_toofewarg), name); + return FAIL; + } + if (!varargs && type->tt_argcount >= 0 && argcount > type->tt_argcount) + { + semsg(_(e_toomanyarg), name); + return FAIL; + } + if (type->tt_args == NULL) + return OK; // cannot check + + + for (i = 0; i < argcount; ++i) + { + type_T *expected; + + if (varargs && i >= type->tt_argcount - 1) + expected = type->tt_args[type->tt_argcount - 1]->tt_member; + else + expected = type->tt_args[i]; + if (check_typval_type(expected, &argvars[i], i + 1) == FAIL) + return FAIL; + } + return OK; +} + /* * Skip over a type definition and return a pointer to just after it. * When "optional" is TRUE then a leading "?" is accepted. From ebbf11c1198b7aec8a1a55f7231ecb4f1a432fa0 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 7 Jan 2021 14:45:03 +0100 Subject: [PATCH 02/22] patch 8.2.2307: a shell command in the vimrc causes terminal output Problem: A shell command in the vimrc causes terminal output. Solution: Do not call starttermcap() after a shell command if the termcap wasn't active before. --- src/ex_cmds.c | 6 +----- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ex_cmds.c b/src/ex_cmds.c index c61810fe6c..24763d94d3 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -1360,8 +1360,8 @@ do_shell( #endif #ifdef MSWIN int winstart = FALSE; - int keep_termcap = FALSE; #endif + int keep_termcap = !termcap_active; /* * Disallow shell commands for "rvim". @@ -1395,9 +1395,7 @@ do_shell( msg_putchar('\r'); // put cursor at start of line if (!autocmd_busy) { -#ifdef MSWIN if (!keep_termcap) -#endif stoptermcap(); } #ifdef MSWIN @@ -1488,9 +1486,7 @@ do_shell( } #endif // FEAT_GUI_MSWIN -#ifdef MSWIN if (!keep_termcap) // if keep_termcap is TRUE didn't stop termcap -#endif starttermcap(); // start termcap if not done by wait_return() /* diff --git a/src/version.c b/src/version.c index 1945a4a30b..3f8ed2a419 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2307, /**/ 2306, /**/ From 328eac2b5d1569c57e1130ecb9f7cca733b84d78 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 7 Jan 2021 19:23:08 +0100 Subject: [PATCH 03/22] patch 8.2.2308: Vim9: no error when assigning lambda to funcref Problem: Vim9: no error when assigning lambda to funcref without return value. Solution: Default return value to "any". (closes #7629) --- src/testdir/test_vim9_assign.vim | 7 +++++++ src/testdir/test_vim9_func.vim | 2 +- src/userfunc.c | 2 +- src/version.c | 2 ++ src/vim9compile.c | 7 +++++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index 2a21ca065f..2d137a3281 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -1091,6 +1091,13 @@ def Test_assign_lambda() assert_equal(123, FuncRef_Any()) END CheckScriptSuccess(lines) + + lines =<< trim END + var Ref: func(number) + Ref = (j) => !j + END + CheckDefFailure(lines, 'E1012: Type mismatch; expected func(number) but got func(any): bool') + CheckScriptFailure(['vim9script'] + lines, 'E1012: Type mismatch; expected func(number) but got func(any): any') enddef def Test_heredoc() diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index c26f2af5bc..9f19ebda9a 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1508,7 +1508,7 @@ def Test_unknown_function() 'delfunc g:NotExist'], 'E700:') enddef -def RefFunc(Ref: func(string): string): string +def RefFunc(Ref: func(any): any): string return Ref('more') enddef diff --git a/src/userfunc.c b/src/userfunc.c index 4c59bb516e..814d400fb2 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -668,7 +668,7 @@ get_lambda_tv( goto errret; } else - fp->uf_ret_type = &t_unknown; + fp->uf_ret_type = &t_any; } fp->uf_lines = newlines; diff --git a/src/version.c b/src/version.c index 3f8ed2a419..f39a0d3618 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2308, /**/ 2307, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index c3aab20d3d..f66b27ff50 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -857,7 +857,9 @@ use_typecheck(type_T *actual, type_T *expected) || (actual->tt_type == VAR_FUNC && (expected->tt_type == VAR_FUNC || expected->tt_type == VAR_PARTIAL) - && (actual->tt_member == &t_any || actual->tt_argcount < 0))) + && (actual->tt_member == &t_any || actual->tt_argcount < 0) + && ((actual->tt_member == &t_void) + == (expected->tt_member == &t_void)))) return TRUE; if ((actual->tt_type == VAR_LIST || actual->tt_type == VAR_DICT) && actual->tt_type == expected->tt_type) @@ -4812,7 +4814,8 @@ compile_return(char_u *arg, int check_return_type, cctx_T *cctx) { stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; if (check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL - || cctx->ctx_ufunc->uf_ret_type == &t_unknown)) + || cctx->ctx_ufunc->uf_ret_type == &t_unknown + || cctx->ctx_ufunc->uf_ret_type == &t_any)) { cctx->ctx_ufunc->uf_ret_type = stack_type; } From c37b655443e0a11a77a9f0707e3259ab4b8b3dda Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 7 Jan 2021 19:36:30 +0100 Subject: [PATCH 04/22] patch 8.2.2309: 0o777 not recognized as octal Problem: 0o777 not recognized as octal. Solution: Use vim_isodigit(). (Ken Takata, closes #7633, closes #7631) --- src/charset.c | 8 +++++++- src/testdir/test_eval_stuff.vim | 2 ++ src/version.c | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/charset.c b/src/charset.c index bf5af37205..4289360e47 100644 --- a/src/charset.c +++ b/src/charset.c @@ -1594,6 +1594,12 @@ vim_isbdigit(int c) return (c == '0' || c == '1'); } + static int +vim_isodigit(int c) +{ + return (c >= '0' && c <= '7'); +} + /* * Vim's own character class functions. These exist because many library * islower()/toupper() etc. do not work properly: they crash when used with @@ -1831,7 +1837,7 @@ vim_str2nr( // binary ptr += 2; else if ((what & STR2NR_OOCT) - && (pre == 'O' || pre == 'o') && vim_isbdigit(ptr[2]) + && (pre == 'O' || pre == 'o') && vim_isodigit(ptr[2]) && (maxlen == 0 || maxlen > 2)) // octal with prefix "0o" ptr += 2; diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim index 316fb42f22..e5f521c85c 100644 --- a/src/testdir/test_eval_stuff.vim +++ b/src/testdir/test_eval_stuff.vim @@ -218,6 +218,7 @@ func Test_vvar_scriptversion4() call assert_equal(15, 0o17) call assert_equal(15, 0O17) call assert_equal(18, 018) + call assert_equal(511, 0o777) call assert_equal(64, 0b1'00'00'00) call assert_equal(1048576, 0x10'00'00) call assert_equal(32768, 0o10'00'00) @@ -233,6 +234,7 @@ func Test_vvar_scriptversion1() call assert_equal(15, 0o17) call assert_equal(15, 0O17) call assert_equal(18, 018) + call assert_equal(511, 0o777) endfunc func Test_scriptversion_fail() diff --git a/src/version.c b/src/version.c index f39a0d3618..f4a1d58c81 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2309, /**/ 2308, /**/ From 43b69b39acb85a2aab2310cba5a2dbac338a4eb9 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 7 Jan 2021 20:23:33 +0100 Subject: [PATCH 05/22] patch 8.2.2310: Vim9: winsaveview() return type is too generic Problem: Vim9: winsaveview() return type is too generic. Solution: use dict instead of dict. (closes #7626) --- src/evalfunc.c | 2 +- src/testdir/test_vim9_builtin.vim | 10 ++++++++++ src/version.c | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/evalfunc.c b/src/evalfunc.c index ed9f06fed4..098d906ecf 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -1753,7 +1753,7 @@ static funcentry_T global_functions[] = {"winrestview", 1, 1, FEARG_1, NULL, ret_void, f_winrestview}, {"winsaveview", 0, 0, 0, NULL, - ret_dict_any, f_winsaveview}, + ret_dict_number, f_winsaveview}, {"winwidth", 1, 1, FEARG_1, NULL, ret_number, f_winwidth}, {"wordcount", 0, 0, 0, NULL, diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 4eb2d76dfd..7aef315dc8 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -786,6 +786,16 @@ def Test_winrestcmd() close enddef +def Test_winsaveview() + var view: dict = winsaveview() + + var lines =<< trim END + var view: list = winsaveview() + END + CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected list but got dict', 1) +enddef + + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index f4a1d58c81..0d9816bc91 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2310, /**/ 2309, /**/ From 17126b13969c3b91516a8e9ff80fb6a1f6924d40 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 7 Jan 2021 22:03:02 +0100 Subject: [PATCH 06/22] patch 8.2.2311: Vim9: cannot assign to variable that shadows command modifier Problem: Vim9: cannot assign to a variable that shadows a command modifier. Solution: Check for assignment after possible command modifier. (closes #7632) --- src/ex_docmd.c | 19 +++++ src/testdir/test_vim9_assign.vim | 21 +++++ src/version.c | 2 + src/vim9compile.c | 139 +++++++++++++++++-------------- 4 files changed, 120 insertions(+), 61 deletions(-) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 5a3a37abcb..9659c20b1f 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2738,6 +2738,25 @@ parse_command_modifiers( } p = skip_range(eap->cmd, TRUE, NULL); + + // In Vim9 script a variable can shadow a command modifier: + // verbose = 123 + // verbose += 123 + // silent! verbose = func() + // verbose.member = 2 + // verbose[expr] = 2 + if (in_vim9script()) + { + char_u *s; + + for (s = p; ASCII_ISALPHA(*s); ++s) + ; + s = skipwhite(s); + if (vim_strchr((char_u *)".[=", *s) != NULL + || (*s != NUL && s[1] == '=')) + break; + } + switch (*p) { // When adding an entry, also modify cmd_exists(). diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index 2d137a3281..55b25ea174 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -1464,5 +1464,26 @@ def Test_unlet() assert_equal('', $ENVVAR) enddef +def Test_assign_command_modifier() + var lines =<< trim END + var verbose = 0 + verbose = 1 + assert_equal(1, verbose) + silent verbose = 2 + assert_equal(2, verbose) + silent verbose += 2 + assert_equal(4, verbose) + silent verbose -= 1 + assert_equal(3, verbose) + + var topleft = {one: 1} + sandbox topleft.one = 3 + assert_equal({one: 3}, topleft) + leftabove topleft[' '] = 4 + assert_equal({one: 3, ' ': 4}, topleft) + END + CheckDefAndScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index 0d9816bc91..c79b219701 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2311, /**/ 2310, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index f66b27ff50..7497e9cc09 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -6216,6 +6216,77 @@ theend: return ret; } +/* + * Check for an assignment at "eap->cmd", compile it if found. + * Return NOTDONE if there is none, FAIL for failure, OK if done. + */ + static int +may_compile_assignment(exarg_T *eap, char_u **line, cctx_T *cctx) +{ + char_u *pskip; + char_u *p; + + // Assuming the command starts with a variable or function name, + // find what follows. + // Skip over "var.member", "var[idx]" and the like. + // Also "&opt = val", "$ENV = val" and "@r = val". + pskip = (*eap->cmd == '&' || *eap->cmd == '$' || *eap->cmd == '@') + ? eap->cmd + 1 : eap->cmd; + p = to_name_end(pskip, TRUE); + if (p > eap->cmd && *p != NUL) + { + char_u *var_end; + int oplen; + int heredoc; + + if (eap->cmd[0] == '@') + var_end = eap->cmd + 2; + else + var_end = find_name_end(pskip, NULL, NULL, + FNE_CHECK_START | FNE_INCL_BR); + oplen = assignment_len(skipwhite(var_end), &heredoc); + if (oplen > 0) + { + size_t len = p - eap->cmd; + + // Recognize an assignment if we recognize the variable + // name: + // "g:var = expr" + // "local = expr" where "local" is a local var. + // "script = expr" where "script" is a script-local var. + // "import = expr" where "import" is an imported var + // "&opt = expr" + // "$ENV = expr" + // "@r = expr" + if (*eap->cmd == '&' + || *eap->cmd == '$' + || *eap->cmd == '@' + || ((len) > 2 && eap->cmd[1] == ':') + || lookup_local(eap->cmd, len, NULL, cctx) == OK + || arg_exists(eap->cmd, len, NULL, NULL, NULL, cctx) == OK + || script_var_exists(eap->cmd, len, FALSE, cctx) == OK + || find_imported(eap->cmd, len, cctx) != NULL) + { + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL || *line == eap->cmd) + return FAIL; + return OK; + } + } + } + + if (*eap->cmd == '[') + { + // [var, var] = expr + *line = compile_assignment(eap->cmd, eap, CMD_SIZE, cctx); + if (*line == NULL) + return FAIL; + if (*line != eap->cmd) + return OK; + } + return NOTDONE; +} + /* * Check if "name" can be "unlet". */ @@ -7838,68 +7909,14 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx) if (!starts_with_colon) { - char_u *pskip; + int assign; - // Assuming the command starts with a variable or function name, - // find what follows. - // Skip over "var.member", "var[idx]" and the like. - // Also "&opt = val", "$ENV = val" and "@r = val". - pskip = (*ea.cmd == '&' || *ea.cmd == '$' || *ea.cmd == '@') - ? ea.cmd + 1 : ea.cmd; - p = to_name_end(pskip, TRUE); - if (p > ea.cmd && *p != NUL) - { - char_u *var_end; - int oplen; - int heredoc; - - if (ea.cmd[0] == '@') - var_end = ea.cmd + 2; - else - var_end = find_name_end(pskip, NULL, NULL, - FNE_CHECK_START | FNE_INCL_BR); - oplen = assignment_len(skipwhite(var_end), &heredoc); - if (oplen > 0) - { - size_t len = p - ea.cmd; - - // Recognize an assignment if we recognize the variable - // name: - // "g:var = expr" - // "local = expr" where "local" is a local var. - // "script = expr" where "script" is a script-local var. - // "import = expr" where "import" is an imported var - // "&opt = expr" - // "$ENV = expr" - // "@r = expr" - if (*ea.cmd == '&' - || *ea.cmd == '$' - || *ea.cmd == '@' - || ((len) > 2 && ea.cmd[1] == ':') - || lookup_local(ea.cmd, len, NULL, &cctx) == OK - || arg_exists(ea.cmd, len, NULL, NULL, - NULL, &cctx) == OK - || script_var_exists(ea.cmd, len, - FALSE, &cctx) == OK - || find_imported(ea.cmd, len, &cctx) != NULL) - { - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL || line == ea.cmd) - goto erret; - goto nextline; - } - } - } - - if (*ea.cmd == '[') - { - // [var, var] = expr - line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); - if (line == NULL) - goto erret; - if (line != ea.cmd) - goto nextline; - } + // Check for assignment after command modifiers. + assign = may_compile_assignment(&ea, &line, &cctx); + if (assign == OK) + goto nextline; + if (assign == FAIL) + goto erret; } /* From 467b59c2eb06f7fe6c2b5b677855cd422adbc45c Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 8 Jan 2021 19:31:39 +0100 Subject: [PATCH 07/22] patch 8.2.2312: build failure with Ruby 3.0 and 32 bits Problem: Build failure with Ruby 3.0 and 32 bits. Solution: Add #ifdef. (closes #7638) --- src/if_ruby.c | 2 ++ src/version.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/if_ruby.c b/src/if_ruby.c index 9e58902141..a8c8379b62 100644 --- a/src/if_ruby.c +++ b/src/if_ruby.c @@ -612,11 +612,13 @@ rb_check_type_stub(VALUE obj, int t) { dll_rb_check_type(obj, t); } +# if VIM_SIZEOF_INT < VIM_SIZEOF_LONG // 64 bits only unsigned long rb_num2uint_stub(VALUE x) { return dll_rb_num2uint(x); } +# endif void ruby_malloc_size_overflow_stub(size_t x, size_t y) { diff --git a/src/version.c b/src/version.c index c79b219701..9604650963 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2312, /**/ 2311, /**/ From ece0b87c0fb0e35ff4c51f9ba81389f56428e42b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 8 Jan 2021 20:40:45 +0100 Subject: [PATCH 08/22] patch 8.2.2313: Vim9: using uninitialized field when parsing range Problem: Vim9: using uninitialized field when parsing range. ":silent!" not respected when parsing range fails. Solution: Initialize ea.skip. On pattern failure handle it like an error. (closes #7636) --- src/testdir/test_vim9_cmd.vim | 6 ++++++ src/version.c | 2 ++ src/vim9execute.c | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim index 5b84feceea..f5cb1c8739 100644 --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -661,6 +661,12 @@ def Test_range_after_command_modifier() bwipe! enddef +def Test_silent_pattern() + new + silent! :/pat/put _ + bwipe! +enddef + def Test_eval_command() var from = 3 var to = 5 diff --git a/src/version.c b/src/version.c index 9604650963..9e49e6ddaa 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2313, /**/ 2312, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index 1376148228..44180b8f19 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3148,11 +3148,13 @@ call_def_function( goto failed; ++ectx.ec_stack.ga_len; tv = STACK_TV_BOT(-1); + ea.line2 = 0; ea.addr_count = 0; ea.addr_type = ADDR_LINES; ea.cmd = iptr->isn_arg.string; + ea.skip = FALSE; if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL) - goto failed; + goto on_error; if (ea.addr_count == 0) tv->vval.v_number = curwin->w_cursor.lnum; else From 299f3036ec21cc9735846b2e0dfdfc5a78b26c1c Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 8 Jan 2021 20:53:09 +0100 Subject: [PATCH 09/22] patch 8.2.2314: Vim9: returning zero takes two instructions Problem: Vim9: returning zero takes two instructions. Solution: Add ISN_RETURN_ZERO. --- src/testdir/test_vim9_disassemble.vim | 72 +++++++++------------------ src/version.c | 2 + src/vim9.h | 1 + src/vim9compile.c | 4 +- src/vim9execute.c | 13 +++++ 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index b0f84a6e1e..f25b04ae3b 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -117,8 +117,7 @@ def Test_disassemble_exec_expr() '\d 2STRING stack\[-1\]\_s*' .. '\d\+ PUSHS ".txt"\_s*' .. '\d\+ EXECCONCAT 4\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -134,8 +133,7 @@ def Test_disassemble_yank_range() '\d EXEC norm! m\[jjm\]\_s*' .. ' :''\[,''\]yank\_s*' .. '\d EXEC :''\[,''\]yank\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -149,8 +147,7 @@ def Test_disassemble_put_expr() ' :3put ="text"\_s*' .. '\d PUSHS "text"\_s*' .. '\d PUT = 3\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -164,8 +161,7 @@ def Test_disassemble_put_range() ' :$-2put a\_s*' .. '\d RANGE $-2\_s*' .. '\d PUT a range\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -273,8 +269,7 @@ def Test_disassemble_store_member() '\d\+ PUSHS "a"\_s*' .. '\d\+ LOAD $1\_s*' .. '\d\+ STOREDICT\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -297,8 +292,7 @@ def Test_disassemble_store_index() '\d LOAD $0\_s*' .. '\d MEMBER dd\_s*' .. '\d STOREINDEX\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -333,8 +327,7 @@ def Test_disassemble_list_assign() '\d\+ STORE $1\_s*' .. '\d\+ SLICE 2\_s*' .. '\d\+ STORE $2\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -362,8 +355,7 @@ def Test_disassemble_list_add() '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -390,8 +382,7 @@ def Test_disassemble_blob_add() '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. '\d\+ BLOBAPPEND\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -580,8 +571,7 @@ def Test_disassemble_closure() '\d LOAD arg\[-1\]\_s*' .. '\d CONCAT\_s*' .. '\d STOREOUTER $0\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) res = execute('disass g:Get') @@ -615,8 +605,7 @@ def Test_disassemble_pcall() '\d PCALL top (argc 1)\_s*' .. '\d PCALL end\_s*' .. '\d DROP\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -865,8 +854,7 @@ def Test_disassemble_function() '\d PUSHS "UserFunc"\_s*' .. '\d BCALL funcref(argc 1)\_s*' .. '\d STORE $2\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -893,8 +881,7 @@ def Test_disassemble_channel() 'var chan1: channel\_s*' .. '\d PUSHCHANNEL 0\_s*' .. '\d STORE $2\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -966,8 +953,7 @@ def Test_nested_func() 'echomsg "inner"\_s*' .. 'enddef\_s*' .. '\d NEWFUNC \d\+ Inner\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -989,8 +975,7 @@ def Test_nested_def_list() '\d DEF /Info\_s*' .. 'def /Info/\_s*' .. '\d DEF /Info/\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', instr) enddef @@ -1122,8 +1107,7 @@ def Test_disassemble_for_loop_unpack() 'endfor\_s*' .. '\d\+ JUMP -> 8\_s*' .. '\d\+ DROP\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', instr) enddef @@ -1143,8 +1127,7 @@ def Test_disassemble_typecast() '\d NEWLIST size 2\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN\_s*', + '\d RETURN 0\_s*', instr) enddef @@ -1631,8 +1614,7 @@ def Test_dsassemble_falsy_op() 'echo "" ?? "empty string"\_s*' .. '\d\+ PUSHS "empty string"\_s*' .. '\d\+ ECHO 1\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -1659,8 +1641,7 @@ def Test_disassemble_compare_const() 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' .. '\d PUSHNR 42.*' .. '\d ECHO 1.*' .. - '\d PUSHNR 0.*' .. - '\d RETURN.*', + '\d RETURN 0', instr) else # condition false, function just returns @@ -1668,8 +1649,7 @@ def Test_disassemble_compare_const() 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '[ \n]*' .. 'echo 42[ \n]*' .. 'endif[ \n]*' .. - '\s*\d PUSHNR 0.*' .. - '\d RETURN.*', + '\d RETURN 0', instr) endif @@ -1707,8 +1687,7 @@ def Test_disassemble_execute() '\d\+ LOAD $1\_s*' .. '\d\+ CONCAT\_s*' .. '\d\+ EXECUTE 1\_s*' .. - '\d\+ PUSHNR 0\_s*' .. - '\d\+ RETURN', + '\d\+ RETURN 0', res) enddef @@ -1727,8 +1706,7 @@ def Test_disassemble_echomsg() "echoerr 'went' .. 'wrong'\\_s*" .. '\d PUSHS "wentwrong"\_s*' .. '\d ECHOERR 1\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -1837,8 +1815,7 @@ def Test_shuffle() '\d SHUFFLE 2 up 1\_s*' .. '\d BCALL append(argc 2)\_s*' .. '\d DROP\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef @@ -1861,8 +1838,7 @@ def Test_silent() '\d PUSHS "error"\_s*' .. '\d ECHOERR 1\_s*' .. '\d CMDMOD_REV\_s*' .. - '\d PUSHNR 0\_s*' .. - '\d RETURN', + '\d RETURN 0', res) enddef diff --git a/src/version.c b/src/version.c index 9e49e6ddaa..7ab3b0142c 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2314, /**/ 2313, /**/ diff --git a/src/vim9.h b/src/vim9.h index 49103ba556..440de27850 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -84,6 +84,7 @@ typedef enum { ISN_PCALL, // call partial, use isn_arg.pfunc ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set ISN_RETURN, // return, result is on top of stack + ISN_RETURN_ZERO, // Push zero, then return ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref ISN_NEWFUNC, // create a global function from a lambda function ISN_DEF, // list functions diff --git a/src/vim9compile.c b/src/vim9compile.c index 7497e9cc09..376f306a41 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -8190,8 +8190,7 @@ nextline: } // Return zero if there is no return at the end. - generate_PUSHNR(&cctx, 0); - generate_instr(&cctx, ISN_RETURN); + generate_instr(&cctx, ISN_RETURN_ZERO); } { @@ -8483,6 +8482,7 @@ delete_instr(isn_T *isn) case ISN_PUSHSPEC: case ISN_PUT: case ISN_RETURN: + case ISN_RETURN_ZERO: case ISN_SHUFFLE: case ISN_SLICE: case ISN_STORE: diff --git a/src/vim9execute.c b/src/vim9execute.c index 44180b8f19..9a76cc9c36 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2205,6 +2205,16 @@ call_def_function( break; // return from a :def function call + case ISN_RETURN_ZERO: + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + ++ectx.ec_stack.ga_len; + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + tv->v_lock = 0; + // FALLTHROUGH + case ISN_RETURN: { garray_T *trystack = &ectx.ec_trystack; @@ -3804,6 +3814,9 @@ ex_disassemble(exarg_T *eap) case ISN_RETURN: smsg("%4d RETURN", current); break; + case ISN_RETURN_ZERO: + smsg("%4d RETURN 0", current); + break; case ISN_FUNCREF: { funcref_T *funcref = &iptr->isn_arg.funcref; From 832ea89ca90cdff019ee7cf31d5c44bfa164313a Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 8 Jan 2021 21:55:26 +0100 Subject: [PATCH 10/22] patch 8.2.2315: Vim9: "enddef" as dict key misintepreted as function end Problem: Vim9: "enddef" as dict key misintepreted as function end. Solution: Check for following colon. (closes #7640) --- src/testdir/test_vim9_func.vim | 8 ++++++++ src/userfunc.c | 6 ++++-- src/version.c | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 9f19ebda9a..fb37401a22 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -116,6 +116,14 @@ def Test_missing_endfunc_enddef() CheckScriptFailure(lines, 'E126:', 2) enddef +def Test_enddef_dict_key() + var d = { + enddef: 'x', + endfunc: 'y', + } + assert_equal({enddef: 'x', endfunc: 'y'}, d) +enddef + def ReturnString(): string return 'string' enddef diff --git a/src/userfunc.c b/src/userfunc.c index 814d400fb2..1918e541f5 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -3444,8 +3444,10 @@ define_function(exarg_T *eap, char_u *name_arg) ; // Check for "endfunction" or "enddef". + // When a ":" follows it must be a dict key; "enddef: value," if (checkforcmd(&p, nesting_def[nesting] - ? "enddef" : "endfunction", 4)) + ? "enddef" : "endfunction", 4) + && *p != ':') { if (nesting-- == 0) { @@ -3484,7 +3486,7 @@ define_function(exarg_T *eap, char_u *name_arg) // not find it. else if (nesting_def[nesting]) { - if (checkforcmd(&p, "endfunction", 4)) + if (checkforcmd(&p, "endfunction", 4) && *p != ':') emsg(_(e_mismatched_endfunction)); } else if (eap->cmdidx == CMD_def && checkforcmd(&p, "enddef", 4)) diff --git a/src/version.c b/src/version.c index 7ab3b0142c..1078665956 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2315, /**/ 2314, /**/ From b657198cb30765468451d7f68fce49b5b4000c5d Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 8 Jan 2021 22:24:19 +0100 Subject: [PATCH 11/22] patch 8.2.2316: Vim9: cannot list a lambda function Problem: Vim9: cannot list a lambda function. Solution: Support the 9 notation, like :disassemble. (closes #7634) --- src/testdir/test_vim9_func.vim | 10 ++++++++++ src/userfunc.c | 10 +++++++++- src/version.c | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index fb37401a22..3d4f4c9b74 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1802,6 +1802,16 @@ def Test_line_continuation_in_lambda() Line_continuation_in_lambda()->assert_equal(['D', 'C', 'B', 'A']) enddef +def Test_list_lambda() + timer_start(1000, (_) => 0) + var body = execute(timer_info()[0].callback + ->string() + ->substitute("('", ' ', '') + ->substitute("')", '', '') + ->substitute('function\zs', ' ', '')) + assert_match('def \d\+(_: any, ...): number\n1 return 0\n enddef', body) +enddef + func Test_silent_echo() CheckScreendump diff --git a/src/userfunc.c b/src/userfunc.c index 1918e541f5..ded9ef7b0f 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -3094,7 +3094,15 @@ define_function(exarg_T *eap, char_u *name_arg) } else { - name = trans_function_name(&p, &is_global, eap->skip, + if (STRNCMP(p, "", 8) == 0) + { + p += 8; + (void)getdigits(&p); + name = vim_strnsave(eap->arg, p - eap->arg); + CLEAR_FIELD(fudi); + } + else + name = trans_function_name(&p, &is_global, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) diff --git a/src/version.c b/src/version.c index 1078665956..f4cfa2ba00 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2316, /**/ 2315, /**/ From 9e0f883f89c915e08ef5fd9e6bf382af57fa9eb2 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 9 Jan 2021 12:09:22 +0100 Subject: [PATCH 12/22] patch 8.2.2317: Vim9: command modifier before list unpack doesn't work Problem: Vim9: command modifier before list unpack doesn't work. Solution: Only recognize "[" directly after the name. (closes #7641) --- src/ex_docmd.c | 11 +++++++---- src/testdir/test_vim9_assign.vim | 34 +++++++++++++++++++------------- src/version.c | 2 ++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 9659c20b1f..a71adb874c 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2745,15 +2745,18 @@ parse_command_modifiers( // silent! verbose = func() // verbose.member = 2 // verbose[expr] = 2 + // But not: + // verbose [a, b] = list if (in_vim9script()) { - char_u *s; + char_u *s, *n; for (s = p; ASCII_ISALPHA(*s); ++s) ; - s = skipwhite(s); - if (vim_strchr((char_u *)".[=", *s) != NULL - || (*s != NUL && s[1] == '=')) + n = skipwhite(s); + if (vim_strchr((char_u *)".=", *n) != NULL + || *s == '[' + || (*n != NUL && n[1] == '=')) break; } diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index 55b25ea174..21480633c7 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -1466,21 +1466,27 @@ enddef def Test_assign_command_modifier() var lines =<< trim END - var verbose = 0 - verbose = 1 - assert_equal(1, verbose) - silent verbose = 2 - assert_equal(2, verbose) - silent verbose += 2 - assert_equal(4, verbose) - silent verbose -= 1 - assert_equal(3, verbose) + var verbose = 0 + verbose = 1 + assert_equal(1, verbose) + silent verbose = 2 + assert_equal(2, verbose) + silent verbose += 2 + assert_equal(4, verbose) + silent verbose -= 1 + assert_equal(3, verbose) - var topleft = {one: 1} - sandbox topleft.one = 3 - assert_equal({one: 3}, topleft) - leftabove topleft[' '] = 4 - assert_equal({one: 3, ' ': 4}, topleft) + var topleft = {one: 1} + sandbox topleft.one = 3 + assert_equal({one: 3}, topleft) + leftabove topleft[' '] = 4 + assert_equal({one: 3, ' ': 4}, topleft) + + var x: number + var y: number + silent [x, y] = [1, 2] + assert_equal(1, x) + assert_equal(2, y) END CheckDefAndScriptSuccess(lines) enddef diff --git a/src/version.c b/src/version.c index f4cfa2ba00..dc1114cc6a 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2317, /**/ 2316, /**/ From e7525c552060dd04aacdbca6bb5fe6460cf4da60 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 9 Jan 2021 13:20:37 +0100 Subject: [PATCH 13/22] patch 8.2.2318: Vim9: string and list index work differently Problem: Vim9: string and list index work differently. Solution: Make string index work like list index. (closes #7643) --- src/eval.c | 91 ----------------------------- src/list.c | 2 +- src/proto/eval.pro | 2 - src/proto/vim9execute.pro | 2 + src/testdir/test_vim9_expr.vim | 21 ++++--- src/version.c | 2 + src/vim9execute.c | 102 +++++++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 101 deletions(-) diff --git a/src/eval.c b/src/eval.c index 373c02753c..5ebe0375f2 100644 --- a/src/eval.c +++ b/src/eval.c @@ -5548,97 +5548,6 @@ eval_isdictc(int c) return ASCII_ISALNUM(c) || c == '_'; } -/* - * Return the character "str[index]" where "index" is the character index. If - * "index" is out of range NULL is returned. - */ - char_u * -char_from_string(char_u *str, varnumber_T index) -{ - size_t nbyte = 0; - varnumber_T nchar = index; - size_t slen; - - if (str == NULL || index < 0) - return NULL; - slen = STRLEN(str); - while (nchar > 0 && nbyte < slen) - { - nbyte += MB_CPTR2LEN(str + nbyte); - --nchar; - } - if (nbyte >= slen) - return NULL; - return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte)); -} - -/* - * Get the byte index for character index "idx" in string "str" with length - * "str_len". - * If going over the end return "str_len". - * If "idx" is negative count from the end, -1 is the last character. - * When going over the start return -1. - */ - static long -char_idx2byte(char_u *str, size_t str_len, varnumber_T idx) -{ - varnumber_T nchar = idx; - size_t nbyte = 0; - - if (nchar >= 0) - { - while (nchar > 0 && nbyte < str_len) - { - nbyte += MB_CPTR2LEN(str + nbyte); - --nchar; - } - } - else - { - nbyte = str_len; - while (nchar < 0 && nbyte > 0) - { - --nbyte; - nbyte -= mb_head_off(str, str + nbyte); - ++nchar; - } - if (nchar < 0) - return -1; - } - return (long)nbyte; -} - -/* - * Return the slice "str[first:last]" using character indexes. - * Return NULL when the result is empty. - */ - char_u * -string_slice(char_u *str, varnumber_T first, varnumber_T last) -{ - long start_byte, end_byte; - size_t slen; - - if (str == NULL) - return NULL; - slen = STRLEN(str); - start_byte = char_idx2byte(str, slen, first); - if (start_byte < 0) - start_byte = 0; // first index very negative: use zero - if (last == -1) - end_byte = (long)slen; - else - { - end_byte = char_idx2byte(str, slen, last); - if (end_byte >= 0 && end_byte < (long)slen) - // end index is inclusive - end_byte += MB_CPTR2LEN(str + end_byte); - } - - if (start_byte >= (long)slen || end_byte <= start_byte) - return NULL; - return vim_strnsave(str + start_byte, end_byte - start_byte); -} - /* * Handle: * - expr[expr], expr[expr:expr] subscript diff --git a/src/list.c b/src/list.c index 56ee5a5778..4531e5885a 100644 --- a/src/list.c +++ b/src/list.c @@ -924,7 +924,7 @@ list_slice_or_index( if (!range) { if (verbose) - semsg(_(e_listidx), n1); + semsg(_(e_listidx), n1_arg); return FAIL; } n1 = n1 < 0 ? 0 : len; diff --git a/src/proto/eval.pro b/src/proto/eval.pro index ec453645a1..cc86a5d263 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -64,8 +64,6 @@ char_u *find_name_end(char_u *arg, char_u **expr_start, char_u **expr_end, int f int eval_isnamec(int c); int eval_isnamec1(int c); int eval_isdictc(int c); -char_u *char_from_string(char_u *str, varnumber_T index); -char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last); int handle_subscript(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro index 8e948c5508..2337a6c450 100644 --- a/src/proto/vim9execute.pro +++ b/src/proto/vim9execute.pro @@ -1,6 +1,8 @@ /* vim9execute.c */ void to_string_error(vartype_T vartype); void funcstack_check_refcount(funcstack_T *funcstack); +char_u *char_from_string(char_u *str, varnumber_T index); +char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last); int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx); int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void ex_disassemble(exarg_T *eap); diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index ae5ca2816c..6d82e5e27d 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -2304,7 +2304,7 @@ def Test_expr7_any_index_slice() # string is permissive, index out of range accepted g:teststring = 'abcdef' assert_equal('b', g:teststring[1]) - assert_equal('', g:teststring[-1]) + assert_equal('f', g:teststring[-1]) assert_equal('', g:teststring[99]) assert_equal('b', g:teststring[1 : 1]) @@ -2368,10 +2368,10 @@ def Test_expr7_any_index_slice() CheckDefExecFailure(['echo g:testblob[-3]'], 'E979:', 1) CheckScriptFailure(['vim9script', 'echo g:testblob[-3]'], 'E979:', 2) - CheckDefExecFailure(['echo g:testlist[4]'], 'E684:', 1) + CheckDefExecFailure(['echo g:testlist[4]'], 'E684: list index out of range: 4', 1) CheckScriptFailure(['vim9script', 'echo g:testlist[4]'], 'E684:', 2) CheckDefExecFailure(['echo g:testlist[-5]'], 'E684:', 1) - CheckScriptFailure(['vim9script', 'echo g:testlist[-5]'], 'E684:', 2) + CheckScriptFailure(['vim9script', 'echo g:testlist[-5]'], 'E684: list index out of range: -5', 2) CheckDefExecFailure(['echo g:testdict["a" : "b"]'], 'E719:', 1) CheckScriptFailure(['vim9script', 'echo g:testdict["a" : "b"]'], 'E719:', 2) @@ -2802,15 +2802,23 @@ enddef def Test_expr7_string_subscript() var lines =<< trim END var text = 'abcdef' - assert_equal('', text[-1]) + assert_equal('f', text[-1]) assert_equal('a', text[0]) assert_equal('e', text[4]) assert_equal('f', text[5]) assert_equal('', text[6]) + text = 'ábçdë' + assert_equal('ë', text[-1]) + assert_equal('d', text[-2]) + assert_equal('ç', text[-3]) + assert_equal('b', text[-4]) + assert_equal('á', text[-5]) + assert_equal('', text[-6]) + text = 'ábçdëf' assert_equal('', text[-999]) - assert_equal('', text[-1]) + assert_equal('f', text[-1]) assert_equal('á', text[0]) assert_equal('b', text[1]) assert_equal('ç', text[2]) @@ -2904,8 +2912,7 @@ def Test_expr7_list_subscript() assert_equal([], list[0 : -6]) assert_equal([], list[0 : -99]) END - CheckDefSuccess(lines) - CheckScriptSuccess(['vim9script'] + lines) + CheckDefAndScriptSuccess(lines) lines = ['var l = [0, 1, 2]', 'echo l[g:astring : g:theone]'] CheckDefExecFailure(lines, 'E1012:') diff --git a/src/version.c b/src/version.c index dc1114cc6a..06d5256910 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2318, /**/ 2317, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index 9a76cc9c36..e7a632a193 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -863,6 +863,108 @@ allocate_if_null(typval_T *tv) } } +/* + * Return the character "str[index]" where "index" is the character index. If + * "index" is out of range NULL is returned. + */ + char_u * +char_from_string(char_u *str, varnumber_T index) +{ + size_t nbyte = 0; + varnumber_T nchar = index; + size_t slen; + + if (str == NULL) + return NULL; + slen = STRLEN(str); + + // do the same as for a list: a negative index counts from the end + if (index < 0) + { + int clen = 0; + + for (nbyte = 0; nbyte < slen; ++clen) + nbyte += MB_CPTR2LEN(str + nbyte); + nchar = clen + index; + if (nchar < 0) + // unlike list: index out of range results in empty string + return NULL; + } + + for (nbyte = 0; nchar > 0 && nbyte < slen; --nchar) + nbyte += MB_CPTR2LEN(str + nbyte); + if (nbyte >= slen) + return NULL; + return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte)); +} + +/* + * Get the byte index for character index "idx" in string "str" with length + * "str_len". + * If going over the end return "str_len". + * If "idx" is negative count from the end, -1 is the last character. + * When going over the start return -1. + */ + static long +char_idx2byte(char_u *str, size_t str_len, varnumber_T idx) +{ + varnumber_T nchar = idx; + size_t nbyte = 0; + + if (nchar >= 0) + { + while (nchar > 0 && nbyte < str_len) + { + nbyte += MB_CPTR2LEN(str + nbyte); + --nchar; + } + } + else + { + nbyte = str_len; + while (nchar < 0 && nbyte > 0) + { + --nbyte; + nbyte -= mb_head_off(str, str + nbyte); + ++nchar; + } + if (nchar < 0) + return -1; + } + return (long)nbyte; +} + +/* + * Return the slice "str[first:last]" using character indexes. + * Return NULL when the result is empty. + */ + char_u * +string_slice(char_u *str, varnumber_T first, varnumber_T last) +{ + long start_byte, end_byte; + size_t slen; + + if (str == NULL) + return NULL; + slen = STRLEN(str); + start_byte = char_idx2byte(str, slen, first); + if (start_byte < 0) + start_byte = 0; // first index very negative: use zero + if (last == -1) + end_byte = (long)slen; + else + { + end_byte = char_idx2byte(str, slen, last); + if (end_byte >= 0 && end_byte < (long)slen) + // end index is inclusive + end_byte += MB_CPTR2LEN(str + end_byte); + } + + if (start_byte >= (long)slen || end_byte <= start_byte) + return NULL; + return vim_strnsave(str + start_byte, end_byte - start_byte); +} + static svar_T * get_script_svar(scriptref_T *sref, ectx_T *ectx) { From 657137ca487c60d63989236115115161def270a5 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 9 Jan 2021 15:45:23 +0100 Subject: [PATCH 14/22] patch 8.2.2319: "exptype_T" can be read as "expected type" Problem: "exptype_T" can be read as "expected type". Solution: Rename to "exprtype_T", expression type. --- src/eval.c | 2 +- src/proto/typval.pro | 2 +- src/proto/vim9compile.pro | 4 ++-- src/structs.h | 2 +- src/typval.c | 2 +- src/version.c | 2 ++ src/vim9.h | 6 +++--- src/vim9compile.c | 26 +++++++++++++------------- src/vim9execute.c | 4 ++-- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/eval.c b/src/eval.c index 5ebe0375f2..9c91e526d3 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2655,7 +2655,7 @@ eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { char_u *p; int getnext; - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; int len = 2; int type_is = FALSE; diff --git a/src/proto/typval.pro b/src/proto/typval.pro index 46f893147a..7a65376c6e 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -18,7 +18,7 @@ char_u *tv_get_string_buf_chk(typval_T *varp, char_u *buf); char_u *tv_stringify(typval_T *varp, char_u *buf); int tv_check_lock(typval_T *tv, char_u *name, int use_gettext); void copy_tv(typval_T *from, typval_T *to); -int typval_compare(typval_T *typ1, typval_T *typ2, exptype_T type, int ic); +int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, int ic); char_u *typval_tostring(typval_T *arg); int tv_islocked(typval_T *tv); int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index 52ccb0608c..7800d3ba59 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -1,6 +1,6 @@ /* vim9compile.c */ int check_defined(char_u *p, size_t len, cctx_T *cctx); -int check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2); +int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2); int use_typecheck(type_T *actual, type_T *expected); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); @@ -9,7 +9,7 @@ char_u *peek_next_line_from_context(cctx_T *cctx); char_u *next_line_from_context(cctx_T *cctx, int skip_comment); char_u *to_name_end(char_u *arg, int use_namespace); char_u *to_name_const_end(char_u *arg); -exptype_T get_compare_type(char_u *p, int *len, int *type_is); +exprtype_T get_compare_type(char_u *p, int *len, int *type_is); void error_white_both(char_u *op, int len); int assignment_len(char_u *p, int *heredoc); void vim9_declare_error(char_u *name); diff --git a/src/structs.h b/src/structs.h index 3324b067ae..9d1f821818 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4029,7 +4029,7 @@ typedef enum EXPR_MULT, // * EXPR_DIV, // / EXPR_REM, // % -} exptype_T; +} exprtype_T; /* * Structure used for reading in json_decode(). diff --git a/src/typval.c b/src/typval.c index d16f0e6df8..e620bedd74 100644 --- a/src/typval.c +++ b/src/typval.c @@ -649,7 +649,7 @@ copy_tv(typval_T *from, typval_T *to) typval_compare( typval_T *typ1, // first operand typval_T *typ2, // second operand - exptype_T type, // operator + exprtype_T type, // operator int ic) // ignore case { int i; diff --git a/src/version.c b/src/version.c index 06d5256910..0b174f5319 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2319, /**/ 2318, /**/ diff --git a/src/vim9.h b/src/vim9.h index 440de27850..7462b29db0 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -105,12 +105,12 @@ typedef enum { ISN_ADDLIST, // add two lists ISN_ADDBLOB, // add two blobs - // operation with two arguments; isn_arg.op.op_type is exptype_T + // operation with two arguments; isn_arg.op.op_type is exprtype_T ISN_OPNR, ISN_OPFLOAT, ISN_OPANY, - // comparative operations; isn_arg.op.op_type is exptype_T, op_ic used + // comparative operations; isn_arg.op.op_type is exprtype_T, op_ic used ISN_COMPAREBOOL, ISN_COMPARESPECIAL, ISN_COMPARENR, @@ -217,7 +217,7 @@ typedef struct { // arguments to ISN_OPNR, ISN_OPFLOAT, etc. typedef struct { - exptype_T op_type; + exprtype_T op_type; int op_ic; // TRUE with '#', FALSE with '?', else MAYBE } opexpr_T; diff --git a/src/vim9compile.c b/src/vim9compile.c index 376f306a41..77200205df 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -669,7 +669,7 @@ generate_two_op(cctx_T *cctx, char_u *op) * Return ISN_DROP when failed. */ static isntype_T -get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) +get_compare_isn(exprtype_T exprtype, vartype_T type1, vartype_T type2) { isntype_T isntype = ISN_DROP; @@ -699,22 +699,22 @@ get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) && (type2 == VAR_NUMBER || type2 ==VAR_FLOAT))) isntype = ISN_COMPAREANY; - if ((exptype == EXPR_IS || exptype == EXPR_ISNOT) + if ((exprtype == EXPR_IS || exprtype == EXPR_ISNOT) && (isntype == ISN_COMPAREBOOL || isntype == ISN_COMPARESPECIAL || isntype == ISN_COMPARENR || isntype == ISN_COMPAREFLOAT)) { semsg(_(e_cannot_use_str_with_str), - exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); + exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); return ISN_DROP; } if (isntype == ISN_DROP - || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL && (type1 == VAR_BOOL || type1 == VAR_SPECIAL || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) - || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL - && exptype != EXPR_IS && exptype != EXPR_ISNOT + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL + && exprtype != EXPR_IS && exprtype != EXPR_ISNOT && (type1 == VAR_BLOB || type2 == VAR_BLOB || type1 == VAR_LIST || type2 == VAR_LIST)))) { @@ -726,7 +726,7 @@ get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) } int -check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2) +check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2) { if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP) return FAIL; @@ -737,7 +737,7 @@ check_compare_types(exptype_T type, typval_T *tv1, typval_T *tv2) * Generate an ISN_COMPARE* instruction with a boolean result. */ static int -generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) +generate_COMPARE(cctx_T *cctx, exprtype_T exprtype, int ic) { isntype_T isntype; isn_T *isn; @@ -752,13 +752,13 @@ generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) // checking. type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; - isntype = get_compare_isn(exptype, type1, type2); + isntype = get_compare_isn(exprtype, type1, type2); if (isntype == ISN_DROP) return FAIL; if ((isn = generate_instr(cctx, isntype)) == NULL) return FAIL; - isn->isn_arg.op.op_type = exptype; + isn->isn_arg.op.op_type = exprtype; isn->isn_arg.op.op_ic = ic; // takes two arguments, puts one bool back @@ -3348,10 +3348,10 @@ get_vim_constant(char_u **arg, typval_T *rettv) } } - exptype_T + exprtype_T get_compare_type(char_u *p, int *len, int *type_is) { - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; int i; switch (p[0]) @@ -4346,7 +4346,7 @@ compile_expr5(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) static int compile_expr4(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { - exptype_T type = EXPR_UNKNOWN; + exprtype_T type = EXPR_UNKNOWN; char_u *p; char_u *next; int len = 2; diff --git a/src/vim9execute.c b/src/vim9execute.c index e7a632a193..9a04b6b818 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2725,11 +2725,11 @@ call_def_function( { typval_T *tv1 = STACK_TV_BOT(-2); typval_T *tv2 = STACK_TV_BOT(-1); - exptype_T exptype = iptr->isn_arg.op.op_type; + exprtype_T exprtype = iptr->isn_arg.op.op_type; int ic = iptr->isn_arg.op.op_ic; SOURCING_LNUM = iptr->isn_lnum; - typval_compare(tv1, tv2, exptype, ic); + typval_compare(tv1, tv2, exprtype, ic); clear_tv(tv2); --ectx.ec_stack.ga_len; } From cff40ff98664f4f5a9631aff1a155caf762ea74b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 9 Jan 2021 16:21:37 +0100 Subject: [PATCH 15/22] patch 8.2.2320: Vim9: no error for comparing bool with string Problem: Vim9: no error for comparing bool with string. Solution: Check for wrong types when comparing. (closes #7639) --- src/errors.h | 2 ++ src/testdir/test_vim9_expr.vim | 12 ++++++++++++ src/typval.c | 24 ++++++++++++++++++++++++ src/version.c | 2 ++ 4 files changed, 40 insertions(+) diff --git a/src/errors.h b/src/errors.h index e71b706004..47e9876f68 100644 --- a/src/errors.h +++ b/src/errors.h @@ -341,3 +341,5 @@ EXTERN char e_mismatched_endfunction[] INIT(= N_("E1151: Mismatched endfunction")); EXTERN char e_mismatched_enddef[] INIT(= N_("E1152: Mismatched enddef")); +EXTERN char e_invalid_operation_for_bool[] + INIT(= N_("E1153: Invalid operation for bool")); diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index 6d82e5e27d..fc42088d81 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -597,6 +597,18 @@ def Test_expr4_equal() CheckDefFailure(["var x = 'a' == "], 'E1097:', 3) CheckDefExecFailure(['var items: any', 'eval 1', 'eval 2', 'if items == []', 'endif'], 'E691:', 4) + + CheckDefExecFailure(['var x: any = "a"', 'echo x == true'], 'E1072: Cannot compare string with bool', 2) + CheckDefExecFailure(["var x: any = true", 'echo x == ""'], 'E1072: Cannot compare bool with string', 2) + CheckDefExecFailure(["var x: any = 99", 'echo x == true'], 'E1138', 2) + CheckDefExecFailure(["var x: any = 'a'", 'echo x == 99'], 'E1030:', 2) + + for op in ['>', '>=', '<', '<=', '=~', '!~'] + CheckDefExecFailure([ + "var a: any = 'a'", + 'var b: any = true', + 'echo a ' .. op .. ' b'], 'E1072:', 3) + endfor enddef " test != comperator diff --git a/src/typval.c b/src/typval.c index e620bedd74..06276b473d 100644 --- a/src/typval.c +++ b/src/typval.c @@ -834,6 +834,30 @@ typval_compare( default: break; // avoid gcc warning } } + else if (in_vim9script() && (typ1->v_type == VAR_BOOL + || typ2->v_type == VAR_BOOL)) + { + if (typ1->v_type != typ2->v_type) + { + semsg(_(e_cannot_compare_str_with_str), + vartype_name(typ1->v_type), vartype_name(typ2->v_type)); + clear_tv(typ1); + return FAIL; + } + n1 = typ1->vval.v_number; + n2 = typ2->vval.v_number; + switch (type) + { + case EXPR_IS: + case EXPR_EQUAL: n1 = (n1 == n2); break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = (n1 != n2); break; + default: + emsg(_(e_invalid_operation_for_bool)); + clear_tv(typ1); + return FAIL; + } + } else { s1 = tv_get_string_buf(typ1, buf1); diff --git a/src/version.c b/src/version.c index 0b174f5319..2ef443b239 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2320, /**/ 2319, /**/ From ab360526ef653b139f4b007a0efbdb3410c8fb4b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 14:02:28 +0100 Subject: [PATCH 16/22] patch 8.2.2321: Vim9: cannot nest closures Problem: Vim9: cannot nest closures. Solution: Add the nesting level to ISN_LOADOUTER and ISN_STOREOUTER. (closes #7150, closes #7635) --- src/structs.h | 2 + src/testdir/test_vim9_disassemble.vim | 6 +- src/testdir/test_vim9_func.vim | 10 +++ src/version.c | 2 + src/vim9.h | 11 ++- src/vim9compile.c | 79 +++++++++++++----- src/vim9execute.c | 110 +++++++++++++++++++++----- 7 files changed, 177 insertions(+), 43 deletions(-) diff --git a/src/structs.h b/src/structs.h index 9d1f821818..1ed185d5b4 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1978,6 +1978,8 @@ struct partial_S // For a compiled closure: the arguments and local variables. garray_T *pt_ectx_stack; // where to find local vars int pt_ectx_frame; // index of function frame in uf_ectx_stack + garray_T *pt_outer_stack; // pt_ectx_stack one level up + int pt_outer_frame; // pt_ectx_frame one level up. funcstack_T *pt_funcstack; // copy of stack, used after context // function returns diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index f25b04ae3b..ca9b90e886 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -567,17 +567,17 @@ def Test_disassemble_closure() var res = execute('disass g:Append') assert_match('\d\_s*' .. 'local ..= arg\_s*' .. - '\d LOADOUTER $0\_s*' .. + '\d LOADOUTER level 1 $0\_s*' .. '\d LOAD arg\[-1\]\_s*' .. '\d CONCAT\_s*' .. - '\d STOREOUTER $0\_s*' .. + '\d STOREOUTER level 1 $0\_s*' .. '\d RETURN 0', res) res = execute('disass g:Get') assert_match('\d\_s*' .. 'return local\_s*' .. - '\d LOADOUTER $0\_s*' .. + '\d LOADOUTER level 1 $0\_s*' .. '\d RETURN', res) diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 3d4f4c9b74..efbf3d52c1 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1812,6 +1812,16 @@ def Test_list_lambda() assert_match('def \d\+(_: any, ...): number\n1 return 0\n enddef', body) enddef +def DoFilterThis(a: string): list + # closure nested inside another closure using argument + var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0) + return ['x', 'y', 'a', 'x2', 'c']->Filter() +enddef + +def Test_nested_closure_using_argument() + assert_equal(['x', 'x2'], DoFilterThis('x')) +enddef + func Test_silent_echo() CheckScreendump diff --git a/src/version.c b/src/version.c index 2ef443b239..891126f4d1 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2321, /**/ 2320, /**/ diff --git a/src/vim9.h b/src/vim9.h index 7462b29db0..52247540af 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -33,7 +33,7 @@ typedef enum { ISN_LOADWDICT, // push w: dict ISN_LOADTDICT, // push t: dict ISN_LOADS, // push s: variable isn_arg.loadstore - ISN_LOADOUTER, // push variable from outer scope isn_arg.number + ISN_LOADOUTER, // push variable from outer scope isn_arg.outer ISN_LOADSCRIPT, // push script-local variable isn_arg.script. ISN_LOADOPT, // push option isn_arg.string ISN_LOADENV, // push environment variable isn_arg.string @@ -47,7 +47,7 @@ typedef enum { ISN_STOREW, // pop into window-local variable isn_arg.string ISN_STORET, // pop into tab-local variable isn_arg.string ISN_STORES, // pop into script variable isn_arg.loadstore - ISN_STOREOUTER, // pop variable into outer scope isn_arg.number + ISN_STOREOUTER, // pop variable into outer scope isn_arg.outer ISN_STORESCRIPT, // pop into script variable isn_arg.script ISN_STOREOPT, // pop into option isn_arg.string ISN_STOREENV, // pop into environment variable isn_arg.string @@ -303,6 +303,12 @@ typedef struct { int unp_semicolon; // last item gets list of remainder } unpack_T; +// arguments to ISN_LOADOUTER and ISN_STOREOUTER +typedef struct { + int outer_idx; // index + int outer_depth; // nesting level, stack frames to go up +} outer_T; + /* * Instruction */ @@ -342,6 +348,7 @@ struct isn_S { put_T put; cmod_T cmdmod; unpack_T unpack; + outer_T outer; } isn_arg; }; diff --git a/src/vim9compile.c b/src/vim9compile.c index 77200205df..a6bb56571e 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -108,7 +108,7 @@ typedef struct { char_u *lv_name; type_T *lv_type; int lv_idx; // index of the variable on the stack - int lv_from_outer; // when TRUE using ctx_outer scope + int lv_from_outer; // nesting level, using ctx_outer scope int lv_const; // when TRUE cannot be assigned to int lv_arg; // when TRUE this is an argument } lvar_T; @@ -149,7 +149,7 @@ static void delete_def_function_contents(dfunc_T *dfunc, int mark_deleted); /* * Lookup variable "name" in the local scope and return it in "lvar". - * "lvar->lv_from_outer" is set accordingly. + * "lvar->lv_from_outer" is incremented accordingly. * If "lvar" is NULL only check if the variable can be found. * Return FAIL if not found. */ @@ -172,7 +172,7 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) if (lvar != NULL) { *lvar = *lvp; - lvar->lv_from_outer = FALSE; + lvar->lv_from_outer = 0; } return OK; } @@ -186,7 +186,7 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) if (lvar != NULL) { cctx->ctx_outer_used = TRUE; - lvar->lv_from_outer = TRUE; + ++lvar->lv_from_outer; } return OK; } @@ -258,7 +258,7 @@ arg_exists( if (arg_exists(name, len, idxp, type, gen_load_outer, cctx->ctx_outer) == OK) { - *gen_load_outer = TRUE; + ++*gen_load_outer; return OK; } } @@ -1175,6 +1175,23 @@ generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) return OK; } +/* + * Generate an ISN_STOREOUTER instruction. + */ + static int +generate_STOREOUTER(cctx_T *cctx, int idx, int level) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, ISN_STOREOUTER, 1)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = level; + + return OK; +} + /* * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) */ @@ -1233,6 +1250,27 @@ generate_LOAD( return OK; } +/* + * Generate an ISN_LOADOUTER instruction + */ + static int +generate_LOADOUTER( + cctx_T *cctx, + int idx, + int nesting, + type_T *type) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_LOADOUTER, type)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = nesting; + + return OK; +} + /* * Generate an ISN_LOADV instruction for v:var. */ @@ -1439,6 +1477,11 @@ generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc) isn->isn_arg.funcref.fr_func = ufunc->uf_dfunc_idx; cctx->ctx_has_closure = 1; + // if the referenced function is a closure, it may use items further up in + // the nested context, including this one. + if (ufunc->uf_flags & FC_CLOSURE) + cctx->ctx_ufunc->uf_flags |= FC_CLOSURE; + if (ga_grow(stack, 1) == FAIL) return FAIL; ((type_T **)stack->ga_data)[stack->ga_len] = @@ -2589,7 +2632,7 @@ compile_load( size_t len = end - *arg; int idx; int gen_load = FALSE; - int gen_load_outer = FALSE; + int gen_load_outer = 0; name = vim_strnsave(*arg, end - *arg); if (name == NULL) @@ -2597,7 +2640,7 @@ compile_load( if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx) == OK) { - if (!gen_load_outer) + if (gen_load_outer == 0) gen_load = TRUE; } else @@ -2608,8 +2651,8 @@ compile_load( { type = lvar.lv_type; idx = lvar.lv_idx; - if (lvar.lv_from_outer) - gen_load_outer = TRUE; + if (lvar.lv_from_outer != 0) + gen_load_outer = lvar.lv_from_outer; else gen_load = TRUE; } @@ -2631,9 +2674,9 @@ compile_load( } if (gen_load) res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); - if (gen_load_outer) + if (gen_load_outer > 0) { - res = generate_LOAD(cctx, ISN_LOADOUTER, idx, NULL, type); + res = generate_LOADOUTER(cctx, idx, gen_load_outer, type); cctx->ctx_outer_used = TRUE; } } @@ -5120,9 +5163,9 @@ generate_loadvar( generate_LOADV(cctx, name + 2, TRUE); break; case dest_local: - if (lvar->lv_from_outer) - generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, - NULL, type); + if (lvar->lv_from_outer > 0) + generate_LOADOUTER(cctx, lvar->lv_idx, lvar->lv_from_outer, + type); else generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); break; @@ -6178,7 +6221,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) // optimization: turn "var = 123" from ISN_PUSHNR + // ISN_STORE into ISN_STORENR - if (!lhs.lhs_lvar->lv_from_outer + if (lhs.lhs_lvar->lv_from_outer == 0 && instr->ga_len == instr_count + 1 && isn->isn_type == ISN_PUSHNR) { @@ -6190,9 +6233,9 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) if (stack->ga_len > 0) --stack->ga_len; } - else if (lhs.lhs_lvar->lv_from_outer) - generate_STORE(cctx, ISN_STOREOUTER, - lhs.lhs_lvar->lv_idx, NULL); + else if (lhs.lhs_lvar->lv_from_outer > 0) + generate_STOREOUTER(cctx, lhs.lhs_lvar->lv_idx, + lhs.lhs_lvar->lv_from_outer); else generate_STORE(cctx, ISN_STORE, lhs.lhs_lvar->lv_idx, NULL); } diff --git a/src/vim9execute.c b/src/vim9execute.c index 9a04b6b818..ca702f67d3 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -60,6 +60,8 @@ struct ectx_S { garray_T *ec_outer_stack; // stack used for closures int ec_outer_frame; // stack frame in ec_outer_stack + garray_T *ec_outer_up_stack; // ec_outer_stack one level up + int ec_outer_up_frame; // ec_outer_frame one level up garray_T ec_trystack; // stack of trycmd_T values int ec_in_catch; // when TRUE in catch or finally block @@ -149,6 +151,8 @@ exe_newlist(int count, ectx_T *ectx) /* * Call compiled function "cdf_idx" from compiled code. + * This adds a stack frame and sets the instruction pointer to the start of the + * called function. * * Stack has: * - current arguments (already there) @@ -248,6 +252,7 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) STACK_TV_BOT(2)->vval.v_string = (void *)ectx->ec_outer_stack; STACK_TV_BOT(3)->vval.v_number = ectx->ec_outer_frame; STACK_TV_BOT(4)->vval.v_number = ectx->ec_frame_idx; + // TODO: save ec_outer_up_stack as well? ectx->ec_frame_idx = ectx->ec_stack.ga_len; // Initialize local variables @@ -266,6 +271,15 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) { ectx->ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; ectx->ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; + ectx->ec_outer_up_stack = ufunc->uf_partial->pt_outer_stack; + ectx->ec_outer_up_frame = ufunc->uf_partial->pt_outer_frame; + } + else if (ufunc->uf_flags & FC_CLOSURE) + { + ectx->ec_outer_stack = &ectx->ec_stack; + ectx->ec_outer_frame = ectx->ec_frame_idx; + ectx->ec_outer_up_stack = ectx->ec_outer_stack; + ectx->ec_outer_up_frame = ectx->ec_outer_frame; } // Set execution state to the start of the called function. @@ -417,6 +431,8 @@ handle_closure_in_use(ectx_T *ectx, int free_arguments) pt->pt_funcstack = funcstack; pt->pt_ectx_stack = &funcstack->fs_ga; pt->pt_ectx_frame = ectx->ec_frame_idx - top; + pt->pt_outer_stack = ectx->ec_outer_stack; + pt->pt_outer_frame = ectx->ec_outer_frame; } } } @@ -598,6 +614,9 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx) /* * Execute a user defined function. + * If the function is compiled this will add a stack frame and set the + * instruction pointer at the start of the function. + * Otherwise the function is called here. * "iptr" can be used to replace the instruction with a more efficient one. */ static int @@ -743,11 +762,18 @@ call_partial(typval_T *tv, int argcount_arg, ectx_T *ectx) if (pt->pt_func != NULL) { + int frame_idx = ectx->ec_frame_idx; int ret = call_ufunc(pt->pt_func, argcount, ectx, NULL); - // closure may need the function context where it was defined - ectx->ec_outer_stack = pt->pt_ectx_stack; - ectx->ec_outer_frame = pt->pt_ectx_frame; + if (ectx->ec_frame_idx != frame_idx) + { + // call_dfunc() added a stack frame, closure may need the + // function context where it was defined. + ectx->ec_outer_stack = pt->pt_ectx_stack; + ectx->ec_outer_frame = pt->pt_ectx_frame; + ectx->ec_outer_up_stack = pt->pt_outer_stack; + ectx->ec_outer_up_frame = pt->pt_outer_frame; + } return ret; } @@ -1041,6 +1067,8 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx) // variables in the current stack. pt->pt_ectx_stack = &ectx->ec_stack; pt->pt_ectx_frame = ectx->ec_frame_idx; + pt->pt_outer_stack = ectx->ec_outer_stack; + pt->pt_outer_frame = ectx->ec_outer_frame; // If this function returns and the closure is still // being used, we need to make a copy of the context @@ -1220,17 +1248,23 @@ call_def_function( // TODO: is this always the right way? ectx.ec_outer_stack = ¤t_ectx->ec_stack; ectx.ec_outer_frame = current_ectx->ec_frame_idx; + ectx.ec_outer_up_stack = current_ectx->ec_outer_stack; + ectx.ec_outer_up_frame = current_ectx->ec_outer_frame; } else { ectx.ec_outer_stack = partial->pt_ectx_stack; ectx.ec_outer_frame = partial->pt_ectx_frame; + ectx.ec_outer_up_stack = partial->pt_outer_stack; + ectx.ec_outer_up_frame = partial->pt_outer_frame; } } else if (ufunc->uf_partial != NULL) { ectx.ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; ectx.ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; + ectx.ec_outer_up_stack = ufunc->uf_partial->pt_outer_stack; + ectx.ec_outer_up_frame = ufunc->uf_partial->pt_outer_frame; } // dummy frame entries @@ -1514,11 +1548,30 @@ call_def_function( // load variable or argument from outer scope case ISN_LOADOUTER: - if (GA_GROW(&ectx.ec_stack, 1) == FAIL) - goto failed; - copy_tv(STACK_OUT_TV_VAR(iptr->isn_arg.number), + { + typval_T *stack; + int depth = iptr->isn_arg.outer.outer_depth; + + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + if (depth <= 1) + stack = ((typval_T *)ectx.ec_outer_stack->ga_data) + + ectx.ec_outer_frame; + else if (depth == 2) + stack = ((typval_T *)ectx.ec_outer_up_stack->ga_data) + + ectx.ec_outer_up_frame; + else + { + SOURCING_LNUM = iptr->isn_lnum; + iemsg("LOADOUTER level > 2 not supported yet"); + goto failed; + } + + copy_tv(stack + STACK_FRAME_SIZE + + iptr->isn_arg.outer.outer_idx, STACK_TV_BOT(0)); - ++ectx.ec_stack.ga_len; + ++ectx.ec_stack.ga_len; + } break; // load v: variable @@ -1719,7 +1772,8 @@ call_def_function( // store variable or argument in outer scope case ISN_STOREOUTER: --ectx.ec_stack.ga_len; - tv = STACK_OUT_TV_VAR(iptr->isn_arg.number); + // TODO: use outer_depth + tv = STACK_OUT_TV_VAR(iptr->isn_arg.outer.outer_idx); clear_tv(tv); *tv = *STACK_TV_BOT(0); break; @@ -3622,17 +3676,27 @@ ex_disassemble(exarg_T *eap) (varnumber_T)(iptr->isn_arg.number)); break; case ISN_LOAD: - case ISN_LOADOUTER: { - char *add = iptr->isn_type == ISN_LOAD ? "" : "OUTER"; - if (iptr->isn_arg.number < 0) - smsg("%4d LOAD%s arg[%lld]", current, add, + smsg("%4d LOAD arg[%lld]", current, (varnumber_T)(iptr->isn_arg.number + STACK_FRAME_SIZE)); else - smsg("%4d LOAD%s $%lld", current, add, - (varnumber_T)(iptr->isn_arg.number)); + smsg("%4d LOAD $%lld", current, + (varnumber_T)(iptr->isn_arg.number)); + } + break; + case ISN_LOADOUTER: + { + if (iptr->isn_arg.number < 0) + smsg("%4d LOADOUTER level %d arg[%d]", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx + + STACK_FRAME_SIZE); + else + smsg("%4d LOADOUTER level %d $%d", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx); } break; case ISN_LOADV: @@ -3699,16 +3763,22 @@ ex_disassemble(exarg_T *eap) break; case ISN_STORE: + if (iptr->isn_arg.number < 0) + smsg("%4d STORE arg[%lld]", current, + iptr->isn_arg.number + STACK_FRAME_SIZE); + else + smsg("%4d STORE $%lld", current, iptr->isn_arg.number); + break; case ISN_STOREOUTER: { - char *add = iptr->isn_type == ISN_STORE ? "" : "OUTER"; - if (iptr->isn_arg.number < 0) - smsg("%4d STORE%s arg[%lld]", current, add, - (varnumber_T)(iptr->isn_arg.number + STACK_FRAME_SIZE)); + smsg("%4d STOREOUTEr level %d arg[%d]", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx + STACK_FRAME_SIZE); else - smsg("%4d STORE%s $%lld", current, add, - (varnumber_T)(iptr->isn_arg.number)); + smsg("%4d STOREOUTER level %d $%d", current, + iptr->isn_arg.outer.outer_depth, + iptr->isn_arg.outer.outer_idx); } break; case ISN_STOREV: From 0186e58639b19933d3d9188d552fe6745265eb1b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 18:33:11 +0100 Subject: [PATCH 17/22] patch 8.2.2322: Vim9: closure nested limiting to one level Problem: Vim9: closure nested limiting to one level. Solution: Add outer_T. Also make STOREOUTER work. --- src/structs.h | 20 ++- src/testdir/test_vim9_func.vim | 7 + src/version.c | 2 + src/vim9.h | 13 +- src/vim9execute.c | 243 +++++++++++++++++---------------- 5 files changed, 156 insertions(+), 129 deletions(-) diff --git a/src/structs.h b/src/structs.h index 1ed185d5b4..712382a6f6 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1965,6 +1965,14 @@ typedef struct funcstack_S int fs_copyID; // for garray_T collection } funcstack_T; +typedef struct outer_S outer_T; +struct outer_S { + garray_T *out_stack; // stack from outer scope + int out_frame_idx; // index of stack frame in out_stack + outer_T *out_up; // outer scope of outer scope or NULL + int out_up_is_copy; // don't free out_up +}; + struct partial_S { int pt_refcount; // reference count @@ -1975,13 +1983,11 @@ struct partial_S int pt_auto; // when TRUE the partial was created for using // dict.member in handle_subscript() - // For a compiled closure: the arguments and local variables. - garray_T *pt_ectx_stack; // where to find local vars - int pt_ectx_frame; // index of function frame in uf_ectx_stack - garray_T *pt_outer_stack; // pt_ectx_stack one level up - int pt_outer_frame; // pt_ectx_frame one level up. - funcstack_T *pt_funcstack; // copy of stack, used after context - // function returns + // For a compiled closure: the arguments and local variables scope + outer_T pt_outer; + + funcstack_T *pt_funcstack; // copy of stack, used after context + // function returns int pt_argc; // number of arguments typval_T *pt_argv; // arguments in allocated array diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index efbf3d52c1..fdad359de0 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1822,6 +1822,13 @@ def Test_nested_closure_using_argument() assert_equal(['x', 'x2'], DoFilterThis('x')) enddef +def Test_triple_nested_closure() + var what = 'x' + var Match = (val: string, cmp: string): bool => stridx(val, cmp) == 0 + var Filter = (l) => filter(l, (_, v) => Match(v, what)) + assert_equal(['x', 'x2'], ['x', 'y', 'a', 'x2', 'c']->Filter()) +enddef + func Test_silent_echo() CheckScreendump diff --git a/src/version.c b/src/version.c index 891126f4d1..cdf27fac7b 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2322, /**/ 2321, /**/ diff --git a/src/vim9.h b/src/vim9.h index 52247540af..b0c465deb2 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -307,7 +307,7 @@ typedef struct { typedef struct { int outer_idx; // index int outer_depth; // nesting level, stack frames to go up -} outer_T; +} isn_outer_T; /* * Instruction @@ -348,7 +348,7 @@ struct isn_S { put_T put; cmod_T cmdmod; unpack_T unpack; - outer_T outer; + isn_outer_T outer; } isn_arg; }; @@ -375,10 +375,13 @@ struct dfunc_S { // Number of entries used by stack frame for a function call. // - ec_dfunc_idx: function index // - ec_iidx: instruction index -// - ec_outer_stack: stack used for closures TODO: can we avoid this? -// - ec_outer_frame: stack frame for closures +// - ec_outer: stack used for closures // - ec_frame_idx: previous frame index -#define STACK_FRAME_SIZE 5 +#define STACK_FRAME_FUNC_OFF 0 +#define STACK_FRAME_IIDX_OFF 1 +#define STACK_FRAME_OUTER_OFF 2 +#define STACK_FRAME_IDX_OFF 3 +#define STACK_FRAME_SIZE 4 #ifdef DEFINE_VIM9_GLOBALS diff --git a/src/vim9execute.c b/src/vim9execute.c index ca702f67d3..957988e658 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -58,10 +58,7 @@ struct ectx_S { garray_T ec_stack; // stack of typval_T values int ec_frame_idx; // index in ec_stack: context of ec_dfunc_idx - garray_T *ec_outer_stack; // stack used for closures - int ec_outer_frame; // stack frame in ec_outer_stack - garray_T *ec_outer_up_stack; // ec_outer_stack one level up - int ec_outer_up_frame; // ec_outer_frame one level up + outer_T *ec_outer; // outer scope used for closures, allocated garray_T ec_trystack; // stack of trycmd_T values int ec_in_catch; // when TRUE in catch or finally block @@ -153,6 +150,7 @@ exe_newlist(int count, ectx_T *ectx) * Call compiled function "cdf_idx" from compiled code. * This adds a stack frame and sets the instruction pointer to the start of the * called function. + * If "pt" is not null use "pt->pt_outer" for ec_outer. * * Stack has: * - current arguments (already there) @@ -164,7 +162,7 @@ exe_newlist(int count, ectx_T *ectx) * - reserved space for local variables */ static int -call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) +call_dfunc(int cdf_idx, partial_T *pt, int argcount_arg, ectx_T *ectx) { int argcount = argcount_arg; dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + cdf_idx; @@ -247,12 +245,12 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) ectx->ec_stack.ga_len += arg_to_add; // Store current execution state in stack frame for ISN_RETURN. - STACK_TV_BOT(0)->vval.v_number = ectx->ec_dfunc_idx; - STACK_TV_BOT(1)->vval.v_number = ectx->ec_iidx; - STACK_TV_BOT(2)->vval.v_string = (void *)ectx->ec_outer_stack; - STACK_TV_BOT(3)->vval.v_number = ectx->ec_outer_frame; - STACK_TV_BOT(4)->vval.v_number = ectx->ec_frame_idx; - // TODO: save ec_outer_up_stack as well? + STACK_TV_BOT(STACK_FRAME_FUNC_OFF)->vval.v_number = ectx->ec_dfunc_idx; + STACK_TV_BOT(STACK_FRAME_IIDX_OFF)->vval.v_number = ectx->ec_iidx; + if (ectx->ec_outer != NULL) + printf("here"); + STACK_TV_BOT(STACK_FRAME_OUTER_OFF)->vval.v_string = (void *)ectx->ec_outer; + STACK_TV_BOT(STACK_FRAME_IDX_OFF)->vval.v_number = ectx->ec_frame_idx; ectx->ec_frame_idx = ectx->ec_stack.ga_len; // Initialize local variables @@ -267,20 +265,32 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) } ectx->ec_stack.ga_len += STACK_FRAME_SIZE + varcount; - if (ufunc->uf_partial != NULL) + if (pt != NULL || ufunc->uf_partial != NULL || ufunc->uf_flags & FC_CLOSURE) { - ectx->ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; - ectx->ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; - ectx->ec_outer_up_stack = ufunc->uf_partial->pt_outer_stack; - ectx->ec_outer_up_frame = ufunc->uf_partial->pt_outer_frame; - } - else if (ufunc->uf_flags & FC_CLOSURE) - { - ectx->ec_outer_stack = &ectx->ec_stack; - ectx->ec_outer_frame = ectx->ec_frame_idx; - ectx->ec_outer_up_stack = ectx->ec_outer_stack; - ectx->ec_outer_up_frame = ectx->ec_outer_frame; + outer_T *outer = ALLOC_CLEAR_ONE(outer_T); + + if (outer == NULL) + return FAIL; + if (pt != NULL) + { + *outer = pt->pt_outer; + outer->out_up_is_copy = TRUE; + } + else if (ufunc->uf_partial != NULL) + { + *outer = ufunc->uf_partial->pt_outer; + outer->out_up_is_copy = TRUE; + } + else + { + outer->out_stack = &ectx->ec_stack; + outer->out_frame_idx = ectx->ec_frame_idx; + outer->out_up = ectx->ec_outer; + } + ectx->ec_outer = outer; } + else + ectx->ec_outer = NULL; // Set execution state to the start of the called function. ectx->ec_dfunc_idx = cdf_idx; @@ -429,10 +439,9 @@ handle_closure_in_use(ectx_T *ectx, int free_arguments) { ++funcstack->fs_refcount; pt->pt_funcstack = funcstack; - pt->pt_ectx_stack = &funcstack->fs_ga; - pt->pt_ectx_frame = ectx->ec_frame_idx - top; - pt->pt_outer_stack = ectx->ec_outer_stack; - pt->pt_outer_frame = ectx->ec_outer_frame; + pt->pt_outer.out_stack = &funcstack->fs_ga; + pt->pt_outer.out_frame_idx = ectx->ec_frame_idx - top; + pt->pt_outer.out_up = ectx->ec_outer; } } } @@ -518,17 +527,25 @@ func_return(ectx_T *ectx) // The return value should be on top of the stack. However, when aborting // it may not be there and ec_frame_idx is the top of the stack. ret_idx = ectx->ec_stack.ga_len - 1; - if (ret_idx == ectx->ec_frame_idx + 4) + if (ret_idx == ectx->ec_frame_idx + STACK_FRAME_IDX_OFF) ret_idx = 0; + if (ectx->ec_outer != NULL) + printf("here"); + vim_free(ectx->ec_outer); + // Restore the previous frame. - ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame_idx)->vval.v_number; - ectx->ec_iidx = STACK_TV(ectx->ec_frame_idx + 1)->vval.v_number; - ectx->ec_outer_stack = - (void *)STACK_TV(ectx->ec_frame_idx + 2)->vval.v_string; - ectx->ec_outer_frame = STACK_TV(ectx->ec_frame_idx + 3)->vval.v_number; + ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_FUNC_OFF)->vval.v_number; + ectx->ec_iidx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_IIDX_OFF)->vval.v_number; + ectx->ec_outer = (void *)STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_OUTER_OFF)->vval.v_string; // restoring ec_frame_idx must be last - ectx->ec_frame_idx = STACK_TV(ectx->ec_frame_idx + 4)->vval.v_number; + ectx->ec_frame_idx = STACK_TV(ectx->ec_frame_idx + + STACK_FRAME_IDX_OFF)->vval.v_number; + if (ectx->ec_outer != NULL) + printf("here"); dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ectx->ec_instr = dfunc->df_instr; @@ -617,10 +634,16 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx) * If the function is compiled this will add a stack frame and set the * instruction pointer at the start of the function. * Otherwise the function is called here. + * If "pt" is not null use "pt->pt_outer" for ec_outer. * "iptr" can be used to replace the instruction with a more efficient one. */ static int -call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx, isn_T *iptr) +call_ufunc( + ufunc_T *ufunc, + partial_T *pt, + int argcount, + ectx_T *ectx, + isn_T *iptr) { typval_T argvars[MAX_FUNC_ARGS]; funcexe_T funcexe; @@ -653,7 +676,7 @@ call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx, isn_T *iptr) iptr->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; iptr->isn_arg.dfunc.cdf_argcount = argcount; } - return call_dfunc(ufunc->uf_dfunc_idx, argcount, ectx); + return call_dfunc(ufunc->uf_dfunc_idx, pt, argcount, ectx); } if (call_prepare(argcount, argvars, ectx) == FAIL) @@ -726,7 +749,7 @@ call_by_name(char_u *name, int argcount, ectx_T *ectx, isn_T *iptr) } if (ufunc != NULL) - return call_ufunc(ufunc, argcount, ectx, iptr); + return call_ufunc(ufunc, NULL, argcount, ectx, iptr); return FAIL; } @@ -761,22 +784,8 @@ call_partial(typval_T *tv, int argcount_arg, ectx_T *ectx) } if (pt->pt_func != NULL) - { - int frame_idx = ectx->ec_frame_idx; - int ret = call_ufunc(pt->pt_func, argcount, ectx, NULL); + return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL); - if (ectx->ec_frame_idx != frame_idx) - { - // call_dfunc() added a stack frame, closure may need the - // function context where it was defined. - ectx->ec_outer_stack = pt->pt_ectx_stack; - ectx->ec_outer_frame = pt->pt_ectx_frame; - ectx->ec_outer_up_stack = pt->pt_outer_stack; - ectx->ec_outer_up_frame = pt->pt_outer_frame; - } - - return ret; - } name = pt->pt_name; } else if (tv->v_type == VAR_FUNC) @@ -1065,10 +1074,10 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx) // The closure needs to find arguments and local // variables in the current stack. - pt->pt_ectx_stack = &ectx->ec_stack; - pt->pt_ectx_frame = ectx->ec_frame_idx; - pt->pt_outer_stack = ectx->ec_outer_stack; - pt->pt_outer_frame = ectx->ec_outer_frame; + pt->pt_outer.out_stack = &ectx->ec_stack; + pt->pt_outer.out_frame_idx = ectx->ec_frame_idx; + pt->pt_outer.out_up = ectx->ec_outer; + pt->pt_outer.out_up_is_copy = TRUE; // If this function returns and the closure is still // being used, we need to make a copy of the context @@ -1135,9 +1144,6 @@ call_def_function( // Get pointer to a local variable on the stack. Negative for arguments. #define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx) -// Like STACK_TV_VAR but use the outer scope -#define STACK_OUT_TV_VAR(idx) (((typval_T *)ectx.ec_outer_stack->ga_data) + ectx.ec_outer_frame + STACK_FRAME_SIZE + idx) - if (ufunc->uf_def_status == UF_NOT_COMPILED || (ufunc->uf_def_status == UF_TO_BE_COMPILED && compile_def_function(ufunc, FALSE, NULL) == FAIL)) @@ -1241,30 +1247,24 @@ call_def_function( ectx.ec_frame_idx = ectx.ec_stack.ga_len; initial_frame_idx = ectx.ec_frame_idx; - if (partial != NULL) + if (partial != NULL || ufunc->uf_partial != NULL) { - if (partial->pt_ectx_stack == NULL && current_ectx != NULL) + ectx.ec_outer = ALLOC_CLEAR_ONE(outer_T); + if (ectx.ec_outer == NULL) + goto failed_early; + if (partial != NULL) { - // TODO: is this always the right way? - ectx.ec_outer_stack = ¤t_ectx->ec_stack; - ectx.ec_outer_frame = current_ectx->ec_frame_idx; - ectx.ec_outer_up_stack = current_ectx->ec_outer_stack; - ectx.ec_outer_up_frame = current_ectx->ec_outer_frame; + if (partial->pt_outer.out_stack == NULL && current_ectx != NULL) + { + if (current_ectx->ec_outer != NULL) + *ectx.ec_outer = *current_ectx->ec_outer; + } + else + *ectx.ec_outer = partial->pt_outer; } else - { - ectx.ec_outer_stack = partial->pt_ectx_stack; - ectx.ec_outer_frame = partial->pt_ectx_frame; - ectx.ec_outer_up_stack = partial->pt_outer_stack; - ectx.ec_outer_up_frame = partial->pt_outer_frame; - } - } - else if (ufunc->uf_partial != NULL) - { - ectx.ec_outer_stack = ufunc->uf_partial->pt_ectx_stack; - ectx.ec_outer_frame = ufunc->uf_partial->pt_ectx_frame; - ectx.ec_outer_up_stack = ufunc->uf_partial->pt_outer_stack; - ectx.ec_outer_up_frame = ufunc->uf_partial->pt_outer_frame; + *ectx.ec_outer = ufunc->uf_partial->pt_outer; + ectx.ec_outer->out_up_is_copy = TRUE; } // dummy frame entries @@ -1546,34 +1546,6 @@ call_def_function( ++ectx.ec_stack.ga_len; break; - // load variable or argument from outer scope - case ISN_LOADOUTER: - { - typval_T *stack; - int depth = iptr->isn_arg.outer.outer_depth; - - if (GA_GROW(&ectx.ec_stack, 1) == FAIL) - goto failed; - if (depth <= 1) - stack = ((typval_T *)ectx.ec_outer_stack->ga_data) - + ectx.ec_outer_frame; - else if (depth == 2) - stack = ((typval_T *)ectx.ec_outer_up_stack->ga_data) - + ectx.ec_outer_up_frame; - else - { - SOURCING_LNUM = iptr->isn_lnum; - iemsg("LOADOUTER level > 2 not supported yet"); - goto failed; - } - - copy_tv(stack + STACK_FRAME_SIZE - + iptr->isn_arg.outer.outer_idx, - STACK_TV_BOT(0)); - ++ectx.ec_stack.ga_len; - } - break; - // load v: variable case ISN_LOADV: if (GA_GROW(&ectx.ec_stack, 1) == FAIL) @@ -1769,15 +1741,6 @@ call_def_function( *tv = *STACK_TV_BOT(0); break; - // store variable or argument in outer scope - case ISN_STOREOUTER: - --ectx.ec_stack.ga_len; - // TODO: use outer_depth - tv = STACK_OUT_TV_VAR(iptr->isn_arg.outer.outer_idx); - clear_tv(tv); - *tv = *STACK_TV_BOT(0); - break; - // store s: variable in old script case ISN_STORES: { @@ -2058,6 +2021,43 @@ call_def_function( } break; + // load or store variable or argument from outer scope + case ISN_LOADOUTER: + case ISN_STOREOUTER: + { + int depth = iptr->isn_arg.outer.outer_depth; + outer_T *outer = ectx.ec_outer; + + while (depth > 1 && outer != NULL) + { + outer = outer->out_up; + --depth; + } + if (outer == NULL) + { + SOURCING_LNUM = iptr->isn_lnum; + iemsg("LOADOUTER depth more than scope levels"); + goto failed; + } + tv = ((typval_T *)outer->out_stack->ga_data) + + outer->out_frame_idx + STACK_FRAME_SIZE + + iptr->isn_arg.outer.outer_idx; + if (iptr->isn_type == ISN_LOADOUTER) + { + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + else + { + --ectx.ec_stack.ga_len; + clear_tv(tv); + *tv = *STACK_TV_BOT(0); + } + } + break; + // unlet item in list or dict variable case ISN_UNLETINDEX: { @@ -2296,7 +2296,7 @@ call_def_function( // call a :def function case ISN_DCALL: SOURCING_LNUM = iptr->isn_lnum; - if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx, + if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx, NULL, iptr->isn_arg.dfunc.cdf_argcount, &ectx) == FAIL) goto on_error; @@ -3555,6 +3555,15 @@ failed_early: vim_free(ectx.ec_stack.ga_data); vim_free(ectx.ec_trystack.ga_data); + while (ectx.ec_outer != NULL) + { + outer_T *up = ectx.ec_outer->out_up_is_copy + ? NULL : ectx.ec_outer->out_up; + + vim_free(ectx.ec_outer); + ectx.ec_outer = up; + } + // Not sure if this is necessary. suppress_errthrow = save_suppress_errthrow; From 31a11b942a56bf75a653eec0976f365f9b389a5a Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 19:23:27 +0100 Subject: [PATCH 18/22] patch 8.2.2323: Vim9: error when inferring type from empty dict/list Problem: Vim9: error when inferring type from empty dict/list. Solution: When the member is t_unknown use t_any. (closes #7009) --- src/testdir/test_vim9_expr.vim | 19 +++++++++++++++++++ src/version.c | 2 ++ src/vim9compile.c | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index fc42088d81..8955a7ccf0 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -2929,6 +2929,16 @@ def Test_expr7_list_subscript() lines = ['var l = [0, 1, 2]', 'echo l[g:astring : g:theone]'] CheckDefExecFailure(lines, 'E1012:') CheckScriptFailure(['vim9script'] + lines, 'E1030:', 3) + + lines =<< trim END + vim9script + var ld = [] + def Func() + eval ld[0].key + enddef + defcompile + END + CheckScriptSuccess(lines) enddef def Test_expr7_dict_subscript() @@ -2937,6 +2947,15 @@ def Test_expr7_dict_subscript() var l = [{lnum: 2}, {lnum: 1}] var res = l[0].lnum > l[1].lnum assert_true(res) + + var dd = {} + def Func1() + eval dd.key1.key2 + enddef + def Func2() + eval dd['key1'].key2 + enddef + defcompile END CheckScriptSuccess(lines) enddef diff --git a/src/version.c b/src/version.c index cdf27fac7b..a21122aa60 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2323, /**/ 2322, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index a6bb56571e..94f30d4395 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1899,7 +1899,10 @@ generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len) } // change dict type to dict member type if (type->tt_type == VAR_DICT) - ((type_T **)stack->ga_data)[stack->ga_len - 1] = type->tt_member; + { + ((type_T **)stack->ga_data)[stack->ga_len - 1] = + type->tt_member == &t_unknown ? &t_any : type->tt_member; + } return OK; } @@ -3793,7 +3796,12 @@ compile_subscript( return FAIL; } if ((*typep)->tt_type == VAR_DICT) + { *typep = (*typep)->tt_member; + if (*typep == &t_unknown) + // empty dict was used + *typep = &t_any; + } else { if (need_type(*typep, &t_dict_any, -2, cctx, @@ -3831,7 +3839,12 @@ compile_subscript( else { if ((*typep)->tt_type == VAR_LIST) + { *typep = (*typep)->tt_member; + if (*typep == &t_unknown) + // empty list was used + *typep = &t_any; + } if (generate_instr_drop(cctx, vtype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX, 1) == FAIL) From 6f02b00bb0958f70bc15534e115b4c6dadff0e06 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 20:22:54 +0100 Subject: [PATCH 19/22] patch 8.2.2324: not easy to get mark en cursor posotion by character count Problem: Not easy to get mark en cursor posotion by character count. Solution: Add functions that use character index. (Yegappan Lakshmanan, closes #7648) --- runtime/doc/eval.txt | 150 +++++++++--- runtime/doc/usr_41.txt | 5 + src/eval.c | 100 +++++++- src/evalfunc.c | 406 +++++++++++++++++++------------ src/proto/eval.pro | 6 +- src/tag.c | 2 +- src/testdir/test_cursor_func.vim | 185 +++++++++++++- src/typval.c | 2 +- src/version.c | 2 + 9 files changed, 668 insertions(+), 190 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 750448dfc6..7848534b5d 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.2. Last change: 2020 Dec 29 +*eval.txt* For Vim version 8.2. Last change: 2021 Jan 10 VIM REFERENCE MANUAL by Bram Moolenaar @@ -49,7 +49,7 @@ There are ten types of variables: *Number* *Integer* Number A 32 or 64 bit signed number. |expr-number| The number of bits is available in |v:numbersize|. - Examples: -123 0x10 0177 0b1011 + Examples: -123 0x10 0177 0o177 0b1011 Float A floating point number. |floating-point-format| *Float* {only when compiled with the |+float| feature} @@ -97,9 +97,10 @@ the Number. Examples: Conversion from a String to a Number only happens in legacy Vim script, not in Vim9 script. It is done by converting the first digits to a number. Hexadecimal "0xf9", Octal "017" or "0o17", and Binary "0b10" -numbers are recognized (NOTE: when using |scriptversion-4| octal with a -leading "0" is not recognized). If the String doesn't start with digits, the -result is zero. +numbers are recognized +NOTE: when using |scriptversion-4| octal with a leading "0" is not recognized. +The 0o notation requires patch 8.2.0886. +If the String doesn't start with digits, the result is zero. Examples: String "456" --> Number 456 ~ String "6bar" --> Number 6 ~ @@ -1150,7 +1151,7 @@ expr7 *expr7* For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one). For '-' the sign of the number is changed. -For '+' the number is unchanged. +For '+' the number is unchanged. Note: "++" has no effect. A String will be converted to a Number first. @@ -1191,6 +1192,7 @@ start with one! If the length of the String is less than the index, the result is an empty String. A negative index always results in an empty string (reason: backward compatibility). Use [-1:] to get the last byte or character. +In Vim9 script a negative index is used like with a list: count from the end. If expr8 is a |List| then it results the item at index expr1. See |list-index| for possible index values. If the index is out of range this results in an @@ -1318,8 +1320,8 @@ When using the lambda form there must be no white space between the } and the number ------ number number constant *expr-number* - *hex-number* *octal-number* *binary-number* + *0x* *hex-number* *0o* *octal-number* *binary-number* Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B) and Octal (starting with 0, 0o or 0O). @@ -1572,7 +1574,7 @@ Note how execute() is used to execute an Ex command. That's ugly though. Lambda expressions have internal names like '42'. If you get an error for a lambda expression, you can find what it is with the following command: > - :function {'42'} + :function 42 See also: |numbered-function| ============================================================================== @@ -2475,12 +2477,13 @@ ch_status({handle} [, {options}]) changenr() Number current change number char2nr({expr} [, {utf8}]) Number ASCII/UTF8 value of first char in {expr} charclass({string}) Number character class of {string} +charcol({expr}) Number column number of cursor or mark charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} chdir({dir}) String change current working directory cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches -col({expr}) Number column nr of cursor or mark +col({expr}) Number column byte index of cursor or mark complete({startcol}, {matches}) none set Insert mode completion complete_add({expr}) Number add completion match complete_check() Number check for key typed during completion @@ -2558,6 +2561,7 @@ getbufvar({expr}, {varname} [, {def}]) getchangelist([{expr}]) List list of change list items getchar([expr]) Number get one character from the user getcharmod() Number modifiers for the last typed character +getcharpos({expr}) List position of cursor, mark, etc. getcharsearch() Dict last character search getcmdline() String return the current command-line getcmdpos() Number return cursor position in command-line @@ -2566,6 +2570,7 @@ getcmdwintype() String return current command-line window type getcompletion({pat}, {type} [, {filtered}]) List list of cmdline completion matches getcurpos([{winnr}]) List position of the cursor +getcursorcharpos([{winnr}]) List character position of the cursor getcwd([{winnr} [, {tabnr}]]) String get the current working directory getenv({name}) String return environment variable getfontname([{name}]) String name of font being used @@ -2828,8 +2833,10 @@ setbufline({expr}, {lnum}, {text}) setbufvar({expr}, {varname}, {val}) none set {varname} in buffer {expr} to {val} setcellwidths({list}) none set character cell width overrides +setcharpos({expr}, {list}) Number set the {expr} position to {list} setcharsearch({dict}) Dict set character search from {dict} setcmdpos({pos}) Number set cursor position in command-line +setcursorcharpos({list}) Number move cursor to position in {list} setenv({name}, {val}) none set environment variable setfperm({fname}, {mode}) Number set {fname} file permissions to {mode} setline({lnum}, {line}) Number set line {lnum} to {line} @@ -3513,8 +3520,8 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()* < The first and third echo result in 3 ('e' plus composing character is 3 bytes), the second echo results in 1 ('e' is one byte). - Only works differently from byteidx() when 'encoding' is set to - a Unicode encoding. + Only works differently from byteidx() when 'encoding' is set + to a Unicode encoding. Can also be used as a |method|: > GetName()->byteidxcomp(idx) @@ -3590,6 +3597,18 @@ charclass({string}) *charclass()* other specific Unicode class The class is used in patterns and word motions. + *charcol()* +charcol({expr}) Same as |col()| but returns the character index of the column + position given with {expr} instead of the byte position. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + charcol('.') returns 3 + col('.') returns 7 + +< Can also be used as a |method|: > + GetPos()->col() +< *charidx()* charidx({string}, {idx} [, {countcc}]) Return the character index of the byte at {idx} in {string}. @@ -3680,7 +3699,8 @@ col({expr}) The result is a Number, which is the byte index of the column out of range then col() returns zero. To get the line number use |line()|. To get both use |getpos()|. - For the screen column position use |virtcol()|. + For the screen column position use |virtcol()|. For the + character position use |charcol()|. Note that only marks in the current file can be used. Examples: > col(".") column of cursor @@ -3981,6 +4001,9 @@ cursor({list}) This is like the return value of |getpos()| or |getcurpos()|, but without the first item. + To position the cursor using the character count, use + |setcursorcharpos()|. + Does not change the jumplist. If {lnum} is greater than the number of lines in the buffer, the cursor will be positioned at the last line in the buffer. @@ -5220,6 +5243,20 @@ getcharmod() *getcharmod()* character itself are obtained. Thus Shift-a results in "A" without a modifier. + *getcharpos()* +getcharpos({expr}) + Get the position for {expr}. Same as |getpos()| but the column + number in the returned List is a character index instead of + a byte index. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + getcharpos('.') returns [0, 5, 3, 0] + getpos('.') returns [0, 5, 7, 0] +< + Can also be used as a |method|: > + GetMark()->getcharpos() + getcharsearch() *getcharsearch()* Return the current character search information as a {dict} with the following entries: @@ -5345,8 +5382,11 @@ getcurpos([{winid}]) includes an extra "curswant" item in the list: [0, lnum, col, off, curswant] ~ The "curswant" number is the preferred column when moving the - cursor vertically. Also see |getpos()|. - The first "bufnum" item is always zero. + cursor vertically. Also see |getcursorcharpos()| and + |getpos()|. + The first "bufnum" item is always zero. The byte position of + the cursor is returned in 'col'. To get the character + position, use |getcursorcharpos()|. The optional {winid} argument can specify the window. It can be the window number or the |window-ID|. The last known @@ -5360,7 +5400,24 @@ getcurpos([{winid}]) call setpos('.', save_cursor) < Note that this only works within the window. See |winrestview()| for restoring more state. - *getcwd()* + + Can also be used as a |method|: > + GetWinid()->getcurpos() + +< *getcursorcharpos()* +getcursorcharpos([{winid}]) + Same as |getcurpos()| but the column number in the returned + List is a character index instead of a byte index. + + Example: + With the cursor on '보' in line 3 with text "여보세요": > + getcursorcharpos() returns [0, 3, 2, 0, 3] + getcurpos() returns [0, 3, 4, 0, 3] + +< Can also be used as a |method|: > + GetWinid()->getcursorcharpos() + +< *getcwd()* getcwd([{winnr} [, {tabnr}]]) The result is a String, which is the name of the current working directory. @@ -5667,16 +5724,18 @@ getpos({expr}) Get the position for {expr}. For possible values of {expr} Note that for '< and '> Visual mode matters: when it is "V" (visual line mode) the column of '< is zero and the column of '> is a large number. + The column number in the returned List is the byte position + within the line. To get the character position in the line, + use |getcharpos()| This can be used to save and restore the position of a mark: > let save_a_mark = getpos("'a") ... call setpos("'a", save_a_mark) -< Also see |getcurpos()| and |setpos()|. +< Also see |getcharpos()|, |getcurpos()| and |setpos()|. Can also be used as a |method|: > GetMark()->getpos() - getqflist([{what}]) *getqflist()* Returns a |List| with all the current quickfix errors. Each list item is a dictionary with these entries: @@ -7542,8 +7601,10 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* < *max()* -max({expr}) Return the maximum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +max({expr}) Return the maximum value of all items in {expr}. Example: > + echo max([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the maximum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7613,8 +7674,10 @@ menu_info({name} [, {mode}]) *menu_info()* < *min()* -min({expr}) Return the minimum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +min({expr}) Return the minimum value of all items in {expr}. Example: > + echo min([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the minimum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7631,13 +7694,13 @@ mkdir({name} [, {path} [, {prot}]]) necessary. Otherwise it must be "". If {prot} is given it is used to set the protection bits of - the new directory. The default is 0755 (rwxr-xr-x: r/w for - the user readable for others). Use 0700 to make it unreadable - for others. This is only used for the last part of {name}. - Thus if you create /tmp/foo/bar then /tmp/foo will be created - with 0755. + the new directory. The default is 0o755 (rwxr-xr-x: r/w for + the user, readable for others). Use 0o700 to make it + unreadable for others. This is only used for the last part of + {name}. Thus if you create /tmp/foo/bar then /tmp/foo will be + created with 0o755. Example: > - :call mkdir($HOME . "/tmp/foo/bar", "p", 0700) + :call mkdir($HOME . "/tmp/foo/bar", "p", 0o700) < This function is not available in the |sandbox|. @@ -9200,6 +9263,19 @@ setcellwidths({list}) *setcellwidths()* < You can use the script $VIMRUNTIME/tools/emoji_list.vim to see the effect for known emoji characters. +setcharpos({expr}, {list}) *setcharpos()* + Same as |setpos()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 8: > + call setcharpos('.', [0, 8, 4, 0]) +< positions the cursor on the fourth character '요'. > + call setpos('.', [0, 8, 4, 0]) +< positions the cursor on the second character '보'. + + Can also be used as a |method|: > + GetPosition()->setcharpos('.') setcharsearch({dict}) *setcharsearch()* Set the current character search information to {dict}, @@ -9242,6 +9318,21 @@ setcmdpos({pos}) *setcmdpos()* Can also be used as a |method|: > GetPos()->setcmdpos() +setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()* +setcursorcharpos({list}) + Same as |cursor()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 4: > + call setcursorcharpos(4, 3) +< positions the cursor on the third character '세'. > + call cursor(4, 3) +< positions the cursor on the first character '여'. + + Can also be used as a |method|: > + GetCursorPos()->setcursorcharpos() + setenv({name}, {val}) *setenv()* Set environment variable {name} to {val}. When {val} is |v:null| the environment variable is deleted. @@ -9353,7 +9444,8 @@ setpos({expr}, {list}) "lnum" and "col" are the position in the buffer. The first column is 1. Use a zero "lnum" to delete a mark. If "col" is - smaller than 1 then 1 is used. + smaller than 1 then 1 is used. To use the character count + instead of the byte count, use |setcharpos()|. The "off" number is only used when 'virtualedit' is set. Then it is the offset in screen columns from the start of the @@ -9373,7 +9465,7 @@ setpos({expr}, {list}) Returns 0 when the position could be set, -1 otherwise. An error message is given if {expr} is invalid. - Also see |getpos()| and |getcurpos()|. + Also see |setcharpos()|, |getpos()| and |getcurpos()|. This does not restore the preferred column for moving vertically; if you set the cursor position with this, |j| and diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index a035038e47..da095e13f4 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -753,6 +753,11 @@ Cursor and mark position: *cursor-functions* *mark-functions* screenchar() get character code at a screen line/row screenchars() get character codes at a screen line/row screenstring() get string of characters at a screen line/row + charcol() character number of the cursor or a mark + getcharpos() get character position of cursor, mark, etc. + setcharpos() set character position of cursor, mark, etc. + getcursorcharpos() get character position of the cursor + setcursorcharpos() set character position of the cursor Working with text in the current buffer: *text-functions* getline() get a line or list of lines from the buffer diff --git a/src/eval.c b/src/eval.c index 9c91e526d3..a0877f649b 100644 --- a/src/eval.c +++ b/src/eval.c @@ -5053,6 +5053,61 @@ string2float( } #endif +/* + * Convert the specified byte index of line 'lnum' in buffer 'buf' to a + * character index. Works only for loaded buffers. Returns -1 on failure. + * The index of the first character is one. + */ + int +buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx) +{ + char_u *str; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + if (*str == NUL) + return 1; + + return mb_charlen_len(str, byteidx + 1); +} + +/* + * Convert the specified character index of line 'lnum' in buffer 'buf' to a + * byte index. Works only for loaded buffers. Returns -1 on failure. The index + * of the first byte and the first character is one. + */ + int +buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + char_u *str; + char_u *t; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + // Convert the character offset to a byte offset + t = str; + while (*t != NUL && --charidx > 0) + t += mb_ptr2len(t); + + return t - str + 1; +} + /* * Translate a String variable into a position. * Returns NULL when there is an error. @@ -5061,7 +5116,8 @@ string2float( var2fpos( typval_T *varp, int dollar_lnum, // TRUE when $ is last line - int *fnum) // set to fnum for '0, 'A, etc. + int *fnum, // set to fnum for '0, 'A, etc. + int charcol) // return character column { char_u *name; static pos_T pos; @@ -5083,7 +5139,10 @@ var2fpos( pos.lnum = list_find_nr(l, 0L, &error); if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) return NULL; // invalid line number - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) + len = (long)mb_charlen(ml_get(pos.lnum)); + else + len = (long)STRLEN(ml_get(pos.lnum)); // Get the column number // We accept "$" for the column number: last column. @@ -5118,18 +5177,29 @@ var2fpos( if (name == NULL) return NULL; if (name[0] == '.') // cursor - return &curwin->w_cursor; + { + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; + } if (name[0] == 'v' && name[1] == NUL) // Visual start { if (VIsual_active) - return &VIsual; - return &curwin->w_cursor; + pos = VIsual; + else + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; } if (name[0] == '\'') // mark { pp = getmark_buf_fnum(curbuf, name[1], FALSE, fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) return NULL; + if (charcol) + pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1; return pp; } @@ -5164,7 +5234,10 @@ var2fpos( else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(ml_get_curline()); + if (charcol) + pos.col = (colnr_T)mb_charlen(ml_get_curline()); + else + pos.col = (colnr_T)STRLEN(ml_get_curline()); } return &pos; } @@ -5184,7 +5257,8 @@ list2fpos( typval_T *arg, pos_T *posp, int *fnump, - colnr_T *curswantp) + colnr_T *curswantp, + int charcol) { list_T *l = arg->vval.v_list; long i = 0; @@ -5216,6 +5290,18 @@ list2fpos( n = list_find_nr(l, i++, NULL); // col if (n < 0) return FAIL; + // If character position is specified, then convert to byte position + if (charcol) + { + buf_T *buf; + + // Get the text for the specified line in a loaded buffer + buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return FAIL; + + n = buf_charidx_to_byteidx(buf, posp->lnum, n); + } posp->col = n; n = list_find_nr(l, i, NULL); // off diff --git a/src/evalfunc.c b/src/evalfunc.c index 098d906ecf..85f8befff6 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -47,6 +47,7 @@ static void f_ceil(typval_T *argvars, typval_T *rettv); #endif static void f_changenr(typval_T *argvars, typval_T *rettv); static void f_char2nr(typval_T *argvars, typval_T *rettv); +static void f_charcol(typval_T *argvars, typval_T *rettv); static void f_charidx(typval_T *argvars, typval_T *rettv); static void f_col(typval_T *argvars, typval_T *rettv); static void f_confirm(typval_T *argvars, typval_T *rettv); @@ -87,12 +88,14 @@ static void f_function(typval_T *argvars, typval_T *rettv); static void f_garbagecollect(typval_T *argvars, typval_T *rettv); static void f_get(typval_T *argvars, typval_T *rettv); static void f_getchangelist(typval_T *argvars, typval_T *rettv); +static void f_getcharpos(typval_T *argvars, typval_T *rettv); static void f_getcharsearch(typval_T *argvars, typval_T *rettv); static void f_getenv(typval_T *argvars, typval_T *rettv); static void f_getfontname(typval_T *argvars, typval_T *rettv); static void f_getjumplist(typval_T *argvars, typval_T *rettv); static void f_getpid(typval_T *argvars, typval_T *rettv); static void f_getcurpos(typval_T *argvars, typval_T *rettv); +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_getpos(typval_T *argvars, typval_T *rettv); static void f_getreg(typval_T *argvars, typval_T *rettv); static void f_getreginfo(typval_T *argvars, typval_T *rettv); @@ -190,7 +193,9 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv); static void f_searchpair(typval_T *argvars, typval_T *rettv); static void f_searchpairpos(typval_T *argvars, typval_T *rettv); static void f_searchpos(typval_T *argvars, typval_T *rettv); +static void f_setcharpos(typval_T *argvars, typval_T *rettv); static void f_setcharsearch(typval_T *argvars, typval_T *rettv); +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_setenv(typval_T *argvars, typval_T *rettv); static void f_setfperm(typval_T *argvars, typval_T *rettv); static void f_setpos(typval_T *argvars, typval_T *rettv); @@ -790,6 +795,8 @@ static funcentry_T global_functions[] = ret_number, f_char2nr}, {"charclass", 1, 1, FEARG_1, NULL, ret_number, f_charclass}, + {"charcol", 1, 1, FEARG_1, NULL, + ret_number, f_charcol}, {"charidx", 2, 3, FEARG_1, NULL, ret_number, f_charidx}, {"chdir", 1, 1, FEARG_1, NULL, @@ -928,6 +935,8 @@ static funcentry_T global_functions[] = ret_number, f_getchar}, {"getcharmod", 0, 0, 0, NULL, ret_number, f_getcharmod}, + {"getcharpos", 1, 1, FEARG_1, NULL, + ret_list_number, f_getcharpos}, {"getcharsearch", 0, 0, 0, NULL, ret_dict_any, f_getcharsearch}, {"getcmdline", 0, 0, 0, NULL, @@ -942,6 +951,8 @@ static funcentry_T global_functions[] = ret_list_string, f_getcompletion}, {"getcurpos", 0, 1, FEARG_1, NULL, ret_list_number, f_getcurpos}, + {"getcursorcharpos", 0, 1, FEARG_1, NULL, + ret_list_number, f_getcursorcharpos}, {"getcwd", 0, 2, FEARG_1, NULL, ret_string, f_getcwd}, {"getenv", 1, 1, FEARG_1, NULL, @@ -1394,10 +1405,14 @@ static funcentry_T global_functions[] = ret_void, f_setbufvar}, {"setcellwidths", 1, 1, FEARG_1, NULL, ret_void, f_setcellwidths}, + {"setcharpos", 2, 2, FEARG_2, NULL, + ret_number, f_setcharpos}, {"setcharsearch", 1, 1, FEARG_1, NULL, ret_void, f_setcharsearch}, {"setcmdpos", 1, 1, FEARG_1, NULL, ret_number, f_setcmdpos}, + {"setcursorcharpos", 1, 3, FEARG_1, NULL, + ret_number, f_setcursorcharpos}, {"setenv", 2, 2, FEARG_2, NULL, ret_void, f_setenv}, {"setfperm", 2, 2, FEARG_1, NULL, @@ -2423,6 +2438,61 @@ f_char2nr(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = tv_get_string(&argvars[0])[0]; } +/* + * Get the current cursor column and store it in 'rettv'. If 'charcol' is TRUE, + * returns the character index of the column. Otherwise, returns the byte index + * of the column. + */ + static void +get_col(typval_T *argvars, typval_T *rettv, int charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) + { + if (fp->col == MAXCOL) + { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + else + col = MAXCOL; + } + else + { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) + { + char_u *p = ml_get_cursor(); + + if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, + curwin->w_virtcol - curwin->w_cursor.coladd)) + { + int l; + + if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) + col += l; + } + } + } + } + rettv->vval.v_number = col; +} + +/* + * "charcol()" function + */ + static void +f_charcol(typval_T *argvars, typval_T *rettv) +{ + get_col(argvars, rettv, TRUE); +} + /* * "charidx()" function */ @@ -2497,42 +2567,7 @@ get_optional_window(typval_T *argvars, int idx) static void f_col(typval_T *argvars, typval_T *rettv) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) - { - if (fp->col == MAXCOL) - { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; - } - else - { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) - { - char_u *p = ml_get_cursor(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) - { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, FALSE); } /* @@ -2633,26 +2668,24 @@ f_cosh(typval_T *argvars, typval_T *rettv) #endif /* - * "cursor(lnum, col)" function, or - * "cursor(list)" - * - * Moves the cursor to the specified line and column. - * Returns 0 when the position could be set, -1 otherwise. + * Set the cursor position. + * If 'charcol' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. */ static void -f_cursor(typval_T *argvars, typval_T *rettv) +set_cursorpos(typval_T *argvars, typval_T *rettv, int charcol) { long line, col; long coladd = 0; int set_curswant = TRUE; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; @@ -2666,15 +2699,24 @@ f_cursor(typval_T *argvars, typval_T *rettv) set_curswant = FALSE; } } - else + else if ((argvars[0].v_type == VAR_NUMBER || + argvars[0].v_type == VAR_STRING) + && argvars[1].v_type == VAR_NUMBER) { line = tv_get_lnum(argvars); if (line < 0) semsg(_(e_invarg2), tv_get_string(&argvars[0])); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) + col = buf_charidx_to_byteidx(curbuf, line, col); if (argvars[2].v_type != VAR_UNKNOWN) coladd = (long)tv_get_number_chk(&argvars[2], NULL); } + else + { + emsg(_(e_invarg)); + return; + } if (line < 0 || col < 0 || coladd < 0) return; // type error; errmsg already given if (line > 0) @@ -2693,6 +2735,19 @@ f_cursor(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = 0; } +/* + * "cursor(lnum, col)" function, or + * "cursor(list)" + * + * Moves the cursor to the specified line and column. + * Returns 0 when the position could be set, -1 otherwise. + */ + static void +f_cursor(typval_T *argvars, typval_T *rettv) +{ + set_cursorpos(argvars, rettv, FALSE); +} + #ifdef MSWIN /* * "debugbreak()" function @@ -3887,6 +3942,88 @@ f_getchangelist(typval_T *argvars, typval_T *rettv) #endif } + static void +getpos_both( + typval_T *argvars, + typval_T *rettv, + int getcurpos, + int charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + list_T *l; + int fnum = -1; + + if (rettv_list_alloc(rettv) == OK) + { + l = rettv->vval.v_list; + if (getcurpos) + { + if (argvars[0].v_type != VAR_UNKNOWN) + { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) + fp = &wp->w_cursor; + } + else + fp = &curwin->w_cursor; + if (fp != NULL && charcol) + { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, + pos.col) - 1; + fp = &pos; + } + } + else + fp = var2fpos(&argvars[0], TRUE, &fnum, charcol); + if (fnum != -1) + list_append_number(l, (varnumber_T)fnum); + else + list_append_number(l, (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum + : (varnumber_T)0); + list_append_number(l, (fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : + (varnumber_T)0); + if (getcurpos) + { + int save_set_curswant = curwin->w_set_curswant; + colnr_T save_curswant = curwin->w_curswant; + colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) + update_curswant(); + list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL + ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) + { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } + } + else + rettv->vval.v_number = FALSE; +} + +/* + * "getcharpos()" function + */ + static void +f_getcharpos(typval_T *argvars UNUSED, typval_T *rettv) +{ + getpos_both(argvars, rettv, FALSE, TRUE); +} + /* * "getcharsearch()" function */ @@ -4019,77 +4156,19 @@ f_getpid(typval_T *argvars UNUSED, typval_T *rettv) rettv->vval.v_number = mch_get_pid(); } - static void -getpos_both( - typval_T *argvars, - typval_T *rettv, - int getcurpos) -{ - pos_T *fp = NULL; - win_T *wp = curwin; - list_T *l; - int fnum = -1; - - if (rettv_list_alloc(rettv) == OK) - { - l = rettv->vval.v_list; - if (getcurpos) - { - if (argvars[0].v_type != VAR_UNKNOWN) - { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) - fp = &wp->w_cursor; - } - else - fp = &curwin->w_cursor; - } - else - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fnum != -1) - list_append_number(l, (varnumber_T)fnum); - else - list_append_number(l, (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum - : (varnumber_T)0); - list_append_number(l, (fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : - (varnumber_T)0); - if (getcurpos) - { - int save_set_curswant = curwin->w_set_curswant; - colnr_T save_curswant = curwin->w_curswant; - colnr_T save_virtcol = curwin->w_virtcol; - - if (wp == curwin) - update_curswant(); - list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (wp == curwin && save_set_curswant) - { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } - } - else - rettv->vval.v_number = FALSE; -} - /* * "getcurpos()" function */ static void f_getcurpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, TRUE); + getpos_both(argvars, rettv, TRUE, FALSE); +} + + static void +f_getcursorcharpos(typval_T *argvars, typval_T *rettv) +{ + getpos_both(argvars, rettv, TRUE, TRUE); } /* @@ -4098,7 +4177,7 @@ f_getcurpos(typval_T *argvars, typval_T *rettv) static void f_getpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, FALSE); + getpos_both(argvars, rettv, FALSE, FALSE); } /* @@ -6183,14 +6262,14 @@ f_line(typval_T *argvars, typval_T *rettv) == OK) { check_cursor(); - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); } restore_win_noblock(save_curwin, save_curtab, TRUE); } } else // use current window - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; @@ -8065,6 +8144,60 @@ f_searchpos(typval_T *argvars, typval_T *rettv) list_append_number(rettv->vval.v_list, (varnumber_T)n); } +/* + * Set the cursor or mark position. + * If 'charpos' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. + */ + static void +set_position(typval_T *argvars, typval_T *rettv, int charpos) +{ + pos_T pos; + int fnum; + char_u *name; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(argvars); + if (name != NULL) + { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) + { + if (pos.col != MAXCOL && --pos.col < 0) + pos.col = 0; + if ((name[0] == '.' && name[1] == NUL)) + { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) + { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = FALSE; + } + check_cursor(); + rettv->vval.v_number = 0; + } + else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) + { + // set mark + if (setmark_pos(name[1], &pos, fnum) == OK) + rettv->vval.v_number = 0; + } + else + emsg(_(e_invarg)); + } + } +} +/* + * "setcharpos()" function + */ + static void +f_setcharpos(typval_T *argvars, typval_T *rettv) +{ + set_position(argvars, rettv, TRUE); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED) { @@ -8106,6 +8239,15 @@ f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED) } } +/* + * "setcursorcharpos" function + */ + static void +f_setcursorcharpos(typval_T *argvars, typval_T *rettv UNUSED) +{ + set_cursorpos(argvars, rettv, TRUE); +} + /* * "setenv()" function */ @@ -8165,41 +8307,7 @@ f_setfperm(typval_T *argvars, typval_T *rettv) static void f_setpos(typval_T *argvars, typval_T *rettv) { - pos_T pos; - int fnum; - char_u *name; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - name = tv_get_string_chk(argvars); - if (name != NULL) - { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) - { - if (pos.col != MAXCOL && --pos.col < 0) - pos.col = 0; - if (name[0] == '.' && name[1] == NUL) - { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) - { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = FALSE; - } - check_cursor(); - rettv->vval.v_number = 0; - } - else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) - { - // set mark - if (setmark_pos(name[1], &pos, fnum) == OK) - rettv->vval.v_number = 0; - } - else - emsg(_(e_invarg)); - } - } + set_position(argvars, rettv, FALSE); } /* @@ -9947,7 +10055,7 @@ f_virtcol(typval_T *argvars, typval_T *rettv) int fnum = curbuf->b_fnum; int len; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], FALSE, &fnum, FALSE); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { diff --git a/src/proto/eval.pro b/src/proto/eval.pro index cc86a5d263..66aa430334 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -55,8 +55,10 @@ char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copy char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); char_u *string_quote(char_u *str, int function); int string2float(char_u *text, float_T *value); -pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum); -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp); +int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx); +pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol); +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int char_col); int get_env_len(char_u **arg); int get_id_len(char_u **arg); int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose); diff --git a/src/tag.c b/src/tag.c index 3dfb8fee5d..e3cd87677c 100644 --- a/src/tag.c +++ b/src/tag.c @@ -4201,7 +4201,7 @@ tagstack_push_items(win_T *wp, list_T *l) // parse 'from' for the cursor position before the tag jump if ((di = dict_find(itemdict, (char_u *)"from", -1)) == NULL) continue; - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, FALSE) != OK) continue; if ((tagname = dict_get_string(itemdict, (char_u *)"tagname", TRUE)) == NULL) diff --git a/src/testdir/test_cursor_func.vim b/src/testdir/test_cursor_func.vim index cbaad24187..19b3416b79 100644 --- a/src/testdir/test_cursor_func.vim +++ b/src/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -123,4 +123,187 @@ func Test_screenpos_number() bwipe! endfunc +func SaveVisualStartCharPos() + call add(g:VisualStartPos, getcharpos('v')) + return '' +endfunc + +" Test for the getcharpos() function +func Test_getcharpos() + call assert_fails('call getcharpos({})', 'E731:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 4, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos) + call assert_equal([0, 2, 9, 0], getcharpos('v')) + let g:VisualStartPos = [] + exe "normal 3Gv$\o\" + call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos) + let g:VisualStartPos = [] + exe "normal 1Gv$\o\" + call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos) + vunmap + + %bw! +endfunc + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', test_null_list())) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + call setcharpos("'m", [0, 2, 9, 0]) + normal `m + call assert_equal([2, 11], [line('.'), col('.')]) + + %bw! + call assert_equal(-1, setcharpos('.', [10, 3, 1, 0])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + return '' +endfunc + +" Test for the charcol() function +func Test_charcol() + call assert_fails('call charcol({})', 'E731:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([7, 9, 5], g:VisualStartCol) + call assert_equal(9, charcol('v')) + let g:VisualStartCol = [] + exe "normal 3Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + let g:VisualStartCol = [] + exe "normal 1Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + vunmap + + %bw! +endfunc + +" Test for getcursorcharpos() +func Test_getcursorcharpos() + call assert_equal(getcursorcharpos(), getcursorcharpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], getcursorcharpos()) + normal 4G9l + call assert_equal([0, 4, 9, 0, 9], getcursorcharpos()) + + let winid = win_getid() + normal 2G5l + wincmd w + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(test_null_list())', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/typval.c b/src/typval.c index 06276b473d..7e4e63d17d 100644 --- a/src/typval.c +++ b/src/typval.c @@ -1579,7 +1579,7 @@ tv_get_lnum(typval_T *argvars) if (lnum <= 0) // no valid number, try using arg like line() { int fnum; - pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum); + pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; diff --git a/src/version.c b/src/version.c index a21122aa60..07c7840988 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2324, /**/ 2323, /**/ From 75ab91ff3403e725a79ac9c7351b78e9aff71d67 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 22:42:50 +0100 Subject: [PATCH 20/22] patch 8.2.2325: Vim9: crash if map() changes the item type Problem: Vim9: crash if map() changes the item type. Solution: Check that the item type is still OK. (closes #7652) Fix problem with mapnew() on range list. --- src/evalfunc.c | 9 +++++++++ src/list.c | 11 +++++++---- src/proto/evalfunc.pro | 1 + src/testdir/test_vim9_builtin.vim | 13 +++++++++++-- src/testdir/test_vim9_expr.vim | 18 +++++++++--------- src/testdir/test_vim9_func.vim | 6 +++--- src/version.c | 2 ++ src/vim9compile.c | 8 ++++++++ 8 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/evalfunc.c b/src/evalfunc.c index 85f8befff6..1abcd5e11c 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -1929,6 +1929,15 @@ internal_func_ret_type(int idx, int argcount, type_T **argtypes) return global_functions[idx].f_retfunc(argcount, argtypes); } +/* + * Return TRUE if "idx" is for the map() function. + */ + int +internal_func_is_map(int idx) +{ + return global_functions[idx].f_func == f_map; +} + /* * Check the argument count to use for internal function "idx". * Returns -1 for failure, 0 if no method base accepted, 1 if method base is diff --git a/src/list.c b/src/list.c index 4531e5885a..2b44ebacb8 100644 --- a/src/list.c +++ b/src/list.c @@ -2188,10 +2188,13 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) int stride = l->lv_u.nonmat.lv_stride; // List from range(): loop over the numbers - l->lv_first = NULL; - l->lv_u.mat.lv_last = NULL; - l->lv_len = 0; - l->lv_u.mat.lv_idx_item = NULL; + if (filtermap != FILTERMAP_MAPNEW) + { + l->lv_first = NULL; + l->lv_u.mat.lv_last = NULL; + l->lv_len = 0; + l->lv_u.mat.lv_idx_item = NULL; + } for (idx = 0; idx < len; ++idx) { diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro index 562a7c83d9..ad4b98a29d 100644 --- a/src/proto/evalfunc.pro +++ b/src/proto/evalfunc.pro @@ -6,6 +6,7 @@ int has_internal_func(char_u *name); char *internal_func_name(int idx); int internal_func_check_arg_types(type_T **types, int idx, int argcount); type_T *internal_func_ret_type(int idx, int argcount, type_T **argtypes); +int internal_func_is_map(int idx); int check_internal_func(int idx, int argcount); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); void call_internal_func_by_idx(int idx, typval_T *argvars, typval_T *rettv); diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 7aef315dc8..673f0c0562 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -231,7 +231,7 @@ def Test_extend_arg_types() assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, s:string_keep)) var res: list> - extend(res, map([1, 2], (_, v) => ({}))) + extend(res, mapnew([1, 2], (_, v) => ({}))) assert_equal([{}, {}], res) CheckDefFailure(['extend([1, 2], 3)'], 'E1013: Argument 2: type mismatch, expected list but got number') @@ -320,6 +320,15 @@ def Test_map_function_arg() CheckDefAndScriptSuccess(lines) enddef +def Test_map_item_type() + var lines =<< trim END + var l = ['a', 'b', 'c'] + map(l, (k, v) => k .. '/' .. v ) + assert_equal(['0/a', '1/b', '2/c'], l) + END + CheckDefAndScriptSuccess(lines) +enddef + def Test_filereadable() assert_false(filereadable("")) assert_false(filereadable(test_null_string())) @@ -728,7 +737,7 @@ enddef def Test_submatch() var pat = 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)' - var Rep = () => range(10)->map((_, v) => submatch(v, true))->string() + var Rep = () => range(10)->mapnew((_, v) => submatch(v, true))->string() var actual = substitute('A123456789', pat, Rep, '') var expected = "[['A123456789'], ['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9']]" actual->assert_equal(expected) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index 8955a7ccf0..50537c858a 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1859,10 +1859,10 @@ def Test_expr7_lambda() # line continuation inside lambda with "cond ? expr : expr" works var ll = range(3) - map(ll, (k, v) => v % 2 ? { + var dll = mapnew(ll, (k, v) => v % 2 ? { ['111']: 111 } : {} ) - assert_equal([{}, {111: 111}, {}], ll) + assert_equal([{}, {111: 111}, {}], dll) ll = range(3) map(ll, (k, v) => v == 8 || v @@ -1946,10 +1946,10 @@ def Test_expr7_new_lambda() # line continuation inside lambda with "cond ? expr : expr" works var ll = range(3) - map(ll, (k, v) => v % 2 ? { + var dll = mapnew(ll, (k, v) => v % 2 ? { ['111']: 111 } : {} ) - assert_equal([{}, {111: 111}, {}], ll) + assert_equal([{}, {111: 111}, {}], dll) ll = range(3) map(ll, (k, v) => v == 8 || v @@ -2964,25 +2964,25 @@ def Test_expr7_subscript_linebreak() var range = range( 3) var l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range # comment - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) l = range # comment - ->map('string(v:key)') + ->mapnew('string(v:key)') assert_equal(['0', '1', '2'], l) assert_equal('1', l[ diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index fdad359de0..e87c33e909 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1763,7 +1763,7 @@ enddef def Shadowed(): list var FuncList: list = [() => 42] - return FuncList->map((_, Shadowed) => Shadowed()) + return FuncList->mapnew((_, Shadowed) => Shadowed()) enddef def Test_lambda_arg_shadows_func() @@ -1792,7 +1792,7 @@ enddef def Line_continuation_in_lambda(): list var x = range(97, 100) - ->map((_, v) => nr2char(v) + ->mapnew((_, v) => nr2char(v) ->toupper()) ->reverse() return x @@ -1908,7 +1908,7 @@ def Test_recursive_call() enddef def TreeWalk(dir: string): list - return readdir(dir)->map((_, val) => + return readdir(dir)->mapnew((_, val) => fnamemodify(dir .. '/' .. val, ':p')->isdirectory() ? {[val]: TreeWalk(dir .. '/' .. val)} : val diff --git a/src/version.c b/src/version.c index 07c7840988..764d09d3f2 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2325, /**/ 2324, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 94f30d4395..bcbc57dd12 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1592,6 +1592,7 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) garray_T *stack = &cctx->ctx_type_stack; int argoff; type_T **argtypes = NULL; + type_T *maptype = NULL; RETURN_OK_IF_SKIP(cctx); argoff = check_internal_func(func_idx, argcount); @@ -1612,6 +1613,8 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) argtypes = ((type_T **)stack->ga_data) + stack->ga_len - argcount; if (internal_func_check_arg_types(argtypes, func_idx, argcount) == FAIL) return FAIL; + if (internal_func_is_map(func_idx)) + maptype = *argtypes; } if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL) @@ -1627,6 +1630,11 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) internal_func_ret_type(func_idx, argcount, argtypes); ++stack->ga_len; + if (maptype != NULL && maptype->tt_member != NULL + && maptype->tt_member != &t_any) + // Check that map() didn't change the item types. + generate_TYPECHECK(cctx, maptype, -1); + return OK; } From 086ae06862077ba228fcae777f2a0b41416c11bb Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 22:56:36 +0100 Subject: [PATCH 21/22] patch 8.2.2326: build error with +eval feature but without +spell Problem: Build error with +eval feature but without +spell. Solution: Adjust #ifdef. (John Marriott) --- src/mbyte.c | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mbyte.c b/src/mbyte.c index a4eff7837f..bafab87ace 100644 --- a/src/mbyte.c +++ b/src/mbyte.c @@ -4299,7 +4299,7 @@ mb_charlen(char_u *str) return count; } -#if defined(FEAT_SPELL) || defined(PROTO) +#if (defined(FEAT_SPELL) || defined(FEAT_EVAL)) || defined(PROTO) /* * Like mb_charlen() but for a string with specified length. */ diff --git a/src/version.c b/src/version.c index 764d09d3f2..63549a2ebc 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2326, /**/ 2325, /**/ From 16a6f91ccb42ebde639a4185322b07719d345e86 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 10 Jan 2021 23:58:28 +0100 Subject: [PATCH 22/22] patch 8.2.2327: debugging code included Problem: Debugging code included. Solution: Remove the debugging code. --- src/version.c | 2 ++ src/vim9execute.c | 6 ------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/version.c b/src/version.c index 63549a2ebc..e64265fb27 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2327, /**/ 2326, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index 957988e658..378b10443a 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -247,8 +247,6 @@ call_dfunc(int cdf_idx, partial_T *pt, int argcount_arg, ectx_T *ectx) // Store current execution state in stack frame for ISN_RETURN. STACK_TV_BOT(STACK_FRAME_FUNC_OFF)->vval.v_number = ectx->ec_dfunc_idx; STACK_TV_BOT(STACK_FRAME_IIDX_OFF)->vval.v_number = ectx->ec_iidx; - if (ectx->ec_outer != NULL) - printf("here"); STACK_TV_BOT(STACK_FRAME_OUTER_OFF)->vval.v_string = (void *)ectx->ec_outer; STACK_TV_BOT(STACK_FRAME_IDX_OFF)->vval.v_number = ectx->ec_frame_idx; ectx->ec_frame_idx = ectx->ec_stack.ga_len; @@ -530,8 +528,6 @@ func_return(ectx_T *ectx) if (ret_idx == ectx->ec_frame_idx + STACK_FRAME_IDX_OFF) ret_idx = 0; - if (ectx->ec_outer != NULL) - printf("here"); vim_free(ectx->ec_outer); // Restore the previous frame. @@ -544,8 +540,6 @@ func_return(ectx_T *ectx) // restoring ec_frame_idx must be last ectx->ec_frame_idx = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_IDX_OFF)->vval.v_number; - if (ectx->ec_outer != NULL) - printf("here"); dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ectx->ec_instr = dfunc->df_instr;