# HG changeset patch # User Bram Moolenaar # Date 1613837703 -3600 # Node ID 9fcd71d0db89a275e5b1f693b4e8d4ef53747875 # Parent 05c1a8485fb9170a132112a8d9581ee4ed87cb02 patch 8.2.2533: Vim9: cannot use a range with :unlet Commit: https://github.com/vim/vim/commit/5b5ae29bd3d7b832b6f15320430f7f191e0abd1f Author: Bram Moolenaar Date: Sat Feb 20 17:04:02 2021 +0100 patch 8.2.2533: Vim9: cannot use a range with :unlet Problem: Vim9: cannot use a range with :unlet. Solution: Implement ISN_UNLETRANGE. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -365,3 +365,7 @@ EXTERN char e_variable_nr_type_mismatch_ INIT(= N_("E1163: Variable %d: type mismatch, expected %s but got %s")); EXTERN char e_vim9cmd_must_be_followed_by_command[] INIT(= N_("E1164: vim9cmd must be followed by a command")); +EXTERN char e_cannot_use_range_with_assignment_str[] + INIT(= N_("E1165: Cannot use a range with an assignment: %s")); +EXTERN char e_cannot_use_range_with_dictionary[] + INIT(= N_("E1166: Cannot use a range with a dictionary")); diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1213,15 +1213,7 @@ get_lval( lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; - lp->ll_li = list_find(lp->ll_list, lp->ll_n1); - if (lp->ll_li == NULL) - { - if (lp->ll_n1 < 0) - { - lp->ll_n1 = 0; - lp->ll_li = list_find(lp->ll_list, lp->ll_n1); - } - } + lp->ll_li = list_find_index(lp->ll_list, &lp->ll_n1); if (lp->ll_li == NULL) { clear_tv(&var2); diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1656,27 +1656,9 @@ do_unlet_var( return FAIL; else if (lp->ll_range) { - listitem_T *li; - listitem_T *ll_li = lp->ll_li; - int ll_n1 = lp->ll_n1; - - while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) - { - li = ll_li->li_next; - if (value_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE)) - return FAIL; - ll_li = li; - ++ll_n1; - } - - // Delete a range of List items. - while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) - { - li = lp->ll_li->li_next; - listitem_remove(lp->ll_list, lp->ll_li); - lp->ll_li = li; - ++lp->ll_n1; - } + if (list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_name, lp->ll_n1, + !lp->ll_empty2, lp->ll_n2) == FAIL) + return FAIL; } else { @@ -1692,6 +1674,43 @@ do_unlet_var( } /* + * Unlet one item or a range of items from a list. + * Return OK or FAIL. + */ + int +list_unlet_range( + list_T *l, + listitem_T *li_first, + char_u *name, + long n1_arg, + int has_n2, + long n2) +{ + listitem_T *li = li_first; + int n1 = n1_arg; + + while (li != NULL && (!has_n2 || n2 >= n1)) + { + if (value_check_lock(li->li_tv.v_lock, name, FALSE)) + return FAIL; + li = li->li_next; + ++n1; + } + + // Delete a range of List items. + li = li_first; + n1 = n1_arg; + while (li != NULL && (!has_n2 || n2 >= n1)) + { + listitem_T *next = li->li_next; + + listitem_remove(l, li); + li = next; + ++n1; + } + return OK; +} +/* * "unlet" a variable. Return OK if it existed, FAIL if not. * When "forceit" is TRUE don't complain if the variable doesn't exist. */ diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -531,6 +531,26 @@ list_find_str(list_T *l, long idx) } /* + * Like list_find() but when a negative index is used that is not found use + * zero and set "idx" to zero. Used for first index of a range. + */ + listitem_T * +list_find_index(list_T *l, long *idx) +{ + listitem_T *li = list_find(l, *idx); + + if (li == NULL) + { + if (*idx < 0) + { + *idx = 0; + li = list_find(l, *idx); + } + } + return li; +} + +/* * Locate "item" list "l" and return its index. * Returns -1 when "item" is not in the list. */ diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -23,6 +23,7 @@ void list_hashtable_vars(hashtab_T *ht, void ex_unlet(exarg_T *eap); void ex_lockvar(exarg_T *eap); void ex_unletlock(exarg_T *eap, char_u *argstart, int deep, int glv_flags, int (*callback)(lval_T *, char_u *, exarg_T *, int, void *), void *cookie); +int list_unlet_range(list_T *l, listitem_T *li_first, char_u *name, long n1_arg, int has_n2, long n2); int do_unlet(char_u *name, int forceit); void item_lock(typval_T *tv, int deep, int lock, int check_refcount); void del_menutrans_vars(void); diff --git a/src/proto/list.pro b/src/proto/list.pro --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -20,6 +20,7 @@ int list_equal(list_T *l1, list_T *l2, i listitem_T *list_find(list_T *l, long n); long list_find_nr(list_T *l, long idx, int *errorp); char_u *list_find_str(list_T *l, long idx); +listitem_T *list_find_index(list_T *l, long *idx); long list_idx_of_item(list_T *l, listitem_T *item); void list_append(list_T *l, listitem_T *item); int list_append_tv(list_T *l, typval_T *tv); diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -9,6 +9,7 @@ let g:existing = 'yes' let g:inc_counter = 1 let $SOME_ENV_VAR = 'some' let g:alist = [7] +let g:adict = #{a: 1} let g:astring = 'text' def Test_assignment_bool() @@ -1414,6 +1415,51 @@ def Test_unlet() unlet ll[-1] assert_equal([1, 3], ll) + ll = [1, 2, 3, 4] + unlet ll[0 : 1] + assert_equal([3, 4], ll) + + ll = [1, 2, 3, 4] + unlet ll[2 : 8] + assert_equal([1, 2], ll) + + ll = [1, 2, 3, 4] + unlet ll[-2 : -1] + assert_equal([1, 2], ll) + + CheckDefFailure([ + 'var ll = [1, 2]', + 'll[1 : 2] = 7', + ], 'E1165:', 2) + CheckDefFailure([ + 'var dd = {a: 1}', + 'unlet dd["a" : "a"]', + ], 'E1166:', 2) + CheckDefExecFailure([ + 'unlet g:adict[0 : 1]', + ], 'E1148:', 1) + CheckDefFailure([ + 'var ll = [1, 2]', + 'unlet ll[0:1]', + ], 'E1004:', 2) + CheckDefFailure([ + 'var ll = [1, 2]', + 'unlet ll[0 :1]', + ], 'E1004:', 2) + CheckDefFailure([ + 'var ll = [1, 2]', + 'unlet ll[0: 1]', + ], 'E1004:', 2) + + CheckDefFailure([ + 'var ll = [1, 2]', + 'unlet ll["x" : 1]', + ], 'E1012:', 2) + CheckDefFailure([ + 'var ll = [1, 2]', + 'unlet ll[0 : "x"]', + ], 'E1012:', 2) + # list of dict unlet var dl = [{a: 1, b: 2}, {c: 3}] unlet dl[0]['b'] diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2533, +/**/ 2532, /**/ 2531, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -61,6 +61,7 @@ typedef enum { ISN_UNLET, // unlet variable isn_arg.unlet.ul_name ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name ISN_UNLETINDEX, // unlet item of list or dict + ISN_UNLETRANGE, // unlet items of list ISN_LOCKCONST, // lock constant value diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -5865,6 +5865,7 @@ compile_assign_unlet( vartype_T dest_type; size_t varlen = lhs->lhs_varlen; garray_T *stack = &cctx->ctx_type_stack; + int range = FALSE; // Compile the "idx" in "var[idx]" or "key" in "var.key". p = var_start + varlen; @@ -5872,6 +5873,27 @@ compile_assign_unlet( { p = skipwhite(p + 1); 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; + } + range = TRUE; + p = skipwhite(p); + if (!IS_WHITE_OR_NUL(p[-1]) || !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 (r == OK && *skipwhite(p) != ']') { // this should not happen @@ -5897,17 +5919,29 @@ compile_assign_unlet( else { dest_type = lhs->lhs_type->tt_type; + if (dest_type == VAR_DICT && range) + { + emsg(e_cannot_use_range_with_dictionary); + return FAIL; + } if (dest_type == VAR_DICT && may_generate_2STRING(-1, cctx) == FAIL) return FAIL; - if (dest_type == VAR_LIST - && need_type(((type_T **)stack->ga_data)[stack->ga_len - 1], + if (dest_type == VAR_LIST) + { + if (range + && need_type(((type_T **)stack->ga_data)[stack->ga_len - 2], &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; + return FAIL; + if (need_type(((type_T **)stack->ga_data)[stack->ga_len - 1], + &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL) + return FAIL; + } } // Load the dict or list. On the stack we then have: // - value (for assignment, not for :unlet) // - index + // - for [a : b] second index // - variable if (lhs->lhs_dest == dest_expr) { @@ -5946,6 +5980,11 @@ compile_assign_unlet( return FAIL; isn->isn_arg.vartype = dest_type; } + else if (range) + { + if (generate_instr_drop(cctx, ISN_UNLETRANGE, 3) == NULL) + return FAIL; + } else { if (generate_instr_drop(cctx, ISN_UNLETINDEX, 2) == NULL) @@ -8907,6 +8946,7 @@ delete_instr(isn_T *isn) case ISN_TRY: case ISN_TRYCONT: case ISN_UNLETINDEX: + case ISN_UNLETRANGE: case ISN_UNPACK: // nothing allocated break; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -879,6 +879,21 @@ error_if_locked(int lock, char *error) } /* + * Give an error if "tv" is not a number and return FAIL. + */ + static int +check_for_number(typval_T *tv) +{ + if (tv->v_type != VAR_NUMBER) + { + semsg(_(e_expected_str_but_got_str), + vartype_name(VAR_NUMBER), vartype_name(tv->v_type)); + return FAIL; + } + return OK; +} + +/* * Store "tv" in variable "name". * This is for s: and g: variables. */ @@ -2178,12 +2193,9 @@ call_def_function( else if (tv_dest->v_type == VAR_LIST) { // unlet a List item, index must be a number - if (tv_idx->v_type != VAR_NUMBER) + SOURCING_LNUM = iptr->isn_lnum; + if (check_for_number(tv_idx) == FAIL) { - SOURCING_LNUM = iptr->isn_lnum; - semsg(_(e_expected_str_but_got_str), - vartype_name(VAR_NUMBER), - vartype_name(tv_idx->v_type)); status = FAIL; } else @@ -2219,6 +2231,58 @@ call_def_function( } break; + // unlet range of items in list variable + case ISN_UNLETRANGE: + { + // Stack contains: + // -3 index1 + // -2 index2 + // -1 dict or list + 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; + + if (tv_dest->v_type == VAR_LIST) + { + // indexes must be a number + SOURCING_LNUM = iptr->isn_lnum; + if (check_for_number(tv_idx1) == FAIL + || check_for_number(tv_idx2) == FAIL) + { + status = FAIL; + } + else + { + list_T *l = tv_dest->vval.v_list; + long n1 = (long)tv_idx1->vval.v_number; + long n2 = (long)tv_idx2->vval.v_number; + listitem_T *li; + + li = list_find_index(l, &n1); + if (li == NULL + || list_unlet_range(l, li, NULL, n1, + TRUE, n2) == FAIL) + status = FAIL; + } + } + else + { + status = FAIL; + SOURCING_LNUM = iptr->isn_lnum; + semsg(_(e_cannot_index_str), + vartype_name(tv_dest->v_type)); + } + + clear_tv(tv_idx1); + clear_tv(tv_idx2); + clear_tv(tv_dest); + ectx.ec_stack.ga_len -= 3; + if (status == FAIL) + goto on_error; + } + break; + // push constant case ISN_PUSHNR: case ISN_PUSHBOOL: @@ -4151,6 +4215,9 @@ ex_disassemble(exarg_T *eap) case ISN_UNLETINDEX: smsg("%4d UNLETINDEX", current); break; + case ISN_UNLETRANGE: + smsg("%4d UNLETRANGE", current); + break; case ISN_LOCKCONST: smsg("%4d LOCKCONST", current); break;