From: Yegappan Lakshmanan Date: Wed, 18 Dec 2024 19:16:20 +0000 (+0100) Subject: patch 9.1.0943: Vim9: vim9compile.c can be further improved X-Git-Tag: v9.1.0943^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d0186c54c2bbcc548c3a30adac339818903bdd4d;p=thirdparty%2Fvim.git patch 9.1.0943: Vim9: vim9compile.c can be further improved Problem: vim9compile.c can be further improved Solution: Refactor the compile_lhs function (Yegappan Lakshmanan) closes: #16245 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt --- diff --git a/src/version.c b/src/version.c index 8618756896..8bd59e3d95 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 943, /**/ 942, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index b8129895db..3666bb35cc 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1643,30 +1643,32 @@ lhs_class_member_modifiable(lhs_T *lhs, char_u *var_start, cctx_T *cctx) } /* - * Figure out the LHS type and other properties for an assignment or one item - * of ":unlet" with an index. - * Returns OK or FAIL. + * Initialize "lhs" with default values */ - int -compile_lhs( - char_u *var_start, - lhs_T *lhs, - cmdidx_T cmdidx, - int heredoc, - int has_cmd, // "var" before "var_start" - int oplen, - cctx_T *cctx) + static void +lhs_init_defaults(lhs_T *lhs) { - 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; lhs->lhs_member_idx = -1; +} - // "dest_end" is the end of the destination, including "[expr]" or +/* + * When compiling a LHS variable name, find the end of the destination and the + * end of the variable name. + */ + static int +lhs_find_var_end( + lhs_T *lhs, + char_u *var_start, + int is_decl, + char_u **var_endp) +{ + char_u *var_end = *var_endp; + + // "lhs_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); @@ -1685,11 +1687,35 @@ compile_lhs( // "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[-1] == ':') --lhs->lhs_dest_end; if (is_decl && var_end == var_start + 2 && var_end[-1] == ':') --var_end; + lhs->lhs_end = lhs->lhs_dest_end; + *var_endp = var_end; + + return OK; +} + +/* + * Set various fields in "lhs" + */ + static int +lhs_init( + lhs_T *lhs, + char_u *var_start, + int is_decl, + int heredoc, + char_u **var_endp) +{ + char_u *var_end = *var_endp; + + lhs_init_defaults(lhs); + + // Find the end of the variable and the destination + if (lhs_find_var_end(lhs, var_start, is_decl, &var_end) == FAIL) + return FAIL; // compute the length of the destination without "[expr]" or ".name" lhs->lhs_varlen = var_end - var_start; @@ -1702,396 +1728,602 @@ compile_lhs( // Something follows after the variable: "var[idx]" or "var.key". lhs->lhs_has_index = TRUE; - if (heredoc) - lhs->lhs_type = &t_list_string; + lhs->lhs_type = heredoc ? &t_list_string : &t_any; + + *var_endp = var_end; + + return OK; +} + +/* + * Compile a LHS class variable name. + */ + static int +compile_lhs_class_variable( + cctx_T *cctx, + lhs_T *lhs, + class_T *defcl, + int is_decl) +{ + if (cctx->ctx_ufunc->uf_defclass != defcl) + { + // A class variable can be accessed without the class name + // only inside a class. + semsg(_(e_class_variable_str_accessible_only_inside_class_str), + lhs->lhs_name, defcl->class_name); + return FAIL; + } + + if (is_decl) + { + semsg(_(e_variable_already_declared_in_class_str), lhs->lhs_name); + return FAIL; + } + + ocmember_T *m = &defcl->class_class_members[lhs->lhs_classmember_idx]; + if (oc_var_check_ro(defcl, m)) + return FAIL; + + lhs->lhs_dest = dest_class_member; + // The class variable is defined either in the current class or + // in one of the parent class in the hierarchy. + lhs->lhs_class = defcl; + lhs->lhs_type = oc_member_type_by_idx(defcl, FALSE, + lhs->lhs_classmember_idx); + + return OK; +} + +/* + * Compile an imported LHS variable + */ + static int +compile_lhs_import_var( + lhs_T *lhs, + imported_T *import, + char_u *var_start, + char_u **var_endp, + char_u **rawnamep) +{ + char_u *var_end = *var_endp; + char_u *dot = vim_strchr(var_start, '.'); + char_u *p; + + // for an import the name is what comes after the dot + if (dot == NULL) + { + semsg(_(e_no_dot_after_imported_name_str), var_start); + return FAIL; + } + + p = skipwhite(dot + 1); + var_end = to_name_end(p, TRUE); + if (var_end == p) + { + semsg(_(e_missing_name_after_imported_name_str), var_start); + return FAIL; + } + + vim_free(lhs->lhs_name); + lhs->lhs_varlen = var_end - p; + lhs->lhs_name = vim_strnsave(p, lhs->lhs_varlen); + if (lhs->lhs_name == NULL) + return FAIL; + *rawnamep = lhs->lhs_name; + lhs->lhs_scriptvar_sid = import->imp_sid; + + // TODO: where do we check this name is exported? + + // Check if something follows: "exp.var[idx]" or + // "exp.var.key". + lhs->lhs_has_index = lhs->lhs_dest_end > skipwhite(var_end); + + *var_endp = var_end; + + return OK; +} + +/* + * Process a script-local variable when compiling a LHS variable name. + */ + static int +compile_lhs_script_var( + cctx_T *cctx, + lhs_T *lhs, + char_u *var_start, + char_u *var_end, + int is_decl) +{ + int script_namespace = FALSE; + int script_var = FALSE; + imported_T *import; + char_u *var_name; + int var_name_len; + + if (lhs->lhs_varlen > 1 && STRNCMP(var_start, "s:", 2) == 0) + script_namespace = TRUE; + + if (script_namespace) + { + var_name = var_start + 2; + var_name_len = lhs->lhs_varlen - 2; + } else - lhs->lhs_type = &t_any; + { + var_name = var_start; + var_name_len = lhs->lhs_varlen; + } - if (cctx->ctx_skip != SKIP_YES) + if (script_var_exists(var_name, var_name_len, cctx, NULL) == OK) + script_var = TRUE; + + import = find_imported(var_start, lhs->lhs_varlen, FALSE); + + if (script_namespace || script_var || import != NULL) { - int declare_error = FALSE; + char_u *rawname = lhs->lhs_name + (lhs->lhs_name[1] == ':' ? 2 : 0); - if (get_var_dest(lhs->lhs_name, &lhs->lhs_dest, cmdidx, - &lhs->lhs_opt_flags, &lhs->lhs_vimvaridx, - &lhs->lhs_type, cctx) == FAIL) + if (script_namespace && current_script_is_vim9()) + { + semsg(_(e_cannot_use_s_colon_in_vim9_script_str), var_start); return FAIL; - if (lhs->lhs_dest != dest_local - && cmdidx != CMD_const && cmdidx != CMD_final) + } + + if (is_decl) { - // Specific kind of variable recognized. - declare_error = is_decl; + if (script_namespace) + semsg(_(e_cannot_declare_script_variable_in_function_str), + lhs->lhs_name); + else + semsg(_(e_variable_already_declared_in_script_str), + lhs->lhs_name); + return FAIL; } - else + else if (cctx->ctx_ufunc->uf_script_ctx_version == SCRIPT_VERSION_VIM9 + && script_namespace + && !script_var && import == NULL) { - class_T *defcl; + semsg(_(e_unknown_variable_str), lhs->lhs_name); + return FAIL; + } - // No specific kind of variable recognized, just a name. - if (check_reserved_name(lhs->lhs_name, lhs->lhs_has_index - && *var_end == '.') == FAIL) + lhs->lhs_dest = current_script_is_vim9() ? dest_script_v9 : + dest_script; + + // existing script-local variables should have a type + lhs->lhs_scriptvar_sid = current_sctx.sc_sid; + if (import != NULL) + { + if (compile_lhs_import_var(lhs, import, var_start, &var_end, + &rawname) == FAIL) return FAIL; + } - if (lookup_local(var_start, lhs->lhs_varlen, - &lhs->lhs_local_lvar, cctx) == OK) - { - lhs->lhs_lvar = &lhs->lhs_local_lvar; - } - else + if (SCRIPT_ID_VALID(lhs->lhs_scriptvar_sid)) + { + // Check writable only when no index follows. + lhs->lhs_scriptvar_idx = get_script_item_idx( + lhs->lhs_scriptvar_sid, rawname, + lhs->lhs_has_index ? ASSIGN_FINAL : + ASSIGN_CONST, cctx, NULL); + if (lhs->lhs_scriptvar_idx >= 0) { - 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; - } - } + scriptitem_T *si = SCRIPT_ITEM(lhs->lhs_scriptvar_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + lhs->lhs_scriptvar_idx; - if (lhs->lhs_lvar != NULL) - { - if (is_decl) - { - // if we come here with what looks like an assignment like - // .= but which has been rejected by assignment_len() from - // may_compile_assignment give a better error message - char_u *p = skipwhite(lhs->lhs_end); - if (p[0] == '.' && p[1] == '=') - emsg(_(e_dot_equal_not_supported_with_script_version_two)); - else if (p[0] == ':') - // type specified in a non-var assignment - semsg(_(e_trailing_characters_str), p); - else - semsg(_(e_variable_already_declared_str), lhs->lhs_name); - return FAIL; - } + lhs->lhs_type = sv->sv_type; } - else if ((lhs->lhs_classmember_idx = cctx_class_member_idx( - cctx, var_start, lhs->lhs_varlen, &defcl)) >= 0) - { - if (cctx->ctx_ufunc->uf_defclass != defcl) - { - // A class variable can be accessed without the class name - // only inside a class. - semsg(_(e_class_variable_str_accessible_only_inside_class_str), - lhs->lhs_name, defcl->class_name); - return FAIL; - } - if (is_decl) - { - semsg(_(e_variable_already_declared_in_class_str), - lhs->lhs_name); - return FAIL; - } + } - ocmember_T *m = - &defcl->class_class_members[lhs->lhs_classmember_idx]; - if (oc_var_check_ro(defcl, m)) - return FAIL; + return OK; + } - lhs->lhs_dest = dest_class_member; - // The class variable is defined either in the current class or - // in one of the parent class in the hierarchy. - lhs->lhs_class = defcl; - lhs->lhs_type = oc_member_type_by_idx(defcl, FALSE, - lhs->lhs_classmember_idx); - } - 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, - cctx, NULL) - : script_var_exists(var_start, lhs->lhs_varlen, - cctx, NULL)) == OK; - imported_T *import = - find_imported(var_start, lhs->lhs_varlen, FALSE); - - if (script_namespace || script_var || import != NULL) - { - char_u *rawname = lhs->lhs_name - + (lhs->lhs_name[1] == ':' ? 2 : 0); + return check_defined(var_start, lhs->lhs_varlen, cctx, NULL, FALSE); +} - if (script_namespace && current_script_is_vim9()) - { - semsg(_(e_cannot_use_s_colon_in_vim9_script_str), - var_start); - return FAIL; - } - if (is_decl) - { - if (script_namespace) - semsg(_(e_cannot_declare_script_variable_in_function_str), - lhs->lhs_name); - else - semsg(_(e_variable_already_declared_in_script_str), - 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; - } +/* + * Compile the LHS destination. + */ + static int +compile_lhs_var_dest( + cctx_T *cctx, + lhs_T *lhs, + int cmdidx, + char_u *var_start, + char_u *var_end, + int is_decl) +{ + int declare_error = FALSE; - lhs->lhs_dest = current_script_is_vim9() - ? dest_script_v9 : dest_script; + 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; - // existing script-local variables should have a type - lhs->lhs_scriptvar_sid = current_sctx.sc_sid; - if (import != NULL) - { - char_u *dot = vim_strchr(var_start, '.'); - char_u *p; + if (lhs->lhs_dest != dest_local && cmdidx != CMD_const + && cmdidx != CMD_final) + { + // Specific kind of variable recognized. + declare_error = is_decl; + } + else + { + class_T *defcl; - // for an import the name is what comes after the dot - if (dot == NULL) - { - semsg(_(e_no_dot_after_imported_name_str), - var_start); - return FAIL; - } - p = skipwhite(dot + 1); - var_end = to_name_end(p, TRUE); - if (var_end == p) - { - semsg(_(e_missing_name_after_imported_name_str), - var_start); - return FAIL; - } - vim_free(lhs->lhs_name); - lhs->lhs_varlen = var_end - p; - lhs->lhs_name = vim_strnsave(p, lhs->lhs_varlen); - if (lhs->lhs_name == NULL) - return FAIL; - rawname = lhs->lhs_name; - lhs->lhs_scriptvar_sid = import->imp_sid; - // TODO: where do we check this name is exported? - - // Check if something follows: "exp.var[idx]" or - // "exp.var.key". - lhs->lhs_has_index = lhs->lhs_dest_end - > skipwhite(var_end); - } - if (SCRIPT_ID_VALID(lhs->lhs_scriptvar_sid)) - { - // Check writable only when no index follows. - lhs->lhs_scriptvar_idx = get_script_item_idx( - lhs->lhs_scriptvar_sid, rawname, - lhs->lhs_has_index ? ASSIGN_FINAL : ASSIGN_CONST, - cctx, NULL); - 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, - NULL, FALSE) == FAIL) - return FAIL; - } - } + // No specific kind of variable recognized, just a name. + if (check_reserved_name(lhs->lhs_name, lhs->lhs_has_index + && *var_end == '.') == FAIL) + return FAIL; - if (declare_error) + if (lookup_local(var_start, lhs->lhs_varlen, &lhs->lhs_local_lvar, + cctx) == OK) { - vim9_declare_error(lhs->lhs_name); - return FAIL; + lhs->lhs_lvar = &lhs->lhs_local_lvar; } - } - - // handle "a:name" as a name, not index "name" in "a" - if (lhs->lhs_varlen > 1 || var_start[lhs->lhs_varlen] != ':') - var_end = lhs->lhs_dest_end; - - if (lhs->lhs_dest != dest_option && lhs->lhs_dest != dest_func_option) - { - if (is_decl && *skipwhite(var_end) == ':') + else { - char_u *p; - - // parse optional type: "let var: type = expr" - if (VIM_ISWHITE(*var_end)) + 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) { - semsg(_(e_no_white_space_allowed_before_colon_str), var_end); - return FAIL; + if (is_decl) + { + semsg(_(e_str_is_used_as_argument), lhs->lhs_name); + return FAIL; + } + lhs->lhs_lvar = &lhs->lhs_arg_lvar; } - if (!VIM_ISWHITE(var_end[1])) + } + + if (lhs->lhs_lvar != NULL) + { + if (is_decl) { - semsg(_(e_white_space_required_after_str_str), ":", var_end); + // if we come here with what looks like an assignment like + // .= but which has been rejected by assignment_len() from + // may_compile_assignment give a better error message + char_u *p = skipwhite(lhs->lhs_end); + if (p[0] == '.' && p[1] == '=') + emsg(_(e_dot_equal_not_supported_with_script_version_two)); + else if (p[0] == ':') + // type specified in a non-var assignment + semsg(_(e_trailing_characters_str), p); + else + semsg(_(e_variable_already_declared_str), lhs->lhs_name); return FAIL; } - p = skipwhite(var_end + 1); - lhs->lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE); - if (lhs->lhs_type == NULL) + } + else if ((lhs->lhs_classmember_idx = cctx_class_member_idx( + cctx, var_start, lhs->lhs_varlen, &defcl)) >= 0) + { + if (compile_lhs_class_variable(cctx, lhs, defcl, is_decl) + == FAIL) + return FAIL; + } + else + { + if (compile_lhs_script_var(cctx, lhs, var_start, var_end, + is_decl) == FAIL) return FAIL; - lhs->lhs_has_type = TRUE; - lhs->lhs_end = p; } - 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_has_index - && lhs->lhs_type->tt_type != VAR_STRING - && lhs->lhs_type->tt_type != VAR_ANY) + if (declare_error) { - emsg(_(e_can_only_concatenate_to_string)); + vim9_declare_error(lhs->lhs_name); return FAIL; } - if (lhs->lhs_lvar == NULL && lhs->lhs_dest == dest_local - && cctx->ctx_skip != SKIP_YES) + return OK; +} + +/* + * When compiling a LHS variable name, for a class or an object, set the LHS + * member type. + */ + static int +compile_lhs_set_oc_member_type( + cctx_T *cctx, + lhs_T *lhs, + char_u *var_start) +{ + class_T *cl = lhs->lhs_type->tt_class; + int is_object = lhs->lhs_type->tt_type == VAR_OBJECT; + char_u *name = var_start + lhs->lhs_varlen + 1; + size_t namelen = lhs->lhs_end - var_start - lhs->lhs_varlen - 1; + + ocmember_T *m = member_lookup(cl, lhs->lhs_type->tt_type, + name, namelen, &lhs->lhs_member_idx); + if (m == NULL) + { + member_not_found_msg(cl, lhs->lhs_type->tt_type, name, namelen); + return FAIL; + } + + if (IS_ENUM(cl)) { - if (oplen > 1 && !heredoc) + if (!inside_class(cctx, cl)) { - // +=, /=, etc. require an existing variable - semsg(_(e_cannot_use_operator_on_new_variable_str), lhs->lhs_name); + semsg(_(e_enumvalue_str_cannot_be_modified), + cl->class_name, m->ocm_name); return FAIL; } - if (!is_decl || (lhs->lhs_has_index && !has_cmd - && cctx->ctx_skip != SKIP_YES)) + if (lhs->lhs_type->tt_type == VAR_OBJECT && + lhs->lhs_member_idx < 2) { - semsg(_(e_unknown_variable_str), lhs->lhs_name); + char *msg = lhs->lhs_member_idx == 0 ? + e_enum_str_name_cannot_be_modified : + e_enum_str_ordinal_cannot_be_modified; + semsg(_(msg), cl->class_name); return FAIL; } + } - // Check the name is valid for a funcref. - 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; - - // New local variable. - int assign = cmdidx == CMD_final ? ASSIGN_FINAL - : cmdidx == CMD_const ? ASSIGN_CONST : ASSIGN_VAR; - lhs->lhs_lvar = reserve_local(cctx, var_start, lhs->lhs_varlen, - assign, lhs->lhs_type); - if (lhs->lhs_lvar == NULL) - return FAIL; - lhs->lhs_new_local = TRUE; + // If it is private member variable, then accessing it outside the + // class is not allowed. + // If it is a read only class variable, then it can be modified + // only inside the class where it is defined. + if ((m->ocm_access != VIM_ACCESS_ALL) && + ((is_object && !inside_class(cctx, cl)) + || (!is_object && cctx->ctx_ufunc->uf_class != cl))) + { + char *msg = (m->ocm_access == VIM_ACCESS_PRIVATE) + ? e_cannot_access_protected_variable_str + : e_variable_is_not_writable_str; + emsg_var_cl_define(msg, m->ocm_name, 0, cl); + return FAIL; } - lhs->lhs_member_type = lhs->lhs_type; - if (lhs->lhs_has_index) + if (!IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc) + && oc_var_check_ro(cl, m)) + return FAIL; + + lhs->lhs_member_type = m->ocm_type; + + return OK; +} + +/* + * When compiling a LHS variable, set the LHS variable type. + */ + static int +compile_lhs_set_type(cctx_T *cctx, lhs_T *lhs, char_u *var_end, int is_decl) +{ + if (is_decl && *skipwhite(var_end) == ':') { - char_u *after = var_start + lhs->lhs_varlen; - char_u *p; + char_u *p; - // Something follows after the variable: "var[idx]" or "var.key". - if (is_decl && cctx->ctx_skip != SKIP_YES) + // parse optional type: "let var: type = expr" + if (VIM_ISWHITE(*var_end)) { - if (has_cmd) - emsg(_(e_cannot_use_index_when_declaring_variable)); - else - semsg(_(e_unknown_variable_str), lhs->lhs_name); + semsg(_(e_no_white_space_allowed_before_colon_str), var_end); return FAIL; } - // Now: var_start[lhs->lhs_varlen] is '[' or '.' - // 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 != '.') - { - lhs->lhs_varlen_total = p - var_start; - break; - } - after = p; - } - if (after > var_start + lhs->lhs_varlen) + if (!VIM_ISWHITE(var_end[1])) { - 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; + semsg(_(e_white_space_required_after_str_str), ":", var_end); + return FAIL; } - int use_class = lhs->lhs_type != NULL - && (lhs->lhs_type->tt_type == VAR_CLASS - || lhs->lhs_type->tt_type == VAR_OBJECT); - if (lhs->lhs_type == NULL - || (use_class ? lhs->lhs_type->tt_class == NULL - : lhs->lhs_type->tt_member == NULL)) + 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; + lhs->lhs_end = p; + } + else if (lhs->lhs_lvar != NULL) + lhs->lhs_type = lhs->lhs_lvar->lv_type; + + return OK; +} + +/* + * Returns TRUE if "lhs" is a concatenable string. + */ + static int +lhs_concatenable(lhs_T *lhs) +{ + return lhs->lhs_dest == dest_global + || lhs->lhs_has_index + || lhs->lhs_type->tt_type == VAR_STRING + || lhs->lhs_type->tt_type == VAR_ANY; +} + +/* + * Create a new local variable when compiling a LHS variable. + */ + static int +compile_lhs_new_local_var( + cctx_T *cctx, + lhs_T *lhs, + char_u *var_start, + int cmdidx, + int oplen, + int is_decl, + int has_cmd, + int heredoc) +{ + if (oplen > 1 && !heredoc) + { + // +=, /=, etc. require an existing variable + semsg(_(e_cannot_use_operator_on_new_variable_str), lhs->lhs_name); + return FAIL; + } + + if (!is_decl || (lhs->lhs_has_index && !has_cmd + && cctx->ctx_skip != SKIP_YES)) + { + semsg(_(e_unknown_variable_str), lhs->lhs_name); + return FAIL; + } + + // Check the name is valid for a funcref. + if (lhs->lhs_type->tt_type == VAR_FUNC + || lhs->lhs_type->tt_type == VAR_PARTIAL) + { + if (var_wrong_func_name(lhs->lhs_name, TRUE)) + return FAIL; + } + + // New local variable. + int assign; + switch (cmdidx) + { + case CMD_final: + assign = ASSIGN_FINAL; break; + case CMD_const: + assign = ASSIGN_CONST; break; + default: + assign = ASSIGN_VAR; break; + } + + lhs->lhs_lvar = reserve_local(cctx, var_start, lhs->lhs_varlen, assign, + lhs->lhs_type); + if (lhs->lhs_lvar == NULL) + return FAIL; + + lhs->lhs_new_local = TRUE; + + return OK; +} + +/* + * When compiling a LHS variable name, set the LHS member type. + */ + static int +compile_lhs_set_member_type( + cctx_T *cctx, + lhs_T *lhs, + char_u *var_start, + int is_decl, + int has_cmd) +{ + lhs->lhs_member_type = lhs->lhs_type; + + if (!lhs->lhs_has_index) + return OK; + + char_u *after = var_start + lhs->lhs_varlen; + char_u *p; + + // Something follows after the variable: "var[idx]" or "var.key". + if (is_decl && cctx->ctx_skip != SKIP_YES) + { + if (has_cmd) + emsg(_(e_cannot_use_index_when_declaring_variable)); + else + semsg(_(e_unknown_variable_str), lhs->lhs_name); + return FAIL; + } + + // Now: var_start[lhs->lhs_varlen] is '[' or '.' + // 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 != '.') { - lhs->lhs_member_type = &t_any; + lhs->lhs_varlen_total = p - var_start; + break; } - else if (use_class) - { - // for an object or class member get the type of the member - class_T *cl = lhs->lhs_type->tt_class; - int is_object = lhs->lhs_type->tt_type == VAR_OBJECT; - char_u *name = var_start + lhs->lhs_varlen + 1; - size_t namelen = lhs->lhs_end - var_start - lhs->lhs_varlen - 1; - - ocmember_T *m = member_lookup(cl, lhs->lhs_type->tt_type, - name, namelen, &lhs->lhs_member_idx); - if (m == NULL) - { - member_not_found_msg(cl, lhs->lhs_type->tt_type, name, namelen); - return FAIL; - } + 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; + } - if (IS_ENUM(cl)) - { - if (!inside_class(cctx, cl)) - { - semsg(_(e_enumvalue_str_cannot_be_modified), - cl->class_name, m->ocm_name); - return FALSE; - } - if (lhs->lhs_type->tt_type == VAR_OBJECT && - lhs->lhs_member_idx < 2) - { - char *msg = lhs->lhs_member_idx == 0 ? - e_enum_str_name_cannot_be_modified : - e_enum_str_ordinal_cannot_be_modified; - semsg(_(msg), cl->class_name); - return FALSE; - } - } + int use_class = lhs->lhs_type != NULL + && (lhs->lhs_type->tt_type == VAR_CLASS + || lhs->lhs_type->tt_type == VAR_OBJECT); - // If it is private member variable, then accessing it outside the - // class is not allowed. - // If it is a read only class variable, then it can be modified - // only inside the class where it is defined. - if ((m->ocm_access != VIM_ACCESS_ALL) && - ((is_object && !inside_class(cctx, cl)) - || (!is_object && cctx->ctx_ufunc->uf_class != cl))) - { - char *msg = (m->ocm_access == VIM_ACCESS_PRIVATE) - ? e_cannot_access_protected_variable_str - : e_variable_is_not_writable_str; - emsg_var_cl_define(msg, m->ocm_name, 0, cl); - return FAIL; - } + if (lhs->lhs_type == NULL + || (use_class ? lhs->lhs_type->tt_class == NULL + : lhs->lhs_type->tt_member == NULL)) + { + lhs->lhs_member_type = &t_any; + } + else if (use_class) + { + // for an object or class member get the type of the member + if (compile_lhs_set_oc_member_type(cctx, lhs, var_start) == FAIL) + return FAIL; + } + else + lhs->lhs_member_type = lhs->lhs_type->tt_member; - if (!IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc) - && oc_var_check_ro(cl, m)) - return FAIL; + return OK; +} - lhs->lhs_member_type = m->ocm_type; - } - else - lhs->lhs_member_type = lhs->lhs_type->tt_member; +/* + * Figure out the LHS type and other properties for an assignment or one item + * of ":unlet" with an index. + * Returns OK or FAIL. + */ + int +compile_lhs( + char_u *var_start, + lhs_T *lhs, + cmdidx_T cmdidx, + int heredoc, + int has_cmd, // "var" before "var_start" + int oplen, + cctx_T *cctx) +{ + char_u *var_end; + int is_decl = is_decl_command(cmdidx); + + if (lhs_init(lhs, var_start, is_decl, heredoc, &var_end) == FAIL) + return FAIL; + + if (cctx->ctx_skip != SKIP_YES) + { + // compile the LHS destination + if (compile_lhs_var_dest(cctx, lhs, cmdidx, var_start, var_end, + is_decl) == FAIL) + return FAIL; + } + + // handle "a:name" as a name, not index "name" in "a" + if (lhs->lhs_varlen > 1 || var_start[lhs->lhs_varlen] != ':') + var_end = lhs->lhs_dest_end; + + if (lhs->lhs_dest != dest_option && lhs->lhs_dest != dest_func_option) + { + // set the LHS variable type + if (compile_lhs_set_type(cctx, lhs, var_end, is_decl) == FAIL) + return FAIL; + } + + if (oplen == 3 && !heredoc && !lhs_concatenable(lhs)) + { + 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 (compile_lhs_new_local_var(cctx, lhs, var_start, cmdidx, oplen, + is_decl, has_cmd, heredoc) == FAIL) + return FAIL; + } + + if (compile_lhs_set_member_type(cctx, lhs, var_start, is_decl, has_cmd) + == FAIL) + return FAIL; + return OK; }