summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-02-20 17:04:02 +0100
committerBram Moolenaar <Bram@vim.org>2021-02-20 17:04:02 +0100
commit5b5ae29bd3d7b832b6f15320430f7f191e0abd1f (patch)
tree94858648f12e7261a37e82308c15dafb5a789cf9
parentada1d870b4a818151cfba1c18962af2369b88df9 (diff)
downloadvim-git-5b5ae29bd3d7b832b6f15320430f7f191e0abd1f.tar.gz
patch 8.2.2533: Vim9: cannot use a range with :unletv8.2.2533
Problem: Vim9: cannot use a range with :unlet. Solution: Implement ISN_UNLETRANGE.
-rw-r--r--src/errors.h4
-rw-r--r--src/eval.c10
-rw-r--r--src/evalvars.c61
-rw-r--r--src/list.c20
-rw-r--r--src/proto/evalvars.pro1
-rw-r--r--src/proto/list.pro1
-rw-r--r--src/testdir/test_vim9_assign.vim46
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h1
-rw-r--r--src/vim9compile.c46
-rw-r--r--src/vim9execute.c77
11 files changed, 231 insertions, 38 deletions
diff --git a/src/errors.h b/src/errors.h
index 05f90b14b..775c5324b 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -365,3 +365,7 @@ EXTERN char e_variable_nr_type_mismatch_expected_str_but_got_str[]
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
index 444c0faf3..bf01ae0fd 100644
--- 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
index ce21fb255..d5c993d8c 100644
--- 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
index 484ce74c9..2af3085cc 100644
--- 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
index 144dc7cd8..cf4db0bf1 100644
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -23,6 +23,7 @@ void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first);
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
index 4ccc89780..192981d86 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -20,6 +20,7 @@ int list_equal(list_T *l1, list_T *l2, int ic, int recursive);
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
index 392a091a1..bcddc4f16 100644
--- 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
index a107650d5..f83f03879 100644
--- 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
index 1cf4b3c65..ca8e64d17 100644
--- 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
index b12d887d9..648f4e031 100644
--- 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
index fc195337b..6734f4f7d 100644
--- 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;