From c620c055ce8505596a7208ba696a32b8a3be4f4b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 15:16:19 +0200 Subject: [PATCH 01/15] patch 8.2.1154: Vim9: crash when using imported function Problem: Vim9: crash when using imported function. Solution: Check for a function type. Set the script context when calling a function. (closes #6412) --- src/evalvars.c | 34 +++++++++++++++++++++++--------- src/proto/scriptfile.pro | 4 ++-- src/scriptfile.c | 13 +++++++----- src/structs.h | 3 +++ src/testdir/test_vim9_script.vim | 33 +++++++++++++++++++++++++++++-- src/version.c | 2 ++ src/vim9execute.c | 15 ++++++++++++-- 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/evalvars.c b/src/evalvars.c index 3dab22bb2e..9cd2ec2c89 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2375,6 +2375,7 @@ eval_variable( { int ret = OK; typval_T *tv = NULL; + int foundFunc = FALSE; dictitem_T *v; int cc; @@ -2402,21 +2403,36 @@ eval_variable( // imported variable from another script if (import != NULL) { - scriptitem_T *si = SCRIPT_ITEM(import->imp_sid); - svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + if (import->imp_funcname != NULL) + { + foundFunc = TRUE; + if (rettv != NULL) + { + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = vim_strsave(import->imp_funcname); + } + } + else + { + scriptitem_T *si = SCRIPT_ITEM(import->imp_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + import->imp_var_vals_idx; - tv = sv->sv_tv; + tv = sv->sv_tv; + } } } - if (tv == NULL) + if (!foundFunc) { - if (rettv != NULL && verbose) - semsg(_(e_undefvar), name); - ret = FAIL; + if (tv == NULL) + { + if (rettv != NULL && verbose) + semsg(_(e_undefvar), name); + ret = FAIL; + } + else if (rettv != NULL) + copy_tv(tv, rettv); } - else if (rettv != NULL) - copy_tv(tv, rettv); name[len] = cc; diff --git a/src/proto/scriptfile.pro b/src/proto/scriptfile.pro index b5b8aae936..fd4c104738 100644 --- a/src/proto/scriptfile.pro +++ b/src/proto/scriptfile.pro @@ -1,9 +1,9 @@ /* scriptfile.c */ void estack_init(void); estack_T *estack_push(etype_T type, char_u *name, long lnum); -void estack_push_ufunc(ufunc_T *ufunc, long lnum); +estack_T *estack_push_ufunc(ufunc_T *ufunc, long lnum); int estack_top_is_ufunc(ufunc_T *ufunc, long lnum); -void estack_pop(void); +estack_T *estack_pop(void); char_u *estack_sfile(void); void ex_runtime(exarg_T *eap); int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); diff --git a/src/scriptfile.c b/src/scriptfile.c index 9ffc66c1b1..ce4bafadaf 100644 --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -68,7 +68,7 @@ estack_push(etype_T type, char_u *name, long lnum) /* * Add a user function to the execution stack. */ - void + estack_T * estack_push_ufunc(ufunc_T *ufunc, long lnum) { estack_T *entry = estack_push(ETYPE_UFUNC, @@ -76,6 +76,7 @@ estack_push_ufunc(ufunc_T *ufunc, long lnum) ? ufunc->uf_name_exp : ufunc->uf_name, lnum); if (entry != NULL) entry->es_info.ufunc = ufunc; + return entry; } /* @@ -97,13 +98,15 @@ estack_top_is_ufunc(ufunc_T *ufunc, long lnum) #endif /* - * Take an item off of the execution stack. + * Take an item off of the execution stack and return it. */ - void + estack_T * estack_pop(void) { - if (exestack.ga_len > 1) - --exestack.ga_len; + if (exestack.ga_len == 0) + return NULL; + --exestack.ga_len; + return ((estack_T *)exestack.ga_data) + exestack.ga_len; } /* diff --git a/src/structs.h b/src/structs.h index cab6885e41..5c606d8839 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1905,6 +1905,9 @@ typedef struct { AutoPatCmd *aucmd; // autocommand info except_T *except; // exception info } es_info; +#if defined(FEAT_EVAL) + scid_T es_save_sid; // saved sc_sid when calling function +#endif } estack_T; // Information returned by get_tty_info(). diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index c1d722865e..a95761fc08 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -911,10 +911,10 @@ func Test_import_fails_without_script() CheckRunVimInTerminal " call indirectly to avoid compilation error for missing functions - call Run_Test_import_fails_without_script() + call Run_Test_import_fails_on_command_line() endfunc -def Run_Test_import_fails_without_script() +def Run_Test_import_fails_on_command_line() let export =<< trim END vim9script export def Foo(): number @@ -1013,6 +1013,35 @@ def Test_vim9script_funcref() delete('Xscript.vim') enddef +" Check that when searcing for "FilterFunc" it doesn't find the import in the +" script where FastFilter() is called from. +def Test_vim9script_funcref_other_script() + let filterLines =<< trim END + vim9script + export def FilterFunc(idx: number, val: number): bool + return idx % 2 == 1 + enddef + export def FastFilter(): list + return range(10)->filter('FilterFunc') + enddef + END + writefile(filterLines, 'Xfilter.vim') + + let lines =<< trim END + vim9script + import {FilterFunc, FastFilter} from './Xfilter.vim' + def Test() + let x: list = FastFilter() + enddef + Test() + END + writefile(lines, 'Ximport.vim') + assert_fails('source Ximport.vim', 'E121:') + + delete('Xfilter.vim') + delete('Ximport.vim') +enddef + def Test_vim9script_reload_delfunc() let first_lines =<< trim END vim9script diff --git a/src/version.c b/src/version.c index 0d3e85e54f..7633792ed5 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1154, /**/ 1153, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index 7afa3fc5ec..a3cea806ee 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -160,6 +160,7 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) int arg_to_add; int vararg_count = 0; int idx; + estack_T *entry; if (dfunc->df_deleted) { @@ -230,7 +231,14 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx) // Set execution state to the start of the called function. ectx->ec_dfunc_idx = cdf_idx; ectx->ec_instr = dfunc->df_instr; - estack_push_ufunc(dfunc->df_ufunc, 1); + entry = estack_push_ufunc(dfunc->df_ufunc, 1); + if (entry != NULL) + { + // Set the script context to the script where the function was defined. + // TODO: save more than the SID? + entry->es_save_sid = current_sctx.sc_sid; + current_sctx.sc_sid = ufunc->uf_script_ctx.sc_sid; + } // Decide where to start execution, handles optional arguments. init_instr_idx(ufunc, argcount, ectx); @@ -386,9 +394,12 @@ func_return(ectx_T *ectx) + ectx->ec_dfunc_idx; int argcount = ufunc_argcount(dfunc->df_ufunc); int top = ectx->ec_frame_idx - argcount; + estack_T *entry; // execution context goes one level up - estack_pop(); + entry = estack_pop(); + if (entry != NULL) + current_sctx.sc_sid = entry->es_save_sid; if (handle_closure_in_use(ectx, TRUE) == FAIL) return FAIL; From 7a4b8980ea5ecaea061caae7816ea62cc4940011 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 17:36:21 +0200 Subject: [PATCH 02/15] patch 8.2.1155: Vim9: cannot handle line break inside lambda Problem: Vim9: cannot handle line break inside lambda. Solution: Pass the compilation context through. (closes #6407, closes #6409) --- src/eval.c | 120 ++++++++++++++++++++------------- src/proto/vim9compile.pro | 2 + src/structs.h | 3 + src/testdir/test_vim9_func.vim | 12 ++++ src/version.c | 2 + src/vim9compile.c | 37 +++++----- 6 files changed, 114 insertions(+), 62 deletions(-) diff --git a/src/eval.c b/src/eval.c index 1468070ae6..68fb9cddf8 100644 --- a/src/eval.c +++ b/src/eval.c @@ -390,11 +390,12 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) garray_T *gap = &evalarg->eval_ga; int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; - if (vim9script && evalarg->eval_cookie != NULL) + if (vim9script + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)) { ga_init2(gap, sizeof(char_u *), 10); + // leave room for "start" if (ga_grow(gap, 1) == OK) - // leave room for "start" ++gap->ga_len; } @@ -406,32 +407,49 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) if (evalarg != NULL) evalarg->eval_flags = save_flags; - if (vim9script && evalarg->eval_cookie != NULL - && evalarg->eval_ga.ga_len > 1) + if (vim9script + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)) { - char_u *p; - size_t endoff = STRLEN(*end); + if (evalarg->eval_ga.ga_len == 1) + { + // just one line, no need to concatenate + ga_clear(gap); + gap->ga_itemsize = 0; + } + else + { + char_u *p; + size_t endoff = STRLEN(*end); - // Line breaks encountered, concatenate all the lines. - *((char_u **)gap->ga_data) = *start; - p = ga_concat_strings(gap, ""); - *((char_u **)gap->ga_data) = NULL; - ga_clear_strings(gap); - gap->ga_itemsize = 0; - if (p == NULL) - return FAIL; - *start = p; - vim_free(evalarg->eval_tofree); - evalarg->eval_tofree = p; - // Compute "end" relative to the end. - *end = *start + STRLEN(*start) - endoff; + // Line breaks encountered, concatenate all the lines. + *((char_u **)gap->ga_data) = *start; + p = ga_concat_strings(gap, ""); + + // free the lines only when using getsourceline() + if (evalarg->eval_cookie != NULL) + { + *((char_u **)gap->ga_data) = NULL; + ga_clear_strings(gap); + } + else + ga_clear(gap); + gap->ga_itemsize = 0; + if (p == NULL) + return FAIL; + *start = p; + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = p; + // Compute "end" relative to the end. + *end = *start + STRLEN(*start) - endoff; + } } return res; } /* - * Top level evaluation function, returning a string. + * Top level evaluation function, returning a string. Does not handle line + * breaks. * When "convert" is TRUE convert a List into a sequence of lines and convert * a Float to a String. * Return pointer to allocated memory, or NULL for failure. @@ -1878,11 +1896,16 @@ eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext) *getnext = FALSE; if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && evalarg != NULL - && evalarg->eval_cookie != NULL + && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL) && (*arg == NUL || (VIM_ISWHITE(arg[-1]) && *arg == '#' && arg[1] != '{'))) { - char_u *p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); + char_u *p; + + if (evalarg->eval_cookie != NULL) + p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie); + else + p = peek_next_line_from_context(evalarg->eval_cctx); if (p != NULL) { @@ -1902,7 +1925,10 @@ eval_next_line(evalarg_T *evalarg) garray_T *gap = &evalarg->eval_ga; char_u *line; - line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); + if (evalarg->eval_cookie != NULL) + line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE); + else + line = next_line_from_context(evalarg->eval_cctx, TRUE); ++evalarg->eval_break_count; if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) { @@ -5034,35 +5060,27 @@ handle_subscript( int ret = OK; dict_T *selfdict = NULL; int check_white = TRUE; + int getnext; + char_u *p; - // When at the end of the line and ".name" follows in the next line then - // consume the line break. Only when rettv is a dict. - if (rettv->v_type == VAR_DICT) + while (ret == OK) { - int getnext; - char_u *p = eval_next_non_blank(*arg, evalarg, &getnext); - - if (getnext && *p == '.' && ASCII_ISALPHA(p[1])) + // When at the end of the line and ".name" or "->{" or "->X" follows in + // the next line then consume the line break. + p = eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext + && ((rettv->v_type == VAR_DICT && *p == '.' + && ASCII_ISALPHA(p[1])) + || (*p == '-' && p[1] == '>' + && (p[2] == '{' || ASCII_ISALPHA(p[2]))))) { *arg = eval_next_line(evalarg); check_white = FALSE; } - } - // "." is ".name" lookup when we found a dict or when evaluating and - // scriptversion is at least 2, where string concatenation is "..". - while (ret == OK - && (((**arg == '[' - || (**arg == '.' && (rettv->v_type == VAR_DICT - || (!evaluate - && (*arg)[1] != '.' - && current_sctx.sc_version >= 2))) - || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC - || rettv->v_type == VAR_PARTIAL))) - && (!check_white || !VIM_ISWHITE(*(*arg - 1)))) - || (**arg == '-' && (*arg)[1] == '>'))) - { - if (**arg == '(') + if ((**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL)) + && (!check_white || !VIM_ISWHITE(*(*arg - 1)))) { ret = call_func_rettv(arg, evalarg, rettv, evaluate, selfdict, NULL); @@ -5079,7 +5097,7 @@ handle_subscript( dict_unref(selfdict); selfdict = NULL; } - else if (**arg == '-') + else if (**arg == '-' && (*arg)[1] == '>') { if (ret == OK) { @@ -5091,7 +5109,13 @@ handle_subscript( ret = eval_method(arg, rettv, evalarg, verbose); } } - else // **arg == '[' || **arg == '.' + // "." is ".name" lookup when we found a dict or when evaluating and + // scriptversion is at least 2, where string concatenation is "..". + else if (**arg == '[' + || (**arg == '.' && (rettv->v_type == VAR_DICT + || (!evaluate + && (*arg)[1] != '.' + && current_sctx.sc_version >= 2)))) { dict_unref(selfdict); if (rettv->v_type == VAR_DICT) @@ -5108,6 +5132,8 @@ handle_subscript( ret = FAIL; } } + else + break; } // Turn "dict.Func" into a partial for "Func" bound to "dict". diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index 05f6d50220..b2677c8fe5 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -8,6 +8,8 @@ char *vartype_name(vartype_T type); char *type_name(type_T *type, char **tofree); int get_script_item_idx(int sid, char_u *name, int check_writable); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); +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_const_end(char_u *arg); 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 5c606d8839..4ae94fd04e 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1765,6 +1765,9 @@ typedef struct { char_u *(*eval_getline)(int, void *, int, int); void *eval_cookie; // argument for eval_getline() + // used when compiling a :def function, NULL otherwise + cctx_T *eval_cctx; + // Used to collect lines while parsing them, so that they can be // concatenated later. Used when "eval_ga.ga_itemsize" is not zero. // "eval_ga.ga_data" is a list of pointers to lines. diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index de30a620d1..f09ecc9b3b 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -965,6 +965,18 @@ def Test_line_continuation_in_def() assert_equal('full', Line_continuation_in_def('.')) enddef +def Line_continuation_in_lambda(): list + let x = range(97, 100) + ->map({_,v -> nr2char(v) + ->toupper()}) + ->reverse() + return x +enddef + +def Test_line_continuation_in_lambda() + assert_equal(['D', 'C', 'B', 'A'], Line_continuation_in_lambda()) +enddef + func Test_silent_echo() CheckScreendump diff --git a/src/version.c b/src/version.c index 7633792ed5..c77d0bc7cd 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1155, /**/ 1154, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 07e0d27277..c3c380852a 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2397,8 +2397,8 @@ comment_start(char_u *p) * comment. Skips over white space. * Returns NULL if there is none. */ - static char_u * -peek_next_line(cctx_T *cctx) + char_u * +peek_next_line_from_context(cctx_T *cctx) { int lnum = cctx->ctx_lnum; @@ -2430,7 +2430,7 @@ may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp) *nextp = NULL; if (*p == NUL || (VIM_ISWHITE(*arg) && comment_start(p))) { - *nextp = peek_next_line(cctx); + *nextp = peek_next_line_from_context(cctx); if (*nextp != NULL) return *nextp; } @@ -2442,7 +2442,7 @@ may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp) * Skips over empty lines. Skips over comment lines if "skip_comment" is TRUE. * Returns NULL when at the end. */ - static char_u * + char_u * next_line_from_context(cctx_T *cctx, int skip_comment) { char_u *line; @@ -3079,9 +3079,14 @@ compile_lambda(char_u **arg, cctx_T *cctx) { typval_T rettv; ufunc_T *ufunc; + evalarg_T evalarg; + + CLEAR_FIELD(evalarg); + evalarg.eval_flags = EVAL_EVALUATE; + evalarg.eval_cctx = cctx; // Get the funcref in "rettv". - if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK) + if (get_lambda_tv(arg, &rettv, &evalarg) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; @@ -3535,6 +3540,7 @@ compile_leader(cctx_T *cctx, char_u *start, char_u *end) /* * Compile whatever comes after "name" or "name()". + * Advances "*arg" only when something was recognized. */ static int compile_subscript( @@ -3550,7 +3556,7 @@ compile_subscript( if (*p == NUL || (VIM_ISWHITE(**arg) && comment_start(p))) { - char_u *next = peek_next_line(cctx); + char_u *next = peek_next_line_from_context(cctx); // If a following line starts with "->{" or "->X" advance to that // line, so that a line break before "->" is allowed. @@ -3560,11 +3566,12 @@ compile_subscript( next = next_line_from_context(cctx, TRUE); if (next == NULL) return FAIL; - *arg = skipwhite(next); + *arg = next; + p = skipwhite(*arg); } } - if (**arg == '(') + if (*p == '(') { garray_T *stack = &cctx->ctx_type_stack; type_T *type; @@ -3576,13 +3583,13 @@ compile_subscript( // funcref(arg) type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - *arg = skipwhite(*arg + 1); + *arg = skipwhite(p + 1); if (compile_arguments(arg, cctx, &argcount) == FAIL) return FAIL; if (generate_PCALL(cctx, argcount, end_leader, type, TRUE) == FAIL) return FAIL; } - else if (**arg == '-' && (*arg)[1] == '>') + else if (*p == '-' && p[1] == '>') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; @@ -3594,7 +3601,7 @@ compile_subscript( return FAIL; *start_leader = end_leader; // don't apply again later - p = *arg + 2; + p += 2; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; @@ -3622,7 +3629,7 @@ compile_subscript( return FAIL; } } - else if (**arg == '[') + else if (*p == '[') { garray_T *stack = &cctx->ctx_type_stack; type_T **typep; @@ -3635,7 +3642,7 @@ compile_subscript( if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - p = *arg + 1; + ++p; *arg = skipwhite(p); if (may_get_next_line(p, arg, cctx) == FAIL) return FAIL; @@ -3671,12 +3678,12 @@ compile_subscript( return FAIL; } } - else if (**arg == '.' && (*arg)[1] != '.') + else if (*p == '.' && p[1] != '.') { if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; - ++*arg; + *arg = p + 1; if (may_get_next_line(*arg, arg, cctx) == FAIL) return FAIL; // dictionary member: dict.name From 002262f4dee452964190b0d022aa7443f602b288 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 17:47:57 +0200 Subject: [PATCH 03/15] patch 8.2.1156: Vim9: No error for invalid command in compiled function Problem: Vim9: No error for invalid command in compiled function. Solution: Handle CMD_SIZE. --- src/testdir/test_vim9_expr.vim | 2 +- src/version.c | 2 ++ src/vim9compile.c | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index b3906c1b98..dd2a8ade51 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1420,7 +1420,7 @@ func Test_expr_fails() call CheckDefFailure(["let x = '1'is2"], 'E488:') call CheckDefFailure(["let x = '1'isnot2"], 'E488:') - call CheckDefExecFailure(["CallMe ('yes')"], 'E492:') + call CheckDefFailure(["CallMe ('yes')"], 'E476:') call CheckDefFailure(["CallMe2('yes','no')"], 'E1069:') call CheckDefFailure(["CallMe2('yes' , 'no')"], 'E1068:') diff --git a/src/version.c b/src/version.c index c77d0bc7cd..e362e27057 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1156, /**/ 1155, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index c3c380852a..07aacb37c0 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -7142,6 +7142,10 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) // TODO: other commands with an expression argument + case CMD_SIZE: + semsg(_("E476: Invalid command: %s"), ea.cmd); + goto erret; + default: // Not recognized, execute with do_cmdline_cmd(). ea.arg = p; From 0a47e0970a0e5d99aa7089169e0bcca0401decce Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 18:30:06 +0200 Subject: [PATCH 04/15] patch 8.2.1157: Vim9: dict.name is not recognized as an expression Problem: Vim9: dict.name is not recognized as an expression. Solution: Recognize ".name". (closes #6418) --- src/ex_docmd.c | 4 +++- src/testdir/test_vim9_cmd.vim | 17 +++++++++++++++++ src/version.c | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 59778eb981..0573897b7c 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -3229,13 +3229,15 @@ find_ex_command( // "varname[]" is an expression. // "g:varname" is an expression. // "varname->expr" is an expression. + // "varname.expr" is an expression. // "(..." is an expression. // "{..." is an dict expression. if (*p == '(' || *p == '{' || (*p == '[' && p > eap->cmd) || p[1] == ':' - || (*p == '-' && p[1] == '>')) + || (*p == '-' && p[1] == '>') + || (*p == '.' && ASCII_ISALPHA(p[1]))) { eap->cmdidx = CMD_eval; return eap->cmd; diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim index 14af261713..801404d9c6 100644 --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -208,6 +208,23 @@ def Test_method_call_linebreak() CheckScriptSuccess(lines) enddef +def Test_dict_member() + let test: dict> = {'data': [3, 1, 2]} + test.data->sort() + assert_equal(#{data: [1, 2, 3]}, test) + test.data + ->reverse() + assert_equal(#{data: [3, 2, 1]}, test) + + let lines =<< trim END + vim9script + let test: dict> = {'data': [3, 1, 2]} + test.data->sort() + assert_equal(#{data: [1, 2, 3]}, test) + END + CheckScriptSuccess(lines) +enddef + def Test_bar_after_command() def RedrawAndEcho() let x = 'did redraw' diff --git a/src/version.c b/src/version.c index e362e27057..a54d33bcbf 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1157, /**/ 1156, /**/ From b335b29e1c3428fd475026412297d5aa63b623e6 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 18:34:57 +0200 Subject: [PATCH 05/15] patch 8.2.1158: build error Problem: Build error. Solution: Add missing change to globals. --- src/globals.h | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/globals.h b/src/globals.h index 41f9781c10..b17a9bf6ce 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1885,7 +1885,7 @@ EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. EXTERN evalarg_T EVALARG_EVALUATE # ifdef DO_INIT - = {EVAL_EVALUATE, 0, NULL, NULL, {0, 0, 0, 0, NULL}, NULL} + = {EVAL_EVALUATE, 0, NULL, NULL, NULL, {0, 0, 0, 0, NULL}, NULL} # endif ; #endif diff --git a/src/version.c b/src/version.c index a54d33bcbf..587f299338 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1158, /**/ 1157, /**/ From 6b7a0a8c201bbace7e69fe0709f72b95092193df Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 18:38:08 +0200 Subject: [PATCH 06/15] patch 8.2.1159: Vim9: no error for missing space after a comma Problem: Vim9: no error for missing space after a comma. Solution: Check for white space. --- src/testdir/test_vim9_expr.vim | 1 + src/testdir/test_vim9_script.vim | 2 +- src/version.c | 2 ++ src/vim9compile.c | 7 +++++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index dd2a8ade51..b4e0604943 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1014,6 +1014,7 @@ def Test_expr7_list() call CheckDefExecFailure(["let x = g:anint[3]"], 'E714:') call CheckDefFailure(["let x = g:list_mixed[xxx]"], 'E1001:') + call CheckDefFailure(["let x = [1,2,3]"], 'E1069:') call CheckDefExecFailure(["let x = g:list_mixed['xx']"], 'E39:') call CheckDefFailure(["let x = g:list_mixed[0"], 'E111:') call CheckDefExecFailure(["let x = g:list_empty[3]"], 'E684:') diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index a95761fc08..f923e14661 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -571,7 +571,7 @@ enddef def Test_try_catch_fails() call CheckDefFailure(['catch'], 'E603:') - call CheckDefFailure(['try', 'echo 0', 'catch','catch'], 'E1033:') + call CheckDefFailure(['try', 'echo 0', 'catch', 'catch'], 'E1033:') call CheckDefFailure(['try', 'echo 0', 'catch /pat'], 'E1067:') call CheckDefFailure(['finally'], 'E606:') call CheckDefFailure(['try', 'echo 0', 'finally', 'echo 1', 'finally'], 'E607:') diff --git a/src/version.c b/src/version.c index 587f299338..1af0262cc7 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1159, /**/ 1158, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 07aacb37c0..0aae8f2138 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3060,7 +3060,14 @@ compile_list(char_u **arg, cctx_T *cctx) break; ++count; if (*p == ',') + { ++p; + if (*p != ']' && !IS_WHITE_OR_NUL(*p)) + { + semsg(_(e_white_after), ","); + return FAIL; + } + } whitep = p; p = skipwhite(p); } From 6110e79a5872dd6c5529f909d1bd670e3325927b Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 19:35:21 +0200 Subject: [PATCH 07/15] patch 8.2.1160: Vim9: memory leak in allocated types Problem: Vim9: memory leak in allocated types. Solution: Free the type pointers. --- src/proto/vim9compile.pro | 1 + src/userfunc.c | 5 +---- src/version.c | 2 ++ src/vim9compile.c | 8 ++++++++ src/vim9script.c | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index b2677c8fe5..5486bd9236 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -1,5 +1,6 @@ /* vim9compile.c */ int check_defined(char_u *p, size_t len, cctx_T *cctx); +void clear_type_list(garray_T *gap); type_T *typval2type(typval_T *tv); int check_type(type_T *expected, type_T *actual, int give_msg); char_u *skip_type(char_u *start); diff --git a/src/userfunc.c b/src/userfunc.c index 121079149a..01c97dedd4 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1069,10 +1069,7 @@ func_clear_items(ufunc_T *fp) VIM_CLEAR(fp->uf_arg_types); VIM_CLEAR(fp->uf_def_arg_idx); VIM_CLEAR(fp->uf_va_name); - while (fp->uf_type_list.ga_len > 0) - vim_free(((type_T **)fp->uf_type_list.ga_data) - [--fp->uf_type_list.ga_len]); - ga_clear(&fp->uf_type_list); + clear_type_list(&fp->uf_type_list); #ifdef FEAT_LUA if (fp->uf_cb_free != NULL) diff --git a/src/version.c b/src/version.c index 1af0262cc7..7b5f8f5d07 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1160, /**/ 1159, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 0aae8f2138..c0e4674de4 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -321,6 +321,14 @@ alloc_type(garray_T *type_gap) return type; } + void +clear_type_list(garray_T *gap) +{ + while (gap->ga_len > 0) + vim_free(((type_T **)gap->ga_data)[--gap->ga_len]); + ga_clear(gap); +} + static type_T * get_list_type(type_T *member_type, garray_T *type_gap) { diff --git a/src/vim9script.c b/src/vim9script.c index 77fe6b074a..c191113c84 100644 --- a/src/vim9script.c +++ b/src/vim9script.c @@ -126,7 +126,7 @@ free_imports(int sid) } ga_clear(&si->sn_imports); ga_clear(&si->sn_var_vals); - ga_clear(&si->sn_type_list); + clear_type_list(&si->sn_type_list); } /* From 8e2730a315b8b06192f5fc822dc218dbb3cff7ae Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 22:01:49 +0200 Subject: [PATCH 08/15] patch 8.2.1161: Vim9: using freed memory Problem: Vim9: using freed memory. Solution: Put pointer back in evalarg instead of freeing it. --- src/eval.c | 60 +++++++++++++++++++++++++++++++--------------- src/proto/eval.pro | 2 +- src/structs.h | 5 +++- src/userfunc.c | 29 ++++++++++++---------- src/version.c | 2 ++ src/vim9compile.c | 2 ++ 6 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/eval.c b/src/eval.c index 68fb9cddf8..6a1bc4c7a0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -379,10 +379,17 @@ skip_expr(char_u **pp) * Skip over an expression at "*pp". * If in Vim9 script and line breaks are encountered, the lines are * concatenated. "evalarg->eval_tofree" will be set accordingly. + * "arg" is advanced to just after the expression. + * "start" is set to the start of the expression, "end" to just after the end. + * Also when the expression is copied to allocated memory. * Return FAIL for an error, OK otherwise. */ int -skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) +skip_expr_concatenate( + char_u **arg, + char_u **start, + char_u **end, + evalarg_T *evalarg) { typval_T rettv; int res; @@ -398,12 +405,14 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) if (ga_grow(gap, 1) == OK) ++gap->ga_len; } + *start = *arg; // Don't evaluate the expression. if (evalarg != NULL) evalarg->eval_flags &= ~EVAL_EVALUATE; - *end = skipwhite(*end); - res = eval1(end, &rettv, evalarg); + *arg = skipwhite(*arg); + res = eval1(arg, &rettv, evalarg); + *end = *arg; if (evalarg != NULL) evalarg->eval_flags = save_flags; @@ -419,7 +428,7 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) else { char_u *p; - size_t endoff = STRLEN(*end); + size_t endoff = STRLEN(*arg); // Line breaks encountered, concatenate all the lines. *((char_u **)gap->ga_data) = *start; @@ -428,7 +437,14 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) // free the lines only when using getsourceline() if (evalarg->eval_cookie != NULL) { + // Do not free the first line, the caller can still use it. *((char_u **)gap->ga_data) = NULL; + // Do not free the last line, "arg" points into it, free it + // later. + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = + ((char_u **)gap->ga_data)[gap->ga_len - 1]; + ((char_u **)gap->ga_data)[gap->ga_len - 1] = NULL; ga_clear_strings(gap); } else @@ -437,8 +453,8 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) if (p == NULL) return FAIL; *start = p; - vim_free(evalarg->eval_tofree); - evalarg->eval_tofree = p; + vim_free(evalarg->eval_tofree_lambda); + evalarg->eval_tofree_lambda = p; // Compute "end" relative to the end. *end = *start + STRLEN(*start) - endoff; } @@ -1936,7 +1952,7 @@ eval_next_line(evalarg_T *evalarg) ((char_u **)gap->ga_data)[gap->ga_len] = line; ++gap->ga_len; } - else + else if (evalarg->eval_cookie != NULL) { vim_free(evalarg->eval_tofree); evalarg->eval_tofree = line; @@ -1962,25 +1978,31 @@ skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg) } /* - * After using "evalarg" filled from "eap" free the memory. + * After using "evalarg" filled from "eap": free the memory. */ void clear_evalarg(evalarg_T *evalarg, exarg_T *eap) { - if (evalarg != NULL && evalarg->eval_tofree != NULL) + if (evalarg != NULL) { - if (eap != NULL) + if (evalarg->eval_tofree != NULL) { - // We may need to keep the original command line, e.g. for - // ":let" it has the variable names. But we may also need the - // new one, "nextcmd" points into it. Keep both. - vim_free(eap->cmdline_tofree); - eap->cmdline_tofree = *eap->cmdlinep; - *eap->cmdlinep = evalarg->eval_tofree; + if (eap != NULL) + { + // We may need to keep the original command line, e.g. for + // ":let" it has the variable names. But we may also need the + // new one, "nextcmd" points into it. Keep both. + vim_free(eap->cmdline_tofree); + eap->cmdline_tofree = *eap->cmdlinep; + *eap->cmdlinep = evalarg->eval_tofree; + } + else + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = NULL; } - else - vim_free(evalarg->eval_tofree); - evalarg->eval_tofree = NULL; + + vim_free(evalarg->eval_tofree_lambda); + evalarg->eval_tofree_lambda = NULL; } } diff --git a/src/proto/eval.pro b/src/proto/eval.pro index 3e87907957..5d04e01acb 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -10,7 +10,7 @@ int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv); int eval_expr_to_bool(typval_T *expr, int *error); char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip); int skip_expr(char_u **pp); -int skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg); +int skip_expr_concatenate(char_u **arg, char_u **start, char_u **end, evalarg_T *evalarg); char_u *eval_to_string(char_u *arg, int convert); char_u *eval_to_string_safe(char_u *arg, int use_sandbox); varnumber_T eval_to_number(char_u *expr); diff --git a/src/structs.h b/src/structs.h index 4ae94fd04e..bdc763b52d 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1773,8 +1773,11 @@ typedef struct { // "eval_ga.ga_data" is a list of pointers to lines. garray_T eval_ga; - // pointer to the line obtained with getsourceline() + // pointer to the last line obtained with getsourceline() char_u *eval_tofree; + + // pointer to the lines concatenated for a lambda. + char_u *eval_tofree_lambda; } evalarg_T; // Flags for expression evaluation. diff --git a/src/userfunc.c b/src/userfunc.c index 01c97dedd4..6858068c82 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -389,8 +389,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) partial_T *pt = NULL; int varargs; int ret; - char_u *start; - char_u *s, *e; + char_u *s; + char_u *start, *end; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; char_u *tofree = NULL; @@ -399,10 +399,10 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) ga_init(&newlines); // First, check if this is a lambda expression. "->" must exist. - start = skipwhite(*arg + 1); - ret = get_function_args(&start, '-', NULL, NULL, NULL, NULL, TRUE, + s = skipwhite(*arg + 1); + ret = get_function_args(&s, '-', NULL, NULL, NULL, NULL, TRUE, NULL, NULL); - if (ret == FAIL || *start != '>') + if (ret == FAIL || *s != '>') return NOTDONE; // Parse the arguments again. @@ -423,8 +423,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) // Get the start and the end of the expression. *arg = skipwhite_and_linebreak(*arg + 1, evalarg); - s = *arg; - ret = skip_expr_concatenate(&s, arg, evalarg); + start = *arg; + ret = skip_expr_concatenate(arg, &start, &end, evalarg); if (ret == FAIL) goto errret; if (evalarg != NULL) @@ -434,7 +434,6 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) evalarg->eval_tofree = NULL; } - e = *arg; *arg = skipwhite_and_linebreak(*arg, evalarg); if (**arg != '}') { @@ -463,13 +462,13 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) goto errret; // Add "return " before the expression. - len = 7 + (int)(e - s) + 1; + len = 7 + (int)(end - start) + 1; p = alloc(len); if (p == NULL) goto errret; ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - vim_strncpy(p + 7, s, e - s); + vim_strncpy(p + 7, start, end - start); if (strstr((char *)p + 7, "a:") == NULL) // No a: variables are used for sure. flags |= FC_NOARGS; @@ -509,7 +508,10 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) } eval_lavars_used = old_eval_lavars; - vim_free(tofree); + if (evalarg->eval_tofree == NULL) + evalarg->eval_tofree = tofree; + else + vim_free(tofree); return OK; errret: @@ -517,7 +519,10 @@ errret: ga_clear_strings(&newlines); vim_free(fp); vim_free(pt); - vim_free(tofree); + if (evalarg->eval_tofree == NULL) + evalarg->eval_tofree = tofree; + else + vim_free(tofree); eval_lavars_used = old_eval_lavars; return FAIL; } diff --git a/src/version.c b/src/version.c index 7b5f8f5d07..8886382893 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1161, /**/ 1160, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index c0e4674de4..941309ffbd 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3113,6 +3113,8 @@ compile_lambda(char_u **arg, cctx_T *cctx) // Compile it into instructions. compile_def_function(ufunc, TRUE, cctx); + clear_evalarg(&evalarg, NULL); + if (ufunc->uf_def_status == UF_COMPILED) return generate_FUNCREF(cctx, ufunc->uf_dfunc_idx); return FAIL; From efaaaa683b7b0cebc128be5c0c257b9d6578ac96 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 22:24:09 +0200 Subject: [PATCH 09/15] patch 8.2.1162: crash when using a lambda Problem: Crash when using a lambda. Solution: Check for evalarg to be NULL. --- src/userfunc.c | 4 ++-- src/version.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/userfunc.c b/src/userfunc.c index 6858068c82..2f253c95fd 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -508,7 +508,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) } eval_lavars_used = old_eval_lavars; - if (evalarg->eval_tofree == NULL) + if (evalarg != NULL && evalarg->eval_tofree == NULL) evalarg->eval_tofree = tofree; else vim_free(tofree); @@ -519,7 +519,7 @@ errret: ga_clear_strings(&newlines); vim_free(fp); vim_free(pt); - if (evalarg->eval_tofree == NULL) + if (evalarg != NULL && evalarg->eval_tofree == NULL) evalarg->eval_tofree = tofree; else vim_free(tofree); diff --git a/src/version.c b/src/version.c index 8886382893..6cd091b582 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1162, /**/ 1161, /**/ From 6e13530ca03dd9cad245221177dd65f712211448 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 22:36:17 +0200 Subject: [PATCH 10/15] patch 8.2.1163: build error Problem: Build error. Solution: Add missing change to globals. --- src/globals.h | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/globals.h b/src/globals.h index b17a9bf6ce..3d40cc1a7b 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1885,7 +1885,7 @@ EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. EXTERN evalarg_T EVALARG_EVALUATE # ifdef DO_INIT - = {EVAL_EVALUATE, 0, NULL, NULL, NULL, {0, 0, 0, 0, NULL}, NULL} + = {EVAL_EVALUATE, 0, NULL, NULL, NULL, {0, 0, 0, 0, NULL}, NULL, NULL} # endif ; #endif diff --git a/src/version.c b/src/version.c index 6cd091b582..91da9bb9d8 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1163, /**/ 1162, /**/ From 96916ac67ad9ed5d79ce87b099f9d01aa4c13745 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 8 Jul 2020 23:09:28 +0200 Subject: [PATCH 11/15] patch 8.2.1164: text cleared by checking terminal properties not redrawn Problem: Text cleared by checking terminal properties not redrawn. (Alexey Radkov) Solution: Mark the screen characters as invalid. (closes #6422) --- src/proto/screen.pro | 1 + src/screen.c | 10 ++++++++++ src/term.c | 2 ++ src/version.c | 2 ++ 4 files changed, 15 insertions(+) diff --git a/src/proto/screen.pro b/src/proto/screen.pro index 919bd419b0..3f475ec12c 100644 --- a/src/proto/screen.pro +++ b/src/proto/screen.pro @@ -31,6 +31,7 @@ int screen_valid(int doclear); void screenalloc(int doclear); void free_screenlines(void); void screenclear(void); +void line_was_clobbered(int screen_lnum); int can_clear(char_u *p); void screen_start(void); void windgoto(int row, int col); diff --git a/src/screen.c b/src/screen.c index 01d6257b7c..0d65b4bbdc 100644 --- a/src/screen.c +++ b/src/screen.c @@ -2981,6 +2981,16 @@ lineinvalid(unsigned off, int width) (void)vim_memset(ScreenAttrs + off, -1, (size_t)width * sizeof(sattr_T)); } +/* + * To be called when characters were sent to the terminal directly, outputting + * test on "screen_lnum". + */ + void +line_was_clobbered(int screen_lnum) +{ + lineinvalid(LineOffset[screen_lnum], (int)Columns); +} + /* * Copy part of a Screenline for vertically split window "wp". */ diff --git a/src/term.c b/src/term.c index 3bda87cccc..07acc11b2a 100644 --- a/src/term.c +++ b/src/term.c @@ -3732,6 +3732,7 @@ check_terminal_behavior(void) screen_stop_highlight(); term_windgoto(1, 0); out_str((char_u *)" "); + line_was_clobbered(1); } if (xcc_status.tr_progress == STATUS_GET) @@ -3761,6 +3762,7 @@ check_terminal_behavior(void) screen_stop_highlight(); term_windgoto(2, 0); out_str((char_u *)" "); + line_was_clobbered(2); } if (did_send) diff --git a/src/version.c b/src/version.c index 91da9bb9d8..f3e023f972 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1164, /**/ 1163, /**/ From e4358906fdbd0b2df1889dad49c79a9c8cee5ac4 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 9 Jul 2020 18:49:23 +0200 Subject: [PATCH 12/15] patch 8.2.1165: insufficient testing for the Tcl interface Problem: Insufficient testing for the Tcl interface. Solution: Add more tests. (Yegappan Lakshmanan, closes #6423) --- src/testdir/test_tcl.vim | 57 ++++++++++++++++++++++++++++++++++++++-- src/version.c | 2 ++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/testdir/test_tcl.vim b/src/testdir/test_tcl.vim index 7045fc24cb..d6b03eba6f 100644 --- a/src/testdir/test_tcl.vim +++ b/src/testdir/test_tcl.vim @@ -23,6 +23,13 @@ func Test_tcldo() call setline(1, ['one', 'two', 'three']) tcldo ::vim::command new call assert_equal(wincount + 1, winnr('$')) + + " Try to run a command in a 'nomodifiable' buffer + call setline(1, ['one', 'two', 'three']) + set nomodifiable + call assert_fails('tcldo set line "abc"', 'cannot save undo information') + set modifiable + %bwipe! endfunc @@ -91,6 +98,9 @@ func Test_vim_buffer() \ 'expected integer but got "x"') call assert_fails('tcl ::vim::buffer list x', \ 'wrong # args: should be "::vim::buffer list "') + " Invalid buffer command + call assert_fails('tcl $::vim::current(buffer) abcd', + \ 'bad option "abcd":') tcl rename eachbuf "" %bwipe! @@ -176,6 +186,8 @@ func Test_vim_window_list() call assert_fails('tcl ::vim::window x', 'unknown option') call assert_fails('tcl ::vim::window list x', \ 'wrong # args: should be "::vim::window option"') + call assert_fails('tcl $::vim::current(window) abcd', + \ 'bad option "abcd":') %bwipe endfunc @@ -248,6 +260,19 @@ func Test_window_cursor() tcl $win cursor $here(row) $here(column) call assert_equal([0, 2, 3, 0], getpos('.')) + " Invalid values for the row and column + tcl array set pos {1 2} + call assert_fails('tcl $win cursor pos', "can't read \"pos(row)\":") + tcl array set pos {row '' abc 2} + call assert_fails('tcl $win cursor pos', "expected integer but got \"''\"") + tcl array set pos {row 1 abc 2} + call assert_fails('tcl $win cursor pos', "can't read \"pos(column)\":") + tcl array set pos {row 1 column ''} + call assert_fails('tcl $win cursor pos', "expected integer but got \"''\"") + + call assert_fails("tcl $win cursor '' 2", "expected integer but got \"''\"") + call assert_fails("tcl $win cursor 1 ''", "expected integer but got \"''\"") + call assert_fails('tcl $win cursor 1 1 1', 'wrong # args:') tcl unset win here @@ -351,6 +376,7 @@ func Test_window_delcmd() call assert_equal('window deleted', TclEval('set msg')) call assert_fails('tcl $::vim::current(window) delcmd', 'wrong # args') + call assert_fails('tcl $::vim::current(window) delcmd x x', 'wrong # args') tcl unset msg bwipe @@ -417,6 +443,14 @@ func Test_buffer_delete() call assert_fails('tcl $::vim::current(buffer) delete', 'wrong # args:') call assert_fails('tcl $::vim::current(buffer) delete 1 2 3', 'wrong # args:') + call assert_fails('tcl $::vim::current(buffer) delete 1 abc', + \ 'expected integer but got "abc"') + + " Try to delete lines from an 'nomodifiable' buffer + set nomodifiable + call assert_fails('tcl $::vim::current(buffer) delete 2 1', + \ 'cannot save undo information') + set modifiable bwipe! endfunc @@ -458,6 +492,12 @@ func Test_buffer_append() call assert_fails('tcl $buf append', 'wrong # args:') call assert_fails('tcl $buf append 1 x x', 'wrong # args:') + " Try to append lines to a 'nomodifiable' buffer + set nomodifiable + call assert_fails('tcl $buf append 1 "first"', + \ 'cannot save undo information') + set modifiable + tcl unset buf bwipe! endfunc @@ -485,6 +525,18 @@ func Test_buffer_set() call assert_fails('tcl $::vim::current(buffer) set 6 "x"', 'line number out of range') call assert_fails('tcl $::vim::current(buffer) set', 'wrong # args:') + call assert_fails('tcl $::vim::current(buffer) set 1 2 {[list "a" "b"]}', + \ 'list element in quotes followed by "]" instead of space') + + " Try to modify a 'nomodifiable' buffer + set nomodifiable + call assert_fails('tcl $::vim::current(buffer) set 1 "x"', + \ 'cannot save undo information') + call assert_fails('tcl $::vim::current(buffer) set 1 {a b}', + \ 'cannot save undo information') + call assert_fails('tcl $::vim::current(buffer) set 1 2 {a b}', + \ 'cannot save undo information') + set modifiable bwipe! endfunc @@ -514,6 +566,7 @@ func Test_buffer_get() call assert_fails('tcl $buf get 0 1', 'line number out of range') call assert_fails('tcl $::vim::current(buffer) get x', 'expected integer but got "x"') + call assert_fails('tcl $::vim::current(buffer) get 1 x', 'expected integer but got "x"') call assert_fails('tcl $::vim::current(buffer) get 1 1 1', 'wrong # args:') tcl unset buf @@ -592,8 +645,8 @@ func Test_buffer_delcmd() q call assert_equal('buffer deleted', TclEval('set msg')) - call assert_fails('tcl $::vim::current(window) delcmd', 'wrong # args') - call assert_fails('tcl $::vim::current(window) delcmd x x', 'wrong # args') + call assert_fails('tcl $::vim::current(buffer) delcmd', 'wrong # args') + call assert_fails('tcl $::vim::current(buffer) delcmd x x', 'wrong # args') tcl unset msg %bwipe diff --git a/src/version.c b/src/version.c index f3e023f972..06f3f27caa 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1165, /**/ 1164, /**/ From ae97b94176062d30ea8c68bb83cde034c5150c78 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 9 Jul 2020 19:16:35 +0200 Subject: [PATCH 13/15] patch 8.2.1166: once mouse move events are enabled getchar() returns them Problem: Once mouse move events are enabled getchar() returns them. Solution: Ignore K_MOUSEMOVE in getchar(). (closes #6424) --- runtime/doc/eval.txt | 5 +++-- src/getchar.c | 4 ++-- src/version.c | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 10b57b7b87..08399b8e0c 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5023,8 +5023,9 @@ getchar([expr]) *getchar()* When the user clicks a mouse button, the mouse event will be returned. The position can then be found in |v:mouse_col|, |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. - |getmousepos()| can also be used. This example positions the - mouse as it would normally happen: > + |getmousepos()| can also be used. Mouse move events will be + ignored. + This example positions the mouse as it would normally happen: > let c = getchar() if c == "\" && v:mouse_win > 0 exe v:mouse_win . "wincmd w" diff --git a/src/getchar.c b/src/getchar.c index 2beffa5784..e040748d66 100644 --- a/src/getchar.c +++ b/src/getchar.c @@ -1501,7 +1501,7 @@ openscript( { update_topline_cursor(); // update cursor position and topline normal_cmd(&oa, FALSE); // execute one command - vpeekc(); // check for end of file + (void)vpeekc(); // check for end of file } while (scriptin[oldcurscript] != NULL); @@ -2045,7 +2045,7 @@ f_getchar(typval_T *argvars, typval_T *rettv) // getchar(0) and char avail: return char n = plain_vgetc(); - if (n == K_IGNORE) + if (n == K_IGNORE || n == K_MOUSEMOVE) continue; break; } diff --git a/src/version.c b/src/version.c index 06f3f27caa..9fcf578ffc 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1166, /**/ 1165, /**/ From 389df259c49d1ca4f7aa129b702f6083985b1e73 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 9 Jul 2020 21:20:47 +0200 Subject: [PATCH 14/15] patch 8.2.1167: Vim9: builtin function method call only supports first arg Problem: Vim9: builtin function method call only supports first argument. Solution: Shift arguments when needed. (closes #6305, closes #6419) --- src/evalfunc.c | 12 +++++++----- src/testdir/test_vim9_disassemble.vim | 18 ++++++++++++++++++ src/testdir/test_vim9_expr.vim | 22 ++++++++++++++++++++++ src/version.c | 2 ++ src/vim9.h | 8 ++++++++ src/vim9compile.c | 18 +++++++++++++++--- src/vim9execute.c | 24 ++++++++++++++++++++++-- 7 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/evalfunc.c b/src/evalfunc.c index 0a100748d9..86a676ca86 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -465,8 +465,8 @@ static funcentry_T global_functions[] = {"acos", 1, 1, FEARG_1, ret_float, FLOAT_FUNC(f_acos)}, {"add", 2, 2, FEARG_1, ret_first_arg, f_add}, {"and", 2, 2, FEARG_1, ret_number, f_and}, - {"append", 2, 2, FEARG_LAST, ret_number, f_append}, - {"appendbufline", 3, 3, FEARG_LAST, ret_number, f_appendbufline}, + {"append", 2, 2, FEARG_2, ret_number, f_append}, + {"appendbufline", 3, 3, FEARG_2, ret_number, f_appendbufline}, {"argc", 0, 1, 0, ret_number, f_argc}, {"argidx", 0, 0, 0, ret_number, f_argidx}, {"arglistid", 0, 2, 0, ret_number, f_arglistid}, @@ -1191,7 +1191,9 @@ internal_func_ret_type(int idx, int argcount, type_T **argtypes) /* * Check the argument count to use for internal function "idx". - * Returns OK or FAIL; + * Returns -1 for failure, 0 if no method base accepted, 1 if method base is + * first argument, 2 if method base is second argument, etc. 9 if method base + * is last argument. */ int check_internal_func(int idx, int argcount) @@ -1204,14 +1206,14 @@ check_internal_func(int idx, int argcount) else if (argcount > global_functions[idx].f_max_argc) res = FCERR_TOOMANY; else - return OK; + return global_functions[idx].f_argtype; name = internal_func_name(idx); if (res == FCERR_TOOMANY) semsg(_(e_toomanyarg), name); else semsg(_(e_toofewarg), name); - return FAIL; + return -1; } int diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index 27d16a5802..a63779a7e8 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -1278,4 +1278,22 @@ def Test_simplify_const_expr() res) enddef +def s:CallAppend() + eval "some text"->append(2) +enddef + +def Test_shuffle() + let res = execute('disass s:CallAppend') + assert_match('\d*_CallAppend\_s*' .. + 'eval "some text"->append(2)\_s*' .. + '\d PUSHS "some text"\_s*' .. + '\d PUSHNR 2\_s*' .. + '\d SHUFFLE 2 up 1\_s*' .. + '\d BCALL append(argc 2)\_s*' .. + '\d DROP\_s*' .. + '\d PUSHNR 0\_s*' .. + '\d RETURN', + res) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index b4e0604943..83b7692090 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1411,6 +1411,28 @@ def Test_expr7_subscript_linebreak() one) enddef +def Test_expr7_method_call() + new + setline(1, ['first', 'last']) + eval 'second'->append(1) + assert_equal(['first', 'second', 'last'], getline(1, '$')) + bwipe! + + let bufnr = bufnr() + let loclist = [#{bufnr: bufnr, lnum: 42, col: 17, text: 'wrong'}] + loclist->setloclist(0) + assert_equal([#{bufnr: bufnr, + lnum: 42, + col: 17, + text: 'wrong', + pattern: '', + valid: 1, + vcol: 0, + nr: 0, + type: '', + module: ''} + ], getloclist(0)) +enddef func Test_expr7_trailing_fails() call CheckDefFailure(['let l = [2]', 'l->{l -> add(l, 8)}'], 'E107') diff --git a/src/version.c b/src/version.c index 9fcf578ffc..c607f4eded 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1167, /**/ 1166, /**/ diff --git a/src/vim9.h b/src/vim9.h index 259bdb9110..10f983ca97 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -124,6 +124,7 @@ typedef enum { ISN_CHECKTYPE, // check value type is isn_arg.type.tc_type ISN_CHECKLEN, // check list length is isn_arg.checklen.cl_min_len + ISN_SHUFFLE, // move item on stack up or down ISN_DROP // pop stack and discard value } isntype_T; @@ -237,6 +238,12 @@ typedef struct { int cl_more_OK; // longer is allowed } checklen_T; +// arguments to ISN_SHUFFLE +typedef struct { + int shfl_item; // item to move (relative to top of stack) + int shfl_up; // places to move upwards +} shuffle_T; + /* * Instruction */ @@ -270,6 +277,7 @@ struct isn_S { unlet_T unlet; funcref_T funcref; checklen_T checklen; + shuffle_T shuffle; } isn_arg; }; diff --git a/src/vim9compile.c b/src/vim9compile.c index 941309ffbd..93ab3c8c05 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1445,20 +1445,31 @@ generate_FOR(cctx_T *cctx, int loop_idx) /* * Generate an ISN_BCALL instruction. + * "method_call" is TRUE for "value->method()" * Return FAIL if the number of arguments is wrong. */ static int -generate_BCALL(cctx_T *cctx, int func_idx, int argcount) +generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) { isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + int argoff; type_T *argtypes[MAX_FUNC_ARGS]; int i; RETURN_OK_IF_SKIP(cctx); - if (check_internal_func(func_idx, argcount) == FAIL) + argoff = check_internal_func(func_idx, argcount); + if (argoff < 0) return FAIL; + if (method_call && argoff > 1) + { + if ((isn = generate_instr(cctx, ISN_SHUFFLE)) == NULL) + return FAIL; + isn->isn_arg.shuffle.shfl_item = argcount; + isn->isn_arg.shuffle.shfl_up = argoff - 1; + } + if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL) return FAIL; isn->isn_arg.bfunc.cbf_idx = func_idx; @@ -2930,7 +2941,7 @@ compile_call( // builtin function idx = find_internal_func(name); if (idx >= 0) - res = generate_BCALL(cctx, idx, argcount); + res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); else semsg(_(e_unknownfunc), namebuf); goto theend; @@ -7397,6 +7408,7 @@ delete_instr(isn_T *isn) case ISN_COMPARESTRING: case ISN_CONCAT: case ISN_DCALL: + case ISN_SHUFFLE: case ISN_DROP: case ISN_ECHO: case ISN_ECHOERR: diff --git a/src/vim9execute.c b/src/vim9execute.c index a3cea806ee..544c4226b2 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -554,7 +554,7 @@ call_by_name(char_u *name, int argcount, ectx_T *ectx, isn_T *iptr) if (func_idx < 0) return FAIL; - if (check_internal_func(func_idx, argcount) == FAIL) + if (check_internal_func(func_idx, argcount) < 0) return FAIL; return call_bfunc(func_idx, argcount, ectx); } @@ -2333,6 +2333,22 @@ call_def_function( } break; + case ISN_SHUFFLE: + { + typval_T tmp_tv; + int item = iptr->isn_arg.shuffle.shfl_item; + int up = iptr->isn_arg.shuffle.shfl_up; + + tmp_tv = *STACK_TV_BOT(-item); + for ( ; up > 0 && item > 1; --up) + { + *STACK_TV_BOT(-item) = *STACK_TV_BOT(-item + 1); + --item; + } + *STACK_TV_BOT(-item) = tmp_tv; + } + break; + case ISN_DROP: --ectx.ec_stack.ga_len; clear_tv(STACK_TV_BOT(0)); @@ -2900,8 +2916,12 @@ ex_disassemble(exarg_T *eap) break; case ISN_2STRING: smsg("%4d 2STRING stack[%lld]", current, (long long)(iptr->isn_arg.number)); - break; + break; + case ISN_SHUFFLE: smsg("%4d SHUFFLE %d up %d", current, + iptr->isn_arg.shuffle.shfl_item, + iptr->isn_arg.shuffle.shfl_up); + break; case ISN_DROP: smsg("%4d DROP", current); break; } } From 92053ce59ecf93838f4d6e3019eef1bc47be4184 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 9 Jul 2020 22:53:30 +0200 Subject: [PATCH 15/15] patch 8.2.1168: wrong method argument for appendbufline() Problem: Wrong method argument for appendbufline(). Solution: Use FEARG_3. --- src/evalfunc.c | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/evalfunc.c b/src/evalfunc.c index 86a676ca86..3f38fa7f54 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -466,7 +466,7 @@ static funcentry_T global_functions[] = {"add", 2, 2, FEARG_1, ret_first_arg, f_add}, {"and", 2, 2, FEARG_1, ret_number, f_and}, {"append", 2, 2, FEARG_2, ret_number, f_append}, - {"appendbufline", 3, 3, FEARG_2, ret_number, f_appendbufline}, + {"appendbufline", 3, 3, FEARG_3, ret_number, f_appendbufline}, {"argc", 0, 1, 0, ret_number, f_argc}, {"argidx", 0, 0, 0, ret_number, f_argidx}, {"arglistid", 0, 2, 0, ret_number, f_arglistid}, diff --git a/src/version.c b/src/version.c index c607f4eded..0b31e54b0a 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1168, /**/ 1167, /**/