]> granicus.if.org Git - vim/commitdiff
patch 8.2.3332: Vim9: cannot assign to range in list v8.2.3332
authorBram Moolenaar <Bram@vim.org>
Wed, 11 Aug 2021 19:49:23 +0000 (21:49 +0200)
committerBram Moolenaar <Bram@vim.org>
Wed, 11 Aug 2021 19:49:23 +0000 (21:49 +0200)
Problem:    Vim9: cannot assign to range in list.
Solution:   Implement overwriting a list range.

src/eval.c
src/list.c
src/proto/eval.pro
src/proto/list.pro
src/testdir/test_listdict.vim
src/testdir/test_vim9_assign.vim
src/version.c
src/vim9compile.c
src/vim9execute.c

index a72519dc412ef30efa04ba634f752df7afb43146..edef8785d5f04b0771ceb33bf4c53b58a8d47fda 100644 (file)
@@ -45,7 +45,6 @@ typedef struct
     int                fi_byte_idx;    // byte index in fi_string
 } forinfo_T;
 
-static int tv_op(typval_T *tv1, typval_T *tv2, char_u  *op);
 static int eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
 static int eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
 static int eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
@@ -827,7 +826,6 @@ get_lval(
     typval_T   var1;
     typval_T   var2;
     int                empty1 = FALSE;
-    listitem_T *ni;
     char_u     *key = NULL;
     int                len;
     hashtab_T  *ht = NULL;
@@ -1210,23 +1208,11 @@ get_lval(
 
            lp->ll_dict = NULL;
            lp->ll_list = lp->ll_tv->vval.v_list;
-           lp->ll_li = list_find_index(lp->ll_list, &lp->ll_n1);
+           lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
            if (lp->ll_li == NULL)
            {
-               // Vim9: Allow for adding an item at the end.
-               if (in_vim9script() && lp->ll_n1 == lp->ll_list->lv_len
-                                                 && lp->ll_list->lv_lock == 0)
-               {
-                   list_append_number(lp->ll_list, 0);
-                   lp->ll_li = list_find_index(lp->ll_list, &lp->ll_n1);
-               }
-               if (lp->ll_li == NULL)
-               {
-                   clear_tv(&var2);
-                   if (!quiet)
-                       semsg(_(e_listidx), lp->ll_n1);
-                   return NULL;
-               }
+               clear_tv(&var2);
+               return NULL;
            }
 
            if (lp->ll_valtype != NULL)
@@ -1244,27 +1230,10 @@ get_lval(
                lp->ll_n2 = (long)tv_get_number(&var2);
                                                    // is number or string
                clear_tv(&var2);
-               if (lp->ll_n2 < 0)
-               {
-                   ni = list_find(lp->ll_list, lp->ll_n2);
-                   if (ni == NULL)
-                   {
-                       if (!quiet)
-                           semsg(_(e_listidx), lp->ll_n2);
-                       return NULL;
-                   }
-                   lp->ll_n2 = list_idx_of_item(lp->ll_list, ni);
-               }
-
-               // Check that lp->ll_n2 isn't before lp->ll_n1.
-               if (lp->ll_n1 < 0)
-                   lp->ll_n1 = list_idx_of_item(lp->ll_list, lp->ll_li);
-               if (lp->ll_n2 < lp->ll_n1)
-               {
-                   if (!quiet)
-                       semsg(_(e_listidx), lp->ll_n2);
+               if (check_range_index_two(lp->ll_list,
+                                           &lp->ll_n1, lp->ll_li,
+                                           &lp->ll_n2, quiet) == FAIL)
                    return NULL;
-               }
            }
 
            lp->ll_tv = &lp->ll_li->li_tv;
@@ -1303,7 +1272,6 @@ set_var_lval(
     int                var_idx)    // index for "let [a, b] = list"
 {
     int                cc;
-    listitem_T *ri;
     dictitem_T *di;
 
     if (lp->ll_tv == NULL)
@@ -1383,9 +1351,6 @@ set_var_lval(
        ;
     else if (lp->ll_range)
     {
-       listitem_T *ll_li = lp->ll_li;
-       int         ll_n1 = lp->ll_n1;
-
        if ((flags & (ASSIGN_CONST | ASSIGN_FINAL))
                                             && (flags & ASSIGN_FOR_LOOP) == 0)
        {
@@ -1393,53 +1358,8 @@ set_var_lval(
            return;
        }
 
-       /*
-        * Check whether any of the list items is locked
-        */
-       for (ri = rettv->vval.v_list->lv_first; ri != NULL && ll_li != NULL; )
-       {
-           if (value_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE))
-               return;
-           ri = ri->li_next;
-           if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1))
-               break;
-           ll_li = ll_li->li_next;
-           ++ll_n1;
-       }
-
-       /*
-        * Assign the List values to the list items.
-        */
-       for (ri = rettv->vval.v_list->lv_first; ri != NULL; )
-       {
-           if (op != NULL && *op != '=')
-               tv_op(&lp->ll_li->li_tv, &ri->li_tv, op);
-           else
-           {
-               clear_tv(&lp->ll_li->li_tv);
-               copy_tv(&ri->li_tv, &lp->ll_li->li_tv);
-           }
-           ri = ri->li_next;
-           if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1))
-               break;
-           if (lp->ll_li->li_next == NULL)
-           {
-               // Need to add an empty item.
-               if (list_append_number(lp->ll_list, 0) == FAIL)
-               {
-                   ri = NULL;
-                   break;
-               }
-           }
-           lp->ll_li = lp->ll_li->li_next;
-           ++lp->ll_n1;
-       }
-       if (ri != NULL)
-           emsg(_(e_list_value_has_more_items_than_targets));
-       else if (lp->ll_empty2
-               ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL)
-               : lp->ll_n1 != lp->ll_n2)
-           emsg(_(e_list_value_does_not_have_enough_items));
+       (void)list_assign_range(lp->ll_list, rettv->vval.v_list,
+                        lp->ll_n1, lp->ll_n2, lp->ll_empty2, op, lp->ll_name);
     }
     else
     {
@@ -1507,7 +1427,7 @@ set_var_lval(
  * and "tv1 .= tv2"
  * Returns OK or FAIL.
  */
-    static int
+    int
 tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
 {
     varnumber_T        n;
index afbc7d395824fad62d6a59ad81561c1295ea4775..b00ef492469ccd3175eb958894482fc2e5d3ab7c 100644 (file)
@@ -761,6 +761,158 @@ list_insert(list_T *l, listitem_T *ni, listitem_T *item)
     }
 }
 
+/*
+ * Get the list item in "l" with index "n1".  "n1" is adjusted if needed.
+ * In Vim9, it is at the end of the list, add an item.
+ * Return NULL if there is no such item.
+ */
+    listitem_T *
+check_range_index_one(list_T *l, long *n1, int quiet)
+{
+    listitem_T *li = list_find_index(l, n1);
+
+    if (li == NULL)
+    {
+       // Vim9: Allow for adding an item at the end.
+       if (in_vim9script() && *n1 == l->lv_len && l->lv_lock == 0)
+       {
+           list_append_number(l, 0);
+           li = list_find_index(l, n1);
+       }
+       if (li == NULL)
+       {
+           if (!quiet)
+               semsg(_(e_listidx), *n1);
+           return NULL;
+       }
+    }
+    return li;
+}
+
+/*
+ * Check that "n2" can be used as the second index in a range of list "l".
+ * If "n1" or "n2" is negative it is changed to the positive index.
+ * "li1" is the item for item "n1".
+ * Return OK or FAIL.
+ */
+    int
+check_range_index_two(
+       list_T      *l,
+       long        *n1,
+       listitem_T  *li1,
+       long        *n2,
+       int         quiet)
+{
+    if (*n2 < 0)
+    {
+       listitem_T      *ni = list_find(l, *n2);
+
+       if (ni == NULL)
+       {
+           if (!quiet)
+               semsg(_(e_listidx), *n2);
+           return FAIL;
+       }
+       *n2 = list_idx_of_item(l, ni);
+    }
+
+    // Check that n2 isn't before n1.
+    if (*n1 < 0)
+       *n1 = list_idx_of_item(l, li1);
+    if (*n2 < *n1)
+    {
+       if (!quiet)
+           semsg(_(e_listidx), *n2);
+       return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Assign values from list "src" into a range of "dest".
+ * "idx1_arg" is the index of the first item in "dest" to be replaced.
+ * "idx2" is the index of last item to be replaced, but when "empty_idx2" is
+ * TRUE then replace all items after "idx1".
+ * "op" is the operator, normally "=" but can be "+=" and the like.
+ * "varname" is used for error messages.
+ * Returns OK or FAIL.
+ */
+    int
+list_assign_range(
+       list_T      *dest,
+       list_T      *src,
+       long        idx1_arg,
+       long        idx2,
+       int         empty_idx2,
+       char_u      *op,
+       char_u      *varname)
+{
+    listitem_T *src_li;
+    listitem_T *dest_li;
+    long       idx1 = idx1_arg;
+    listitem_T *first_li = list_find_index(dest, &idx1);
+    long       idx;
+
+    /*
+     * Check whether any of the list items is locked before making any changes.
+     */
+    idx = idx1;
+    dest_li = first_li;
+    for (src_li = src->lv_first; src_li != NULL && dest_li != NULL; )
+    {
+       if (value_check_lock(dest_li->li_tv.v_lock, varname, FALSE))
+           return FAIL;
+       src_li = src_li->li_next;
+       if (src_li == NULL || (!empty_idx2 && idx2 == idx))
+           break;
+       dest_li = dest_li->li_next;
+       ++idx;
+    }
+
+    /*
+     * Assign the List values to the list items.
+     */
+    idx = idx1;
+    dest_li = first_li;
+    for (src_li = src->lv_first; src_li != NULL; )
+    {
+       if (op != NULL && *op != '=')
+           tv_op(&dest_li->li_tv, &src_li->li_tv, op);
+       else
+       {
+           clear_tv(&dest_li->li_tv);
+           copy_tv(&src_li->li_tv, &dest_li->li_tv);
+       }
+       src_li = src_li->li_next;
+       if (src_li == NULL || (!empty_idx2 && idx2 == idx))
+           break;
+       if (dest_li->li_next == NULL)
+       {
+           // Need to add an empty item.
+           if (list_append_number(dest, 0) == FAIL)
+           {
+               src_li = NULL;
+               break;
+           }
+       }
+       dest_li = dest_li->li_next;
+       ++idx;
+    }
+    if (src_li != NULL)
+    {
+       emsg(_(e_list_value_has_more_items_than_targets));
+       return FAIL;
+    }
+    if (empty_idx2
+           ? (dest_li != NULL && dest_li->li_next != NULL)
+           : idx != idx2)
+    {
+       emsg(_(e_list_value_does_not_have_enough_items));
+       return FAIL;
+    }
+    return OK;
+}
+
 /*
  * Flatten "list" to depth "maxdepth".
  * It does nothing if "maxdepth" is 0.
index 2204f26af87c7d87b0b1680379bb31700d3fc12b..ed19c929cb908a92f12b73709ba7ed508d956647 100644 (file)
@@ -26,6 +26,7 @@ int eval_foldexpr(char_u *arg, int *cp);
 char_u *get_lval(char_u *name, typval_T *rettv, lval_T *lp, int unlet, int skip, int flags, int fne_flags);
 void clear_lval(lval_T *lp);
 void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, int flags, char_u *op, int var_idx);
+int tv_op(typval_T *tv1, typval_T *tv2, char_u *op);
 void *eval_for_line(char_u *arg, int *errp, exarg_T *eap, evalarg_T *evalarg);
 void skip_for_lines(void *fi_void, evalarg_T *evalarg);
 int next_for_item(void *fi_void, char_u *arg);
index c3c3227f59c2b99c9e197980068ab714e2a53628..97e6c4730df30c067eaff626ba8588fe14a961b4 100644 (file)
@@ -30,6 +30,9 @@ int list_append_string(list_T *l, char_u *str, int len);
 int list_append_number(list_T *l, varnumber_T n);
 int list_insert_tv(list_T *l, typval_T *tv, listitem_T *item);
 void list_insert(list_T *l, listitem_T *ni, listitem_T *item);
+listitem_T *check_range_index_one(list_T *l, long *n1, int quiet);
+int check_range_index_two(list_T *l, long *n1, listitem_T *li1, long *n2, int quiet);
+int list_assign_range(list_T *dest, list_T *src, long idx1_arg, long idx2, int empty_idx2, char_u *op, char_u *varname);
 void f_flatten(typval_T *argvars, typval_T *rettv);
 void f_flattennew(typval_T *argvars, typval_T *rettv);
 int list_extend(list_T *l1, list_T *l2, listitem_T *bef);
index 90f5679c99496a4850957a4359223338295f783e..1e7c8125c2881242677e97bc70492a405fadc6c3 100644 (file)
@@ -164,11 +164,14 @@ endfunc
 
 " test for range assign
 func Test_list_range_assign()
-  let l = [0]
-  let l[:] = [1, 2]
-  call assert_equal([1, 2], l)
-  let l[-4:-1] = [5, 6]
-  call assert_equal([5, 6], l)
+  let lines =<< trim END
+      VAR l = [0]
+      LET l[:] = [1, 2]
+      call assert_equal([1, 2], l)
+      LET l[-4 : -1] = [5, 6]
+      call assert_equal([5, 6], l)
+  END
+  call CheckLegacyAndVim9Success(lines)
 endfunc
 
 " Test removing items in list
index a96e97cadadf327bb06d3f267781b71884275ecf..dffec4f6899747b2bee670f65243d7c90ec72c02 100644 (file)
@@ -1821,7 +1821,7 @@ def Test_unlet()
   CheckDefFailure([
     'var ll = [1, 2]',
     'll[1 : 2] = 7',
-    ], 'E1165:', 2)
+    ], 'E1012: Type mismatch; expected list<number> but got number', 2)
   CheckDefFailure([
     'var dd = {a: 1}',
     'unlet dd["a" : "a"]',
index 213086728a7958b7b8adc6ea2fb161970deba935..9579c27890bf9d67f720c2f4aa95f9f9bdc0e223 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3332,
 /**/
     3331,
 /**/
index c2becbe5dec4331a53f13bd332cb85379a4ec8b5..eb26190520b8f2c0697d0bfd6dfc4f29560aa783 100644 (file)
@@ -6484,6 +6484,29 @@ compile_assign_lhs(
     return OK;
 }
 
+/*
+ * Return TRUE if "lhs" has a range index: "[expr : expr]".
+ */
+    static int
+has_list_index(char_u *idx_start, cctx_T *cctx)
+{
+    char_u  *p = idx_start;
+    int            save_skip;
+
+    if (*p != '[')
+       return FALSE;
+
+    p = skipwhite(p + 1);
+    if (*p == ':')
+       return TRUE;
+
+    save_skip = cctx->ctx_skip;
+    cctx->ctx_skip = SKIP_YES;
+    (void)compile_expr0(&p, cctx);
+    cctx->ctx_skip = save_skip;
+    return *skipwhite(p) == ':';
+}
+
 /*
  * For an assignment with an index, compile the "idx" in "var[idx]" or "key" in
  * "var.key".
@@ -6652,8 +6675,10 @@ compile_assign_unlet(
 
     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)
+    if (is_assign && range
+           && lhs->lhs_type->tt_type != VAR_LIST
+           && lhs->lhs_type != &t_blob
+           && lhs->lhs_type != &t_any)
     {
        semsg(_(e_cannot_use_range_with_assignment_str), var_start);
        return FAIL;
@@ -7029,7 +7054,11 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
                        SOURCING_LNUM = start_lnum;
                        where.wt_index = var_count > 0 ? var_idx + 1 : 0;
                        where.wt_variable = var_count > 0;
-                       if (lhs.lhs_has_index)
+                       // If assigning to a list or dict member, use the
+                       // member type.  Not for "list[:] =".
+                       if (lhs.lhs_has_index
+                               && !has_list_index(var_start + lhs.lhs_varlen,
+                                                                        cctx))
                            use_type = lhs.lhs_member_type;
                        if (need_type_where(rhs_type, use_type, -1, where,
                                    cctx, FALSE, is_const) == FAIL)
index e6ba700787376bdfc7b8cb49c8fbea2079044600..84de4fee5a3e891275d79e06c819cf4d254d3848 100644 (file)
@@ -2503,14 +2503,53 @@ exec_instructions(ectx_T *ectx)
                    // -4 value to be stored
                    // -3 first index or "none"
                    // -2 second index or "none"
-                   // -1 destination blob
+                   // -1 destination list or blob
                    tv = STACK_TV_BOT(-4);
-                   if (tv_dest->v_type != VAR_BLOB)
+                   if (tv_dest->v_type == VAR_LIST)
                    {
-                       status = FAIL;
-                       emsg(_(e_blob_required));
+                       long    n1;
+                       long    n2;
+                       int     error = FALSE;
+
+                       SOURCING_LNUM = iptr->isn_lnum;
+                       n1 = (long)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 = list_len(tv_dest->vval.v_list) - 1;
+                           else
+                               n2 = (long)tv_get_number_chk(tv_idx2, &error);
+                           if (error)
+                               status = FAIL;
+                           else
+                           {
+                               listitem_T *li1 = check_range_index_one(
+                                       tv_dest->vval.v_list, &n1, FALSE);
+
+                               if (li1 == NULL)
+                                   status = FAIL;
+                               else
+                               {
+                                   status = check_range_index_two(
+                                           tv_dest->vval.v_list,
+                                           &n1, li1, &n2, FALSE);
+                                   if (status != FAIL)
+                                       status = list_assign_range(
+                                               tv_dest->vval.v_list,
+                                               tv->vval.v_list,
+                                               n1,
+                                               n2,
+                                               tv_idx2->v_type == VAR_SPECIAL,
+                                               (char_u *)"=",
+                                               (char_u *)"[unknown]");
+                               }
+                           }
+                       }
                    }
-                   else
+                   else if (tv_dest->v_type == VAR_BLOB)
                    {
                        varnumber_T n1;
                        varnumber_T n2;
@@ -2530,7 +2569,7 @@ exec_instructions(ectx_T *ectx)
                                status = FAIL;
                            else
                            {
-                               long    bloblen = blob_len(tv_dest->vval.v_blob);
+                               long  bloblen = blob_len(tv_dest->vval.v_blob);
 
                                if (check_blob_index(bloblen,
                                                             n1, FALSE) == FAIL
@@ -2543,6 +2582,11 @@ exec_instructions(ectx_T *ectx)
                            }
                        }
                    }
+                   else
+                   {
+                       status = FAIL;
+                       emsg(_(e_blob_required));
+                   }
 
                    clear_tv(tv_idx1);
                    clear_tv(tv_idx2);
@@ -5469,7 +5513,7 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
            case ISN_ANYINDEX: smsg("%s%4d ANYINDEX", pfx, current); break;
            case ISN_ANYSLICE: smsg("%s%4d ANYSLICE", pfx, current); break;
            case ISN_SLICE: smsg("%s%4d SLICE %lld",
-                                        pfx, current, iptr->isn_arg.number); break;
+                                   pfx, current, iptr->isn_arg.number); break;
            case ISN_GETITEM: smsg("%s%4d ITEM %lld%s", pfx, current,
                                         iptr->isn_arg.getitem.gi_index,
                                         iptr->isn_arg.getitem.gi_with_op ?
@@ -5490,7 +5534,8 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                                          type_name(ct->ct_type, &tofree),
                                          (int)ct->ct_off);
                      else
-                         smsg("%s%4d CHECKTYPE %s stack[%d] arg %d", pfx, current,
+                         smsg("%s%4d CHECKTYPE %s stack[%d] arg %d",
+                                         pfx, current,
                                          type_name(ct->ct_type, &tofree),
                                          (int)ct->ct_off,
                                          (int)ct->ct_arg_idx);