Patch 8.2.2301
Problem: Vim9: cannot unlet a dict or list item.
Solution: Add ISN_UNLETINDEX. Refactor assignment code to use for unlet.
Files: src/vim9compile.c, src/vim9.h, src/vim9execute.c,
src/testdir/test_vim9_assign.vim
*** ../vim-8.2.2300/src/vim9compile.c 2021-01-04 17:40:08.422342256 +0100
--- src/vim9compile.c 2021-01-04 21:46:42.379615428 +0100
***************
*** 5026,5031 ****
--- 5026,5032 ----
NULL
};
+ // Destination for an assignment or ":unlet" with an index.
typedef enum {
dest_local,
dest_option,
***************
*** 5040,5045 ****
--- 5041,5075 ----
dest_expr,
} assign_dest_T;
+ // Used by compile_lhs() to store information about the LHS of an assignment
+ // and one argument of ":unlet" with an index.
+ typedef struct {
+ assign_dest_T lhs_dest; // type of destination
+
+ char_u *lhs_name; // allocated name including
+ // "[expr]" or ".name".
+ size_t lhs_varlen; // length of the variable without
+ // "[expr]" or ".name"
+ char_u *lhs_dest_end; // end of the destination, including
+ // "[expr]" or ".name".
+
+ int lhs_has_index; // has "[expr]" or ".name"
+
+ int lhs_new_local; // create new local variable
+ int lhs_opt_flags; // for when destination is an option
+ int lhs_vimvaridx; // for when destination is a v:var
+
+ lvar_T lhs_local_lvar; // used for existing local destination
+ lvar_T lhs_arg_lvar; // used for argument destination
+ lvar_T *lhs_lvar; // points to destination lvar
+ int lhs_scriptvar_sid;
+ int lhs_scriptvar_idx;
+
+ int lhs_has_type; // type was specified
+ type_T *lhs_type;
+ type_T *lhs_member_type;
+ } lhs_T;
+
/*
* Generate the load instruction for "name".
*/
***************
*** 5322,5327 ****
--- 5352,5768 ----
return FAIL;
}
+ static int
+ is_decl_command(int cmdidx)
+ {
+ return cmdidx == CMD_let || cmdidx == CMD_var
+ || cmdidx == CMD_final || cmdidx == CMD_const;
+ }
+
+ /*
+ * Figure out the LHS type and other properties for an assignment or one item
+ * of ":unlet" with an index.
+ * Returns OK or FAIL.
+ */
+ static int
+ compile_lhs(
+ char_u *var_start,
+ lhs_T *lhs,
+ int cmdidx,
+ int heredoc,
+ int oplen,
+ cctx_T *cctx)
+ {
+ char_u *var_end;
+ int is_decl = is_decl_command(cmdidx);
+
+ CLEAR_POINTER(lhs);
+ lhs->lhs_dest = dest_local;
+ lhs->lhs_vimvaridx = -1;
+ lhs->lhs_scriptvar_idx = -1;
+
+ // "dest_end" is the end of the destination, including "[expr]" or
+ // ".name".
+ // "var_end" is the end of the variable/option/etc. name.
+ lhs->lhs_dest_end = skip_var_one(var_start, FALSE);
+ if (*var_start == '@')
+ var_end = var_start + 2;
+ else
+ {
+ // skip over the leading "&", "&l:", "&g:" and "$"
+ var_end = skip_option_env_lead(var_start);
+ var_end = to_name_end(var_end, TRUE);
+ }
+
+ // "a: type" is declaring variable "a" with a type, not dict "a:".
+ if (is_decl && lhs->lhs_dest_end == var_start + 2
+ && lhs->lhs_dest_end[-1] == ':')
+ --lhs->lhs_dest_end;
+ if (is_decl && var_end == var_start + 2 && var_end[-1] == ':')
+ --var_end;
+
+ // compute the length of the destination without "[expr]" or ".name"
+ lhs->lhs_varlen = var_end - var_start;
+ lhs->lhs_name = vim_strnsave(var_start, lhs->lhs_varlen);
+ if (lhs->lhs_name == NULL)
+ return FAIL;
+ if (heredoc)
+ lhs->lhs_type = &t_list_string;
+ else
+ lhs->lhs_type = &t_any;
+
+ if (cctx->ctx_skip != SKIP_YES)
+ {
+ int declare_error = FALSE;
+
+ if (get_var_dest(lhs->lhs_name, &lhs->lhs_dest, cmdidx,
+ &lhs->lhs_opt_flags, &lhs->lhs_vimvaridx,
+ &lhs->lhs_type, cctx) == FAIL)
+ return FAIL;
+ if (lhs->lhs_dest != dest_local)
+ {
+ // Specific kind of variable recognized.
+ declare_error = is_decl;
+ }
+ else
+ {
+ int idx;
+
+ // No specific kind of variable recognized, just a name.
+ for (idx = 0; reserved[idx] != NULL; ++idx)
+ if (STRCMP(reserved[idx], lhs->lhs_name) == 0)
+ {
+ semsg(_(e_cannot_use_reserved_name), lhs->lhs_name);
+ return FAIL;
+ }
+
+
+ if (lookup_local(var_start, lhs->lhs_varlen,
+ &lhs->lhs_local_lvar, cctx) == OK)
+ lhs->lhs_lvar = &lhs->lhs_local_lvar;
+ else
+ {
+ CLEAR_FIELD(lhs->lhs_arg_lvar);
+ if (arg_exists(var_start, lhs->lhs_varlen,
+ &lhs->lhs_arg_lvar.lv_idx, &lhs->lhs_arg_lvar.lv_type,
+ &lhs->lhs_arg_lvar.lv_from_outer, cctx) == OK)
+ {
+ if (is_decl)
+ {
+ semsg(_(e_str_is_used_as_argument), lhs->lhs_name);
+ return FAIL;
+ }
+ lhs->lhs_lvar = &lhs->lhs_arg_lvar;
+ }
+ }
+ if (lhs->lhs_lvar != NULL)
+ {
+ if (is_decl)
+ {
+ semsg(_(e_variable_already_declared), lhs->lhs_name);
+ return FAIL;
+ }
+ }
+ else
+ {
+ int script_namespace = lhs->lhs_varlen > 1
+ && STRNCMP(var_start, "s:", 2) == 0;
+ int script_var = (script_namespace
+ ? script_var_exists(var_start + 2, lhs->lhs_varlen - 2,
+ FALSE, cctx)
+ : script_var_exists(var_start, lhs->lhs_varlen,
+ TRUE, cctx)) == OK;
+ imported_T *import =
+ find_imported(var_start, lhs->lhs_varlen, cctx);
+
+ if (script_namespace || script_var || import != NULL)
+ {
+ char_u *rawname = lhs->lhs_name
+ + (lhs->lhs_name[1] == ':' ? 2 : 0);
+
+ if (is_decl)
+ {
+ if (script_namespace)
+
semsg(_(e_cannot_declare_script_variable_in_function),
+ lhs->lhs_name);
+ else
+ semsg(_(e_variable_already_declared_in_script),
+ lhs->lhs_name);
+ return FAIL;
+ }
+ else if (cctx->ctx_ufunc->uf_script_ctx_version
+ == SCRIPT_VERSION_VIM9
+ && script_namespace
+ && !script_var && import == NULL)
+ {
+ semsg(_(e_unknown_variable_str), lhs->lhs_name);
+ return FAIL;
+ }
+
+ lhs->lhs_dest = dest_script;
+
+ // existing script-local variables should have a type
+ lhs->lhs_scriptvar_sid = current_sctx.sc_sid;
+ if (import != NULL)
+ lhs->lhs_scriptvar_sid = import->imp_sid;
+ if (SCRIPT_ID_VALID(lhs->lhs_scriptvar_sid))
+ {
+ lhs->lhs_scriptvar_idx = get_script_item_idx(
+ lhs->lhs_scriptvar_sid,
+ rawname, TRUE, cctx);
+ if (lhs->lhs_scriptvar_idx >= 0)
+ {
+ scriptitem_T *si = SCRIPT_ITEM(
+ lhs->lhs_scriptvar_sid);
+ svar_T *sv =
+ ((svar_T *)si->sn_var_vals.ga_data)
+ + lhs->lhs_scriptvar_idx;
+ lhs->lhs_type = sv->sv_type;
+ }
+ }
+ }
+ else if (check_defined(var_start, lhs->lhs_varlen, cctx)
+ == FAIL)
+ return FAIL;
+ }
+ }
+
+ if (declare_error)
+ {
+ vim9_declare_error(lhs->lhs_name);
+ return FAIL;
+ }
+ }
+
+ // handle "a:name" as a name, not index "name" on "a"
+ if (lhs->lhs_varlen > 1 || var_start[lhs->lhs_varlen] != ':')
+ var_end = lhs->lhs_dest_end;
+
+ if (lhs->lhs_dest != dest_option)
+ {
+ if (is_decl && *var_end == ':')
+ {
+ char_u *p;
+
+ // parse optional type: "let var: type = expr"
+ if (!VIM_ISWHITE(var_end[1]))
+ {
+ semsg(_(e_white_space_required_after_str), ":");
+ return FAIL;
+ }
+ p = skipwhite(var_end + 1);
+ lhs->lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE);
+ if (lhs->lhs_type == NULL)
+ return FAIL;
+ lhs->lhs_has_type = TRUE;
+ }
+ else if (lhs->lhs_lvar != NULL)
+ lhs->lhs_type = lhs->lhs_lvar->lv_type;
+ }
+
+ if (oplen == 3 && !heredoc && lhs->lhs_dest != dest_global
+ && lhs->lhs_type->tt_type != VAR_STRING
+ && lhs->lhs_type->tt_type != VAR_ANY)
+ {
+ emsg(_(e_can_only_concatenate_to_string));
+ return FAIL;
+ }
+
+ if (lhs->lhs_lvar == NULL && lhs->lhs_dest == dest_local
+ && cctx->ctx_skip != SKIP_YES)
+ {
+ if (oplen > 1 && !heredoc)
+ {
+ // +=, /=, etc. require an existing variable
+ semsg(_(e_cannot_use_operator_on_new_variable), lhs->lhs_name);
+ return FAIL;
+ }
+ if (!is_decl)
+ {
+ semsg(_(e_unknown_variable_str), lhs->lhs_name);
+ return FAIL;
+ }
+
+ // new local variable
+ if ((lhs->lhs_type->tt_type == VAR_FUNC
+ || lhs->lhs_type->tt_type == VAR_PARTIAL)
+ && var_wrong_func_name(lhs->lhs_name, TRUE))
+ return FAIL;
+ lhs->lhs_lvar = reserve_local(cctx, var_start, lhs->lhs_varlen,
+ cmdidx == CMD_final || cmdidx == CMD_const, lhs->lhs_type);
+ if (lhs->lhs_lvar == NULL)
+ return FAIL;
+ lhs->lhs_new_local = TRUE;
+ }
+
+ lhs->lhs_member_type = lhs->lhs_type;
+ if (lhs->lhs_dest_end > var_start + lhs->lhs_varlen)
+ {
+ // Something follows after the variable: "var[idx]" or "var.key".
+ // TODO: should we also handle "->func()" here?
+ if (is_decl)
+ {
+ emsg(_(e_cannot_use_index_when_declaring_variable));
+ return FAIL;
+ }
+
+ if (var_start[lhs->lhs_varlen] == '['
+ || var_start[lhs->lhs_varlen] == '.')
+ {
+ char_u *after = var_start + lhs->lhs_varlen;
+ char_u *p;
+
+ // Only the last index is used below, if there are others
+ // before it generate code for the expression. Thus for
+ // "ll[1][2]" the expression is "ll[1]" and "[2]" is the index.
+ for (;;)
+ {
+ p = skip_index(after);
+ if (*p != '[' && *p != '.')
+ break;
+ after = p;
+ }
+ if (after > var_start + lhs->lhs_varlen)
+ {
+ lhs->lhs_varlen = after - var_start;
+ lhs->lhs_dest = dest_expr;
+ // We don't know the type before evaluating the expression,
+ // use "any" until then.
+ lhs->lhs_type = &t_any;
+ }
+
+ lhs->lhs_has_index = TRUE;
+ if (lhs->lhs_type->tt_member == NULL)
+ lhs->lhs_member_type = &t_any;
+ else
+ lhs->lhs_member_type = lhs->lhs_type->tt_member;
+ }
+ else
+ {
+ semsg("Not supported yet: %s", var_start);
+ return FAIL;
+ }
+ }
+ return OK;
+ }
+
+ /*
+ * Assignment to a list or dict member, or ":unlet" for the item, using the
+ * information in "lhs".
+ * Returns OK or FAIL.
+ */
+ static int
+ compile_assign_unlet(
+ char_u *var_start,
+ lhs_T *lhs,
+ int is_assign,
+ type_T *rhs_type,
+ cctx_T *cctx)
+ {
+ char_u *p;
+ int r;
+ vartype_T dest_type;
+ size_t varlen = lhs->lhs_varlen;
+ garray_T *stack = &cctx->ctx_type_stack;
+
+ // Compile the "idx" in "var[idx]" or "key" in "var.key".
+ p = var_start + varlen;
+ if (*p == '[')
+ {
+ p = skipwhite(p + 1);
+ r = compile_expr0(&p, cctx);
+ if (r == OK && *skipwhite(p) != ']')
+ {
+ // this should not happen
+ emsg(_(e_missbrac));
+ r = FAIL;
+ }
+ }
+ else // if (*p == '.')
+ {
+ char_u *key_end = to_name_end(p + 1, TRUE);
+ char_u *key = vim_strnsave(p + 1, key_end - p - 1);
+
+ r = generate_PUSHS(cctx, key);
+ }
+ if (r == FAIL)
+ return FAIL;
+
+ if (lhs->lhs_type == &t_any)
+ {
+ // Index on variable of unknown type: check at runtime.
+ dest_type = VAR_ANY;
+ }
+ else
+ {
+ dest_type = lhs->lhs_type->tt_type;
+ if (dest_type == VAR_DICT && may_generate_2STRING(-1, cctx) == FAIL)
+ return FAIL;
+ if (dest_type == VAR_LIST
+ && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type
+ != VAR_NUMBER)
+ {
+ emsg(_(e_number_exp));
+ return FAIL;
+ }
+ }
+
+ // Load the dict or list. On the stack we then have:
+ // - value (for assignment, not for :unlet)
+ // - index
+ // - variable
+ if (lhs->lhs_dest == dest_expr)
+ {
+ int c = var_start[varlen];
+
+ // Evaluate "ll[expr]" of "ll[expr][idx]"
+ p = var_start;
+ var_start[varlen] = NUL;
+ if (compile_expr0(&p, cctx) == OK && p != var_start + varlen)
+ {
+ // this should not happen
+ emsg(_(e_missbrac));
+ return FAIL;
+ }
+ var_start[varlen] = c;
+
+ lhs->lhs_type = stack->ga_len == 0 ? &t_void
+ : ((type_T **)stack->ga_data)[stack->ga_len - 1];
+ // now we can properly check the type
+ if (lhs->lhs_type->tt_member != NULL && rhs_type != &t_void
+ && need_type(rhs_type, lhs->lhs_type->tt_member, -2, cctx,
+ FALSE, FALSE) == FAIL)
+ return FAIL;
+ }
+ else
+ generate_loadvar(cctx, lhs->lhs_dest, lhs->lhs_name,
+ lhs->lhs_lvar, lhs->lhs_type);
+
+ if (dest_type == VAR_LIST || dest_type == VAR_DICT || dest_type ==
VAR_ANY)
+ {
+ if (is_assign)
+ {
+ isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3);
+
+ if (isn == NULL)
+ return FAIL;
+ isn->isn_arg.vartype = dest_type;
+ }
+ else
+ {
+ if (generate_instr_drop(cctx, ISN_UNLETINDEX, 2) == NULL)
+ return FAIL;
+ }
+ }
+ else
+ {
+ emsg(_(e_indexable_type_required));
+ return FAIL;
+ }
+
+ return OK;
+ }
+
/*
* Compile declaration and assignment:
* "let name"
***************
*** 5342,5362 ****
char_u *ret = NULL;
int var_count = 0;
int var_idx;
- int scriptvar_sid = 0;
- int scriptvar_idx = -1;
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;
type_T *rhs_type = &t_any;
- char_u *name = NULL;
char_u *sp;
! int is_decl = cmdidx == CMD_let || cmdidx == CMD_var
! || cmdidx == CMD_final || cmdidx == CMD_const;
// Skip over the "var" or "[var, var]" to get to any "=".
p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
--- 5783,5798 ----
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 *rhs_type = &t_any;
char_u *sp;
! int is_decl = is_decl_command(cmdidx);
! lhs_T lhs;
// Skip over the "var" or "[var, var]" to get to any "=".
p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
***************
*** 5370,5375 ****
--- 5806,5812 ----
emsg(_(e_cannot_use_list_for_declaration));
return NULL;
}
+ lhs.lhs_name = NULL;
sp = p;
p = skipwhite(p);
***************
*** 5407,5414 ****
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);
--- 5844,5849 ----
***************
*** 5461,5736 ****
var_start = arg;
for (var_idx = 0; var_idx == 0 || var_idx < var_count; var_idx++)
{
- char_u *var_end;
- char_u *dest_end;
- size_t varlen;
- int new_local = FALSE;
- assign_dest_T dest = dest_local;
- int opt_flags = 0;
- int vimvaridx = -1;
- lvar_T local_lvar;
- lvar_T *lvar = NULL;
- lvar_T arg_lvar;
- int has_type = FALSE;
- int has_index = FALSE;
int instr_count = -1;
! // "dest_end" is the end of the destination, including "[expr]" or
! // ".name".
! // "var_end" is the end of the variable/option/etc. name.
! dest_end = skip_var_one(var_start, FALSE);
! if (*var_start == '@')
! var_end = var_start + 2;
! else
! {
! // skip over the leading "&", "&l:", "&g:" and "$"
! var_end = skip_option_env_lead(var_start);
! var_end = to_name_end(var_end, TRUE);
! }
!
! // "a: type" is declaring variable "a" with a type, not dict "a:".
! if (is_decl && dest_end == var_start + 2 && dest_end[-1] == ':')
! --dest_end;
! if (is_decl && var_end == var_start + 2 && var_end[-1] == ':')
! --var_end;
!
! // compute the length of the destination without "[expr]" or ".name"
! varlen = var_end - 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 != SKIP_YES)
! {
! int declare_error = FALSE;
!
! if (get_var_dest(name, &dest, cmdidx, &opt_flags,
! &vimvaridx, &type, cctx) == FAIL)
! goto theend;
! if (dest != dest_local)
! {
! // Specific kind of variable recognized.
! declare_error = is_decl;
! }
! else
! {
! int idx;
!
! // No specific kind of variable recognized, just a name.
! for (idx = 0; reserved[idx] != NULL; ++idx)
! if (STRCMP(reserved[idx], name) == 0)
! {
! semsg(_(e_cannot_use_reserved_name), name);
! goto theend;
! }
!
!
! if (lookup_local(var_start, varlen, &local_lvar, cctx) == OK)
! lvar = &local_lvar;
! else
! {
! CLEAR_FIELD(arg_lvar);
! if (arg_exists(var_start, varlen,
! &arg_lvar.lv_idx, &arg_lvar.lv_type,
! &arg_lvar.lv_from_outer, cctx) == OK)
! {
! if (is_decl)
! {
! semsg(_(e_str_is_used_as_argument), name);
! goto theend;
! }
! lvar = &arg_lvar;
! }
! }
! if (lvar != NULL)
! {
! if (is_decl)
! {
! semsg(_(e_variable_already_declared), name);
! goto theend;
! }
! }
! else
! {
! int script_namespace = varlen > 1
! && STRNCMP(var_start, "s:", 2) == 0;
! int script_var = (script_namespace
! ? script_var_exists(var_start + 2, varlen - 2,
! FALSE, cctx)
! : script_var_exists(var_start, varlen,
! TRUE, cctx)) == OK;
! imported_T *import =
! find_imported(var_start, varlen, cctx);
!
! if (script_namespace || script_var || import != NULL)
! {
! char_u *rawname = name + (name[1] == ':' ? 2 : 0);
!
! if (is_decl)
! {
! if (script_namespace)
!
semsg(_(e_cannot_declare_script_variable_in_function),
! name);
! else
! semsg(_(e_variable_already_declared_in_script),
! name);
! goto theend;
! }
! else if (cctx->ctx_ufunc->uf_script_ctx_version
! == SCRIPT_VERSION_VIM9
! && script_namespace
! && !script_var && import == NULL)
! {
! semsg(_(e_unknown_variable_str), name);
! goto theend;
! }
!
! dest = dest_script;
! // existing script-local variables should have a type
! scriptvar_sid = current_sctx.sc_sid;
! if (import != NULL)
! scriptvar_sid = import->imp_sid;
! if (SCRIPT_ID_VALID(scriptvar_sid))
! {
! scriptvar_idx = get_script_item_idx(scriptvar_sid,
! rawname, TRUE, cctx);
! if (scriptvar_idx >= 0)
! {
! scriptitem_T *si = SCRIPT_ITEM(scriptvar_sid);
! svar_T *sv =
! ((svar_T *)si->sn_var_vals.ga_data)
! + scriptvar_idx;
! type = sv->sv_type;
! }
! }
! }
! else if (check_defined(var_start, varlen, cctx) == FAIL)
! goto theend;
! }
! }
!
! if (declare_error)
! {
! vim9_declare_error(name);
! goto theend;
! }
! }
!
! // handle "a:name" as a name, not index "name" on "a"
! if (varlen > 1 || var_start[varlen] != ':')
! var_end = dest_end;
!
! if (dest != dest_option)
! {
! if (is_decl && *var_end == ':')
! {
! // parse optional type: "let var: type = expr"
! if (!VIM_ISWHITE(var_end[1]))
! {
! semsg(_(e_white_space_required_after_str), ":");
! goto theend;
! }
! p = skipwhite(var_end + 1);
! type = parse_type(&p, cctx->ctx_type_list, TRUE);
! if (type == NULL)
! goto theend;
! 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(_(e_can_only_concatenate_to_string));
goto theend;
- }
! if (lvar == NULL && dest == dest_local && cctx->ctx_skip != SKIP_YES)
{
! if (oplen > 1 && !heredoc)
! {
! // +=, /=, etc. require an existing variable
! semsg(_(e_cannot_use_operator_on_new_variable), name);
! goto theend;
! }
! if (!is_decl)
! {
! semsg(_(e_unknown_variable_str), name);
! goto theend;
! }
!
! // new local variable
! if ((type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
! && var_wrong_func_name(name, TRUE))
! goto theend;
! lvar = reserve_local(cctx, var_start, varlen,
! cmdidx == CMD_final || cmdidx == CMD_const, type);
! if (lvar == NULL)
! goto theend;
! new_local = TRUE;
! }
!
! member_type = type;
! if (dest_end > var_start + varlen)
! {
! // Something follows after the variable: "var[idx]" or "var.key".
! // TODO: should we also handle "->func()" here?
! if (is_decl)
! {
! emsg(_(e_cannot_use_index_when_declaring_variable));
! goto theend;
! }
!
! if (var_start[varlen] == '[' || var_start[varlen] == '.')
! {
! char_u *after = var_start + varlen;
!
! // Only the last index is used below, if there are others
! // before it generate code for the expression. Thus for
! // "ll[1][2]" the expression is "ll[1]" and "[2]" is the index.
! for (;;)
! {
! p = skip_index(after);
! if (*p != '[' && *p != '.')
! break;
! after = p;
! }
! if (after > var_start + varlen)
! {
! varlen = after - var_start;
! dest = dest_expr;
! // We don't know the type before evaluating the expression,
! // use "any" until then.
! type = &t_any;
! }
!
! has_index = TRUE;
! if (type->tt_member == NULL)
! member_type = &t_any;
! else
! member_type = type->tt_member;
! }
! else
! {
! semsg("Not supported yet: %s", var_start);
! goto theend;
! }
! }
! else if (lvar == &arg_lvar)
! {
! semsg(_(e_cannot_assign_to_argument), name);
goto theend;
}
! if (!is_decl && lvar != NULL && lvar->lv_const && !has_index)
{
! semsg(_(e_cannot_assign_to_constant), name);
goto theend;
}
--- 5896,5920 ----
var_start = arg;
for (var_idx = 0; var_idx == 0 || var_idx < var_count; var_idx++)
{
int instr_count = -1;
! vim_free(lhs.lhs_name);
! /*
! * Figure out the LHS type and other properties.
! */
! if (compile_lhs(var_start, &lhs, cmdidx, heredoc, oplen, cctx) == FAIL)
goto theend;
! if (!lhs.lhs_has_index && lhs.lhs_lvar == &lhs.lhs_arg_lvar)
{
! semsg(_(e_cannot_assign_to_argument), lhs.lhs_name);
goto theend;
}
! if (!is_decl && lhs.lhs_lvar != NULL
! && lhs.lhs_lvar->lv_const && !lhs.lhs_has_index)
{
! semsg(_(e_cannot_assign_to_constant), lhs.lhs_name);
goto theend;
}
***************
*** 5758,5766 ****
// 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");
--- 5942,5951 ----
// for "+=", "*=", "..=" etc. first load the current value
if (*op != '=')
{
! generate_loadvar(cctx, lhs.lhs_dest, lhs.lhs_name,
! lhs.lhs_lvar, lhs.lhs_type);
! if (lhs.lhs_has_index)
{
// TODO: get member from list or dict
emsg("Index with operation not supported yet");
***************
*** 5770,5787 ****
// 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;
wp = op + oplen;
if (may_get_next_line_error(wp, &p, cctx) == FAIL)
{
! if (new_local)
++cctx->ctx_locals.ga_len;
goto theend;
}
r = compile_expr0_ext(&p, cctx, &is_const);
! if (new_local)
++cctx->ctx_locals.ga_len;
if (r == FAIL)
goto theend;
--- 5955,5972 ----
// Compile the expression. Temporarily hide the new local
// variable here, it is not available to this expression.
! if (lhs.lhs_new_local)
--cctx->ctx_locals.ga_len;
instr_count = instr->ga_len;
wp = op + oplen;
if (may_get_next_line_error(wp, &p, cctx) == FAIL)
{
! if (lhs.lhs_new_local)
++cctx->ctx_locals.ga_len;
goto theend;
}
r = compile_expr0_ext(&p, cctx, &is_const);
! if (lhs.lhs_new_local)
++cctx->ctx_locals.ga_len;
if (r == FAIL)
goto theend;
***************
*** 5802,5815 ****
rhs_type = stack->ga_len == 0 ? &t_void
: ((type_T **)stack->ga_data)[stack->ga_len - 1];
! if (lvar != NULL && (is_decl || !has_type))
{
if ((rhs_type->tt_type == VAR_FUNC
|| rhs_type->tt_type == VAR_PARTIAL)
! && var_wrong_func_name(name, TRUE))
goto theend;
! if (new_local && !has_type)
{
if (rhs_type->tt_type == VAR_VOID)
{
--- 5987,6000 ----
rhs_type = stack->ga_len == 0 ? &t_void
: ((type_T **)stack->ga_data)[stack->ga_len - 1];
! if (lhs.lhs_lvar != NULL && (is_decl || !lhs.lhs_has_type))
{
if ((rhs_type->tt_type == VAR_FUNC
|| rhs_type->tt_type == VAR_PARTIAL)
! && var_wrong_func_name(lhs.lhs_name, TRUE))
goto theend;
! if (lhs.lhs_new_local && !lhs.lhs_has_type)
{
if (rhs_type->tt_type == VAR_VOID)
{
***************
*** 5821,5849 ****
// An empty list or dict has a &t_unknown member,
// for a variable that implies &t_any.
if (rhs_type == &t_list_empty)
! lvar->lv_type = &t_list_any;
else if (rhs_type == &t_dict_empty)
! lvar->lv_type = &t_dict_any;
else if (rhs_type == &t_unknown)
! lvar->lv_type = &t_any;
else
! lvar->lv_type = rhs_type;
}
}
else if (*op == '=')
{
! type_T *use_type = lvar->lv_type;
// without operator check type here, otherwise below
! if (has_index)
! use_type = member_type;
if (need_type(rhs_type, use_type, -1, cctx,
FALSE, is_const) == FAIL)
goto theend;
}
}
! else if (*p != '=' && need_type(rhs_type, member_type, -1,
! cctx, FALSE, FALSE) == FAIL)
goto theend;
}
else if (cmdidx == CMD_final)
--- 6006,6034 ----
// An empty list or dict has a &t_unknown member,
// for a variable that implies &t_any.
if (rhs_type == &t_list_empty)
! lhs.lhs_lvar->lv_type = &t_list_any;
else if (rhs_type == &t_dict_empty)
! lhs.lhs_lvar->lv_type = &t_dict_any;
else if (rhs_type == &t_unknown)
! lhs.lhs_lvar->lv_type = &t_any;
else
! lhs.lhs_lvar->lv_type = rhs_type;
}
}
else if (*op == '=')
{
! type_T *use_type = lhs.lhs_lvar->lv_type;
// without operator check type here, otherwise below
! if (lhs.lhs_has_index)
! use_type = lhs.lhs_member_type;
if (need_type(rhs_type, use_type, -1, cctx,
FALSE, is_const) == FAIL)
goto theend;
}
}
! else if (*p != '=' && need_type(rhs_type, lhs.lhs_member_type,
! -1, cctx, FALSE, FALSE) == FAIL)
goto theend;
}
else if (cmdidx == CMD_final)
***************
*** 5856,5862 ****
emsg(_(e_const_requires_a_value));
goto theend;
}
! else if (!has_type || dest == dest_option)
{
emsg(_(e_type_or_initialization_required));
goto theend;
--- 6041,6047 ----
emsg(_(e_const_requires_a_value));
goto theend;
}
! else if (!lhs.lhs_has_type || lhs.lhs_dest == dest_option)
{
emsg(_(e_type_or_initialization_required));
goto theend;
***************
*** 5866,5872 ****
// 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);
--- 6051,6057 ----
// variables are always initialized
if (ga_grow(instr, 1) == FAIL)
goto theend;
! switch (lhs.lhs_member_type->tt_type)
{
case VAR_BOOL:
generate_PUSHBOOL(cctx, VVAL_FALSE);
***************
*** 5923,5929 ****
if (*op == '.')
expected = &t_string;
else
! expected = member_type;
stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (
#ifdef FEAT_FLOAT
--- 6108,6114 ----
if (*op == '.')
expected = &t_string;
else
! expected = lhs.lhs_member_type;
stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (
#ifdef FEAT_FLOAT
***************
*** 5942,6098 ****
else if (*op == '+')
{
if (generate_add_instr(cctx,
! operator_type(member_type, stacktype),
! member_type, stacktype) == FAIL)
goto theend;
}
else if (generate_two_op(cctx, op) == FAIL)
goto theend;
}
! if (has_index)
{
! int r;
! vartype_T dest_type;
!
! // Compile the "idx" in "var[idx]" or "key" in "var.key".
! p = var_start + varlen;
! if (*p == '[')
! {
! p = skipwhite(p + 1);
! r = compile_expr0(&p, cctx);
! if (r == OK && *skipwhite(p) != ']')
! {
! // this should not happen
! emsg(_(e_missbrac));
! r = FAIL;
! }
! }
! else // if (*p == '.')
! {
! char_u *key_end = to_name_end(p + 1, TRUE);
! char_u *key = vim_strnsave(p + 1, key_end - p - 1);
!
! r = generate_PUSHS(cctx, key);
! }
! if (r == FAIL)
! goto theend;
!
! if (type == &t_any)
! {
! // Index on variable of unknown type: check at runtime.
! dest_type = VAR_ANY;
! }
! else
! {
! dest_type = type->tt_type;
! if (dest_type == VAR_DICT
! && may_generate_2STRING(-1, cctx) == FAIL)
! goto theend;
! if (dest_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
! if (dest == dest_expr)
! {
! int c = var_start[varlen];
!
! // Evaluate "ll[expr]" of "ll[expr][idx]"
! p = var_start;
! var_start[varlen] = NUL;
! if (compile_expr0(&p, cctx) == OK && p != var_start + varlen)
! {
! // this should not happen
! emsg(_(e_missbrac));
! goto theend;
! }
! var_start[varlen] = c;
!
! type = stack->ga_len == 0 ? &t_void
! : ((type_T **)stack->ga_data)[stack->ga_len - 1];
! // now we can properly check the type
! if (type->tt_member != NULL
! && need_type(rhs_type, type->tt_member, -2, cctx,
! FALSE, FALSE) == FAIL)
! goto theend;
! }
! else
! generate_loadvar(cctx, dest, name, lvar, type);
!
! if (dest_type == VAR_LIST || dest_type == VAR_DICT
! || dest_type == VAR_ANY)
! {
! isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3);
!
! if (isn == NULL)
! goto theend;
! isn->isn_arg.vartype = dest_type;
! }
! else
! {
! emsg(_(e_indexable_type_required));
goto theend;
- }
}
else
{
! if (is_decl && cmdidx == CMD_const
! && (dest == dest_script || dest == dest_local))
// ":const var": lock the value, but not referenced variables
generate_LOCKCONST(cctx);
if (is_decl
! && (type->tt_type == VAR_DICT || type->tt_type == VAR_LIST)
! && type->tt_member != NULL
! && type->tt_member != &t_any
! && type->tt_member != &t_unknown)
// Set the type in the list or dict, so that it can be checked,
// also in legacy script.
! generate_SETTYPE(cctx, type);
! if (dest != dest_local)
{
! if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
! scriptvar_idx, scriptvar_sid, type, name) == FAIL)
goto theend;
}
! else 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);
}
}
if (var_idx + 1 < var_count)
! var_start = skipwhite(dest_end + 1);
}
// for "[var, var] = expr" drop the "expr" value
--- 6127,6202 ----
else if (*op == '+')
{
if (generate_add_instr(cctx,
! operator_type(lhs.lhs_member_type, stacktype),
! lhs.lhs_member_type, stacktype) == FAIL)
goto theend;
}
else if (generate_two_op(cctx, op) == FAIL)
goto theend;
}
! if (lhs.lhs_has_index)
{
! // Use the info in "lhs" to store the value at the index in the
! // list or dict.
! if (compile_assign_unlet(var_start, &lhs, TRUE, rhs_type, cctx)
! == FAIL)
goto theend;
}
else
{
! if (is_decl && cmdidx == CMD_const && (lhs.lhs_dest == dest_script
! || lhs.lhs_dest == dest_local))
// ":const var": lock the value, but not referenced variables
generate_LOCKCONST(cctx);
if (is_decl
! && (lhs.lhs_type->tt_type == VAR_DICT
! || lhs.lhs_type->tt_type == VAR_LIST)
! && lhs.lhs_type->tt_member != NULL
! && lhs.lhs_type->tt_member != &t_any
! && lhs.lhs_type->tt_member != &t_unknown)
// Set the type in the list or dict, so that it can be checked,
// also in legacy script.
! generate_SETTYPE(cctx, lhs.lhs_type);
! if (lhs.lhs_dest != dest_local)
{
! if (generate_store_var(cctx, lhs.lhs_dest,
! lhs.lhs_opt_flags, lhs.lhs_vimvaridx,
! lhs.lhs_scriptvar_idx, lhs.lhs_scriptvar_sid,
! lhs.lhs_type, lhs.lhs_name) == FAIL)
goto theend;
}
! else if (lhs.lhs_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 (!lhs.lhs_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 = lhs.lhs_lvar->lv_idx;
isn->isn_arg.storenr.stnr_val = val;
if (stack->ga_len > 0)
--stack->ga_len;
}
! else if (lhs.lhs_lvar->lv_from_outer)
! generate_STORE(cctx, ISN_STOREOUTER,
! lhs.lhs_lvar->lv_idx, NULL);
else
! generate_STORE(cctx, ISN_STORE, lhs.lhs_lvar->lv_idx, NULL);
}
}
if (var_idx + 1 < var_count)
! var_start = skipwhite(lhs.lhs_dest_end + 1);
}
// for "[var, var] = expr" drop the "expr" value
***************
*** 6105,6111 ****
ret = skipwhite(end);
theend:
! vim_free(name);
return ret;
}
--- 6209,6215 ----
ret = skipwhite(end);
theend:
! vim_free(lhs.lhs_name);
return ret;
}
***************
*** 6137,6176 ****
int deep UNUSED,
void *coookie)
{
! cctx_T *cctx = coookie;
! if (lvp->ll_tv == NULL)
{
! char_u *p = lvp->ll_name;
! int cc = *name_end;
! int ret = OK;
! // Normal name. Only supports g:, w:, t: and b: namespaces.
! *name_end = NUL;
! if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL)
! {
! *name_end = cc;
! goto failed;
! }
! if (*p == '$')
! ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit);
! else if (check_vim9_unlet(p) == FAIL)
ret = FAIL;
else
! ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit);
! *name_end = cc;
! return ret;
}
! failed:
! // TODO: unlet {list}[idx]
! // TODO: unlet {dict}[key]
! // complication: {list} can be global while "idx" is local, thus we can't
! // call ex_unlet().
! emsg("Sorry, :unlet not fully implemented yet");
! return FAIL;
}
/*
--- 6241,6300 ----
int deep UNUSED,
void *coookie)
{
! cctx_T *cctx = coookie;
! char_u *p = lvp->ll_name;
! int cc = *name_end;
! int ret = OK;
!
! if (cctx->ctx_skip == SKIP_YES)
! return OK;
! *name_end = NUL;
! if (*p == '$')
{
! // :unlet $ENV_VAR
! ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit);
! }
! else if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL)
! {
! lhs_T lhs;
! // This is similar to assigning: lookup the list/dict, compile the
! // idx/key. Then instead of storing the value unlet the item.
! // unlet {list}[idx]
! // unlet {dict}[key] dict.key
! //
! // Figure out the LHS type and other properties.
! //
! ret = compile_lhs(p, &lhs, CMD_unlet, FALSE, 0, cctx);
! // : unlet an indexed item
! if (!lhs.lhs_has_index)
! {
! iemsg("called compile_lhs() without an index");
ret = FAIL;
+ }
else
! {
! // Use the info in "lhs" to unlet the item at the index in the
! // list or dict.
! ret = compile_assign_unlet(p, &lhs, FALSE, &t_void, cctx);
! }
! vim_free(lhs.lhs_name);
! }
! else if (check_vim9_unlet(p) == FAIL)
! {
! ret = FAIL;
! }
! else
! {
! // Normal name. Only supports g:, w:, t: and b: namespaces.
! ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit);
}
! *name_end = cc;
! return ret;
}
/*
***************
*** 6188,6194 ****
return NULL;
}
- // TODO: this doesn't work for local variables
ex_unletlock(eap, p, 0, GLV_NO_AUTOLOAD | GLV_COMPILING,
compile_unlet, cctx);
return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd;
--- 6312,6317 ----
***************
*** 8345,8350 ****
--- 8468,8474 ----
case ISN_STRSLICE:
case ISN_THROW:
case ISN_TRY:
+ case ISN_UNLETINDEX:
case ISN_UNPACK:
// nothing allocated
break;
*** ../vim-8.2.2300/src/vim9.h 2021-01-02 15:49:23.702765420 +0100
--- src/vim9.h 2021-01-04 20:55:55.978346176 +0100
***************
*** 60,65 ****
--- 60,66 ----
ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name
+ ISN_UNLETINDEX, // unlet item of list or dict
ISN_LOCKCONST, // lock constant value
*** ../vim-8.2.2300/src/vim9execute.c 2021-01-04 16:15:55.066084896 +0100
--- src/vim9execute.c 2021-01-04 21:41:05.168838698 +0100
***************
*** 1783,1788 ****
--- 1783,1792 ----
typval_T *tv_dest = STACK_TV_BOT(-1);
int status = OK;
+ // Stack contains:
+ // -3 value to be stored
+ // -2 index
+ // -1 dict or list
tv = STACK_TV_BOT(-3);
SOURCING_LNUM = iptr->isn_lnum;
if (dest_type == VAR_ANY)
***************
*** 1898,1903 ****
--- 1902,1992 ----
}
break;
+ // unlet item in list or dict variable
+ case ISN_UNLETINDEX:
+ {
+ typval_T *tv_idx = STACK_TV_BOT(-2);
+ typval_T *tv_dest = STACK_TV_BOT(-1);
+ int status = OK;
+
+ // Stack contains:
+ // -2 index
+ // -1 dict or list
+ if (tv_dest->v_type == VAR_DICT)
+ {
+ // unlet a dict item, index must be a string
+ if (tv_idx->v_type != VAR_STRING)
+ {
+ semsg(_(e_expected_str_but_got_str),
+ vartype_name(VAR_STRING),
+ vartype_name(tv_idx->v_type));
+ status = FAIL;
+ }
+ else
+ {
+ dict_T *d = tv_dest->vval.v_dict;
+ char_u *key = tv_idx->vval.v_string;
+ dictitem_T *di = NULL;
+
+ if (key == NULL)
+ key = (char_u *)"";
+ if (d != NULL)
+ di = dict_find(d, key, (int)STRLEN(key));
+ if (di == NULL)
+ {
+ // NULL dict is equivalent to empty dict
+ semsg(_(e_dictkey), key);
+ status = FAIL;
+ }
+ else
+ {
+ // TODO: check for dict or item locked
+ dictitem_remove(d, di);
+ }
+ }
+ }
+ else if (tv_dest->v_type == VAR_LIST)
+ {
+ // unlet a List item, index must be a number
+ if (tv_idx->v_type != VAR_NUMBER)
+ {
+ semsg(_(e_expected_str_but_got_str),
+ vartype_name(VAR_NUMBER),
+ vartype_name(tv_idx->v_type));
+ status = FAIL;
+ }
+ else
+ {
+ list_T *l = tv_dest->vval.v_list;
+ varnumber_T n = tv_idx->vval.v_number;
+ listitem_T *li = NULL;
+
+ li = list_find(l, n);
+ if (li == NULL)
+ {
+ semsg(_(e_listidx), n);
+ status = FAIL;
+ }
+ else
+ // TODO: check for list or item locked
+ listitem_remove(l, li);
+ }
+ }
+ else
+ {
+ status = FAIL;
+ semsg(_(e_cannot_index_str),
+ vartype_name(tv_dest->v_type));
+ }
+
+ clear_tv(tv_idx);
+ clear_tv(tv_dest);
+ ectx.ec_stack.ga_len -= 2;
+ if (status == FAIL)
+ goto on_error;
+ }
+ break;
+
// push constant
case ISN_PUSHNR:
case ISN_PUSHBOOL:
***************
*** 3649,3654 ****
--- 3738,3746 ----
iptr->isn_arg.unlet.ul_forceit ? "!" : "",
iptr->isn_arg.unlet.ul_name);
break;
+ case ISN_UNLETINDEX:
+ smsg("%4d UNLETINDEX", current);
+ break;
case ISN_LOCKCONST:
smsg("%4d LOCKCONST", current);
break;
*** ../vim-8.2.2300/src/testdir/test_vim9_assign.vim 2021-01-04
13:37:50.251107339 +0100
--- src/testdir/test_vim9_assign.vim 2021-01-04 21:54:46.100892865 +0100
***************
*** 1349,1362 ****
assert_false(exists('s:somevar'))
unlet! s:somevar
# can compile unlet before variable exists
! # This doesn't work yet
! #g:someDict = {key: 'val'}
! #var k = 'key'
! #unlet g:someDict[k]
! #assert_equal({}, g:someDict)
! #unlet g:someDict
! #assert_false(exists('g:someDict'))
CheckScriptFailure([
'vim9script',
--- 1349,1395 ----
assert_false(exists('s:somevar'))
unlet! s:somevar
+ # dict unlet
+ var dd = {a: 1, b: 2, c: 3}
+ unlet dd['a']
+ unlet dd.c
+ assert_equal({b: 2}, dd)
+
+ # list unlet
+ var ll = [1, 2, 3, 4]
+ unlet ll[1]
+ unlet ll[-1]
+ assert_equal([1, 3], ll)
+
+ # list of dict unlet
+ var dl = [{a: 1, b: 2}, {c: 3}]
+ unlet dl[0]['b']
+ assert_equal([{a: 1}, {c: 3}], dl)
+
+ CheckDefExecFailure([
+ 'var ll = test_null_list()',
+ 'unlet ll[0]',
+ ], 'E684:')
+ CheckDefExecFailure([
+ 'var ll = [1]',
+ 'unlet ll[2]',
+ ], 'E684:')
+ CheckDefExecFailure([
+ 'var dd = test_null_dict()',
+ 'unlet dd["a"]',
+ ], 'E716:')
+ CheckDefExecFailure([
+ 'var dd = {a: 1}',
+ 'unlet dd["b"]',
+ ], 'E716:')
+
# can compile unlet before variable exists
! g:someDict = {key: 'val'}
! var k = 'key'
! unlet g:someDict[k]
! assert_equal({}, g:someDict)
! unlet g:someDict
! assert_false(exists('g:someDict'))
CheckScriptFailure([
'vim9script',
*** ../vim-8.2.2300/src/version.c 2021-01-04 17:40:08.422342256 +0100
--- src/version.c 2021-01-04 21:56:06.948506161 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2301,
/**/
--
Bare feet magnetize sharp metal objects so they point upward from the
floor -- especially in the dark.
/// Bram Moolenaar -- [email protected] -- 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 ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/202101042057.104KvtaO2549815%40masaka.moolenaar.net.