changeset 20859:876e16c48bd1 v8.2.0981

patch 8.2.0981: Vim9: cannot compile "[var, var] = list" Commit: https://github.com/vim/vim/commit/47a519a933e8bcaf703a5feaac5c01491a658ee3 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jun 14 23:05:10 2020 +0200 patch 8.2.0981: Vim9: cannot compile "[var, var] = list" Problem: Vim9: cannot compile "[var, var] = list". Solution: Implement list assignment.
author Bram Moolenaar <Bram@vim.org>
date Sun, 14 Jun 2020 23:15:04 +0200
parents e20d1d3b411c
children 2665521ed2cd
files src/eval.c src/evalvars.c src/proto/evalvars.pro src/testdir/test_vim9_script.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 8 files changed, 775 insertions(+), 592 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -1431,7 +1431,7 @@ eval_for_line(
     if (fi == NULL)
 	return NULL;
 
-    expr = skip_var_list(arg, TRUE, &fi->fi_varcount, &fi->fi_semicolon);
+    expr = skip_var_list(arg, TRUE, &fi->fi_varcount, &fi->fi_semicolon, FALSE);
     if (expr == NULL)
 	return fi;
 
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -164,7 +164,6 @@ static dict_T		vimvardict;		// Dictionar
 // 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);
@@ -709,7 +708,7 @@ ex_let(exarg_T *eap)
     if (eap->arg == eap->cmd)
 	flags |= LET_NO_COMMAND;
 
-    argend = skip_var_list(arg, TRUE, &var_count, &semicolon);
+    argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
     if (argend == NULL)
 	return;
     if (argend > arg && argend[-1] == '.')  // for var.='str'
@@ -916,7 +915,8 @@ ex_let_vars(
  * 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".
+ * 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,7 +924,8 @@ skip_var_list(
     char_u	*arg,
     int		include_type,
     int		*var_count,
-    int		*semicolon)
+    int		*semicolon,
+    int		silent)
 {
     char_u	*p, *s;
 
@@ -935,10 +936,11 @@ skip_var_list(
 	for (;;)
 	{
 	    p = skipwhite(p + 1);	// skip whites after '[', ';' or ','
-	    s = skip_var_one(p, TRUE);
+	    s = skip_var_one(p, FALSE);
 	    if (s == p)
 	    {
-		semsg(_(e_invarg2), p);
+		if (!silent)
+		    semsg(_(e_invarg2), p);
 		return NULL;
 	    }
 	    ++*var_count;
@@ -957,7 +959,8 @@ skip_var_list(
 	    }
 	    else if (*p != ',')
 	    {
-		semsg(_(e_invarg2), p);
+		if (!silent)
+		    semsg(_(e_invarg2), p);
 		return NULL;
 	    }
 	}
@@ -972,7 +975,7 @@ skip_var_list(
  * l[idx].
  * In Vim9 script also skip over ": type" if "include_type" is TRUE.
  */
-    static char_u *
+    char_u *
 skip_var_one(char_u *arg, int include_type)
 {
     char_u *end;
@@ -981,10 +984,13 @@ skip_var_one(char_u *arg, int include_ty
 	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 == ':')
+    if (include_type && current_sctx.sc_version == SCRIPT_VERSION_VIM9)
     {
-	end = skip_type(skipwhite(end + 1));
+	// "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;
 }
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -16,7 +16,8 @@ void restore_vimvar(int idx, typval_T *s
 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);
+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);
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -223,6 +223,14 @@ def Test_assignment_default()
   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'
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    981,
+/**/
     980,
 /**/
     979,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -112,6 +112,7 @@ typedef enum {
     // 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
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -136,6 +136,7 @@ struct cctx_S {
 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,6 +1054,38 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *
 }
 
 /*
+ * 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,344 +4606,60 @@ generate_loadvar(
 }
 
 /*
- * compile "let var [= expr]", "const var = expr" and "var = expr"
+ * 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_end;
+    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;
-    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	*name = NULL;
     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)
+
+    // 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;
-
-    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 (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);
-    }
-
-    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;
+	return NULL;
     }
 
     if (heredoc)
@@ -4934,35 +4683,17 @@ compile_assignment(char_u *arg, exarg_T 
 	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;
+	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)
 	{
@@ -4970,287 +4701,681 @@ compile_assignment(char_u *arg, exarg_T 
 
 	    stacktype = stack->ga_len == 0 ? &t_void
 			      : ((type_T **)stack->ga_data)[stack->ga_len - 1];
-	    if (lvar != NULL && (is_decl || !has_type))
+	    if (stacktype->tt_type == VAR_VOID)
 	    {
-		if (new_local && !has_type)
+		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)
 		{
-		    if (stacktype->tt_type == VAR_VOID)
+		    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)
 		    {
-			emsg(_("E1031: Cannot use void value"));
+			semsg(_("E1034: Cannot use reserved name %s"), name);
 			goto theend;
 		    }
-		    else
+
+		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)
 		    {
-			// 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;
+			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
 		{
-		    type_T *use_type = lvar->lv_type;
-
-		    if (has_index)
+		    // 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))
 		    {
-			use_type = use_type->tt_member;
-			if (use_type == NULL)
-			    use_type = &t_void;
+			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;
+			}
 		    }
-		    if (need_type(stacktype, use_type, -1, cctx) == FAIL)
+		    else if (*p != '=' && need_type(stacktype, member_type, -1,
+								 cctx) == FAIL)
 			goto theend;
 		}
 	    }
-	    else if (*p != '=' && need_type(stacktype, member_type, -1,
-								 cctx) == FAIL)
+	    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 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:
+	    }
+	    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);
+			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;
+			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;
 	}
-    }
-    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
+
+	if (oplen > 0 && *op != '=')
 	{
-	    isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1);
-
-	    if (isn == NULL)
+	    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;
-	    switch (*op)
+
+	    if (*op == '.')
+		generate_instr_drop(cctx, ISN_CONCAT, 1);
+	    else
 	    {
-		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;
+		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) != ']')
+
+	if (has_index)
 	{
-	    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
+	    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;
+	    {
+		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
 	{
-	    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] != ':')
+	    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:
 		    {
-			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()
+			char_u	    *rawname = name + (name[1] == ':' ? 2 : 0);
+			imported_T  *import = NULL;
+			int	    sid = current_sctx.sc_sid;
+			int	    idx;
+
 			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);
+			    import = find_imported(name, 0, cctx);
+			    if (import != NULL)
+				sid = import->imp_sid;
 			}
-			generate_OLDSCRIPT(cctx, ISN_STORES, name_s, 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,
+			    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)
+		    }
+		    break;
+		case dest_local:
+		    if (lvar != NULL)
 		    {
-			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;
+			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);
 		    }
-		    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;
+		    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,12 +6700,22 @@ compile_def_function(ufunc_T *ufunc, int
 			    || find_imported(ea.cmd, len, &cctx) != NULL)
 		    {
 			line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx);
-			if (line == NULL)
+			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,6 +6781,8 @@ compile_def_function(ufunc_T *ufunc, int
 	    case CMD_let:
 	    case CMD_const:
 		    line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
+		    if (line == p)
+			line = NULL;
 		    break;
 
 	    case CMD_unlet:
@@ -6957,6 +7094,7 @@ delete_instr(isn_T *isn)
 	case ISN_EXECUTE:
 	case ISN_FOR:
 	case ISN_INDEX:
+	case ISN_GETITEM:
 	case ISN_MEMBER:
 	case ISN_JUMP:
 	case ISN_LOAD:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2114,6 +2114,31 @@ call_def_function(
 		}
 		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,6 +2814,8 @@ ex_disassemble(exarg_T *eap)
 	    // 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;