To: vim_dev@googlegroups.com Subject: Patch 8.2.0981 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0981 Problem: Vim9: cannot compile "[var, var] = list". Solution: Implement list assignment. Files: src/vim9compile.c, src/vim9.h, src/vim9execute.c, src/evalvars.c, src/proto/evalvars.pro src/eval.c, src/testdir/test_vim9_script.vim *** ../vim-8.2.0980/src/vim9compile.c 2020-06-13 19:00:06.887160162 +0200 --- src/vim9compile.c 2020-06-14 23:02:42.941468243 +0200 *************** *** 136,141 **** --- 136,142 ---- static char e_var_notfound[] = N_("E1001: variable not found: %s"); static char e_syntax_at[] = N_("E1002: Syntax error at %s"); static char e_used_as_arg[] = N_("E1006: %s is used as an argument"); + static char e_cannot_use_void[] = N_("E1031: Cannot use void value"); static void delete_def_function_contents(dfunc_T *dfunc); static void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); *************** *** 1053,1058 **** --- 1054,1091 ---- } /* + * Generate an ISN_GETITEM instruction with "index". + */ + static int + generate_GETITEM(cctx_T *cctx, int index) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + type_T *item_type = &t_any; + + RETURN_OK_IF_SKIP(cctx); + + if (type->tt_type == VAR_LIST) + item_type = type->tt_member; + else if (type->tt_type != VAR_ANY) + { + emsg(_(e_listreq)); + return FAIL; + } + if ((isn = generate_instr(cctx, ISN_GETITEM)) == NULL) + return FAIL; + isn->isn_arg.number = index; + + // add the item type to the type stack + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = item_type; + ++stack->ga_len; + return OK; + } + + /* * Generate an ISN_STORE instruction. */ static int *************** *** 4573,5256 **** } /* ! * compile "let var [= expr]", "const var = expr" and "var = expr" * "arg" points to "var". */ static char_u * compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) { ! char_u *var_end; char_u *p; char_u *end = arg; char_u *ret = NULL; int var_count = 0; int semicolon = 0; - size_t varlen; garray_T *instr = &cctx->ctx_instr; garray_T *stack = &cctx->ctx_type_stack; - int new_local = FALSE; char_u *op; - int opt_type; - assign_dest_T dest = dest_local; - int opt_flags = 0; - int vimvaridx = -1; int oplen = 0; int heredoc = FALSE; type_T *type = &t_any; type_T *member_type = &t_any; ! lvar_T *lvar = NULL; ! lvar_T arg_lvar; ! char_u *name; char_u *sp; - int has_type = FALSE; - int has_index = FALSE; int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; - int instr_count = -1; ! var_end = skip_var_list(arg, FALSE, &var_count, &semicolon); ! if (var_end == NULL) ! return NULL; ! if (var_count > 0) { ! // TODO: let [var, var] = list ! emsg("Cannot handle a list yet"); return NULL; } ! p = (*arg == '&' || *arg == '$' || *arg == '@') ? arg + 1 : arg; ! p = to_name_end(p, TRUE); ! // "a: type" is declaring variable "a" with a type, not "a:". ! if (is_decl && var_end == arg + 2 && var_end[-1] == ':') ! --var_end; ! if (is_decl && p == arg + 2 && p[-1] == ':') ! --p; ! varlen = p - arg; ! name = vim_strnsave(arg, varlen); ! if (name == NULL) return NULL; ! if (cctx->ctx_skip != TRUE) { ! if (*arg == '&') { ! int cc; ! long numval; ! dest = dest_option; ! if (cmdidx == CMD_const) { ! emsg(_(e_const_option)); goto theend; } ! if (is_decl) ! { ! semsg(_("E1052: Cannot declare an option: %s"), arg); goto theend; ! } ! p = arg; ! p = find_option_end(&p, &opt_flags); ! if (p == NULL) { ! // cannot happen? ! emsg(_(e_letunexp)); ! goto theend; } ! cc = *p; ! *p = NUL; ! opt_type = get_option_value(arg + 1, &numval, NULL, opt_flags); ! *p = cc; ! if (opt_type == -3) { ! semsg(_(e_unknown_option), arg); ! goto theend; ! } ! if (opt_type == -2 || opt_type == 0) type = &t_string; ! else ! type = &t_number; // both number and boolean option ! } ! else if (*arg == '$') ! { ! dest = dest_env; ! type = &t_string; ! if (is_decl) ! { ! semsg(_("E1065: Cannot declare an environment variable: %s"), name); ! goto theend; } ! } ! else if (*arg == '@') ! { ! if (!valid_yank_reg(arg[1], TRUE)) { ! emsg_invreg(arg[1]); ! goto theend; } ! dest = dest_reg; ! type = &t_string; ! if (is_decl) { ! semsg(_("E1066: Cannot declare a register: %s"), name); ! goto theend; } ! } ! else if (STRNCMP(arg, "g:", 2) == 0) ! { ! dest = dest_global; ! if (is_decl) { ! semsg(_("E1016: Cannot declare a global variable: %s"), name); ! goto theend; } ! } ! else if (STRNCMP(arg, "b:", 2) == 0) ! { ! dest = dest_buffer; ! if (is_decl) { ! semsg(_("E1078: Cannot declare a buffer variable: %s"), name); ! goto theend; } ! } ! else if (STRNCMP(arg, "w:", 2) == 0) ! { ! dest = dest_window; ! if (is_decl) { ! semsg(_("E1079: Cannot declare a window variable: %s"), name); ! goto theend; } ! } ! else if (STRNCMP(arg, "t:", 2) == 0) ! { ! dest = dest_tab; ! if (is_decl) { ! semsg(_("E1080: Cannot declare a tab variable: %s"), name); ! goto theend; ! } ! } ! else if (STRNCMP(arg, "v:", 2) == 0) ! { ! typval_T *vtv; ! int di_flags; ! vimvaridx = find_vim_var(name + 2, &di_flags); ! if (vimvaridx < 0) ! { ! semsg(_(e_var_notfound), arg); ! goto theend; } ! // We use the current value of "sandbox" here, is that OK? ! if (var_check_ro(di_flags, name, FALSE)) ! goto theend; ! dest = dest_vimvar; ! vtv = get_vim_var_tv(vimvaridx); ! type = typval2type(vtv); ! if (is_decl) { ! semsg(_("E1064: Cannot declare a v: variable: %s"), name); ! goto theend; ! } ! } ! else ! { ! int idx; ! for (idx = 0; reserved[idx] != NULL; ++idx) ! if (STRCMP(reserved[idx], name) == 0) { ! semsg(_("E1034: Cannot use reserved name %s"), name); ! goto theend; } ! ! lvar = lookup_local(arg, varlen, cctx); ! if (lvar == NULL) ! { ! CLEAR_FIELD(arg_lvar); ! if (lookup_arg(arg, varlen, ! &arg_lvar.lv_idx, &arg_lvar.lv_type, ! &arg_lvar.lv_from_outer, cctx) == OK) { if (is_decl) { ! semsg(_(e_used_as_arg), name); goto theend; } - lvar = &arg_lvar; } ! } ! if (lvar != NULL) ! { ! if (is_decl) { ! semsg(_("E1017: Variable already declared: %s"), name); ! goto theend; } ! else if (lvar->lv_const) { ! semsg(_("E1018: Cannot assign to a constant: %s"), name); goto theend; } ! } ! else if (STRNCMP(arg, "s:", 2) == 0 ! || lookup_script(arg, varlen) == OK ! || find_imported(arg, varlen, cctx) != NULL) ! { ! dest = dest_script; ! if (is_decl) { ! semsg(_("E1054: Variable already declared in the script: %s"), ! name); goto theend; } } - else if (name[1] == ':' && name[2] != NUL) - { - semsg(_("E1082: Cannot use a namespaced variable: %s"), name); - goto theend; - } - else if (!is_decl) - { - semsg(_("E1089: unknown variable: %s"), name); - goto theend; - } } - } ! // handle "a:name" as a name, not index "name" on "a" ! if (varlen > 1 || arg[varlen] != ':') ! p = var_end; ! if (dest != dest_option) ! { ! if (is_decl && *p == ':') { ! // parse optional type: "let var: type = expr" ! if (!VIM_ISWHITE(p[1])) { ! semsg(_(e_white_after), ":"); ! goto theend; } ! p = skipwhite(p + 1); ! type = parse_type(&p, cctx->ctx_type_list); ! has_type = TRUE; ! } ! else if (lvar != NULL) ! type = lvar->lv_type; ! } ! ! sp = p; ! p = skipwhite(p); ! op = p; ! oplen = assignment_len(p, &heredoc); ! if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) ! { ! char_u buf[4]; ! ! vim_strncpy(buf, op, oplen); ! semsg(_(e_white_both), buf); ! } ! ! if (oplen == 3 && !heredoc && dest != dest_global ! && type->tt_type != VAR_STRING && type->tt_type != VAR_ANY) ! { ! emsg(_("E1019: Can only concatenate to string")); ! goto theend; ! } ! ! if (lvar == NULL && dest == dest_local && cctx->ctx_skip != TRUE) ! { ! if (oplen > 1 && !heredoc) ! { ! // +=, /=, etc. require an existing variable ! semsg(_("E1020: cannot use an operator on a new variable: %s"), ! name); ! goto theend; } ! // new local variable ! if (type->tt_type == VAR_FUNC && var_check_func_name(name, TRUE)) ! goto theend; ! lvar = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); ! if (lvar == NULL) ! goto theend; ! new_local = TRUE; ! } ! ! member_type = type; ! if (var_end > arg + varlen) ! { ! // Something follows after the variable: "var[idx]". ! if (is_decl) { ! emsg(_("E1087: cannot use an index when declaring a variable")); goto theend; } ! if (arg[varlen] == '[') { ! has_index = TRUE; ! if (type->tt_member == NULL) { ! semsg(_("E1088: cannot use an index on %s"), name); goto theend; } - member_type = type->tt_member; - } - else - { - semsg("Not supported yet: %s", arg); - goto theend; - } - } - else if (lvar == &arg_lvar) - { - semsg(_("E1090: Cannot assign to argument %s"), name); - goto theend; - } - - if (heredoc) - { - list_T *l; - listitem_T *li; ! // [let] varname =<< [trim] {end} ! eap->getline = exarg_getline; ! eap->cookie = cctx; ! l = heredoc_get(eap, op + 3, FALSE); ! ! // Push each line and the create the list. ! FOR_ALL_LIST_ITEMS(l, li) ! { ! generate_PUSHS(cctx, li->li_tv.vval.v_string); ! li->li_tv.vval.v_string = NULL; } - generate_NEWLIST(cctx, l->lv_len); - type = &t_list_string; - member_type = &t_list_string; - list_free(l); - p += STRLEN(p); - } - else if (oplen > 0) - { - int r; ! // for "+=", "*=", "..=" etc. first load the current value ! if (*op != '=') { ! generate_loadvar(cctx, dest, name, lvar, type); ! if (has_index) { ! // TODO: get member from list or dict ! emsg("Index with operation not supported yet"); goto theend; } } ! ! // Compile the expression. Temporarily hide the new local variable ! // here, it is not available to this expression. ! if (new_local) ! --cctx->ctx_locals.ga_len; ! instr_count = instr->ga_len; ! p = skipwhite(p + oplen); ! r = compile_expr0(&p, cctx); ! if (new_local) ! ++cctx->ctx_locals.ga_len; ! if (r == FAIL) goto theend; ! if (cctx->ctx_skip != TRUE) { ! type_T *stacktype; ! ! stacktype = stack->ga_len == 0 ? &t_void ! : ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (lvar != NULL && (is_decl || !has_type)) { ! if (new_local && !has_type) { ! if (stacktype->tt_type == VAR_VOID) ! { ! emsg(_("E1031: Cannot use void value")); ! goto theend; ! } ! else { ! // An empty list or dict has a &t_void member, for a ! // variable that implies &t_any. ! if (stacktype == &t_list_empty) ! lvar->lv_type = &t_list_any; ! else if (stacktype == &t_dict_empty) ! lvar->lv_type = &t_dict_any; ! else ! lvar->lv_type = stacktype; } } else { ! type_T *use_type = lvar->lv_type; ! if (has_index) { ! use_type = use_type->tt_member; ! if (use_type == NULL) ! use_type = &t_void; } ! if (need_type(stacktype, use_type, -1, cctx) == FAIL) goto theend; } } ! else if (*p != '=' && need_type(stacktype, member_type, -1, ! cctx) == FAIL) goto theend; ! } ! } ! else if (cmdidx == CMD_const) ! { ! emsg(_(e_const_req_value)); ! goto theend; ! } ! else if (!has_type || dest == dest_option) ! { ! emsg(_(e_type_req)); ! goto theend; ! } ! else ! { ! // variables are always initialized ! if (ga_grow(instr, 1) == FAIL) ! goto theend; ! switch (member_type->tt_type) ! { ! case VAR_BOOL: ! generate_PUSHBOOL(cctx, VVAL_FALSE); ! break; ! case VAR_FLOAT: #ifdef FEAT_FLOAT ! generate_PUSHF(cctx, 0.0); #endif ! break; ! case VAR_STRING: ! generate_PUSHS(cctx, NULL); ! break; ! case VAR_BLOB: ! generate_PUSHBLOB(cctx, NULL); ! break; ! case VAR_FUNC: ! generate_PUSHFUNC(cctx, NULL, &t_func_void); ! break; ! case VAR_LIST: ! generate_NEWLIST(cctx, 0); ! break; ! case VAR_DICT: ! generate_NEWDICT(cctx, 0); ! break; ! case VAR_JOB: ! generate_PUSHJOB(cctx, NULL); ! break; ! case VAR_CHANNEL: ! generate_PUSHCHANNEL(cctx, NULL); ! break; ! case VAR_NUMBER: ! case VAR_UNKNOWN: ! case VAR_ANY: ! case VAR_PARTIAL: ! case VAR_VOID: ! case VAR_SPECIAL: // cannot happen ! generate_PUSHNR(cctx, 0); ! break; } - } - end = p; ! if (oplen > 0 && *op != '=') ! { ! type_T *expected = &t_number; ! type_T *stacktype; ! ! // TODO: if type is known use float or any operation ! ! if (*op == '.') ! expected = &t_string; ! stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (need_type(stacktype, expected, -1, cctx) == FAIL) ! goto theend; ! ! if (*op == '.') ! generate_instr_drop(cctx, ISN_CONCAT, 1); ! else { ! isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); ! if (isn == NULL) goto theend; ! switch (*op) { ! case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; ! case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; ! case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; ! case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; ! case '%': isn->isn_arg.op.op_type = EXPR_REM; break; } } - } - - if (has_index) - { - int r; ! // Compile the "idx" in "var[idx]". ! if (new_local) ! --cctx->ctx_locals.ga_len; ! p = skipwhite(arg + varlen + 1); ! r = compile_expr0(&p, cctx); ! if (new_local) ! ++cctx->ctx_locals.ga_len; ! if (r == FAIL) ! goto theend; ! if (*skipwhite(p) != ']') { ! emsg(_(e_missbrac)); ! goto theend; ! } ! if (type->tt_type == VAR_DICT ! && may_generate_2STRING(-1, cctx) == FAIL) ! goto theend; ! if (type->tt_type == VAR_LIST ! && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type != VAR_NUMBER) ! { ! emsg(_(e_number_exp)); ! goto theend; ! } ! // Load the dict or list. On the stack we then have: ! // - value ! // - index ! // - variable ! generate_loadvar(cctx, dest, name, lvar, type); ! if (type->tt_type == VAR_LIST) ! { ! if (generate_instr_drop(cctx, ISN_STORELIST, 3) == FAIL) ! return FAIL; ! } ! else if (type->tt_type == VAR_DICT) ! { ! if (generate_instr_drop(cctx, ISN_STOREDICT, 3) == FAIL) ! return FAIL; } else { ! emsg(_(e_listreq)); ! goto theend; ! } ! } ! else ! { ! switch (dest) ! { ! case dest_option: ! generate_STOREOPT(cctx, name + 1, opt_flags); ! break; ! case dest_global: ! // include g: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREG, 0, name); ! break; ! case dest_buffer: ! // include b: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREB, 0, name); ! break; ! case dest_window: ! // include w: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREW, 0, name); ! break; ! case dest_tab: ! // include t: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STORET, 0, name); ! break; ! case dest_env: ! generate_STORE(cctx, ISN_STOREENV, 0, name + 1); ! break; ! case dest_reg: ! generate_STORE(cctx, ISN_STOREREG, name[1], NULL); ! break; ! case dest_vimvar: ! generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); ! break; ! case dest_script: ! { ! char_u *rawname = name + (name[1] == ':' ? 2 : 0); ! imported_T *import = NULL; ! int sid = current_sctx.sc_sid; ! int idx; ! ! if (name[1] != ':') ! { ! import = find_imported(name, 0, cctx); ! if (import != NULL) ! sid = import->imp_sid; ! } ! ! idx = get_script_item_idx(sid, rawname, TRUE); ! // TODO: specific type ! if (idx < 0) { ! char_u *name_s = name; - // Include s: in the name for store_var() if (name[1] != ':') { ! int len = (int)STRLEN(name) + 3; ! ! name_s = alloc(len); ! if (name_s == NULL) ! name_s = name; ! else ! vim_snprintf((char *)name_s, len, "s:%s", name); } ! generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); ! if (name_s != name) ! vim_free(name_s); ! } ! else ! generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, sid, idx, &t_any); ! } ! break; ! case dest_local: ! if (lvar != NULL) ! { ! isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; ! ! // optimization: turn "var = 123" from ISN_PUSHNR + ! // ISN_STORE into ISN_STORENR ! if (!lvar->lv_from_outer && instr->ga_len == instr_count + 1 ! && isn->isn_type == ISN_PUSHNR) { ! varnumber_T val = isn->isn_arg.number; ! isn->isn_type = ISN_STORENR; ! isn->isn_arg.storenr.stnr_idx = lvar->lv_idx; ! isn->isn_arg.storenr.stnr_val = val; ! if (stack->ga_len > 0) ! --stack->ga_len; ! } ! else if (lvar->lv_from_outer) ! generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); ! else ! generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); ! } ! break; } } ret = end; theend: --- 4606,5381 ---- } /* ! * Compile declaration and assignment: ! * "let var", "let var = expr", "const var = expr" and "var = expr" * "arg" points to "var". + * Return NULL for an error. + * Return "arg" if it does not look like a variable list. */ static char_u * compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) { ! char_u *var_start; char_u *p; char_u *end = arg; char_u *ret = NULL; int var_count = 0; + int var_idx; int semicolon = 0; garray_T *instr = &cctx->ctx_instr; garray_T *stack = &cctx->ctx_type_stack; char_u *op; int oplen = 0; int heredoc = FALSE; type_T *type = &t_any; type_T *member_type = &t_any; ! char_u *name = NULL; char_u *sp; int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; ! // Skip over the "var" or "[var, var]" to get to any "=". ! p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE); ! if (p == NULL) ! return *arg == '[' ? arg : NULL; ! ! if (var_count > 0 && is_decl) { ! emsg(_("E1092: Cannot use a list for a declaration")); return NULL; } ! sp = p; ! p = skipwhite(p); ! op = p; ! oplen = assignment_len(p, &heredoc); ! if (var_count > 0 && oplen == 0) ! // can be something like "[1, 2]->func()" ! return arg; ! if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) ! { ! char_u buf[4]; ! ! vim_strncpy(buf, op, oplen); ! semsg(_(e_white_both), buf); return NULL; + } + + if (heredoc) + { + list_T *l; + listitem_T *li; + + // [let] varname =<< [trim] {end} + eap->getline = exarg_getline; + eap->cookie = cctx; + l = heredoc_get(eap, op + 3, FALSE); ! // Push each line and the create the list. ! FOR_ALL_LIST_ITEMS(l, li) ! { ! generate_PUSHS(cctx, li->li_tv.vval.v_string); ! li->li_tv.vval.v_string = NULL; ! } ! generate_NEWLIST(cctx, l->lv_len); ! type = &t_list_string; ! member_type = &t_list_string; ! list_free(l); ! p += STRLEN(p); ! end = p; ! } ! else if (var_count > 0) { ! // for "[var, var] = expr" evaluate the expression here, loop over the ! // list of variables below. ! ! p = skipwhite(op + oplen); ! if (compile_expr0(&p, cctx) == FAIL) ! return NULL; ! end = p; ! ! if (cctx->ctx_skip != TRUE) { ! type_T *stacktype; ! stacktype = stack->ga_len == 0 ? &t_void ! : ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (stacktype->tt_type == VAR_VOID) { ! emsg(_(e_cannot_use_void)); goto theend; } ! if (need_type(stacktype, &t_list_any, -1, cctx) == FAIL) goto theend; ! // TODO: check length of list to be var_count (or more if ! // "semicolon" set) ! } ! } ! ! /* ! * Loop over variables in "[var, var] = expr". ! * For "var = expr" and "let var: type" this is done only once. ! */ ! if (var_count > 0) ! var_start = skipwhite(arg + 1); // skip over the "[" ! else ! var_start = arg; ! for (var_idx = 0; var_idx == 0 || var_idx < var_count; var_idx++) ! { ! char_u *var_end = skip_var_one(var_start, FALSE); ! size_t varlen; ! int new_local = FALSE; ! int opt_type; ! int opt_flags = 0; ! assign_dest_T dest = dest_local; ! int vimvaridx = -1; ! lvar_T *lvar = NULL; ! lvar_T arg_lvar; ! int has_type = FALSE; ! int has_index = FALSE; ! int instr_count = -1; ! ! p = (*var_start == '&' || *var_start == '$' ! || *var_start == '@') ? var_start + 1 : var_start; ! p = to_name_end(p, TRUE); ! ! // "a: type" is declaring variable "a" with a type, not "a:". ! if (is_decl && var_end == var_start + 2 && var_end[-1] == ':') ! --var_end; ! if (is_decl && p == var_start + 2 && p[-1] == ':') ! --p; ! ! varlen = p - var_start; ! vim_free(name); ! name = vim_strnsave(var_start, varlen); ! if (name == NULL) ! return NULL; ! if (!heredoc) ! type = &t_any; ! ! if (cctx->ctx_skip != TRUE) ! { ! if (*var_start == '&') { ! int cc; ! long numval; ! ! dest = dest_option; ! if (cmdidx == CMD_const) ! { ! emsg(_(e_const_option)); ! goto theend; ! } ! if (is_decl) ! { ! semsg(_("E1052: Cannot declare an option: %s"), var_start); ! goto theend; ! } ! p = var_start; ! p = find_option_end(&p, &opt_flags); ! if (p == NULL) ! { ! // cannot happen? ! emsg(_(e_letunexp)); ! goto theend; ! } ! cc = *p; ! *p = NUL; ! opt_type = get_option_value(var_start + 1, &numval, ! NULL, opt_flags); ! *p = cc; ! if (opt_type == -3) ! { ! semsg(_(e_unknown_option), var_start); ! goto theend; ! } ! if (opt_type == -2 || opt_type == 0) ! type = &t_string; ! else ! type = &t_number; // both number and boolean option } ! else if (*var_start == '$') { ! dest = dest_env; type = &t_string; ! if (is_decl) ! { ! semsg(_("E1065: Cannot declare an environment variable: %s"), name); ! goto theend; ! } } ! else if (*var_start == '@') { ! if (!valid_yank_reg(var_start[1], TRUE)) ! { ! emsg_invreg(var_start[1]); ! goto theend; ! } ! dest = dest_reg; ! type = &t_string; ! if (is_decl) ! { ! semsg(_("E1066: Cannot declare a register: %s"), name); ! goto theend; ! } } ! else if (STRNCMP(var_start, "g:", 2) == 0) { ! dest = dest_global; ! if (is_decl) ! { ! semsg(_("E1016: Cannot declare a global variable: %s"), ! name); ! goto theend; ! } } ! else if (STRNCMP(var_start, "b:", 2) == 0) { ! dest = dest_buffer; ! if (is_decl) ! { ! semsg(_("E1078: Cannot declare a buffer variable: %s"), ! name); ! goto theend; ! } } ! else if (STRNCMP(var_start, "w:", 2) == 0) { ! dest = dest_window; ! if (is_decl) ! { ! semsg(_("E1079: Cannot declare a window variable: %s"), ! name); ! goto theend; ! } } ! else if (STRNCMP(var_start, "t:", 2) == 0) { ! dest = dest_tab; ! if (is_decl) ! { ! semsg(_("E1080: Cannot declare a tab variable: %s"), name); ! goto theend; ! } } ! else if (STRNCMP(var_start, "v:", 2) == 0) { ! typval_T *vtv; ! int di_flags; ! vimvaridx = find_vim_var(name + 2, &di_flags); ! if (vimvaridx < 0) ! { ! semsg(_(e_var_notfound), var_start); ! goto theend; ! } ! // We use the current value of "sandbox" here, is that OK? ! if (var_check_ro(di_flags, name, FALSE)) ! goto theend; ! dest = dest_vimvar; ! vtv = get_vim_var_tv(vimvaridx); ! type = typval2type(vtv); ! if (is_decl) ! { ! semsg(_("E1064: Cannot declare a v: variable: %s"), name); ! goto theend; ! } } ! else { ! int idx; ! for (idx = 0; reserved[idx] != NULL; ++idx) ! if (STRCMP(reserved[idx], name) == 0) ! { ! semsg(_("E1034: Cannot use reserved name %s"), name); ! goto theend; ! } ! ! lvar = lookup_local(var_start, varlen, cctx); ! if (lvar == NULL) { ! CLEAR_FIELD(arg_lvar); ! if (lookup_arg(var_start, varlen, ! &arg_lvar.lv_idx, &arg_lvar.lv_type, ! &arg_lvar.lv_from_outer, cctx) == OK) ! { ! if (is_decl) ! { ! semsg(_(e_used_as_arg), name); ! goto theend; ! } ! lvar = &arg_lvar; ! } } ! if (lvar != NULL) { if (is_decl) { ! semsg(_("E1017: Variable already declared: %s"), name); ! goto theend; ! } ! else if (lvar->lv_const) ! { ! semsg(_("E1018: Cannot assign to a constant: %s"), ! name); goto theend; } } ! else if (STRNCMP(var_start, "s:", 2) == 0 ! || lookup_script(var_start, varlen) == OK ! || find_imported(var_start, varlen, cctx) != NULL) { ! dest = dest_script; ! if (is_decl) ! { ! semsg(_("E1054: Variable already declared in the script: %s"), ! name); ! goto theend; ! } } ! else if (name[1] == ':' && name[2] != NUL) { ! semsg(_("E1082: Cannot use a namespaced variable: %s"), ! name); goto theend; } ! else if (!is_decl) { ! semsg(_("E1089: unknown variable: %s"), name); goto theend; } } } ! // handle "a:name" as a name, not index "name" on "a" ! if (varlen > 1 || var_start[varlen] != ':') ! p = var_end; ! if (dest != dest_option) { ! if (is_decl && *p == ':') { ! // parse optional type: "let var: type = expr" ! if (!VIM_ISWHITE(p[1])) ! { ! semsg(_(e_white_after), ":"); ! goto theend; ! } ! p = skipwhite(p + 1); ! type = parse_type(&p, cctx->ctx_type_list); ! has_type = TRUE; } ! else if (lvar != NULL) ! type = lvar->lv_type; } ! if (oplen == 3 && !heredoc && dest != dest_global ! && type->tt_type != VAR_STRING ! && type->tt_type != VAR_ANY) { ! emsg(_("E1019: Can only concatenate to string")); goto theend; } ! if (lvar == NULL && dest == dest_local && cctx->ctx_skip != TRUE) { ! if (oplen > 1 && !heredoc) { ! // +=, /=, etc. require an existing variable ! semsg(_("E1020: cannot use an operator on a new variable: %s"), ! name); goto theend; } ! // new local variable ! if (type->tt_type == VAR_FUNC && var_check_func_name(name, TRUE)) ! goto theend; ! lvar = reserve_local(cctx, var_start, varlen, ! cmdidx == CMD_const, type); ! if (lvar == NULL) ! goto theend; ! new_local = TRUE; } ! member_type = type; ! if (var_end > var_start + varlen) { ! // Something follows after the variable: "var[idx]". ! if (is_decl) ! { ! emsg(_("E1087: cannot use an index when declaring a variable")); ! goto theend; ! } ! if (var_start[varlen] == '[') { ! has_index = TRUE; ! if (type->tt_member == NULL) ! { ! semsg(_("E1088: cannot use an index on %s"), name); ! goto theend; ! } ! member_type = type->tt_member; ! } ! else ! { ! semsg("Not supported yet: %s", var_start); goto theend; } } ! else if (lvar == &arg_lvar) ! { ! semsg(_("E1090: Cannot assign to argument %s"), name); goto theend; + } ! if (!heredoc) { ! if (oplen > 0) { ! // For "var = expr" evaluate the expression. ! if (var_count == 0) { ! int r; ! ! // for "+=", "*=", "..=" etc. first load the current value ! if (*op != '=') { ! generate_loadvar(cctx, dest, name, lvar, type); ! ! if (has_index) ! { ! // TODO: get member from list or dict ! emsg("Index with operation not supported yet"); ! goto theend; ! } } + + // Compile the expression. Temporarily hide the new local + // variable here, it is not available to this expression. + if (new_local) + --cctx->ctx_locals.ga_len; + instr_count = instr->ga_len; + p = skipwhite(op + oplen); + r = compile_expr0(&p, cctx); + if (new_local) + ++cctx->ctx_locals.ga_len; + if (r == FAIL) + goto theend; } else { ! // For "[var, var] = expr" get the "var_idx" item from the ! // list. ! if (generate_GETITEM(cctx, var_idx) == FAIL) ! return FAIL; ! } ! if (cctx->ctx_skip != TRUE) ! { ! type_T *stacktype; ! ! stacktype = stack->ga_len == 0 ? &t_void ! : ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (lvar != NULL && (is_decl || !has_type)) { ! if (new_local && !has_type) ! { ! if (stacktype->tt_type == VAR_VOID) ! { ! emsg(_(e_cannot_use_void)); ! goto theend; ! } ! else ! { ! // An empty list or dict has a &t_void member, ! // for a variable that implies &t_any. ! if (stacktype == &t_list_empty) ! lvar->lv_type = &t_list_any; ! else if (stacktype == &t_dict_empty) ! lvar->lv_type = &t_dict_any; ! else ! lvar->lv_type = stacktype; ! } ! } ! else ! { ! type_T *use_type = lvar->lv_type; ! ! if (has_index) ! { ! use_type = use_type->tt_member; ! if (use_type == NULL) ! use_type = &t_void; ! } ! if (need_type(stacktype, use_type, -1, cctx) ! == FAIL) ! goto theend; ! } } ! else if (*p != '=' && need_type(stacktype, member_type, -1, ! cctx) == FAIL) goto theend; } } ! else if (cmdidx == CMD_const) ! { ! emsg(_(e_const_req_value)); goto theend; ! } ! else if (!has_type || dest == dest_option) ! { ! emsg(_(e_type_req)); ! goto theend; ! } ! else ! { ! // variables are always initialized ! if (ga_grow(instr, 1) == FAIL) ! goto theend; ! switch (member_type->tt_type) ! { ! case VAR_BOOL: ! generate_PUSHBOOL(cctx, VVAL_FALSE); ! break; ! case VAR_FLOAT: #ifdef FEAT_FLOAT ! generate_PUSHF(cctx, 0.0); #endif ! break; ! case VAR_STRING: ! generate_PUSHS(cctx, NULL); ! break; ! case VAR_BLOB: ! generate_PUSHBLOB(cctx, NULL); ! break; ! case VAR_FUNC: ! generate_PUSHFUNC(cctx, NULL, &t_func_void); ! break; ! case VAR_LIST: ! generate_NEWLIST(cctx, 0); ! break; ! case VAR_DICT: ! generate_NEWDICT(cctx, 0); ! break; ! case VAR_JOB: ! generate_PUSHJOB(cctx, NULL); ! break; ! case VAR_CHANNEL: ! generate_PUSHCHANNEL(cctx, NULL); ! break; ! case VAR_NUMBER: ! case VAR_UNKNOWN: ! case VAR_ANY: ! case VAR_PARTIAL: ! case VAR_VOID: ! case VAR_SPECIAL: // cannot happen ! generate_PUSHNR(cctx, 0); ! break; ! } ! } ! if (var_count == 0) ! end = p; } ! if (oplen > 0 && *op != '=') { ! type_T *expected = &t_number; ! type_T *stacktype; ! // TODO: if type is known use float or any operation ! ! if (*op == '.') ! expected = &t_string; ! stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (need_type(stacktype, expected, -1, cctx) == FAIL) goto theend; ! ! if (*op == '.') ! generate_instr_drop(cctx, ISN_CONCAT, 1); ! else { ! isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); ! ! if (isn == NULL) ! goto theend; ! switch (*op) ! { ! case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; ! case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; ! case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; ! case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; ! case '%': isn->isn_arg.op.op_type = EXPR_REM; break; ! } } } ! if (has_index) { ! int r; ! ! // Compile the "idx" in "var[idx]". ! if (new_local) ! --cctx->ctx_locals.ga_len; ! p = skipwhite(var_start + varlen + 1); ! r = compile_expr0(&p, cctx); ! if (new_local) ! ++cctx->ctx_locals.ga_len; ! if (r == FAIL) ! goto theend; ! if (*skipwhite(p) != ']') ! { ! emsg(_(e_missbrac)); ! goto theend; ! } ! if (type->tt_type == VAR_DICT ! && may_generate_2STRING(-1, cctx) == FAIL) ! goto theend; ! if (type->tt_type == VAR_LIST ! && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type != VAR_NUMBER) ! { ! emsg(_(e_number_exp)); ! goto theend; ! } ! // Load the dict or list. On the stack we then have: ! // - value ! // - index ! // - variable ! generate_loadvar(cctx, dest, name, lvar, type); ! if (type->tt_type == VAR_LIST) ! { ! if (generate_instr_drop(cctx, ISN_STORELIST, 3) == FAIL) ! return FAIL; ! } ! else if (type->tt_type == VAR_DICT) ! { ! if (generate_instr_drop(cctx, ISN_STOREDICT, 3) == FAIL) ! return FAIL; ! } ! else ! { ! emsg(_(e_listreq)); ! goto theend; ! } } else { ! switch (dest) ! { ! case dest_option: ! generate_STOREOPT(cctx, name + 1, opt_flags); ! break; ! case dest_global: ! // include g: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREG, 0, name); ! break; ! case dest_buffer: ! // include b: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREB, 0, name); ! break; ! case dest_window: ! // include w: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STOREW, 0, name); ! break; ! case dest_tab: ! // include t: with the name, easier to execute that way ! generate_STORE(cctx, ISN_STORET, 0, name); ! break; ! case dest_env: ! generate_STORE(cctx, ISN_STOREENV, 0, name + 1); ! break; ! case dest_reg: ! generate_STORE(cctx, ISN_STOREREG, name[1], NULL); ! break; ! case dest_vimvar: ! generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); ! break; ! case dest_script: { ! char_u *rawname = name + (name[1] == ':' ? 2 : 0); ! imported_T *import = NULL; ! int sid = current_sctx.sc_sid; ! int idx; if (name[1] != ':') { ! import = find_imported(name, 0, cctx); ! if (import != NULL) ! sid = import->imp_sid; } ! ! idx = get_script_item_idx(sid, rawname, TRUE); ! // TODO: specific type ! if (idx < 0) ! { ! char_u *name_s = name; ! ! // Include s: in the name for store_var() ! if (name[1] != ':') ! { ! int len = (int)STRLEN(name) + 3; ! ! name_s = alloc(len); ! if (name_s == NULL) ! name_s = name; ! else ! vim_snprintf((char *)name_s, len, ! "s:%s", name); ! } ! generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); ! if (name_s != name) ! vim_free(name_s); ! } ! else ! generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, sid, idx, &t_any); ! } ! break; ! case dest_local: ! if (lvar != NULL) { ! isn_T *isn = ((isn_T *)instr->ga_data) ! + instr->ga_len - 1; ! // optimization: turn "var = 123" from ISN_PUSHNR + ! // ISN_STORE into ISN_STORENR ! if (!lvar->lv_from_outer ! && instr->ga_len == instr_count + 1 ! && isn->isn_type == ISN_PUSHNR) ! { ! varnumber_T val = isn->isn_arg.number; ! ! isn->isn_type = ISN_STORENR; ! isn->isn_arg.storenr.stnr_idx = lvar->lv_idx; ! isn->isn_arg.storenr.stnr_val = val; ! if (stack->ga_len > 0) ! --stack->ga_len; ! } ! else if (lvar->lv_from_outer) ! generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); ! else ! generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); ! } ! break; ! } } + + if (var_idx + 1 < var_count) + var_start = skipwhite(var_end + 1); } + + // for "[var, var] = expr" drop the "expr" value + if (var_count > 0 && generate_instr_drop(cctx, ISN_DROP, 1) == NULL) + goto theend; + ret = end; theend: *************** *** 6575,6586 **** || find_imported(ea.cmd, len, &cctx) != NULL) { line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); ! if (line == NULL) goto erret; continue; } } } } /* --- 6700,6721 ---- || find_imported(ea.cmd, len, &cctx) != NULL) { line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); ! if (line == NULL || line == ea.cmd) goto erret; continue; } } } + + if (*ea.cmd == '[') + { + // [var, var] = expr + line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); + if (line == NULL) + goto erret; + if (line != ea.cmd) + continue; + } } /* *************** *** 6646,6651 **** --- 6781,6788 ---- case CMD_let: case CMD_const: line = compile_assignment(p, &ea, ea.cmdidx, &cctx); + if (line == p) + line = NULL; break; case CMD_unlet: *************** *** 6957,6962 **** --- 7094,7100 ---- case ISN_EXECUTE: case ISN_FOR: case ISN_INDEX: + case ISN_GETITEM: case ISN_MEMBER: case ISN_JUMP: case ISN_LOAD: *** ../vim-8.2.0980/src/vim9.h 2020-05-10 19:10:27.968996544 +0200 --- src/vim9.h 2020-06-14 22:24:20.966364382 +0200 *************** *** 112,117 **** --- 112,118 ---- // expression operations ISN_CONCAT, ISN_INDEX, // [expr] list index + ISN_GETITEM, // push list item, isn_arg.number is the index ISN_MEMBER, // dict[member] ISN_STRINGMEMBER, // dict.member using isn_arg.string ISN_2BOOL, // convert value to bool, invert if isn_arg.number != 0 *** ../vim-8.2.0980/src/vim9execute.c 2020-05-25 22:36:46.629735032 +0200 --- src/vim9execute.c 2020-06-14 22:32:11.272486172 +0200 *************** *** 2114,2119 **** --- 2114,2144 ---- } break; + case ISN_GETITEM: + { + listitem_T *li; + int index = iptr->isn_arg.number; + + // get list item: list is at stack-1, push item + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_LIST) + { + emsg(_(e_listreq)); + goto failed; + } + if ((li = list_find(tv->vval.v_list, index)) == NULL) + { + semsg(_(e_listidx), index); + goto failed; + } + + if (GA_GROW(&ectx.ec_stack, 1) == FAIL) + goto failed; + ++ectx.ec_stack.ga_len; + copy_tv(&li->li_tv, STACK_TV_BOT(-1)); + } + break; + case ISN_MEMBER: { dict_T *dict; *************** *** 2789,2794 **** --- 2814,2821 ---- // expression operations case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_INDEX: smsg("%4d INDEX", current); break; + case ISN_GETITEM: smsg("%4d ITEM %lld", + current, iptr->isn_arg.number); break; case ISN_MEMBER: smsg("%4d MEMBER", current); break; case ISN_STRINGMEMBER: smsg("%4d MEMBER %s", current, iptr->isn_arg.string); break; *** ../vim-8.2.0980/src/evalvars.c 2020-06-13 19:00:06.887160162 +0200 --- src/evalvars.c 2020-06-14 22:55:12.023184203 +0200 *************** *** 164,170 **** // for VIM_VERSION_ defines #include "version.h" - static char_u *skip_var_one(char_u *arg, int include_type); static void list_glob_vars(int *first); static void list_buf_vars(int *first); static void list_win_vars(int *first); --- 164,169 ---- *************** *** 709,715 **** if (eap->arg == eap->cmd) flags |= LET_NO_COMMAND; ! argend = skip_var_list(arg, TRUE, &var_count, &semicolon); if (argend == NULL) return; if (argend > arg && argend[-1] == '.') // for var.='str' --- 708,714 ---- if (eap->arg == eap->cmd) flags |= LET_NO_COMMAND; ! argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE); if (argend == NULL) return; if (argend > arg && argend[-1] == '.') // for var.='str' *************** *** 916,922 **** * Skip over assignable variable "var" or list of variables "[var, var]". * Used for ":let varvar = expr" and ":for varvar in expr". * For "[var, var]" increment "*var_count" for each variable. ! * for "[var, var; var]" set "semicolon". * Return NULL for an error. */ char_u * --- 915,922 ---- * Skip over assignable variable "var" or list of variables "[var, var]". * Used for ":let varvar = expr" and ":for varvar in expr". * For "[var, var]" increment "*var_count" for each variable. ! * for "[var, var; var]" set "semicolon" to 1. ! * If "silent" is TRUE do not give an "invalid argument" error message. * Return NULL for an error. */ char_u * *************** *** 924,930 **** char_u *arg, int include_type, int *var_count, ! int *semicolon) { char_u *p, *s; --- 924,931 ---- char_u *arg, int include_type, int *var_count, ! int *semicolon, ! int silent) { char_u *p, *s; *************** *** 935,944 **** for (;;) { p = skipwhite(p + 1); // skip whites after '[', ';' or ',' ! s = skip_var_one(p, TRUE); if (s == p) { ! semsg(_(e_invarg2), p); return NULL; } ++*var_count; --- 936,946 ---- for (;;) { p = skipwhite(p + 1); // skip whites after '[', ';' or ',' ! s = skip_var_one(p, FALSE); if (s == p) { ! if (!silent) ! semsg(_(e_invarg2), p); return NULL; } ++*var_count; *************** *** 957,963 **** } else if (*p != ',') { ! semsg(_(e_invarg2), p); return NULL; } } --- 959,966 ---- } else if (*p != ',') { ! if (!silent) ! semsg(_(e_invarg2), p); return NULL; } } *************** *** 972,978 **** * l[idx]. * In Vim9 script also skip over ": type" if "include_type" is TRUE. */ ! static char_u * skip_var_one(char_u *arg, int include_type) { char_u *end; --- 975,981 ---- * l[idx]. * In Vim9 script also skip over ": type" if "include_type" is TRUE. */ ! char_u * skip_var_one(char_u *arg, int include_type) { char_u *end; *************** *** 981,990 **** return arg + 2; end = find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); ! if (include_type && current_sctx.sc_version == SCRIPT_VERSION_VIM9 ! && *end == ':') { ! end = skip_type(skipwhite(end + 1)); } return end; } --- 984,996 ---- return arg + 2; end = find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); ! if (include_type && current_sctx.sc_version == SCRIPT_VERSION_VIM9) { ! // "a: type" is declaring variable "a" with a type, not "a:". ! if (end == arg + 2 && end[-1] == ':') ! --end; ! if (*end == ':') ! end = skip_type(skipwhite(end + 1)); } return end; } *** ../vim-8.2.0980/src/proto/evalvars.pro 2020-06-07 14:50:47.271846855 +0200 --- src/proto/evalvars.pro 2020-06-14 22:55:34.475099868 +0200 *************** *** 16,22 **** list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get); void ex_let(exarg_T *eap); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); ! char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon); 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); --- 16,23 ---- list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get); void ex_let(exarg_T *eap); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); ! char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon, int silent); ! char_u *skip_var_one(char_u *arg, int include_type); 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); *** ../vim-8.2.0980/src/eval.c 2020-06-07 20:49:02.077891881 +0200 --- src/eval.c 2020-06-14 22:54:56.343243035 +0200 *************** *** 1431,1437 **** if (fi == NULL) return NULL; ! expr = skip_var_list(arg, TRUE, &fi->fi_varcount, &fi->fi_semicolon); if (expr == NULL) return fi; --- 1431,1437 ---- if (fi == NULL) return NULL; ! expr = skip_var_list(arg, TRUE, &fi->fi_varcount, &fi->fi_semicolon, FALSE); if (expr == NULL) return fi; *** ../vim-8.2.0980/src/testdir/test_vim9_script.vim 2020-06-14 12:50:20.959092684 +0200 --- src/testdir/test_vim9_script.vim 2020-06-14 23:03:32.653276923 +0200 *************** *** 223,228 **** --- 223,236 ---- assert_equal(5678, nr) enddef + def Test_assignment_var_list() + let v1: string + let v2: string + [v1, v2] = ['one', 'two'] + assert_equal('one', v1) + assert_equal('two', v2) + enddef + def Mess(): string v:foldstart = 123 return 'xxx' *** ../vim-8.2.0980/src/version.c 2020-06-14 20:04:28.432529296 +0200 --- src/version.c 2020-06-14 23:03:59.185174632 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 981, /**/ -- ARTHUR: I am your king! WOMAN: Well, I didn't vote for you. ARTHUR: You don't vote for kings. WOMAN: Well, 'ow did you become king then? The Quest for the Holy Grail (Monty Python) /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///