diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt index e693cba1df..6b676dfabd 100644 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -137,18 +137,21 @@ arguments). Vim9 functions ~ A function defined with `:def` is compiled. Execution is many times faster, -often 10x to 100x times. +often 10 to 100 times. Many errors are already found when compiling, before the function is executed. The syntax is strict, to enforce code that is easy to read and understand. -Compilation is done when either of these is encountered: +Compilation is done when any of these is encountered: - the first time the function is called -- when the `:defcompile` command is encountered in the script where the +- when the `:defcompile` command is encountered in the script after the function was defined - `:disassemble` is used for the function. - a function that is compiled calls the function or uses it as a function reference + *E1091* +If compilation fails it is not tried again on the next call, instead this +error is given: "E1091: Function is not compiled: {name}". `:def` has no options like `:function` does: "range", "abort", "dict" or "closure". A `:def` function always aborts on an error (unless `:silent!` was @@ -161,7 +164,7 @@ functions. Arguments are accessed by name, without "a:", just like any other language. There is no "a:" dictionary or "a:000" list. - + *vim9-variable-arguments* Variable arguments are defined as the last argument, with a name and have a list type, similar to TypeScript. For example, a list of numbers: > def MyFunc(...itemlist: list) @@ -176,6 +179,15 @@ should use its default value. Example: > ... enddef MyFunc(v:none, 'LAST') # first argument uses default value 'one' +< + *vim9-ignored-argument* +The argument "_" (an underscore) can be used to ignore the argument. This is +most useful in callbacks where you don't need it, but do need to give an +argument to match the call. E.g. when using map() two arguments are passed, +the key and the value, to ignore the key: > + map(myList, (_, v) => v * 2) +There is no error for using the "_" argument multiple times. No type needs to +be given. Functions and variables are script-local by default ~ @@ -323,6 +335,18 @@ The "g:" prefix is not needed for auto-load functions. Since `&opt = value` is now assigning a value to option "opt", ":&" cannot be used to repeat a `:substitute` command. +For an unpack assignment the underscore can be used to ignore a list item, +similar to how a function argument can be ignored: > + [a, _, c] = theList + [a, b; _] = longList + +< *E1092* +Declaring more than one variable at a time, using the unpack notation, is +currently not supported: > + var [v1, v2] = GetValues() # Error! +That is because the type needs to be inferred from the list item type, which +isn't that easy. + Constants ~ *vim9-const* *vim9-final* @@ -356,13 +380,6 @@ The constant only applies to the value itself, not what it refers to. > NAMES[1] = ["Emma"] # Error! NAMES[1][0] = "Emma" # OK, now females[0] == "Emma" -< *E1092* -Declaring more than one variable at a time, using the unpack notation, is -currently not supported: > - var [v1, v2] = GetValues() # Error! -That is because the type needs to be inferred from the list item type, which -isn't that easy. - Omitting :call and :eval ~ @@ -433,6 +450,15 @@ But you can use a backslash to concatenate the lines before parsing: > filter(list, (k, \ v) \ => v > 0) +< *vim9-lambda-arguments* +In legacy script a lambda could be called with any number of extra arguments, +there was no way to warn for not using them. In Vim9 script the number of +arguments must match. If you do want to accept any arguments, or any further +arguments, use "..._", which makes the function accept +|vim9-variable-arguments|. Example: > + var Callback = (..._) => 'anything' + echo Callback(1, 2, 3) # displays "anything" + < *inline-function* Additionally, a lambda can contain statements in {}: > var Lambda = (arg) => { diff --git a/src/blob.c b/src/blob.c index 264962e197..114dacdec2 100644 --- a/src/blob.c +++ b/src/blob.c @@ -259,6 +259,135 @@ failed: return NULL; } + int +blob_slice_or_index( + blob_T *blob, + int is_range, + varnumber_T n1, + varnumber_T n2, + int exclusive, + typval_T *rettv) +{ + long len = blob_len(blob); + + if (is_range) + { + // The resulting variable is a sub-blob. If the indexes + // are out of range the result is empty. + if (n1 < 0) + { + n1 = len + n1; + if (n1 < 0) + n1 = 0; + } + if (n2 < 0) + n2 = len + n2; + else if (n2 >= len) + n2 = len - (exclusive ? 0 : 1); + if (exclusive) + --n2; + if (n1 >= len || n2 < 0 || n1 > n2) + { + clear_tv(rettv); + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } + else + { + blob_T *new_blob = blob_alloc(); + long i; + + if (new_blob != NULL) + { + if (ga_grow(&new_blob->bv_ga, n2 - n1 + 1) == FAIL) + { + blob_free(new_blob); + return FAIL; + } + new_blob->bv_ga.ga_len = n2 - n1 + 1; + for (i = n1; i <= n2; i++) + blob_set(new_blob, i - n1, blob_get(blob, i)); + + clear_tv(rettv); + rettv_blob_set(rettv, new_blob); + } + } + } + else + { + // The resulting variable is a byte value. + // If the index is too big or negative that is an error. + if (n1 < 0) + n1 = len + n1; + if (n1 < len && n1 >= 0) + { + int v = blob_get(blob, n1); + + clear_tv(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = v; + } + else + { + semsg(_(e_blobidx), n1); + return FAIL; + } + } + return OK; +} + +/* + * Check if "n1"- is a valid index for a blobl with length "bloblen". + */ + int +check_blob_index(long bloblen, varnumber_T n1, int quiet) +{ + if (n1 < 0 || n1 > bloblen) + { + if (!quiet) + semsg(_(e_blobidx), n1); + return FAIL; + } + return OK; +} + +/* + * Check if "n1"-"n2" is a valid range for a blob with length "bloblen". + */ + int +check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet) +{ + if (n2 < 0 || n2 >= bloblen || n2 < n1) + { + if (!quiet) + semsg(_(e_blobidx), n2); + return FAIL; + } + return OK; +} + +/* + * Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src". + * Caller must make sure "src" is a blob. + * Returns FAIL if the number of bytes does not match. + */ + int +blob_set_range(blob_T *dest, long n1, long n2, typval_T *src) +{ + int il, ir; + + if (n2 - n1 + 1 != blob_len(src->vval.v_blob)) + { + emsg(_("E972: Blob value does not have the right number of bytes")); + return FAIL; + } + + ir = 0; + for (il = n1; il <= n2; il++) + blob_set(dest, il, blob_get(src->vval.v_blob, ir++)); + return OK; +} + /* * "remove({blob})" function */ diff --git a/src/drawscreen.c b/src/drawscreen.c index 97eb8bc8a2..b035138192 100644 --- a/src/drawscreen.c +++ b/src/drawscreen.c @@ -297,7 +297,9 @@ update_screen(int type_arg) // Remove the cursor before starting to do anything, because // scrolling may make it difficult to redraw the text under // it. - if (gui.in_use && wp == curwin) + // Also remove the cursor if it needs to be hidden due to an + // ongoing cursor-less sleep. + if (gui.in_use && (wp == curwin || cursor_is_sleeping())) { gui_cursor_col = gui.cursor_col; gui_cursor_row = gui.cursor_row; @@ -306,7 +308,6 @@ update_screen(int type_arg) } } #endif - win_update(wp); } diff --git a/src/errors.h b/src/errors.h index 0225531cb0..99f3aaff5a 100644 --- a/src/errors.h +++ b/src/errors.h @@ -395,3 +395,11 @@ EXTERN char e_cannot_lock_unlock_local_variable[] INIT(= N_("E1178: Cannot lock or unlock a local variable")); EXTERN char e_failed_to_extract_pwd_from_str_check_your_shell_config[] INIT(= N_("E1179: Failed to extract PWD from %s, check your shell's config related to OSC 7")); +EXTERN char e_variable_arguments_type_must_be_list_str[] + INIT(= N_("E1180: Variable arguments type must be a list: %s")); +EXTERN char e_cannot_use_underscore_here[] + INIT(= N_("E1181: Cannot use an underscore here")); +EXTERN char e_blob_required[] + INIT(= N_("E1182: Blob required")); +EXTERN char e_cannot_use_range_with_assignment_operator_str[] + INIT(= N_("E1183: Cannot use a range with an assignment operator: %s")); diff --git a/src/eval.c b/src/eval.c index 05b6584424..f8e922f911 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1175,12 +1175,8 @@ get_lval( lp->ll_n1 = (long)tv_get_number(&var1); clear_tv(&var1); - if (lp->ll_n1 < 0 - || lp->ll_n1 > bloblen - || (lp->ll_range && lp->ll_n1 == bloblen)) + if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL) { - if (!quiet) - semsg(_(e_blobidx), lp->ll_n1); clear_tv(&var2); return NULL; } @@ -1188,14 +1184,9 @@ get_lval( { lp->ll_n2 = (long)tv_get_number(&var2); clear_tv(&var2); - if (lp->ll_n2 < 0 - || lp->ll_n2 >= bloblen - || lp->ll_n2 < lp->ll_n1) - { - if (!quiet) - semsg(_(e_blobidx), lp->ll_n2); + if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) + == FAIL) return NULL; - } } lp->ll_blob = lp->ll_tv->vval.v_blob; lp->ll_tv = NULL; @@ -1319,23 +1310,12 @@ set_var_lval( if (lp->ll_range && rettv->v_type == VAR_BLOB) { - int il, ir; - if (lp->ll_empty2) lp->ll_n2 = blob_len(lp->ll_blob) - 1; - if (lp->ll_n2 - lp->ll_n1 + 1 != blob_len(rettv->vval.v_blob)) - { - emsg(_("E972: Blob value does not have the right number of bytes")); + if (blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, + rettv) == FAIL) return; - } - if (lp->ll_empty2) - lp->ll_n2 = blob_len(lp->ll_blob); - - ir = 0; - for (il = lp->ll_n1; il <= lp->ll_n2; il++) - blob_set(lp->ll_blob, il, - blob_get(rettv->vval.v_blob, ir++)); } else { @@ -1362,7 +1342,8 @@ set_var_lval( { typval_T tv; - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_cannot_mod)); *endp = cc; @@ -1401,7 +1382,8 @@ set_var_lval( listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a range")); return; @@ -1460,7 +1442,8 @@ set_var_lval( /* * Assign to a List or Dictionary item. */ - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a list or dict")); return; @@ -1786,7 +1769,9 @@ next_for_item(void *fi_void, char_u *arg) { forinfo_T *fi = (forinfo_T *)fi_void; int result; - int flag = in_vim9script() ? ASSIGN_DECL : 0; + int flag = ASSIGN_FOR_LOOP | (in_vim9script() + ? (ASSIGN_FINAL | ASSIGN_DECL | ASSIGN_NO_MEMBER_TYPE) + : 0); listitem_T *item; if (fi->fi_blob != NULL) @@ -3514,7 +3499,12 @@ eval7( { int flags = evalarg == NULL ? 0 : evalarg->eval_flags; - if ((in_vim9script() ? **arg : *skipwhite(*arg)) == '(') + if (evaluate && in_vim9script() && len == 1 && *s == '_') + { + emsg(_(e_cannot_use_underscore_here)); + ret = FAIL; + } + else if ((in_vim9script() ? **arg : *skipwhite(*arg)) == '(') { // "name(..." recursive! *arg = skipwhite(*arg); @@ -4156,68 +4146,8 @@ eval_index_inner( break; case VAR_BLOB: - len = blob_len(rettv->vval.v_blob); - if (is_range) - { - // The resulting variable is a sub-blob. If the indexes - // are out of range the result is empty. - if (n1 < 0) - { - n1 = len + n1; - if (n1 < 0) - n1 = 0; - } - if (n2 < 0) - n2 = len + n2; - else if (n2 >= len) - n2 = len - (exclusive ? 0 : 1); - if (exclusive) - --n2; - if (n1 >= len || n2 < 0 || n1 > n2) - { - clear_tv(rettv); - rettv->v_type = VAR_BLOB; - rettv->vval.v_blob = NULL; - } - else - { - blob_T *blob = blob_alloc(); - long i; - - if (blob != NULL) - { - if (ga_grow(&blob->bv_ga, n2 - n1 + 1) == FAIL) - { - blob_free(blob); - return FAIL; - } - blob->bv_ga.ga_len = n2 - n1 + 1; - for (i = n1; i <= n2; i++) - blob_set(blob, i - n1, - blob_get(rettv->vval.v_blob, i)); - - clear_tv(rettv); - rettv_blob_set(rettv, blob); - } - } - } - else - { - // The resulting variable is a byte value. - // If the index is too big or negative that is an error. - if (n1 < 0) - n1 = len + n1; - if (n1 < len && n1 >= 0) - { - int v = blob_get(rettv->vval.v_blob, n1); - - clear_tv(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = v; - } - else - semsg(_(e_blobidx), n1); - } + blob_slice_or_index(rettv->vval.v_blob, is_range, n1, n2, + exclusive, rettv); break; case VAR_LIST: diff --git a/src/evalvars.c b/src/evalvars.c index 4544cf83ff..184759dab4 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -972,8 +972,8 @@ ex_let_vars( { arg = skipwhite(arg + 1); ++var_idx; - arg = ex_let_one(arg, &item->li_tv, TRUE, flags, (char_u *)",;]", - op, var_idx); + arg = ex_let_one(arg, &item->li_tv, TRUE, + flags | ASSIGN_UNPACK, (char_u *)",;]", op, var_idx); item = item->li_next; if (arg == NULL) return FAIL; @@ -998,8 +998,8 @@ ex_let_vars( l->lv_refcount = 1; ++var_idx; - arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, flags, - (char_u *)"]", op, var_idx); + arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, + flags | ASSIGN_UNPACK, (char_u *)"]", op, var_idx); clear_tv(<v); if (arg == NULL) return FAIL; @@ -1317,7 +1317,8 @@ ex_let_one( // ":let $VAR = expr": Set environment variable. if (*arg == '$') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock an environment variable")); return NULL; @@ -1367,9 +1368,11 @@ ex_let_one( // ":let &option = expr": Set option value. // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. + // ":for &ts in range(8)": Set option value for for loop else if (*arg == '&') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_const_option)); return NULL; @@ -1468,7 +1471,8 @@ ex_let_one( // ":let @r = expr": Set register contents. else if (*arg == '@') { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_("E996: Cannot lock a register")); return NULL; @@ -1521,7 +1525,7 @@ ex_let_one( else { set_var_lval(&lv, p, tv, copy, flags, op, var_idx); - arg_end = p; + arg_end = lv.ll_name_end; } } clear_lval(&lv); @@ -3160,7 +3164,7 @@ set_var_const( type_T *type, typval_T *tv_arg, int copy, // make copy of value in "tv" - int flags, // ASSIGN_CONST, ASSIGN_FINAL, etc. + int flags_arg, // ASSIGN_CONST, ASSIGN_FINAL, etc. int var_idx) // index for ":let [a, b] = list" { typval_T *tv = tv_arg; @@ -3171,6 +3175,7 @@ set_var_const( int is_script_local; int vim9script = in_vim9script(); int var_in_vim9script; + int flags = flags_arg; ht = find_var_ht(name, &varname); if (ht == NULL || *varname == NUL) @@ -3189,7 +3194,19 @@ set_var_const( vim9_declare_error(name); goto failed; } + if ((flags & ASSIGN_FOR_LOOP) && name[1] == ':' + && vim_strchr((char_u *)"gwbt", name[0]) != NULL) + // Do not make g:var, w:var, b:var or t:var final. + flags &= ~ASSIGN_FINAL; + var_in_vim9script = is_script_local && current_script_is_vim9(); + if (var_in_vim9script && name[0] == '_' && name[1] == NUL) + { + // For "[a, _] = list" the underscore is ignored. + if ((flags & ASSIGN_UNPACK) == 0) + emsg(_(e_cannot_use_underscore_here)); + goto failed; + } di = find_var_in_ht(ht, 0, varname, TRUE); @@ -3215,7 +3232,8 @@ set_var_const( // Item already exists. Allowed to replace when reloading. if ((di->di_flags & DI_FLAGS_RELOAD) == 0) { - if (flags & (ASSIGN_CONST | ASSIGN_FINAL)) + if ((flags & (ASSIGN_CONST | ASSIGN_FINAL)) + && (flags & ASSIGN_FOR_LOOP) == 0) { emsg(_(e_cannot_mod)); goto failed; @@ -3250,7 +3268,8 @@ set_var_const( // A Vim9 script-local variable is also present in sn_all_vars and // sn_var_vals. It may set "type" from "tv". if (var_in_vim9script) - update_vim9_script_var(FALSE, di, flags, tv, &type); + update_vim9_script_var(FALSE, di, flags, tv, &type, + (flags & ASSIGN_NO_MEMBER_TYPE) == 0); } // existing variable, need to clear the value @@ -3348,7 +3367,8 @@ set_var_const( // A Vim9 script-local variable is also added to sn_all_vars and // sn_var_vals. It may set "type" from "tv". if (var_in_vim9script) - update_vim9_script_var(TRUE, di, flags, tv, &type); + update_vim9_script_var(TRUE, di, flags, tv, &type, + (flags & ASSIGN_NO_MEMBER_TYPE) == 0); } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 85e422e662..79ccfa5147 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -3435,22 +3435,25 @@ find_ex_command( // "varname.key" is an expression. || (*p == '.' && ASCII_ISALPHA(p[1])))) { - char_u *after = p; + char_u *after = eap->cmd; // When followed by "=" or "+=" then it is an assignment. + // Skip over the whole thing, it can be: + // name.member = val + // name[a : b] = val + // name[idx] = val + // name[idx].member = val + // etc. + eap->cmdidx = CMD_eval; ++emsg_silent; - if (*after == '.') - after = skipwhite(after + 1); if (skip_expr(&after, NULL) == OK) + { after = skipwhite(after); - else - after = (char_u *)""; - if (*after == '=' || (*after != NUL && after[1] == '=') + if (*after == '=' || (*after != NUL && after[1] == '=') || (after[0] == '.' && after[1] == '.' && after[2] == '=')) - eap->cmdidx = CMD_var; - else - eap->cmdidx = CMD_eval; + eap->cmdidx = CMD_var; + } --emsg_silent; return eap->cmd; } @@ -7378,7 +7381,7 @@ do_sleep(long msec, int hide_cursor) # endif if (hide_cursor) - cursor_off(); + cursor_sleep(); else cursor_on(); @@ -7430,6 +7433,9 @@ do_sleep(long msec, int hide_cursor) // input buffer, otherwise a following call to input() fails. if (got_int) (void)vpeekc(); + + if (hide_cursor) + cursor_unsleep(); } /* diff --git a/src/globals.h b/src/globals.h index b058cfadf7..b19acdb722 100644 --- a/src/globals.h +++ b/src/globals.h @@ -230,6 +230,9 @@ EXTERN int did_endif INIT(= FALSE); // just had ":endif" EXTERN int did_emsg; // set by emsg() when the message // is displayed or thrown #ifdef FEAT_EVAL +EXTERN int did_emsg_silent INIT(= 0); // incremented by emsg() when + // emsg_silent was set and did_emsg + // is not incremented EXTERN int did_emsg_def; // set by emsg() when emsg_silent // is set before calling a function EXTERN int did_emsg_cumul; // cumulative did_emsg, increased diff --git a/src/gui.c b/src/gui.c index 2d3f0a0a63..2b280e9c4a 100644 --- a/src/gui.c +++ b/src/gui.c @@ -1139,6 +1139,11 @@ gui_update_cursor( || gui.row != gui.cursor_row || gui.col != gui.cursor_col) { gui_undraw_cursor(); + + // If a cursor-less sleep is ongoing, leave the cursor invisible + if (cursor_is_sleeping()) + return; + if (gui.row < 0) return; #ifdef HAVE_INPUT_METHOD diff --git a/src/list.c b/src/list.c index 76327abc87..56b2188ff4 100644 --- a/src/list.c +++ b/src/list.c @@ -2059,7 +2059,7 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) { // Check that map() does not change the type of the dict. ga_init2(&type_list, sizeof(type_T *), 10); - type = typval2type(argvars, get_copyID(), &type_list); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); } if (argvars[0].v_type == VAR_BLOB) @@ -2565,7 +2565,7 @@ extend(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg, int is_new) { // Check that map() does not change the type of the dict. ga_init2(&type_list, sizeof(type_T *), 10); - type = typval2type(argvars, get_copyID(), &type_list); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); } if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) diff --git a/src/message.c b/src/message.c index de5183cf97..4e374391e5 100644 --- a/src/message.c +++ b/src/message.c @@ -685,6 +685,9 @@ emsg_core(char_u *s) */ if (emsg_silent != 0) { +#ifdef FEAT_EVAL + ++did_emsg_silent; +#endif if (emsg_noredir == 0) { msg_start(); diff --git a/src/proto/blob.pro b/src/proto/blob.pro index 3bc6625456..3adaf0ffe5 100644 --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -13,5 +13,9 @@ int read_blob(FILE *fd, blob_T *blob); int write_blob(FILE *fd, blob_T *blob); char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf); blob_T *string2blob(char_u *str); +int blob_slice_or_index(blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, int exclusive, typval_T *rettv); +int check_blob_index(long bloblen, varnumber_T n1, int quiet); +int check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet); +int blob_set_range(blob_T *dest, long n1, long n2, typval_T *src); void blob_remove(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/proto/term.pro b/src/proto/term.pro index 1b3780204b..efb2555343 100644 --- a/src/proto/term.pro +++ b/src/proto/term.pro @@ -56,6 +56,9 @@ void scroll_start(void); void cursor_on_force(void); void cursor_on(void); void cursor_off(void); +int cursor_is_sleeping(void); +void cursor_sleep(void); +void cursor_unsleep(void); void term_cursor_mode(int forced); void term_cursor_color(char_u *color); int blink_state_is_inverted(void); diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro index c43120c452..cb5a30d2e0 100644 --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -12,7 +12,7 @@ void ex_import(exarg_T *eap); int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx, int verbose); char_u *handle_import(char_u *arg_start, garray_T *gap, int import_sid, evalarg_T *evalarg, void *cctx); char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg); -void update_vim9_script_var(int create, dictitem_T *di, int flags, typval_T *tv, type_T **type); +void update_vim9_script_var(int create, dictitem_T *di, int flags, typval_T *tv, type_T **type, int do_member); void hide_script_var(scriptitem_T *si, int idx, int func_defined); void free_all_script_vars(scriptitem_T *si); svar_T *find_typval_in_script(typval_T *dest); diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro index b5aa6ee921..a512fa4d55 100644 --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -9,7 +9,7 @@ type_T *alloc_func_type(type_T *ret_type, int argcount, garray_T *type_gap); type_T *get_func_type(type_T *ret_type, int argcount, garray_T *type_gap); int func_type_add_arg_types(type_T *functype, int argcount, garray_T *type_gap); int need_convert_to_bool(type_T *type, typval_T *tv); -type_T *typval2type(typval_T *tv, int copyID, garray_T *type_gap); +type_T *typval2type(typval_T *tv, int copyID, garray_T *type_gap, int do_member); type_T *typval2type_vimvar(typval_T *tv, garray_T *type_gap); int check_typval_arg_type(type_T *expected, typval_T *actual_tv, int arg_idx); int check_typval_type(type_T *expected, typval_T *actual_tv, where_T where); diff --git a/src/structs.h b/src/structs.h index 94550e66f3..bdec01b235 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1583,10 +1583,11 @@ typedef struct funccall_S funccall_T; // values used for "uf_def_status" typedef enum { - UF_NOT_COMPILED, - UF_TO_BE_COMPILED, - UF_COMPILING, - UF_COMPILED + UF_NOT_COMPILED, // executed with interpreter + UF_TO_BE_COMPILED, // to be compiled before execution + UF_COMPILING, // in compile_def_function() + UF_COMPILED, // successfully compiled + UF_COMPILE_ERROR // compilation error, cannot execute } def_status_T; /* diff --git a/src/syntax.c b/src/syntax.c index d4ec0d339f..c3572d8435 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -5990,12 +5990,17 @@ get_id_list( break; } if (name[1] == 'A') - id = SYNID_ALLBUT; + id = SYNID_ALLBUT + current_syn_inc_tag; else if (name[1] == 'T') - id = SYNID_TOP; + { + if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) + id = curwin->w_s->b_syn_topgrp; + else + id = SYNID_TOP + current_syn_inc_tag; + } else - id = SYNID_CONTAINED; - id += current_syn_inc_tag; + id = SYNID_CONTAINED + current_syn_inc_tag; + } else if (name[1] == '@') { diff --git a/src/term.c b/src/term.c index 06903f44fc..6f0e860d5f 100644 --- a/src/term.c +++ b/src/term.c @@ -3938,8 +3938,12 @@ scroll_start(void) } } +// True if cursor is not visible static int cursor_is_off = FALSE; +// True if cursor is not visible due to an ongoing cursor-less sleep +static int cursor_is_asleep = FALSE; + /* * Enable the cursor without checking if it's already enabled. */ @@ -3948,6 +3952,7 @@ cursor_on_force(void) { out_str(T_VE); cursor_is_off = FALSE; + cursor_is_asleep = FALSE; } /* @@ -3956,7 +3961,7 @@ cursor_on_force(void) void cursor_on(void) { - if (cursor_is_off) + if (cursor_is_off && !cursor_is_asleep) cursor_on_force(); } @@ -3973,6 +3978,35 @@ cursor_off(void) } } +/* + * Check whether the cursor is invisible due to an ongoing cursor-less sleep + */ + int +cursor_is_sleeping(void) +{ + return cursor_is_asleep; +} + +/* + * Disable the cursor and mark it disabled by cursor-less sleep + */ + void +cursor_sleep(void) +{ + cursor_is_asleep = TRUE; + cursor_off(); +} + +/* + * Enable the cursor and mark it not disabled by cursor-less sleep + */ + void +cursor_unsleep(void) +{ + cursor_is_asleep = FALSE; + cursor_on(); +} + #if defined(CURSOR_SHAPE) || defined(PROTO) /* * Set cursor shape to match Insert or Replace mode. diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim index 3eff715d05..3699f3bb19 100644 --- a/src/testdir/test_blob.vim +++ b/src/testdir/test_blob.vim @@ -1,5 +1,7 @@ " Tests for the Blob types +source vim9.vim + func TearDown() " Run garbage collection after every test call test_garbagecollect_now() @@ -9,73 +11,112 @@ endfunc " Blob creation from constant func Test_blob_create() - let b = 0zDEADBEEF - call assert_equal(v:t_blob, type(b)) - call assert_equal(4, len(b)) - call assert_equal(0xDE, b[0]) - call assert_equal(0xAD, b[1]) - call assert_equal(0xBE, b[2]) - call assert_equal(0xEF, b[3]) - call assert_fails('let x = b[4]') + let lines =<< trim END + VAR b = 0zDEADBEEF + call assert_equal(v:t_blob, type(b)) + call assert_equal(4, len(b)) + call assert_equal(0xDE, b[0]) + call assert_equal(0xAD, b[1]) + call assert_equal(0xBE, b[2]) + call assert_equal(0xEF, b[3]) + call assert_fails('VAR x = b[4]') - call assert_equal(0xDE, get(b, 0)) - call assert_equal(0xEF, get(b, 3)) + call assert_equal(0xDE, get(b, 0)) + call assert_equal(0xEF, get(b, 3)) - call assert_fails('let b = 0z1', 'E973:') - call assert_fails('let b = 0z1x', 'E973:') - call assert_fails('let b = 0z12345', 'E973:') + call assert_fails('VAR b = 0z1', 'E973:') + call assert_fails('VAR b = 0z1x', 'E973:') + call assert_fails('VAR b = 0z12345', 'E973:') - call assert_equal(0z, test_null_blob()) + call assert_equal(0z, test_null_blob()) - let b = 0z001122.33445566.778899.aabbcc.dd - call assert_equal(0z00112233445566778899aabbccdd, b) - call assert_fails('let b = 0z1.1') - call assert_fails('let b = 0z.') - call assert_fails('let b = 0z001122.') - call assert_fails('call get("", 1)', 'E896:') - call assert_equal(0, len(test_null_blob())) + LET b = 0z001122.33445566.778899.aabbcc.dd + call assert_equal(0z00112233445566778899aabbccdd, b) + call assert_fails('VAR b = 0z1.1') + call assert_fails('VAR b = 0z.') + call assert_fails('VAR b = 0z001122.') + call assert_fails('call get("", 1)', 'E896:') + call assert_equal(0, len(test_null_blob())) + END + call CheckLegacyAndVim9Success(lines) endfunc " assignment to a blob func Test_blob_assign() - let b = 0zDEADBEEF - let b2 = b[1:2] - call assert_equal(0zADBE, b2) + let lines =<< trim END + VAR b = 0zDEADBEEF + VAR b2 = b[1 : 2] + call assert_equal(0zADBE, b2) - let bcopy = b[:] - call assert_equal(b, bcopy) - call assert_false(b is bcopy) + VAR bcopy = b[:] + call assert_equal(b, bcopy) + call assert_false(b is bcopy) - let b = 0zDEADBEEF - let b2 = b - call assert_true(b is b2) - let b[:] = 0z11223344 - call assert_equal(0z11223344, b) - call assert_equal(0z11223344, b2) - call assert_true(b is b2) + LET b = 0zDEADBEEF + LET b2 = b + call assert_true(b is b2) + LET b[:] = 0z11223344 + call assert_equal(0z11223344, b) + call assert_equal(0z11223344, b2) + call assert_true(b is b2) - let b = 0zDEADBEEF - let b[3:] = 0z66 - call assert_equal(0zDEADBE66, b) - let b[:1] = 0z8899 - call assert_equal(0z8899BE66, b) + LET b = 0zDEADBEEF + LET b[3 :] = 0z66 + call assert_equal(0zDEADBE66, b) + LET b[: 1] = 0z8899 + call assert_equal(0z8899BE66, b) - call assert_fails('let b[2:3] = 0z112233', 'E972:') - call assert_fails('let b[2:3] = 0z11', 'E972:') - call assert_fails('let b[3:2] = 0z', 'E979:') + LET b = 0zDEADBEEF + LET b += 0z99 + call assert_equal(0zDEADBEEF99, b) - let b = 0zDEADBEEF - let b += 0z99 - call assert_equal(0zDEADBEEF99, b) + VAR l = [0z12] + VAR m = deepcopy(l) + LET m[0] = 0z34 #" E742 or E741 should not occur. + END + call CheckLegacyAndVim9Success(lines) - call assert_fails('let b .= 0z33', 'E734:') - call assert_fails('let b .= "xx"', 'E734:') - call assert_fails('let b += "xx"', 'E734:') - call assert_fails('let b[1:1] .= 0z55', 'E734:') + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b[2 : 3] = 0z112233 + END + call CheckLegacyAndVim9Failure(lines, 'E972:') - let l = [0z12] - let m = deepcopy(l) - let m[0] = 0z34 " E742 or E741 should not occur. + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b[2 : 3] = 0z11 + END + call CheckLegacyAndVim9Failure(lines, 'E972:') + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b[3 : 2] = 0z + END + call CheckLegacyAndVim9Failure(lines, 'E979:') + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b ..= 0z33 + END + call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:']) + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b ..= "xx" + END + call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:']) + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b += "xx" + END + call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:']) + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b[1 : 1] ..= 0z55 + END + call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1183:', 'E734:']) endfunc func Test_blob_get_range() diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index 8ee36794a4..1cce8a0b19 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -1497,6 +1497,7 @@ endfunc func Test_balloon_show() CheckFeature balloon_eval + " This won't do anything but must not crash either. call balloon_show('hi!') if !has('gui_running') @@ -2650,4 +2651,12 @@ func Test_browsedir() call assert_fails('call browsedir("open", [])', 'E730:') endfunc +func HasDefault(msg = 'msg') + return a:msg +endfunc + +func Test_default_arg_value() + call assert_equal('msg', HasDefault()) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_syntax.vim b/src/testdir/test_syntax.vim index 1a413f6ea9..bc268a14bb 100644 --- a/src/testdir/test_syntax.vim +++ b/src/testdir/test_syntax.vim @@ -920,4 +920,21 @@ func Test_syn_contained_transparent() bw! endfunc +func Test_syn_include_contains_TOP() + let l:case = "TOP in included syntax means its group list name" + new + syntax include @INCLUDED syntax/c.vim + syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED + + call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ]) + let l:expected = ["cCppOutIf2"] + eval AssertHighlightGroups(3, 1, l:expected, 1) + " cCppOutElse has contains=TOP + let l:expected = ["cType"] + eval AssertHighlightGroups(5, 1, l:expected, 1, l:case) + syntax clear + bw! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index c35084d550..958b7b9809 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -256,6 +256,14 @@ def Test_assign_unpack() [v1, v2] = [1, 2] assert_equal(1, v1) assert_equal(2, v2) + + [v1, _, v2, _] = [1, 99, 2, 77] + assert_equal(1, v1) + assert_equal(2, v2) + + [v1, v2; _] = [1, 2, 3, 4, 5] + assert_equal(1, v1) + assert_equal(2, v2) END CheckDefAndScriptSuccess(lines) diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index f537eef82c..5c13d51277 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -506,7 +506,7 @@ def Test_filter_wrong_dict_key_type() enddef def Test_filter_return_type() - var l = filter([1, 2, 3], () => 1) + var l = filter([1, 2, 3], (_, _) => 1) var res = 0 for n in l res += n @@ -516,7 +516,7 @@ enddef def Test_filter_missing_argument() var dict = {aa: [1], ab: [2], ac: [3], de: [4]} - var res = dict->filter((k) => k =~ 'a' && k !~ 'b') + var res = dict->filter((k, _) => k =~ 'a' && k !~ 'b') res->assert_equal({aa: [1], ac: [3]}) enddef diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index e24e72f06b..3ea2ad9a40 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -386,6 +386,33 @@ def Test_disassemble_blob_add() res) enddef +def s:BlobIndexSlice() + var b: blob = 0z112233 + echo b[1] + echo b[1 : 2] +enddef + +def Test_disassemble_blob_index_slice() + var res = execute('disass s:BlobIndexSlice') + assert_match('\d*_BlobIndexSlice\_s*' .. + 'var b: blob = 0z112233\_s*' .. + '\d PUSHBLOB 0z112233\_s*' .. + '\d STORE $0\_s*' .. + 'echo b\[1\]\_s*' .. + '\d LOAD $0\_s*' .. + '\d PUSHNR 1\_s*' .. + '\d BLOBINDEX\_s*' .. + '\d ECHO 1\_s*' .. + 'echo b\[1 : 2\]\_s*' .. + '\d LOAD $0\_s*' .. + '\d PUSHNR 1\_s*' .. + '\d\+ PUSHNR 2\_s*' .. + '\d\+ BLOBSLICE\_s*' .. + '\d\+ ECHO 1\_s*' .. + '\d\+ RETURN 0', + res) +enddef + def s:ScriptFuncUnlet() g:somevar = "value" unlet g:somevar @@ -770,7 +797,7 @@ def Test_disassemble_const_expr() 'if has("gui_running")\_s*' .. '\d PUSHS "gui_running"\_s*' .. '\d BCALL has(argc 1)\_s*' .. - '\d 2BOOL (!!val)\_s*' .. + '\d COND2BOOL\_s*' .. '\d JUMP_IF_FALSE -> \d\_s*' .. ' echo "yes"\_s*' .. '\d PUSHS "yes"\_s*' .. @@ -1537,13 +1564,13 @@ def Test_disassemble_return_bool() assert_match('ReturnBool\_s*' .. 'var name: bool = 1 && 0 || 1\_s*' .. '0 PUSHNR 1\_s*' .. - '1 2BOOL (!!val)\_s*' .. + '1 COND2BOOL\_s*' .. '2 JUMP_IF_COND_FALSE -> 5\_s*' .. '3 PUSHNR 0\_s*' .. - '4 2BOOL (!!val)\_s*' .. + '4 COND2BOOL\_s*' .. '5 JUMP_IF_COND_TRUE -> 8\_s*' .. '6 PUSHNR 1\_s*' .. - '7 2BOOL (!!val)\_s*' .. + '7 COND2BOOL\_s*' .. '\d STORE $0\_s*' .. 'return name\_s*' .. '\d\+ LOAD $0\_s*' .. @@ -2018,5 +2045,17 @@ def Test_profiled() res) enddef +def s:EchoMessages() + echohl ErrorMsg | echom v:exception | echohl NONE +enddef + +def Test_disassemble_nextcmd() + # splitting commands and removing trailing blanks should not change the line + var res = execute('disass s:EchoMessages') + assert_match('\d*_EchoMessages\_s*' .. + 'echohl ErrorMsg | echom v:exception | echohl NONE', + 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 44ff105c62..456b42612e 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -282,6 +282,20 @@ def Test_expr2() g:vals = [] assert_equal(false, Record(0) || Record(false) || Record(0)) assert_equal([0, false, 0], g:vals) + + g:vals = [] + var x = 1 + if x || true + g:vals = [1] + endif + assert_equal([1], g:vals) + + g:vals = [] + x = 3 + if true || x + g:vals = [1] + endif + assert_equal([1], g:vals) END CheckDefAndScriptSuccess(lines) enddef @@ -357,6 +371,9 @@ def Test_expr2_fails() # TODO: should fail at compile time call CheckDefExecAndScriptFailure(["var x = 3 || 7"], 'E1023:', 1) + call CheckDefAndScriptFailure(["if 3"], 'E1023:', 1) + call CheckDefExecAndScriptFailure(['var x = 3', 'if x', 'endif'], 'E1023:', 2) + call CheckDefAndScriptFailure2(["var x = [] || false"], 'E1012: Type mismatch; expected bool but got list', 'E745:', 1) enddef @@ -1605,6 +1622,26 @@ def Test_expr7_blob() assert_equal(g:blob_empty, 0z) assert_equal(g:blob_one, 0z01) assert_equal(g:blob_long, 0z0102.0304) + + var testblob = 0z010203 + assert_equal(0x01, testblob[0]) + assert_equal(0x02, testblob[1]) + assert_equal(0x03, testblob[-1]) + assert_equal(0x02, testblob[-2]) + + assert_equal(0z01, testblob[0 : 0]) + assert_equal(0z0102, testblob[0 : 1]) + assert_equal(0z010203, testblob[0 : 2]) + assert_equal(0z010203, testblob[0 : ]) + assert_equal(0z0203, testblob[1 : ]) + assert_equal(0z0203, testblob[1 : 2]) + assert_equal(0z0203, testblob[1 : -1]) + assert_equal(0z03, testblob[-1 : -1]) + assert_equal(0z02, testblob[-2 : -2]) + + # blob slice accepts out of range + assert_equal(0z, testblob[3 : 3]) + assert_equal(0z, testblob[0 : -4]) END CheckDefAndScriptSuccess(lines) diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 730cf40aaa..a941a61022 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -74,6 +74,30 @@ def TestCompilingErrorInTry() delete('Xdir', 'rf') enddef +def Test_autoload_name_mismatch() + var dir = 'Xdir/autoload' + mkdir(dir, 'p') + + var lines =<< trim END + vim9script + def scriptX#Function() + # comment + g:runtime = 'yes' + enddef + END + writefile(lines, dir .. '/script.vim') + + var save_rtp = &rtp + exe 'set rtp=' .. getcwd() .. '/Xdir' + lines =<< trim END + call script#Function() + END + CheckScriptFailure(lines, 'E746:', 2) + + &rtp = save_rtp + delete(dir, 'rf') +enddef + def CallRecursive(n: number): number return CallRecursive(n + 1) enddef @@ -791,10 +815,20 @@ def Test_call_funcref_wrong_args() enddef def Test_call_lambda_args() + var lines =<< trim END + var Callback = (..._) => 'anything' + assert_equal('anything', Callback()) + assert_equal('anything', Callback(1)) + assert_equal('anything', Callback('a', 2)) + + assert_equal('xyz', ((a: string): string => a)('xyz')) + END + CheckDefAndScriptSuccess(lines) + CheckDefFailure(['echo ((i) => 0)()'], 'E119: Not enough arguments for function: ((i) => 0)()') - var lines =<< trim END + lines =<< trim END var Ref = (x: number, y: number) => x + y echo Ref(1, 'x') END @@ -836,6 +870,11 @@ def Test_call_lambda_args() enddef END CheckDefFailure(lines, 'E1167:') + + lines =<< trim END + echo ((a) => a)('aa', 'bb') + END + CheckDefAndScriptFailure(lines, 'E118:', 1) enddef def FilterWithCond(x: string, Cond: func(string): bool): bool @@ -918,13 +957,22 @@ def Test_call_def_varargs() lines =<< trim END vim9script - def Func(...l: any) + def Func(...l: list) echo l enddef Func(0) END CheckScriptSuccess(lines) + lines =<< trim END + vim9script + def Func(...l: any) + echo l + enddef + Func(0) + END + CheckScriptFailure(lines, 'E1180:', 2) + lines =<< trim END vim9script def Func(..._l: list) @@ -1474,7 +1522,7 @@ def Test_redef_failure() so Xdef delete('Xdef') - g:Func0()->assert_equal(0) + assert_fails('g:Func0()', 'E1091:') g:Func1()->assert_equal('Func1') g:Func2()->assert_equal('Func2') @@ -2078,7 +2126,7 @@ def Test_script_var_in_lambda() var lines =<< trim END vim9script var script = 'test' - assert_equal(['test'], map(['one'], () => script)) + assert_equal(['test'], map(['one'], (_, _) => script)) END CheckScriptSuccess(lines) enddef @@ -2114,7 +2162,7 @@ def Test_list_lambda() ->substitute("('", ' ', '') ->substitute("')", '', '') ->substitute('function\zs', ' ', '')) - assert_match('def \d\+(_: any, ...): number\n1 return 0\n enddef', body) + assert_match('def \d\+(_: any): number\n1 return 0\n enddef', body) enddef def DoFilterThis(a: string): list @@ -2331,7 +2379,7 @@ def Test_block_scoped_var() var x = ['a', 'b', 'c'] if 1 var y = 'x' - map(x, () => y) + map(x, (_, _) => y) endif var z = x assert_equal(['x', 'x', 'x'], z) @@ -2363,7 +2411,7 @@ def Test_did_emsg_reset() vim9script au BufWinLeave * # def Func() - popup_menu('', {callback: () => popup_create('', {})->popup_close()}) + popup_menu('', {callback: (a, b) => popup_create('', {})->popup_close()}) eval [][0] enddef nno call Func() @@ -2567,6 +2615,82 @@ def Test_check_func_arg_types() CheckScriptFailure(lines + ['echo H(G(F2))'], 'E1013:') enddef +def Test_compile_error() + var lines =<< trim END + def g:Broken() + echo 'a' + {} + enddef + call g:Broken() + END + # First call: compilation error + CheckScriptFailure(lines, 'E1051: Wrong argument type for +') + + # Second call won't try compiling again + assert_fails('call g:Broken()', 'E1091: Function is not compiled: Broken') + delfunc g:Broken + + # No error when compiling with :silent! + lines =<< trim END + def g:Broken() + echo 'a' + [] + enddef + silent! defcompile + END + CheckScriptSuccess(lines) + + # Calling the function won't try compiling again + assert_fails('call g:Broken()', 'E1091: Function is not compiled: Broken') + delfunc g:Broken +enddef + +def Test_ignored_argument() + var lines =<< trim END + vim9script + def Ignore(_, _): string + return 'yes' + enddef + assert_equal('yes', Ignore(1, 2)) + + func Ok(_) + return a:_ + endfunc + assert_equal('ok', Ok('ok')) + + func Oktoo() + let _ = 'too' + return _ + endfunc + assert_equal('too', Oktoo()) + + assert_equal([[1], [2], [3]], range(3)->mapnew((_, v) => [v]->map((_, w) => w + 1))) + END + CheckScriptSuccess(lines) + + lines =<< trim END + def Ignore(_: string): string + return _ + enddef + defcompile + END + CheckScriptFailure(lines, 'E1181:', 1) + + lines =<< trim END + var _ = 1 + END + CheckDefAndScriptFailure(lines, 'E1181:', 1) +enddef + +def Test_too_many_arguments() + var lines =<< trim END + echo [0, 1, 2]->map(() => 123) + END + CheckDefExecAndScriptFailure(lines, 'E1106: 2 arguments too many', 1) + + lines =<< trim END + echo [0, 1, 2]->map((_) => 123) + END + CheckDefExecAndScriptFailure(lines, 'E1106: One argument too many', 1) +enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index cb1592abff..03da2fd86f 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2295,70 +2295,89 @@ def Test_for_outside_of_function() enddef def Test_for_loop() - var result = '' - for cnt in range(7) - if cnt == 4 - break - endif - if cnt == 2 - continue - endif - result ..= cnt .. '_' - endfor - assert_equal('0_1_3_', result) + var lines =<< trim END + var result = '' + for cnt in range(7) + if cnt == 4 + break + endif + if cnt == 2 + continue + endif + result ..= cnt .. '_' + endfor + assert_equal('0_1_3_', result) - var concat = '' - for str in eval('["one", "two"]') - concat ..= str - endfor - assert_equal('onetwo', concat) + var concat = '' + for str in eval('["one", "two"]') + concat ..= str + endfor + assert_equal('onetwo', concat) - var total = 0 - for nr in - [1, 2, 3] - total += nr - endfor - assert_equal(6, total) + var total = 0 + for nr in + [1, 2, 3] + total += nr + endfor + assert_equal(6, total) - total = 0 - for nr - in [1, 2, 3] - total += nr - endfor - assert_equal(6, total) + total = 0 + for nr + in [1, 2, 3] + total += nr + endfor + assert_equal(6, total) - total = 0 - for nr - in - [1, 2, 3] - total += nr - endfor - assert_equal(6, total) + total = 0 + for nr + in + [1, 2, 3] + total += nr + endfor + assert_equal(6, total) - var res = "" - for [n: number, s: string] in [[1, 'a'], [2, 'b']] - res ..= n .. s - endfor - assert_equal('1a2b', res) + # with type + total = 0 + for n: number in [1, 2, 3] + total += n + endfor + assert_equal(6, total) - # loop over string - res = '' - for c in 'aéc̀d' - res ..= c .. '-' - endfor - assert_equal('a-é-c̀-d-', res) + # unpack with type + var res = '' + for [n: number, s: string] in [[1, 'a'], [2, 'b']] + res ..= n .. s + endfor + assert_equal('1a2b', res) - res = '' - for c in '' - res ..= c .. '-' - endfor - assert_equal('', res) + # loop over string + res = '' + for c in 'aéc̀d' + res ..= c .. '-' + endfor + assert_equal('a-é-c̀-d-', res) - res = '' - for c in test_null_string() - res ..= c .. '-' - endfor - assert_equal('', res) + res = '' + for c in '' + res ..= c .. '-' + endfor + assert_equal('', res) + + res = '' + for c in test_null_string() + res ..= c .. '-' + endfor + assert_equal('', res) + + var foo: list> = [ + {a: 'Cat'} + ] + for dd in foo + dd.counter = 12 + endfor + assert_equal([{a: 'Cat', counter: 12}], foo) + END + CheckDefAndScriptSuccess(lines) enddef def Test_for_loop_fails() @@ -2381,6 +2400,14 @@ def Test_for_loop_fails() g:adict = {a: 1} CheckDefExecFailure(['for i in g:adict', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported') unlet g:adict + + var lines =<< trim END + var d: list> = [{a: 0}] + for e in d + e = {a: 0, b: ''} + endfor + END + CheckDefAndScriptFailure2(lines, 'E1018:', 'E46:', 3) enddef def Test_for_loop_script_var() @@ -2471,20 +2498,23 @@ def Test_for_loop_unpack() enddef def Test_for_loop_with_try_continue() - var looped = 0 - var cleanup = 0 - for i in range(3) - looped += 1 - try - eval [][0] - catch - continue - finally - cleanup += 1 - endtry - endfor - assert_equal(3, looped) - assert_equal(3, cleanup) + var lines =<< trim END + var looped = 0 + var cleanup = 0 + for i in range(3) + looped += 1 + try + eval [][0] + catch + continue + finally + cleanup += 1 + endtry + endfor + assert_equal(3, looped) + assert_equal(3, cleanup) + END + CheckDefAndScriptSuccess(lines) enddef def Test_while_loop() @@ -3518,7 +3548,7 @@ func Test_no_redraw_when_restoring_cpo() vim9script set cpo+=M exe 'set rtp^=' .. getcwd() .. '/Xdir' - au CmdlineEnter : ++once timer_start(0, () => script#func()) + au CmdlineEnter : ++once timer_start(0, (_) => script#func()) setline(1, 'some text') END call writefile(lines, 'XTest_redraw_cpo') @@ -3644,7 +3674,7 @@ enddef def Test_catch_exception_in_callback() var lines =<< trim END vim9script - def Callback(...l: any) + def Callback(...l: list) try var x: string var y: string @@ -3669,10 +3699,10 @@ def Test_no_unknown_error_after_error() var lines =<< trim END vim9script var source: list - def Out_cb(...l: any) + def Out_cb(...l: list) eval [][0] enddef - def Exit_cb(...l: any) + def Exit_cb(...l: list) sleep 1m source += l enddef @@ -3681,7 +3711,7 @@ def Test_no_unknown_error_after_error() sleep 10m endwhile # wait for Exit_cb() to be called - sleep 100m + sleep 200m END writefile(lines, 'Xdef') assert_fails('so Xdef', ['E684:', 'E1012:']) diff --git a/src/testdir/vim9.vim b/src/testdir/vim9.vim index f17f141dba..9efa65552e 100644 --- a/src/testdir/vim9.vim +++ b/src/testdir/vim9.vim @@ -133,3 +133,85 @@ def CheckDefExecAndScriptFailure2( CheckDefExecFailure(lines, errorDef, lnum) CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1) enddef + + +" Check that "lines" inside a legacy function has no error. +func CheckLegacySuccess(lines) + let cwd = getcwd() + let fname = 'XlegacySuccess' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc'], fname) + try + exe 'so ' .. fname + call Func() + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Check that "lines" inside a legacy function results in the expected error +func CheckLegacyFailure(lines, error) + let cwd = getcwd() + let fname = 'XlegacyFails' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname) + try + call assert_fails('so ' .. fname, a:error) + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +def CheckLegacyAndVim9Success(lines: list) + var legacylines = lines->mapnew((_, v) => + v->substitute('\', 'let', 'g') + ->substitute('\', 'let', 'g') + ->substitute('#"', ' "', 'g')) + CheckLegacySuccess(legacylines) + + var vim9lines = lines->mapnew((_, v) => + v->substitute('\', 'var', 'g') + ->substitute('\, error: any) + var legacyError: string + var defError: string + var scriptError: string + + if type(error) == type('string') + legacyError = error + defError = error + scriptError = error + else + legacyError = error[0] + defError = error[1] + scriptError = error[2] + endif + + var legacylines = lines->mapnew((_, v) => + v->substitute('\', 'let', 'g') + ->substitute('\', 'let', 'g') + ->substitute('#"', ' "', 'g')) + CheckLegacyFailure(legacylines, legacyError) + + var vim9lines = lines->mapnew((_, v) => + v->substitute('\', 'var', 'g') + ->substitute('\= 0) - semsg(_(e_non_empty_string_required_for_argument_nr), idx + 1); - else - emsg(_(e_non_empty_string_required)); + semsg(_(e_non_empty_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; diff --git a/src/userfunc.c b/src/userfunc.c index af107b0db6..99d4e0334e 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -55,7 +55,7 @@ func_tbl_get(void) /* * Get one function argument. - * If "argtypes" is not NULL also get the type: "arg: type". + * If "argtypes" is not NULL also get the type: "arg: type" (:def function). * If "types_optional" is TRUE a missing type is OK, use "any". * If "evalarg" is not NULL use it to check for an already declared name. * Return a pointer to after the type. @@ -68,10 +68,12 @@ one_function_arg( garray_T *argtypes, int types_optional, evalarg_T *evalarg, + int is_vararg, int skip) { char_u *p = arg; char_u *arg_copy = NULL; + int is_underscore = FALSE; while (ASCII_ISALNUM(*p) || *p == '_') ++p; @@ -106,15 +108,16 @@ one_function_arg( *p = c; return arg; } - - // Check for duplicate argument name. - for (i = 0; i < newargs->ga_len; ++i) - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg_copy) == 0) - { - semsg(_("E853: Duplicate argument name: %s"), arg_copy); - vim_free(arg_copy); - return arg; - } + is_underscore = arg_copy[0] == '_' && arg_copy[1] == NUL; + if (argtypes == NULL || !is_underscore) + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; ++i) + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg_copy) == 0) + { + semsg(_("E853: Duplicate argument name: %s"), arg_copy); + vim_free(arg_copy); + return arg; + } ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg_copy; newargs->ga_len++; @@ -145,7 +148,7 @@ one_function_arg( if (!skip) type = vim_strnsave(type, p - type); } - else if (*skipwhite(p) != '=' && !types_optional) + else if (*skipwhite(p) != '=' && !types_optional && !is_underscore) { semsg(_(e_missing_argument_type_for_str), arg_copy == NULL ? arg : arg_copy); @@ -155,7 +158,8 @@ one_function_arg( { if (type == NULL && types_optional) // lambda arguments default to "any" type - type = vim_strsave((char_u *)"any"); + type = vim_strsave((char_u *) + (is_vararg ? "list" : "any")); ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type; } } @@ -250,7 +254,7 @@ get_function_args( arg = p; p = one_function_arg(p, newargs, argtypes, types_optional, - evalarg, skip); + evalarg, TRUE, skip); if (p == arg) break; if (*skipwhite(p) == '=') @@ -264,7 +268,7 @@ get_function_args( { arg = p; p = one_function_arg(p, newargs, argtypes, types_optional, - evalarg, skip); + evalarg, FALSE, skip); if (p == arg) break; @@ -360,12 +364,14 @@ err_ret: static int parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs) { + int len = 0; + ga_init2(&fp->uf_type_list, sizeof(type_T *), 10); if (argtypes->ga_len > 0) { // When "varargs" is set the last name/type goes into uf_va_name // and uf_va_type. - int len = argtypes->ga_len - (varargs ? 1 : 0); + len = argtypes->ga_len - (varargs ? 1 : 0); if (len > 0) fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len); @@ -388,25 +394,35 @@ parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs) fp->uf_arg_types[i] = type; } } - if (varargs) - { - char_u *p; - - // Move the last argument "...name: type" to uf_va_name and - // uf_va_type. - fp->uf_va_name = ((char_u **)fp->uf_args.ga_data) - [fp->uf_args.ga_len - 1]; - --fp->uf_args.ga_len; - p = ((char_u **)argtypes->ga_data)[len]; - if (p == NULL) - // todo: get type from default value - fp->uf_va_type = &t_any; - else - fp->uf_va_type = parse_type(&p, &fp->uf_type_list, TRUE); - if (fp->uf_va_type == NULL) - return FAIL; - } } + + if (varargs) + { + char_u *p; + + // Move the last argument "...name: type" to uf_va_name and + // uf_va_type. + fp->uf_va_name = ((char_u **)fp->uf_args.ga_data) + [fp->uf_args.ga_len - 1]; + --fp->uf_args.ga_len; + p = ((char_u **)argtypes->ga_data)[len]; + if (p == NULL) + // TODO: get type from default value + fp->uf_va_type = &t_list_any; + else + { + fp->uf_va_type = parse_type(&p, &fp->uf_type_list, TRUE); + if (fp->uf_va_type != NULL && fp->uf_va_type->tt_type != VAR_LIST) + { + semsg(_(e_variable_arguments_type_must_be_list_str), + ((char_u **)argtypes->ga_data)[len]); + return FAIL; + } + } + if (fp->uf_va_type == NULL) + return FAIL; + } + return OK; } @@ -957,8 +973,7 @@ lambda_function_body( garray_T *default_args, char_u *ret_type) { - int evaluate = evalarg != NULL - && (evalarg->eval_flags & EVAL_EVALUATE); + int evaluate = (evalarg->eval_flags & EVAL_EVALUATE); ufunc_T *ufunc = NULL; exarg_T eap; garray_T newlines; @@ -1164,6 +1179,9 @@ get_lambda_tv( // Recognize "{" as the start of a function body. if (equal_arrow && **arg == '{') { + if (evalarg == NULL) + // cannot happen? + goto theend; if (lambda_function_body(arg, rettv, evalarg, pnewargs, types_optional ? &argtypes : NULL, varargs, &default_args, ret_type) == FAIL) @@ -1236,7 +1254,8 @@ get_lambda_tv( ga_init(&fp->uf_def_args); if (types_optional) { - if (parse_argument_types(fp, &argtypes, FALSE) == FAIL) + if (parse_argument_types(fp, &argtypes, + in_vim9script() && varargs) == FAIL) goto errret; if (ret_type != NULL) { @@ -1263,8 +1282,9 @@ get_lambda_tv( #endif if (sandbox) flags |= FC_SANDBOX; - // can be called with more args than uf_args.ga_len - fp->uf_varargs = TRUE; + // In legacy script a lambda can be called with more args than + // uf_args.ga_len. In Vim9 script "...name" has to be used. + fp->uf_varargs = !in_vim9script() || varargs; fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; @@ -2168,6 +2188,8 @@ call_user_func( int islambda = FALSE; char_u numbuf[NUMBUFLEN]; char_u *name; + typval_T *tv_to_free[MAX_FUNC_ARGS]; + int tv_to_free_len = 0; #ifdef FEAT_PROFILE profinfo_T profile_info; #endif @@ -2313,6 +2335,7 @@ call_user_func( if (isdefault) { char_u *default_expr = NULL; + def_rettv.v_type = VAR_NUMBER; def_rettv.vval.v_number = -1; @@ -2354,6 +2377,10 @@ call_user_func( v->di_tv = isdefault ? def_rettv : argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (isdefault) + // Need to free this later, no matter where it's stored. + tv_to_free[tv_to_free_len++] = &v->di_tv; + if (addlocal) { // Named arguments should be accessed without the "a:" prefix in @@ -2543,6 +2570,8 @@ call_user_func( did_emsg |= save_did_emsg; funcdepth_decrement(); + for (i = 0; i < tv_to_free_len; ++i) + clear_tv(tv_to_free[i]); cleanup_function_call(fc); } @@ -3189,7 +3218,7 @@ list_func_head(ufunc_T *fp, int indent) msg_puts(", "); msg_puts("..."); msg_puts((char *)fp->uf_va_name); - if (fp->uf_va_type) + if (fp->uf_va_type != NULL) { char *tofree; @@ -4038,7 +4067,11 @@ define_function(exarg_T *eap, char_u *name_arg) } if (j == FAIL) { + linenr_T save_lnum = SOURCING_LNUM; + + SOURCING_LNUM = sourcing_lnum_top; semsg(_("E746: Function name does not match script file name: %s"), name); + SOURCING_LNUM = save_lnum; goto erret; } } diff --git a/src/version.c b/src/version.c index 786065be4e..926ab86684 100644 --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,64 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2767, +/**/ + 2766, +/**/ + 2765, +/**/ + 2764, +/**/ + 2763, +/**/ + 2762, +/**/ + 2761, +/**/ + 2760, +/**/ + 2759, +/**/ + 2758, +/**/ + 2757, +/**/ + 2756, +/**/ + 2755, +/**/ + 2754, +/**/ + 2753, +/**/ + 2752, +/**/ + 2751, +/**/ + 2750, +/**/ + 2749, +/**/ + 2748, +/**/ + 2747, +/**/ + 2746, +/**/ + 2745, +/**/ + 2744, +/**/ + 2743, +/**/ + 2742, +/**/ + 2741, +/**/ + 2740, +/**/ + 2739, /**/ 2738, /**/ diff --git a/src/vim.h b/src/vim.h index 9a5aafe35e..dbf3b0d28e 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2163,10 +2163,13 @@ typedef enum { } estack_arg_T; // Flags for assignment functions. -#define ASSIGN_FINAL 1 // ":final" -#define ASSIGN_CONST 2 // ":const" -#define ASSIGN_NO_DECL 4 // "name = expr" without ":let"/":const"/":final" -#define ASSIGN_DECL 8 // may declare variable if it does not exist +#define ASSIGN_FINAL 0x01 // ":final" +#define ASSIGN_CONST 0x02 // ":const" +#define ASSIGN_NO_DECL 0x04 // "name = expr" without ":let"/":const"/":final" +#define ASSIGN_DECL 0x08 // may declare variable if it does not exist +#define ASSIGN_UNPACK 0x10 // using [a, b] = list +#define ASSIGN_NO_MEMBER_TYPE 0x20 // use "any" for list and dict member type +#define ASSIGN_FOR_LOOP 0x40 // assigning to loop variable #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff diff --git a/src/vim9.h b/src/vim9.h index 0c8a949e87..4a10e12b1c 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -57,6 +57,8 @@ typedef enum { ISN_STORENR, // store number into local variable isn_arg.storenr.stnr_idx ISN_STOREINDEX, // store into list or dictionary, type isn_arg.vartype, // value/index/variable on stack + ISN_STORERANGE, // store into blob, + // value/index 1/index 2/variable on stack ISN_UNLET, // unlet variable isn_arg.unlet.ul_name ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name @@ -133,6 +135,8 @@ typedef enum { ISN_LISTAPPEND, // append to a list, like add() ISN_LISTINDEX, // [expr] list index ISN_LISTSLICE, // [expr:expr] list slice + ISN_BLOBINDEX, // [expr] blob index + ISN_BLOBSLICE, // [expr:expr] blob slice ISN_ANYINDEX, // [expr] runtime index ISN_ANYSLICE, // [expr:expr] runtime slice ISN_SLICE, // drop isn_arg.number items from start of list diff --git a/src/vim9compile.c b/src/vim9compile.c index 00a8f56469..6286114055 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -422,6 +422,10 @@ check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg) int c = p[len]; ufunc_T *ufunc = NULL; + // underscore argument is OK + if (len == 1 && *p == '_') + return OK; + if (script_var_exists(p, len, cctx) == OK) { if (is_arg) @@ -986,7 +990,7 @@ bool_on_stack(cctx_T *cctx) if (type == &t_bool) return OK; - if (type == &t_any || type == &t_number) + if (type == &t_any || type == &t_number || type == &t_number_bool) // Number 0 and 1 are OK to use as a bool. "any" could also be a bool. // This requires a runtime type check. return generate_COND2BOOL(cctx); @@ -1791,8 +1795,9 @@ func_needs_compiling(ufunc_T *ufunc, int profile UNUSED) { switch (ufunc->uf_def_status) { - case UF_NOT_COMPILED: break; - case UF_TO_BE_COMPILED: return TRUE; + case UF_TO_BE_COMPILED: + return TRUE; + case UF_COMPILED: { #ifdef FEAT_PROFILE @@ -1805,7 +1810,11 @@ func_needs_compiling(ufunc_T *ufunc, int profile UNUSED) break; #endif } - case UF_COMPILING: break; + + case UF_NOT_COMPILED: + case UF_COMPILE_ERROR: + case UF_COMPILING: + break; } return FALSE; } @@ -1834,7 +1843,8 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) return FAIL; } - if (ufunc->uf_def_status != UF_NOT_COMPILED) + if (ufunc->uf_def_status != UF_NOT_COMPILED + && ufunc->uf_def_status != UF_COMPILE_ERROR) { int i; @@ -1856,7 +1866,8 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) continue; expected = ufunc->uf_arg_types[i]; } - else if (ufunc->uf_va_type == NULL || ufunc->uf_va_type == &t_any) + else if (ufunc->uf_va_type == NULL + || ufunc->uf_va_type == &t_list_any) // possibly a lambda or "...: any" expected = &t_any; else @@ -1968,7 +1979,7 @@ generate_PCALL( for (i = 0; i < argcount; ++i) { - int offset = -argcount + i - 1; + int offset = -argcount + i - (at_top ? 0 : 1); type_T *actual = ((type_T **)stack->ga_data)[ stack->ga_len + offset]; type_T *expected; @@ -2714,8 +2725,18 @@ compile_member(int is_slice, cctx_T *cctx) } else if (vtype == VAR_BLOB) { - emsg("Sorry, blob index and slice not implemented yet"); - return FAIL; + if (is_slice) + { + *typep = &t_blob; + if (generate_instr_drop(cctx, ISN_BLOBSLICE, 2) == FAIL) + return FAIL; + } + else + { + *typep = &t_number; + if (generate_instr_drop(cctx, ISN_BLOBINDEX, 1) == FAIL) + return FAIL; + } } else if (vtype == VAR_LIST || *typep == &t_any) { @@ -4077,7 +4098,7 @@ compile_subscript( // list index: list[123] // dict member: dict[key] // string index: text[123] - // TODO: blob index + // blob index: blob[123] // TODO: more arguments // TODO: recognize list or dict at runtime if (generate_ppconst(cctx, ppconst) == FAIL) @@ -4409,6 +4430,12 @@ compile_expr7( // "name" or "name()" p = to_name_end(*arg, TRUE); + if (p - *arg == (size_t)1 && **arg == '_') + { + emsg(_(e_cannot_use_underscore_here)); + return FAIL; + } + if (*p == '(') { r = compile_call(arg, p - *arg, cctx, ppconst, 0); @@ -6037,38 +6064,48 @@ compile_lhs( compile_assign_index( char_u *var_start, lhs_T *lhs, - int is_assign, int *range, cctx_T *cctx) { size_t varlen = lhs->lhs_varlen; char_u *p; int r = OK; + int need_white_before = TRUE; + int empty_second; p = var_start + varlen; if (*p == '[') { p = skipwhite(p + 1); - r = compile_expr0(&p, cctx); + if (*p == ':') + { + // empty first index, push zero + r = generate_PUSHNR(cctx, 0); + need_white_before = FALSE; + } + else + r = compile_expr0(&p, cctx); if (r == OK && *skipwhite(p) == ':') { // unlet var[idx : idx] - if (is_assign) - { - semsg(_(e_cannot_use_range_with_assignment_str), p); - return FAIL; - } + // blob[idx : idx] = value *range = TRUE; p = skipwhite(p); - if (!IS_WHITE_OR_NUL(p[-1]) || !IS_WHITE_OR_NUL(p[1])) + empty_second = *skipwhite(p + 1) == ']'; + if ((need_white_before && !IS_WHITE_OR_NUL(p[-1])) + || (!empty_second && !IS_WHITE_OR_NUL(p[1]))) { semsg(_(e_white_space_required_before_and_after_str_at_str), ":", p); return FAIL; } p = skipwhite(p + 1); - r = compile_expr0(&p, cctx); + if (*p == ']') + // empty second index, push "none" + r = generate_PUSHSPEC(cctx, VVAL_NONE); + else + r = compile_expr0(&p, cctx); } if (r == OK && *skipwhite(p) != ']') @@ -6148,8 +6185,14 @@ compile_assign_unlet( garray_T *stack = &cctx->ctx_type_stack; int range = FALSE; - if (compile_assign_index(var_start, lhs, is_assign, &range, cctx) == FAIL) + if (compile_assign_index(var_start, lhs, &range, cctx) == FAIL) return FAIL; + if (is_assign && range && lhs->lhs_type != &t_blob + && lhs->lhs_type != &t_any) + { + semsg(_(e_cannot_use_range_with_assignment_str), var_start); + return FAIL; + } if (lhs->lhs_type == &t_any) { @@ -6186,15 +6229,24 @@ compile_assign_unlet( if (compile_load_lhs(lhs, var_start, rhs_type, cctx) == FAIL) return FAIL; - if (dest_type == VAR_LIST || dest_type == VAR_DICT || dest_type == VAR_ANY) + if (dest_type == VAR_LIST || dest_type == VAR_DICT + || dest_type == VAR_BLOB || dest_type == VAR_ANY) { if (is_assign) { - isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3); + if (range) + { + if (generate_instr_drop(cctx, ISN_STORERANGE, 4) == NULL) + return FAIL; + } + else + { + isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3); - if (isn == NULL) - return FAIL; - isn->isn_arg.vartype = dest_type; + if (isn == NULL) + return FAIL; + isn->isn_arg.vartype = dest_type; + } } else if (range) { @@ -6352,6 +6404,17 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) { int instr_count = -1; + if (var_start[0] == '_' && !eval_isnamec(var_start[1])) + { + // Ignore underscore in "[a, _, b] = list". + if (var_count > 0) + { + var_start = skipwhite(var_start + 2); + continue; + } + emsg(_(e_cannot_use_underscore_here)); + goto theend; + } vim_free(lhs.lhs_name); /* @@ -6405,8 +6468,14 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) // Get member from list or dict. First compile the // index value. if (compile_assign_index(var_start, &lhs, - TRUE, &range, cctx) == FAIL) + &range, cctx) == FAIL) goto theend; + if (range) + { + semsg(_(e_cannot_use_range_with_assignment_operator_str), + var_start); + return FAIL; + } // Get the member. if (compile_member(FALSE, cctx) == FAIL) @@ -7521,7 +7590,7 @@ compile_for(char_u *arg_start, cctx_T *cctx) // Reserve a variable to store "var". // TODO: check for type - var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any); + var_lvar = reserve_local(cctx, arg, varlen, TRUE, &t_any); if (var_lvar == NULL) // out of memory or used as an argument goto failed; @@ -8417,11 +8486,13 @@ compile_def_function( cctx_T *outer_cctx) { char_u *line = NULL; + char_u *line_to_free = NULL; char_u *p; char *errormsg = NULL; // error message cctx_T cctx; garray_T *instr; int did_emsg_before = did_emsg; + int did_emsg_silent_before = did_emsg_silent; int ret = FAIL; sctx_T save_current_sctx = current_sctx; int save_estack_compiling = estack_compiling; @@ -8577,6 +8648,14 @@ compile_def_function( #endif break; } + // Make a copy, splitting off nextcmd and removing trailing spaces + // may change it. + if (line != NULL) + { + line = vim_strsave(line); + vim_free(line_to_free); + line_to_free = line; + } } CLEAR_FIELD(ea); @@ -8960,6 +9039,9 @@ nextline: generate_instr(&cctx, ISN_RETURN_ZERO); } + // When compiled with ":silent!" and there was an error don't consider the + // function compiled. + if (emsg_silent == 0 || did_emsg_silent == did_emsg_silent_before) { dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; @@ -8987,7 +9069,7 @@ nextline: ret = OK; erret: - if (ret == FAIL) + if (ufunc->uf_def_status == UF_COMPILING) { int idx; dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) @@ -9006,14 +9088,11 @@ erret: --def_functions.ga_len; ufunc->uf_dfunc_idx = 0; } - ufunc->uf_def_status = UF_NOT_COMPILED; + ufunc->uf_def_status = UF_COMPILE_ERROR; while (cctx.ctx_scope != NULL) drop_scope(&cctx); - // Don't execute this function body. - ga_clear_strings(&ufunc->uf_lines); - if (errormsg != NULL) emsg(errormsg); else if (did_emsg == did_emsg_before) @@ -9025,6 +9104,7 @@ erret: if (do_estack_push) estack_pop(); + vim_free(line_to_free); free_imported(&cctx); free_locals(&cctx); ga_clear(&cctx.ctx_type_stack); @@ -9069,7 +9149,7 @@ set_function_type(ufunc_T *ufunc) if (varargs) { ufunc->uf_func_type->tt_args[argcount] = - ufunc->uf_va_type == NULL ? &t_any : ufunc->uf_va_type; + ufunc->uf_va_type == NULL ? &t_list_any : ufunc->uf_va_type; ufunc->uf_func_type->tt_flags = TTFLAG_VARARGS; } } @@ -9212,6 +9292,8 @@ delete_instr(isn_T *isn) case ISN_ANYSLICE: case ISN_BCALL: case ISN_BLOBAPPEND: + case ISN_BLOBINDEX: + case ISN_BLOBSLICE: case ISN_CATCH: case ISN_CHECKLEN: case ISN_CHECKNR: @@ -9274,6 +9356,7 @@ delete_instr(isn_T *isn) case ISN_SLICE: case ISN_STORE: case ISN_STOREINDEX: + case ISN_STORERANGE: case ISN_STORENR: case ISN_STOREOUTER: case ISN_STOREREG: diff --git a/src/vim9execute.c b/src/vim9execute.c index d6c4764882..b7db0e2944 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1297,6 +1297,7 @@ call_def_function( #define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx) if (ufunc->uf_def_status == UF_NOT_COMPILED + || ufunc->uf_def_status == UF_COMPILE_ERROR || (func_needs_compiling(ufunc, PROFILING(ufunc)) && compile_def_function(ufunc, FALSE, PROFILING(ufunc), NULL) == FAIL)) @@ -1335,6 +1336,16 @@ call_def_function( ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10); ga_init2(&ectx.ec_funcrefs, sizeof(partial_T *), 10); + idx = argc - ufunc->uf_args.ga_len; + if (idx > 0 && ufunc->uf_va_name == NULL) + { + if (idx == 1) + emsg(_(e_one_argument_too_many)); + else + semsg(_(e_nr_arguments_too_many), idx); + goto failed_early; + } + // Put arguments on the stack, but no more than what the function expects. // A lambda can be called with more arguments than it uses. for (idx = 0; idx < argc @@ -1374,7 +1385,7 @@ call_def_function( // Check the type of the list items. tv = STACK_TV_BOT(-1); if (ufunc->uf_va_type != NULL - && ufunc->uf_va_type != &t_any + && ufunc->uf_va_type != &t_list_any && ufunc->uf_va_type->tt_member != &t_any && tv->vval.v_list != NULL) { @@ -2208,6 +2219,10 @@ call_def_function( clear_tv(tv); } } + else if (status == OK && dest_type == VAR_BLOB) + { + // TODO + } else { status = FAIL; @@ -2225,6 +2240,70 @@ call_def_function( } break; + // store value in blob range + case ISN_STORERANGE: + { + typval_T *tv_idx1 = STACK_TV_BOT(-3); + typval_T *tv_idx2 = STACK_TV_BOT(-2); + typval_T *tv_dest = STACK_TV_BOT(-1); + int status = OK; + + // Stack contains: + // -4 value to be stored + // -3 first index or "none" + // -2 second index or "none" + // -1 destination blob + tv = STACK_TV_BOT(-4); + if (tv_dest->v_type != VAR_BLOB) + { + status = FAIL; + emsg(_(e_blob_required)); + } + else + { + varnumber_T n1; + varnumber_T n2; + int error = FALSE; + + n1 = tv_get_number_chk(tv_idx1, &error); + if (error) + status = FAIL; + else + { + if (tv_idx2->v_type == VAR_SPECIAL + && tv_idx2->vval.v_number == VVAL_NONE) + n2 = blob_len(tv_dest->vval.v_blob) - 1; + else + n2 = tv_get_number_chk(tv_idx2, &error); + if (error) + status = FAIL; + else + { + long bloblen = blob_len(tv_dest->vval.v_blob); + + if (check_blob_index(bloblen, + n1, FALSE) == FAIL + || check_blob_range(bloblen, + n1, n2, FALSE) == FAIL) + status = FAIL; + else + status = blob_set_range( + tv_dest->vval.v_blob, n1, n2, tv); + } + } + } + + clear_tv(tv_idx1); + clear_tv(tv_idx2); + clear_tv(tv_dest); + ectx.ec_stack.ga_len -= 4; + clear_tv(tv); + + if (status == FAIL) + goto on_error; + } + break; + // load or store variable or argument from outer scope case ISN_LOADOUTER: case ISN_STOREOUTER: @@ -3404,16 +3483,21 @@ call_def_function( case ISN_LISTINDEX: case ISN_LISTSLICE: + case ISN_BLOBINDEX: + case ISN_BLOBSLICE: { - int is_slice = iptr->isn_type == ISN_LISTSLICE; - list_T *list; + int is_slice = iptr->isn_type == ISN_LISTSLICE + || iptr->isn_type == ISN_BLOBSLICE; + int is_blob = iptr->isn_type == ISN_BLOBINDEX + || iptr->isn_type == ISN_BLOBSLICE; varnumber_T n1, n2; + typval_T *val_tv; // list index: list is at stack-2, index at stack-1 // list slice: list is at stack-3, indexes at stack-2 and // stack-1 - tv = is_slice ? STACK_TV_BOT(-3) : STACK_TV_BOT(-2); - list = tv->vval.v_list; + // Same for blob. + val_tv = is_slice ? STACK_TV_BOT(-3) : STACK_TV_BOT(-2); tv = STACK_TV_BOT(-1); n1 = n2 = tv->vval.v_number; @@ -3429,9 +3513,18 @@ call_def_function( ectx.ec_stack.ga_len -= is_slice ? 2 : 1; tv = STACK_TV_BOT(-1); SOURCING_LNUM = iptr->isn_lnum; - if (list_slice_or_index(list, is_slice, n1, n2, FALSE, - tv, TRUE) == FAIL) - goto on_error; + if (is_blob) + { + if (blob_slice_or_index(val_tv->vval.v_blob, is_slice, + n1, n2, FALSE, tv) == FAIL) + goto on_error; + } + else + { + if (list_slice_or_index(val_tv->vval.v_list, is_slice, + n1, n2, FALSE, tv, TRUE) == FAIL) + goto on_error; + } } break; @@ -4337,6 +4430,10 @@ ex_disassemble(exarg_T *eap) } break; + case ISN_STORERANGE: + smsg("%4d STORERANGE", current); + break; + // constants case ISN_PUSHNR: smsg("%4d PUSHNR %lld", current, @@ -4677,6 +4774,8 @@ ex_disassemble(exarg_T *eap) case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_STRINDEX: smsg("%4d STRINDEX", current); break; case ISN_STRSLICE: smsg("%4d STRSLICE", current); break; + case ISN_BLOBINDEX: smsg("%4d BLOBINDEX", current); break; + case ISN_BLOBSLICE: smsg("%4d BLOBSLICE", current); break; case ISN_LISTAPPEND: smsg("%4d LISTAPPEND", current); break; case ISN_BLOBAPPEND: smsg("%4d BLOBAPPEND", current); break; case ISN_LISTINDEX: smsg("%4d LISTINDEX", current); break; diff --git a/src/vim9script.c b/src/vim9script.c index 2f2f2dce67..796b7d13cb 100644 --- a/src/vim9script.c +++ b/src/vim9script.c @@ -713,7 +713,8 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg) * When "create" is TRUE this is a new variable, otherwise find and update an * existing variable. * "flags" can have ASSIGN_FINAL or ASSIGN_CONST. - * When "*type" is NULL use "tv" for the type and update "*type". + * When "*type" is NULL use "tv" for the type and update "*type". If + * "do_member" is TRUE also use the member type, otherwise use "any". */ void update_vim9_script_var( @@ -721,7 +722,8 @@ update_vim9_script_var( dictitem_T *di, int flags, typval_T *tv, - type_T **type) + type_T **type, + int do_member) { scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); hashitem_T *hi; @@ -774,7 +776,8 @@ update_vim9_script_var( if (sv != NULL) { if (*type == NULL) - *type = typval2type(tv, get_copyID(), &si->sn_type_list); + *type = typval2type(tv, get_copyID(), &si->sn_type_list, + do_member); sv->sv_type = *type; } diff --git a/src/vim9type.c b/src/vim9type.c index 2485efb41e..8b0f2f15e2 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -252,9 +252,10 @@ func_type_add_arg_types( /* * Get a type_T for a typval_T. * "type_gap" is used to temporarily create types in. + * When "do_member" is TRUE also get the member type, otherwise use "any". */ static type_T * -typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) +typval2type_int(typval_T *tv, int copyID, garray_T *type_gap, int do_member) { type_T *type; type_T *member_type = &t_any; @@ -274,6 +275,8 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) if (l == NULL || l->lv_first == NULL) return &t_list_empty; + if (!do_member) + return &t_list_any; if (l->lv_first == &range_list_item) return &t_list_number; if (l->lv_copyID == copyID) @@ -282,9 +285,9 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) l->lv_copyID = copyID; // Use the common type of all members. - member_type = typval2type(&l->lv_first->li_tv, copyID, type_gap); + member_type = typval2type(&l->lv_first->li_tv, copyID, type_gap, TRUE); for (li = l->lv_first->li_next; li != NULL; li = li->li_next) - common_type(typval2type(&li->li_tv, copyID, type_gap), + common_type(typval2type(&li->li_tv, copyID, type_gap, TRUE), member_type, &member_type, type_gap); return get_list_type(member_type, type_gap); } @@ -297,6 +300,8 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) if (d == NULL || d->dv_hashtab.ht_used == 0) return &t_dict_empty; + if (!do_member) + return &t_dict_any; if (d->dv_copyID == copyID) // avoid recursion return &t_dict_any; @@ -305,9 +310,9 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap) // Use the common type of all values. dict_iterate_start(tv, &iter); dict_iterate_next(&iter, &value); - member_type = typval2type(value, copyID, type_gap); + member_type = typval2type(value, copyID, type_gap, TRUE); while (dict_iterate_next(&iter, &value) != NULL) - common_type(typval2type(value, copyID, type_gap), + common_type(typval2type(value, copyID, type_gap, TRUE), member_type, &member_type, type_gap); return get_dict_type(member_type, type_gap); } @@ -378,11 +383,12 @@ need_convert_to_bool(type_T *type, typval_T *tv) /* * Get a type_T for a typval_T. * "type_list" is used to temporarily create types in. + * When "do_member" is TRUE also get the member type, otherwise use "any". */ type_T * -typval2type(typval_T *tv, int copyID, garray_T *type_gap) +typval2type(typval_T *tv, int copyID, garray_T *type_gap, int do_member) { - type_T *type = typval2type_int(tv, copyID, type_gap); + type_T *type = typval2type_int(tv, copyID, type_gap, do_member); if (type != NULL && type != &t_bool && (tv->v_type == VAR_NUMBER @@ -404,7 +410,7 @@ typval2type_vimvar(typval_T *tv, garray_T *type_gap) return &t_list_string; if (tv->v_type == VAR_DICT) // e.g. for v:completed_item return &t_dict_any; - return typval2type(tv, get_copyID(), type_gap); + return typval2type(tv, get_copyID(), type_gap, TRUE); } int @@ -429,7 +435,7 @@ check_typval_type(type_T *expected, typval_T *actual_tv, where_T where) int res = FAIL; ga_init2(&type_list, sizeof(type_T *), 10); - actual_type = typval2type(actual_tv, get_copyID(), &type_list); + actual_type = typval2type(actual_tv, get_copyID(), &type_list, TRUE); if (actual_type != NULL) res = check_type(expected, actual_type, TRUE, where); clear_type_list(&type_list); @@ -1210,7 +1216,7 @@ f_typename(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_STRING; ga_init2(&type_list, sizeof(type_T *), 10); - type = typval2type(argvars, get_copyID(), &type_list); + type = typval2type(argvars, get_copyID(), &type_list, TRUE); name = type_name(type, &tofree); if (tofree != NULL) rettv->vval.v_string = (char_u *)tofree;