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 -- Br...@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 ///