diff options
-rw-r--r-- | src/nvim/eval.c | 465 | ||||
-rw-r--r-- | src/nvim/eval/executor.c | 324 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 13 | ||||
-rw-r--r-- | test/functional/legacy/edit_spec.lua | 6 | ||||
-rw-r--r-- | test/old/testdir/test_autoload.vim | 1 | ||||
-rw-r--r-- | test/old/testdir/test_blob.vim | 20 | ||||
-rw-r--r-- | test/old/testdir/test_edit.vim | 5 | ||||
-rw-r--r-- | test/old/testdir/test_fold.vim | 57 | ||||
-rw-r--r-- | test/old/testdir/test_functions.vim | 2 | ||||
-rw-r--r-- | test/old/testdir/test_listdict.vim | 21 | ||||
-rw-r--r-- | test/old/testdir/test_method.vim | 7 | ||||
-rw-r--r-- | test/old/testdir/test_partial.vim | 14 | ||||
-rw-r--r-- | test/old/testdir/test_spellrare.vim | 18 | ||||
-rw-r--r-- | test/old/testdir/test_substitute.vim | 14 | ||||
-rw-r--r-- | test/old/testdir/test_vimscript.vim | 8 |
15 files changed, 684 insertions, 291 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 247948ffe9..02694b5f3a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -122,6 +122,8 @@ static const char e_empty_function_name[] = N_("E1192: Empty function name"); static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[] = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob"); +static const char e_cannot_use_partial_here[] + = N_("E1265: Cannot use a partial here"); static char * const namespace_char = "abglstvw"; @@ -152,6 +154,12 @@ typedef struct { int fi_byte_idx; // byte index in fi_string } forinfo_T; +typedef enum { + GLV_FAIL, + GLV_OK, + GLV_STOP, +} glv_status_T; + // values for vv_flags: #define VV_COMPAT 1 // compatible, also used without "v:" #define VV_RO 2 // read-only @@ -1381,22 +1389,207 @@ Object eval_foldtext(win_T *wp) return retval; } +/// Get an Dict lval variable that can be assigned a value to: "name", +/// "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc. +/// "name" points to the start of the name. +/// If "rettv" is not NULL it points to the value to be assigned. +/// "unlet" is true for ":unlet": slightly different behavior when something is +/// wrong; must end in space or cmd separator. +/// +/// flags: +/// GLV_QUIET: do not give error messages +/// GLV_READ_ONLY: will not change the variable +/// GLV_NO_AUTOLOAD: do not use script autoloading +/// +/// The Dict is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on +/// failure. Returns GLV_STOP to stop processing the characters following +/// 'key_end'. +static glv_status_T get_lval_dict_item(char *name, lval_T *lp, char *key, int len, char **key_end, + typval_T *var1, int flags, bool unlet, typval_T *rettv) +{ + bool quiet = flags & GLV_QUIET; + char *p = *key_end; + + if (len == -1) { + // "[key]": get key from "var1" + key = (char *)tv_get_string(var1); // is number or string + } + lp->ll_list = NULL; + + // a NULL dict is equivalent with an empty dict + if (lp->ll_tv->vval.v_dict == NULL) { + lp->ll_tv->vval.v_dict = tv_dict_alloc(); + lp->ll_tv->vval.v_dict->dv_refcount++; + } + lp->ll_dict = lp->ll_tv->vval.v_dict; + + lp->ll_di = tv_dict_find(lp->ll_dict, key, len); + + // When assigning to a scope dictionary check that a function and + // variable name is valid (only variable name unless it is l: or + // g: dictionary). Disallow overwriting a builtin function. + if (rettv != NULL && lp->ll_dict->dv_scope != 0) { + char prevval; + if (len != -1) { + prevval = key[len]; + key[len] = NUL; + } else { + prevval = 0; // Avoid compiler warning. + } + bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE + && tv_is_func(*rettv) + && var_wrong_func_name(key, lp->ll_di == NULL)) + || !valid_varname(key)); + if (len != -1) { + key[len] = prevval; + } + if (wrong) { + return GLV_FAIL; + } + } + + if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) + && len == -1 && rettv == NULL) { + semsg(e_illvar, "v:['lua']"); + return GLV_FAIL; + } + + if (lp->ll_di == NULL) { + // Can't add "v:" or "a:" variable. + if (lp->ll_dict == &vimvardict + || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) { + semsg(_(e_illvar), name); + return GLV_FAIL; + } + + // Key does not exist in dict: may need to add it. + if (*p == '[' || *p == '.' || unlet) { + if (!quiet) { + semsg(_(e_dictkey), key); + } + return GLV_FAIL; + } + if (len == -1) { + lp->ll_newkey = xstrdup(key); + } else { + lp->ll_newkey = xmemdupz(key, (size_t)len); + } + *key_end = p; + return GLV_STOP; + // existing variable, need to check if it can be changed + } else if (!(flags & GLV_READ_ONLY) + && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name)) + || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) { + return GLV_FAIL; + } + + lp->ll_tv = &lp->ll_di->di_tv; + + return GLV_OK; +} + +/// Get an blob lval variable that can be assigned a value to: "name", +/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. +/// +/// 'var1' specifies the starting blob index and 'var2' specifies the ending +/// blob index. If the first index is not specified in a range, then 'empty1' +/// is true. If 'quiet' is true, then error messages are not displayed for +/// invalid indexes. +/// +/// The blob is returned in 'lp'. Returns OK on success and FAIL on failure. +static int get_lval_blob(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, bool quiet) +{ + const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob); + + // Get the number and item for the only or first index of the List. + if (empty1) { + lp->ll_n1 = 0; + } else { + // Is number or string. + lp->ll_n1 = (int)tv_get_number(var1); + } + + if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) { + return FAIL; + } + if (lp->ll_range && !lp->ll_empty2) { + lp->ll_n2 = (int)tv_get_number(var2); + if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) { + return FAIL; + } + } + + lp->ll_blob = lp->ll_tv->vval.v_blob; + lp->ll_tv = NULL; + + return OK; +} + +/// Get a List lval variable that can be assigned a value to: "name", +/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. +/// +/// 'var1' specifies the starting List index and 'var2' specifies the ending +/// List index. If the first index is not specified in a range, then 'empty1' +/// is true. If 'quiet' is true, then error messages are not displayed for +/// invalid indexes. +/// +/// The List is returned in 'lp'. Returns OK on success and FAIL on failure. +static int get_lval_list(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, int flags, + bool quiet) +{ + // Get the number and item for the only or first index of the List. + if (empty1) { + lp->ll_n1 = 0; + } else { + // Is number or string. + lp->ll_n1 = (int)tv_get_number(var1); + } + + lp->ll_dict = NULL; + lp->ll_list = lp->ll_tv->vval.v_list; + lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet); + if (lp->ll_li == NULL) { + return FAIL; + } + + // May need to find the item or absolute index for the second + // index of a range. + // When no index given: "lp->ll_empty2" is true. + // Otherwise "lp->ll_n2" is set to the second index. + if (lp->ll_range && !lp->ll_empty2) { + lp->ll_n2 = (int)tv_get_number(var2); // Is number or string. + if (tv_list_check_range_index_two(lp->ll_list, + &lp->ll_n1, lp->ll_li, + &lp->ll_n2, quiet) == FAIL) { + return FAIL; + } + } + + lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); + + return OK; +} + /// Get the lval of a list/dict/blob subitem starting at "p". Loop /// until no more [idx] or .key is following. /// +/// If "rettv" is not NULL it points to the value to be assigned. +/// "unlet" is true for ":unlet". +/// /// @param[in] flags @see GetLvalFlags. /// /// @return A pointer to the character after the subscript on success or NULL on /// failure. static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv, hashtab_T *ht, - dictitem_T *v, int unlet, int flags) + dictitem_T *v, bool unlet, int flags) { - int quiet = flags & GLV_QUIET; + bool quiet = flags & GLV_QUIET; typval_T var1; var1.v_type = VAR_UNKNOWN; typval_T var2; var2.v_type = VAR_UNKNOWN; bool empty1 = false; + int rc = FAIL; // Loop until no more [idx] or .key is following. while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { @@ -1426,7 +1619,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv if (!quiet) { emsg(_("E708: [:] must come last")); } - return NULL; + goto done; } int len = -1; @@ -1450,12 +1643,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv } else { empty1 = false; if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive! - return NULL; + goto done; } if (!tv_check_str(&var1)) { // Not a number or string. - tv_clear(&var1); - return NULL; + goto done; } p = skipwhite(p); } @@ -1466,8 +1658,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv if (!quiet) { emsg(_(e_cannot_slice_dictionary)); } - tv_clear(&var1); - return NULL; + goto done; } if (rettv != NULL && !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL) @@ -1475,8 +1666,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv if (!quiet) { emsg(_("E709: [:] requires a List or Blob value")); } - tv_clear(&var1); - return NULL; + goto done; } p = skipwhite(p + 1); if (*p == ']') { @@ -1485,14 +1675,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv lp->ll_empty2 = false; // Recursive! if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) { - tv_clear(&var1); - return NULL; + goto done; } if (!tv_check_str(&var2)) { // Not a number or string. - tv_clear(&var1); - tv_clear(&var2); - return NULL; + goto done; } } lp->ll_range = true; @@ -1504,9 +1691,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv if (!quiet) { emsg(_(e_missbrac)); } - tv_clear(&var1); - tv_clear(&var2); - return NULL; + goto done; } // Skip to past ']'. @@ -1514,142 +1699,37 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv } if (lp->ll_tv->v_type == VAR_DICT) { - if (len == -1) { - // "[key]": get key from "var1" - key = (char *)tv_get_string(&var1); // is number or string - } - lp->ll_list = NULL; - lp->ll_dict = lp->ll_tv->vval.v_dict; - lp->ll_di = tv_dict_find(lp->ll_dict, key, len); - - // When assigning to a scope dictionary check that a function and - // variable name is valid (only variable name unless it is l: or - // g: dictionary). Disallow overwriting a builtin function. - if (rettv != NULL && lp->ll_dict->dv_scope != 0) { - char prevval; - if (len != -1) { - prevval = key[len]; - key[len] = NUL; - } else { - prevval = 0; // Avoid compiler warning. - } - bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE - && tv_is_func(*rettv) - && var_wrong_func_name(key, lp->ll_di == NULL)) - || !valid_varname(key)); - if (len != -1) { - key[len] = prevval; - } - if (wrong) { - tv_clear(&var1); - return NULL; - } + glv_status_T glv_status = get_lval_dict_item(name, lp, key, len, &p, &var1, + flags, unlet, rettv); + if (glv_status == GLV_FAIL) { + goto done; } - - if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) - && len == -1 && rettv == NULL) { - tv_clear(&var1); - semsg(e_illvar, "v:['lua']"); - return NULL; - } - - if (lp->ll_di == NULL) { - // Can't add "v:" or "a:" variable. - if (lp->ll_dict == &vimvardict - || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) { - semsg(_(e_illvar), name); - tv_clear(&var1); - return NULL; - } - - // Key does not exist in dict: may need to add it. - if (*p == '[' || *p == '.' || unlet) { - if (!quiet) { - semsg(_(e_dictkey), key); - } - tv_clear(&var1); - return NULL; - } - if (len == -1) { - lp->ll_newkey = xstrdup(key); - } else { - lp->ll_newkey = xmemdupz(key, (size_t)len); - } - tv_clear(&var1); + if (glv_status == GLV_STOP) { break; - // existing variable, need to check if it can be changed - } else if (!(flags & GLV_READ_ONLY) - && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name)) - || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) { - tv_clear(&var1); - return NULL; } - - tv_clear(&var1); - lp->ll_tv = &lp->ll_di->di_tv; } else if (lp->ll_tv->v_type == VAR_BLOB) { - // Get the number and item for the only or first index of the List. - if (empty1) { - lp->ll_n1 = 0; - } else { - // Is number or string. - lp->ll_n1 = (int)tv_get_number(&var1); + if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL) { + goto done; } - tv_clear(&var1); - - const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob); - if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) { - tv_clear(&var2); - return NULL; - } - if (lp->ll_range && !lp->ll_empty2) { - lp->ll_n2 = (int)tv_get_number(&var2); - tv_clear(&var2); - if (tv_blob_check_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; break; } else { - // Get the number and item for the only or first index of the List. - if (empty1) { - lp->ll_n1 = 0; - } else { - // Is number or string. - lp->ll_n1 = (int)tv_get_number(&var1); + if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL) { + goto done; } - tv_clear(&var1); - - lp->ll_dict = NULL; - lp->ll_list = lp->ll_tv->vval.v_list; - lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet); - if (lp->ll_li == NULL) { - tv_clear(&var2); - return NULL; - } - - // May need to find the item or absolute index for the second - // index of a range. - // When no index given: "lp->ll_empty2" is true. - // Otherwise "lp->ll_n2" is set to the second index. - if (lp->ll_range && !lp->ll_empty2) { - lp->ll_n2 = (int)tv_get_number(&var2); // Is number or string. - tv_clear(&var2); - if (tv_list_check_range_index_two(lp->ll_list, - &lp->ll_n1, lp->ll_li, - &lp->ll_n2, quiet) == FAIL) { - return NULL; - } - } - - lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); } + + tv_clear(&var1); + tv_clear(&var2); + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; } + rc = OK; + +done: tv_clear(&var1); - return p; + tv_clear(&var2); + return rc == OK ? p : NULL; } /// Get an lvalue @@ -3481,20 +3561,22 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const int len; char *name = *arg; char *lua_funcname = NULL; + char *alias = NULL; if (strnequal(name, "v:lua.", 6)) { lua_funcname = name + 6; *arg = (char *)skip_luafunc_name(lua_funcname); *arg = skipwhite(*arg); // to detect trailing whitespace later len = (int)(*arg - lua_funcname); } else { - char *alias; len = get_name_len((const char **)arg, &alias, evaluate, true); if (alias != NULL) { name = alias; } } - int ret; + char *tofree = NULL; + int ret = OK; + if (len <= 0) { if (verbose) { if (lua_funcname == NULL) { @@ -3505,25 +3587,79 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const } ret = FAIL; } else { - if (**arg != '(') { - if (verbose) { - semsg(_(e_missingparen), name); - } - ret = FAIL; - } else if (ascii_iswhite((*arg)[-1])) { - if (verbose) { - emsg(_(e_nowhitespace)); + *arg = skipwhite(*arg); + + // If there is no "(" immediately following, but there is further on, + // it can be "dict.Func()", "list[nr]", etc. + // Does not handle anything where "(" is part of the expression. + char *paren; + if (**arg != '(' && lua_funcname == NULL && alias == NULL + && (paren = vim_strchr(*arg, '(')) != NULL) { + *arg = name; + *paren = NUL; + typval_T ref; + ref.v_type = VAR_UNKNOWN; + if (eval7(arg, &ref, evalarg, false) == FAIL) { + *arg = name + len; + ret = FAIL; + } else if (*skipwhite(*arg) != NUL) { + if (verbose) { + semsg(_(e_trailing_arg), *arg); + } + ret = FAIL; + } else if (ref.v_type == VAR_FUNC && ref.vval.v_string != NULL) { + name = ref.vval.v_string; + ref.vval.v_string = NULL; + tofree = name; + len = (int)strlen(name); + } else if (ref.v_type == VAR_PARTIAL && ref.vval.v_partial != NULL) { + if (ref.vval.v_partial->pt_argc > 0 || ref.vval.v_partial->pt_dict != NULL) { + if (verbose) { + emsg(_(e_cannot_use_partial_here)); + } + ret = FAIL; + } else { + name = xstrdup(partial_name(ref.vval.v_partial)); + tofree = name; + if (name == NULL) { + ret = FAIL; + name = *arg; + } else { + len = (int)strlen(name); + } + } + } else { + if (verbose) { + semsg(_(e_not_callable_type_str), name); + } + ret = FAIL; } - ret = FAIL; - } else if (lua_funcname != NULL) { - if (evaluate) { - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = vvlua_partial; - rettv->vval.v_partial->pt_refcount++; + tv_clear(&ref); + *paren = '('; + } + + if (ret == OK) { + if (**arg != '(') { + if (verbose) { + semsg(_(e_missingparen), name); + } + ret = FAIL; + } else if (ascii_iswhite((*arg)[-1])) { + if (verbose) { + emsg(_(e_nowhitespace)); + } + ret = FAIL; + } else if (lua_funcname != NULL) { + if (evaluate) { + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = vvlua_partial; + rettv->vval.v_partial->pt_refcount++; + } + ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname); + } else { + ret = eval_func(arg, evalarg, name, len, rettv, + evaluate ? EVAL_EVALUATE : 0, &base); } - ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname); - } else { - ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base); } } @@ -3532,6 +3668,11 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const if (evaluate) { tv_clear(&base); } + xfree(tofree); + + if (alias != NULL) { + xfree(alias); + } return ret; } @@ -3670,7 +3811,7 @@ static int check_can_index(typval_T *rettv, bool evaluate, bool verbose) /// slice() function void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (check_can_index(argvars, true, false) != OK) { + if (check_can_index(&argvars[0], true, false) != OK) { return; } diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 3255e78d09..5b92f217d1 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -21,6 +21,174 @@ char *e_list_index_out_of_range_nr = N_("E684: List index out of range: %" PRId64); +/// Handle "blob1 += blob2". +/// Returns OK or FAIL. +static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op != '+' || tv2->v_type != VAR_BLOB) { + return FAIL; + } + + // Blob += Blob + if (tv2->vval.v_blob == NULL) { + return OK; + } + + if (tv1->vval.v_blob == NULL) { + tv1->vval.v_blob = tv2->vval.v_blob; + tv1->vval.v_blob->bv_refcount++; + return OK; + } + + blob_T *const b1 = tv1->vval.v_blob; + blob_T *const b2 = tv2->vval.v_blob; + const int len = tv_blob_len(b2); + + for (int i = 0; i < len; i++) { + ga_append(&b1->bv_ga, tv_blob_get(b2, i)); + } + + return OK; +} + +/// Handle "list1 += list2". +/// Returns OK or FAIL. +static int tv_op_list(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op != '+' || tv2->v_type != VAR_LIST) { + return FAIL; + } + + // List += List + if (tv2->vval.v_list == NULL) { + return OK; + } + + if (tv1->vval.v_list == NULL) { + tv1->vval.v_list = tv2->vval.v_list; + tv_list_ref(tv1->vval.v_list); + } else { + tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); + } + + return OK; +} + +/// Handle number operations: +/// nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr +/// +/// Returns OK or FAIL. +static int tv_op_number(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n = tv_get_number(tv1); + if (tv2->v_type == VAR_FLOAT) { + float_T f = (float_T)n; + if (*op == '%') { + return FAIL; + } + switch (*op) { + case '+': + f += tv2->vval.v_float; break; + case '-': + f -= tv2->vval.v_float; break; + case '*': + f *= tv2->vval.v_float; break; + case '/': + f /= tv2->vval.v_float; break; + } + tv_clear(tv1); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f; + } else { + switch (*op) { + case '+': + n += tv_get_number(tv2); break; + case '-': + n -= tv_get_number(tv2); break; + case '*': + n *= tv_get_number(tv2); break; + case '/': + n = num_divide(n, tv_get_number(tv2)); break; + case '%': + n = num_modulus(n, tv_get_number(tv2)); break; + } + tv_clear(tv1); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n; + } + + return OK; +} + +/// Handle "str1 .= str2" +/// Returns OK or FAIL. +static int tv_op_string(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (tv2->v_type == VAR_FLOAT) { + return FAIL; + } + + // str .= str + const char *tvs = tv_get_string(tv1); + char numbuf[NUMBUFLEN]; + char *const s = concat_str(tvs, tv_get_string_buf(tv2, numbuf)); + tv_clear(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = s; + + return OK; +} + +/// Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" +/// and "tv1 .= tv2" +/// Returns OK or FAIL. +static int tv_op_nr_or_string(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (tv2->v_type == VAR_LIST) { + return FAIL; + } + + if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { + return tv_op_number(tv1, tv2, op); + } + + return tv_op_string(tv1, tv2, op); +} + +/// Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2". +/// Returns OK or FAIL. +static int tv_op_float(typval_T *tv1, const typval_T *tv2, const char *op) + FUNC_ATTR_NONNULL_ALL +{ + if (*op == '%' || *op == '.' + || (tv2->v_type != VAR_FLOAT + && tv2->v_type != VAR_NUMBER + && tv2->v_type != VAR_STRING)) { + return FAIL; + } + + const float_T f = (tv2->v_type == VAR_FLOAT + ? tv2->vval.v_float + : (float_T)tv_get_number(tv2)); + switch (*op) { + case '+': + tv1->vval.v_float += f; break; + case '-': + tv1->vval.v_float -= f; break; + case '*': + tv1->vval.v_float *= f; break; + case '/': + tv1->vval.v_float /= f; break; + } + + return OK; +} + /// Handle tv1 += tv2, -=, *=, /=, %=, .= /// /// @param[in,out] tv1 First operand, modified typval. @@ -29,125 +197,45 @@ char *e_list_index_out_of_range_nr /// /// @return OK or FAIL. int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *const op) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED + FUNC_ATTR_NONNULL_ALL { - // Can't do anything with a Funcref, a Dict or special value on the right. - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT - && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) { - switch (tv1->v_type) { - case VAR_DICT: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_BOOL: - case VAR_SPECIAL: - break; - case VAR_BLOB: - if (*op != '+' || tv2->v_type != VAR_BLOB) { - break; - } - // Blob += Blob - if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) { - blob_T *const b1 = tv1->vval.v_blob; - blob_T *const b2 = tv2->vval.v_blob; - for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b1->bv_ga, tv_blob_get(b2, i)); - } - } - return OK; - case VAR_LIST: - if (*op != '+' || tv2->v_type != VAR_LIST) { - break; - } - // List += List - if (tv1->vval.v_list != NULL && tv2->vval.v_list != NULL) { - tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); - } - return OK; - case VAR_NUMBER: - case VAR_STRING: - if (tv2->v_type == VAR_LIST) { - break; - } - if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { - // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr - varnumber_T n = tv_get_number(tv1); - if (tv2->v_type == VAR_FLOAT) { - float_T f = (float_T)n; - - if (*op == '%') { - break; - } - switch (*op) { - case '+': - f += tv2->vval.v_float; break; - case '-': - f -= tv2->vval.v_float; break; - case '*': - f *= tv2->vval.v_float; break; - case '/': - f /= tv2->vval.v_float; break; - } - tv_clear(tv1); - tv1->v_type = VAR_FLOAT; - tv1->vval.v_float = f; - } else { - switch (*op) { - case '+': - n += tv_get_number(tv2); break; - case '-': - n -= tv_get_number(tv2); break; - case '*': - n *= tv_get_number(tv2); break; - case '/': - n = num_divide(n, tv_get_number(tv2)); break; - case '%': - n = num_modulus(n, tv_get_number(tv2)); break; - } - tv_clear(tv1); - tv1->v_type = VAR_NUMBER; - tv1->vval.v_number = n; - } - } else { - // str .= str - if (tv2->v_type == VAR_FLOAT) { - break; - } - const char *tvs = tv_get_string(tv1); - char numbuf[NUMBUFLEN]; - char *const s = - concat_str(tvs, tv_get_string_buf(tv2, numbuf)); - tv_clear(tv1); - tv1->v_type = VAR_STRING; - tv1->vval.v_string = s; - } - return OK; - case VAR_FLOAT: { - if (*op == '%' || *op == '.' - || (tv2->v_type != VAR_FLOAT - && tv2->v_type != VAR_NUMBER - && tv2->v_type != VAR_STRING)) { - break; - } - const float_T f = (tv2->v_type == VAR_FLOAT - ? tv2->vval.v_float - : (float_T)tv_get_number(tv2)); - switch (*op) { - case '+': - tv1->vval.v_float += f; break; - case '-': - tv1->vval.v_float -= f; break; - case '*': - tv1->vval.v_float *= f; break; - case '/': - tv1->vval.v_float /= f; break; - } - return OK; - } - case VAR_UNKNOWN: - abort(); - } + // Can't do anything with a Funcref or Dict on the right. + // v:true and friends only work with "..=". + if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT + || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) && *op == '.')) { + semsg(_(e_letwrong), op); + return FAIL; + } + + int retval = FAIL; + + switch (tv1->v_type) { + case VAR_DICT: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_BOOL: + case VAR_SPECIAL: + break; + case VAR_BLOB: + retval = tv_op_blob(tv1, tv2, op); + break; + case VAR_LIST: + retval = tv_op_list(tv1, tv2, op); + break; + case VAR_NUMBER: + case VAR_STRING: + retval = tv_op_nr_or_string(tv1, tv2, op); + break; + case VAR_FLOAT: + retval = tv_op_float(tv1, tv2, op); + break; + case VAR_UNKNOWN: + abort(); + } + + if (retval != OK) { + semsg(_(e_letwrong), op); } - semsg(_(e_letwrong), op); - return FAIL; + return retval; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 22e7f383a5..bd977523c6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3880,6 +3880,7 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) } } + const int called_emsg_start = called_emsg; for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) { set_vim_var_nr(VV_KEY, idx); set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx)); @@ -3887,6 +3888,10 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) if (indexof_eval_expr(expr)) { return idx; } + + if (called_emsg != called_emsg_start) { + return -1; + } } return -1; @@ -3916,6 +3921,7 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) } } + const int called_emsg_start = called_emsg; for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { set_vim_var_nr(VV_KEY, idx); tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL)); @@ -3926,6 +3932,10 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) if (found) { return idx; } + + if (called_emsg != called_emsg_start) { + return -1; + } } return -1; @@ -3942,7 +3952,8 @@ static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + if ((argvars[1].v_type == VAR_STRING + && (argvars[1].vval.v_string == NULL || *argvars[1].vval.v_string == NUL)) || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) { return; } diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index f3d18a2541..2d98188f9b 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -44,6 +44,12 @@ describe('edit', function() {1:~ }|*4 =^ | ]]) + feed([['r'<CR><Esc>]]) + expect('r') + -- Test for inserting null and empty list + feed('a<C-R>=v:_null_list<CR><Esc>') + feed('a<C-R>=[]<CR><Esc>') + expect('r') end) -- oldtest: Test_edit_ctrl_r_failed() diff --git a/test/old/testdir/test_autoload.vim b/test/old/testdir/test_autoload.vim index e89fe3943b..156387a2d2 100644 --- a/test/old/testdir/test_autoload.vim +++ b/test/old/testdir/test_autoload.vim @@ -21,5 +21,4 @@ func Test_source_autoload() call assert_equal(1, g:loaded_sourced_vim) endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_blob.vim b/test/old/testdir/test_blob.vim index 3886be48bd..fbc080059e 100644 --- a/test/old/testdir/test_blob.vim +++ b/test/old/testdir/test_blob.vim @@ -75,6 +75,13 @@ func Test_blob_assign() VAR l = [0z12] VAR m = deepcopy(l) LET m[0] = 0z34 #" E742 or E741 should not occur. + + VAR blob1 = 0z10 + LET blob1 += v:_null_blob + call assert_equal(0z10, blob1) + LET blob1 = v:_null_blob + LET blob1 += 0z20 + call assert_equal(0z20, blob1) END call CheckLegacyAndVim9Success(lines) @@ -332,6 +339,17 @@ func Test_blob_for_loop() call assert_equal(5, i) END call CheckLegacyAndVim9Success(lines) + + " Test for skipping the loop var assignment in a for loop + let lines =<< trim END + VAR blob = 0z998877 + VAR c = 0 + for _ in blob + LET c += 1 + endfor + call assert_equal(3, c) + END + call CheckLegacyAndVim9Success(lines) endfunc func Test_blob_concatenate() @@ -851,6 +869,7 @@ func Test_indexof() call assert_equal(-1, indexof(b, v:_null_string)) " Nvim doesn't have null functions " call assert_equal(-1, indexof(b, test_null_function())) + call assert_equal(-1, indexof(b, "")) let b = 0z01020102 call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0})) @@ -862,6 +881,7 @@ func Test_indexof() " failure cases call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:') call assert_fails('let i = indexof(b, {})', 'E1256:') + call assert_fails('let i = indexof(b, " ")', 'E15:') endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index d43dcc40c1..037282bf1a 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -1973,6 +1973,11 @@ func Test_edit_insert_reg() let @r = 'sample' call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") call assert_equal('"', g:Line) + + " Test for inserting an null and an empty list + call feedkeys("a\<C-R>=test_null_list()\<CR>", "xt") + call feedkeys("a\<C-R>=[]\<CR>", "xt") + call assert_equal(['r'], getbufline('', 1, '$')) call test_override('ALL', 0) close! endfunc diff --git a/test/old/testdir/test_fold.vim b/test/old/testdir/test_fold.vim index a9842ae437..36f72f5e01 100644 --- a/test/old/testdir/test_fold.vim +++ b/test/old/testdir/test_fold.vim @@ -1469,6 +1469,63 @@ func Test_foldtext_scriptlocal_func() delfunc s:FoldText endfunc +" Test for setting 'foldtext' from the modeline and executing the expression +" in a sandbox +func Test_foldtext_in_modeline() + func ModelineFoldText() + call feedkeys('aFoo', 'xt') + return "folded text" + endfunc + let lines =<< trim END + func T() + let i = 1 + endfunc + " vim: foldenable foldtext=ModelineFoldText() + END + call writefile(lines, 'Xmodelinefoldtext', 'D') + + set modeline modelineexpr + split Xmodelinefoldtext + + call cursor(1, 1) + normal! zf3j + call assert_equal('folded text', foldtextresult(1)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldText +endfunc + +" Test for setting 'foldexpr' from the modeline and executing the expression +" in a sandbox +func Test_foldexpr_in_modeline() + func ModelineFoldExpr() + call feedkeys('aFoo', 'xt') + return strlen(matchstr(getline(v:lnum),'^\s*')) + endfunc + let lines =<< trim END + aaa + bbb + ccc + ccc + bbb + aaa + " vim: foldenable foldmethod=expr foldexpr=ModelineFoldExpr() + END + call writefile(lines, 'Xmodelinefoldexpr', 'D') + + set modeline modelineexpr + split Xmodelinefoldexpr + + call assert_equal(2, foldlevel(3)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldExpr +endfunc + " Make sure a fold containing a nested fold is split correctly when using " foldmethod=indent func Test_fold_split() diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 3faa720850..01e9ae3bf2 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -3731,6 +3731,8 @@ func Test_slice() call assert_equal('', 'ὰ̳β̳́γ̳̂δ̳̃ε̳̄ζ̳̅'->slice(1, -6)) END call CheckLegacyAndVim9Success(lines) + + call assert_equal(0, slice(v:true, 1)) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 5e4a3fd1f8..678734dafb 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -57,6 +57,9 @@ func Test_list_slice() assert_equal([1, 2], l[-3 : -1]) END call CheckDefAndScriptSuccess(lines) + + call assert_fails('let l[[]] = 1', 'E730: Using a List as a String') + call assert_fails('let l[1 : []] = [1]', 'E730: Using a List as a String') endfunc " List identity @@ -175,6 +178,19 @@ func Test_list_assign() END call CheckScriptFailure(['vim9script'] + lines, 'E688:') call CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 1') + + let lines =<< trim END + VAR l = [2] + LET l += v:_null_list + call assert_equal([2], l) + LET l = v:_null_list + LET l += [1] + call assert_equal([1], l) + END + call CheckLegacyAndVim9Success(lines) + + let d = {'abc': [1, 2, 3]} + call assert_fails('let d.abc[0:0z10] = [10, 20]', 'E976: Using a Blob as a String') endfunc " test for range assign @@ -440,6 +456,9 @@ func Test_dict_assign() n.key = 3 END call CheckDefFailure(lines, 'E1141:') + + let d = {'abc': {}} + call assert_fails("let d.abc[0z10] = 10", 'E976: Using a Blob as a String') endfunc " Function in script-local List or Dict @@ -1449,6 +1468,8 @@ func Test_indexof() call assert_equal(-1, indexof(l, v:_null_string)) " Nvim doesn't have null functions " call assert_equal(-1, indexof(l, test_null_function())) + call assert_equal(-1, indexof(l, "")) + call assert_fails('let i = indexof(l, " ")', 'E15:') " failure cases call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:') diff --git a/test/old/testdir/test_method.vim b/test/old/testdir/test_method.vim index 1b57bba282..ca1ca7d573 100644 --- a/test/old/testdir/test_method.vim +++ b/test/old/testdir/test_method.vim @@ -134,6 +134,13 @@ func Test_method_syntax() call assert_fails('eval [1, 2, 3]-> sort()', 'E15:') call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:') + + " Test for using a method name containing a curly brace name + let s = 'len' + call assert_equal(4, "xxxx"->str{s}()) + + " Test for using a method in an interpolated string + call assert_equal('4', $'{"xxxx"->strlen()}') endfunc func Test_method_lambda() diff --git a/test/old/testdir/test_partial.vim b/test/old/testdir/test_partial.vim index d049cc9e4b..b5933cdd6d 100644 --- a/test/old/testdir/test_partial.vim +++ b/test/old/testdir/test_partial.vim @@ -403,4 +403,18 @@ func Test_compare_partials() call assert_false(F1 is N1) endfunc +func Test_partial_method() + func Foo(x, y, z) + return x + y + z + endfunc + let d = {"Fn": function('Foo', [10, 20])} + call assert_fails('echo 30->d.Fn()', 'E1265: Cannot use a partial here') + delfunc Foo +endfunc + +func Test_non_callable_type_as_method() + let d = {"Fn": 10} + call assert_fails('echo 30->d.Fn()', 'E1085: Not a callable type: d.Fn') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_spellrare.vim b/test/old/testdir/test_spellrare.vim index bbb13c27c2..ceb35cbd17 100644 --- a/test/old/testdir/test_spellrare.vim +++ b/test/old/testdir/test_spellrare.vim @@ -11,15 +11,15 @@ func Test_spellrareword() " Create a small word list to test that spellbadword('...') " can return ['...', 'rare']. let lines =<< trim END - foo - foobar/? - foobara/? -END - call writefile(lines, 'Xwords', 'D') - - mkspell! Xwords.spl Xwords - set spelllang=Xwords.spl - call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) + foo + foobar/? + foobara/? + END + call writefile(lines, 'Xwords', 'D') + + mkspell! Xwords.spl Xwords + set spelllang=Xwords.spl + call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) new call setline(1, ['foo', '', 'foo bar foo bar foobara foo foo foo foobar', '', 'End']) diff --git a/test/old/testdir/test_substitute.vim b/test/old/testdir/test_substitute.vim index f69a3c525b..f0a25b2804 100644 --- a/test/old/testdir/test_substitute.vim +++ b/test/old/testdir/test_substitute.vim @@ -1507,4 +1507,18 @@ func Test_substitute_expr_recursive() exe bufnr .. "bw!" endfunc +" Test for changing 'cpo' in a substitute expression +func Test_substitute_expr_cpo() + func XSubExpr() + set cpo= + return 'x' + endfunc + + let save_cpo = &cpo + call assert_equal('xxx', substitute('abc', '.', '\=XSubExpr()', 'g')) + call assert_equal(save_cpo, &cpo) + + delfunc XSubExpr +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index 28868a07d6..7f29c7f651 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -7449,6 +7449,14 @@ func Test_for_over_string() let res ..= c .. '-' endfor call assert_equal('', res) + + " Test for using "_" as the loop variable + let i = 0 + let s = 'abc' + for _ in s + call assert_equal(s[i], _) + let i += 1 + endfor endfunc " Test for deeply nested :source command {{{1 |