To: vim_dev@googlegroups.com Subject: Patch 8.2.3332 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3332 Problem: Vim9: cannot assign to range in list. Solution: Implement overwriting a list range. Files: src/vim9compile.c, src/vim9execute.c, src/list.c, src/proto/list.pro, src/eval.c, src/proto/eval.pro, src/testdir/test_listdict.vim, src/testdir/test_vim9_assign.vim *** ../vim-8.2.3331/src/vim9compile.c 2021-08-10 22:51:59.369449616 +0200 --- src/vim9compile.c 2021-08-11 18:20:36.658036480 +0200 *************** *** 6485,6490 **** --- 6485,6513 ---- } /* + * 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,6659 **** 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; --- 6675,6684 ---- if (compile_assign_index(var_start, lhs, &range, cctx) == FAIL) return FAIL; ! 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,7035 **** 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) use_type = lhs.lhs_member_type; if (need_type_where(rhs_type, use_type, -1, where, cctx, FALSE, is_const) == FAIL) --- 7054,7064 ---- SOURCING_LNUM = start_lnum; where.wt_index = var_count > 0 ? var_idx + 1 : 0; where.wt_variable = var_count > 0; ! // 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) *** ../vim-8.2.3331/src/vim9execute.c 2021-08-07 15:50:20.179575314 +0200 --- src/vim9execute.c 2021-08-11 21:41:37.372657009 +0200 *************** *** 2503,2516 **** // -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; --- 2503,2555 ---- // -4 value to be stored // -3 first index or "none" // -2 second index or "none" ! // -1 destination list or blob tv = STACK_TV_BOT(-4); ! if (tv_dest->v_type == VAR_LIST) { ! 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 if (tv_dest->v_type == VAR_BLOB) { varnumber_T n1; varnumber_T n2; *************** *** 2530,2536 **** status = FAIL; else { ! long bloblen = blob_len(tv_dest->vval.v_blob); if (check_blob_index(bloblen, n1, FALSE) == FAIL --- 2569,2575 ---- status = FAIL; else { ! long bloblen = blob_len(tv_dest->vval.v_blob); if (check_blob_index(bloblen, n1, FALSE) == FAIL *************** *** 2543,2548 **** --- 2582,2592 ---- } } } + else + { + status = FAIL; + emsg(_(e_blob_required)); + } clear_tv(tv_idx1); clear_tv(tv_idx2); *************** *** 5469,5475 **** 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; case ISN_GETITEM: smsg("%s%4d ITEM %lld%s", pfx, current, iptr->isn_arg.getitem.gi_index, iptr->isn_arg.getitem.gi_with_op ? --- 5513,5519 ---- 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; 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,5496 **** type_name(ct->ct_type, &tofree), (int)ct->ct_off); else ! 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); --- 5534,5541 ---- type_name(ct->ct_type, &tofree), (int)ct->ct_off); else ! 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); *** ../vim-8.2.3331/src/list.c 2021-08-09 19:59:01.442811242 +0200 --- src/list.c 2021-08-11 21:41:14.040726023 +0200 *************** *** 762,767 **** --- 762,919 ---- } /* + * 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. * Returns FAIL when out of memory. *** ../vim-8.2.3331/src/proto/list.pro 2021-08-09 19:59:01.442811242 +0200 --- src/proto/list.pro 2021-08-11 21:41:18.316713385 +0200 *************** *** 30,35 **** --- 30,38 ---- 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); *** ../vim-8.2.3331/src/eval.c 2021-08-05 20:39:59.350053671 +0200 --- src/eval.c 2021-08-11 21:41:29.288680935 +0200 *************** *** 45,51 **** 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); --- 45,50 ---- *************** *** 827,833 **** typval_T var1; typval_T var2; int empty1 = FALSE; - listitem_T *ni; char_u *key = NULL; int len; hashtab_T *ht = NULL; --- 826,831 ---- *************** *** 1210,1232 **** 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); 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; ! } } if (lp->ll_valtype != NULL) --- 1208,1218 ---- lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; ! lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, quiet); if (lp->ll_li == NULL) { ! clear_tv(&var2); ! return NULL; } if (lp->ll_valtype != NULL) *************** *** 1244,1270 **** 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); return NULL; - } } lp->ll_tv = &lp->ll_li->li_tv; --- 1230,1239 ---- lp->ll_n2 = (long)tv_get_number(&var2); // is number or string clear_tv(&var2); ! 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,1309 **** int var_idx) // index for "let [a, b] = list" { int cc; - listitem_T *ri; dictitem_T *di; if (lp->ll_tv == NULL) --- 1272,1277 ---- *************** *** 1383,1391 **** ; 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) { --- 1351,1356 ---- *************** *** 1393,1445 **** 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)); } else { --- 1358,1365 ---- return; } ! (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,1513 **** * and "tv1 .= tv2" * Returns OK or FAIL. */ ! static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) { varnumber_T n; --- 1427,1433 ---- * and "tv1 .= tv2" * Returns OK or FAIL. */ ! int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) { varnumber_T n; *** ../vim-8.2.3331/src/proto/eval.pro 2021-07-10 21:28:55.327050110 +0200 --- src/proto/eval.pro 2021-08-11 19:21:22.061939013 +0200 *************** *** 26,31 **** --- 26,32 ---- 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); *** ../vim-8.2.3331/src/testdir/test_listdict.vim 2021-07-27 22:00:39.753712380 +0200 --- src/testdir/test_listdict.vim 2021-08-11 20:50:07.721236929 +0200 *************** *** 164,174 **** " 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) endfunc " Test removing items in list --- 164,177 ---- " test for range assign func Test_list_range_assign() ! 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 *** ../vim-8.2.3331/src/testdir/test_vim9_assign.vim 2021-08-07 16:30:35.109065179 +0200 --- src/testdir/test_vim9_assign.vim 2021-08-11 20:47:38.117656303 +0200 *************** *** 1821,1827 **** CheckDefFailure([ 'var ll = [1, 2]', 'll[1 : 2] = 7', ! ], 'E1165:', 2) CheckDefFailure([ 'var dd = {a: 1}', 'unlet dd["a" : "a"]', --- 1821,1827 ---- CheckDefFailure([ 'var ll = [1, 2]', 'll[1 : 2] = 7', ! ], 'E1012: Type mismatch; expected list but got number', 2) CheckDefFailure([ 'var dd = {a: 1}', 'unlet dd["a" : "a"]', *** ../vim-8.2.3331/src/version.c 2021-08-11 17:13:49.548650233 +0200 --- src/version.c 2021-08-11 18:14:33.218960489 +0200 *************** *** 757,758 **** --- 757,760 ---- { /* Add new patch number below this line */ + /**/ + 3332, /**/ -- There can't be a crisis today, my schedule is already full. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///