To: vim_dev@googlegroups.com Subject: Patch 8.2.2301 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///