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 <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/vim9compile.c b/src/vim9compile.c
index b812989..3666bb3 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1643,30 +1643,32 @@
 }
 
 /*
- * 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 @@
 
     // "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,213 +1728,571 @@
 	// Something follows after the variable: "var[idx]" or "var.key".
 	lhs->lhs_has_index = TRUE;
 
-    if (heredoc)
-	lhs->lhs_type = &t_list_string;
-    else
-	lhs->lhs_type = &t_any;
+    lhs->lhs_type = heredoc ? &t_list_string : &t_any;
 
-    if (cctx->ctx_skip != SKIP_YES)
+    *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)
     {
-	int	    declare_error = FALSE;
+	// 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 (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
-				 && cmdidx != CMD_const && cmdidx != CMD_final)
+    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
+    {
+	var_name = var_start;
+	var_name_len = lhs->lhs_varlen;
+    }
+
+    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)
+    {
+	char_u *rawname = lhs->lhs_name + (lhs->lhs_name[1] == ':' ? 2 : 0);
+
+	if (script_namespace && current_script_is_vim9())
 	{
-	    // Specific kind of variable recognized.
-	    declare_error = is_decl;
+	    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;
+	}
+
+	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 (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;
+	    }
+	}
+
+	return OK;
+    }
+
+    return check_defined(var_start, lhs->lhs_varlen, cctx, NULL, FALSE);
+}
+
+/*
+ * 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;
+
+    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 && cmdidx != CMD_const
+						&& cmdidx != CMD_final)
+    {
+	// Specific kind of variable recognized.
+	declare_error = is_decl;
+    }
+    else
+    {
+	class_T	*defcl;
+
+	// 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 (lookup_local(var_start, lhs->lhs_varlen, &lhs->lhs_local_lvar,
+								cctx) == OK)
+	{
+	    lhs->lhs_lvar = &lhs->lhs_local_lvar;
 	}
 	else
 	{
-	    class_T	*defcl;
-
-	    // 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 (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)
+	    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)
 		{
-		    // 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);
+		    semsg(_(e_str_is_used_as_argument), lhs->lhs_name);
 		    return FAIL;
 		}
-	    }
-	    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;
-
-		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);
-
-		    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;
-		    }
-
-		    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)
-		    {
-			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;
-			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;
+		lhs->lhs_lvar = &lhs->lhs_arg_lvar;
 	    }
 	}
 
-	if (declare_error)
+	if (lhs->lhs_lvar != NULL)
 	{
-	    vim9_declare_error(lhs->lhs_name);
+	    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;
+	    }
+	}
+	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;
+	}
+    }
+
+    if (declare_error)
+    {
+	vim9_declare_error(lhs->lhs_name);
+	return FAIL;
+    }
+
+    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 (!inside_class(cctx, cl))
+	{
+	    semsg(_(e_enumvalue_str_cannot_be_modified),
+		    cl->class_name, m->ocm_name);
 	    return FAIL;
 	}
+	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 FAIL;
+	}
+    }
+
+    // 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 (!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 *p;
+
+	// parse optional type: "let var: type = expr"
+	if (VIM_ISWHITE(*var_end))
+	{
+	    semsg(_(e_no_white_space_allowed_before_colon_str), var_end);
+	    return FAIL;
+	}
+
+	if (!VIM_ISWHITE(var_end[1]))
+	{
+	    semsg(_(e_white_space_required_after_str_str), ":", var_end);
+	    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;
+	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_varlen_total = p - var_start;
+	    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;
+    }
+
+    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))
+    {
+	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;
+
+    return OK;
+}
+
+/*
+ * 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"
@@ -1917,181 +2301,29 @@
 
     if (lhs->lhs_dest != dest_option && lhs->lhs_dest != dest_func_option)
     {
-	if (is_decl && *skipwhite(var_end) == ':')
-	{
-	    char_u *p;
-
-	    // parse optional type: "let var: type = expr"
-	    if (VIM_ISWHITE(*var_end))
-	    {
-		semsg(_(e_no_white_space_allowed_before_colon_str), var_end);
-		return FAIL;
-	    }
-	    if (!VIM_ISWHITE(var_end[1]))
-	    {
-		semsg(_(e_white_space_required_after_str_str), ":", var_end);
-		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;
-	    lhs->lhs_end = p;
-	}
-	else if (lhs->lhs_lvar != NULL)
-	    lhs->lhs_type = lhs->lhs_lvar->lv_type;
+	// set the LHS variable type
+	if (compile_lhs_set_type(cctx, lhs, var_end, is_decl) == FAIL)
+	    return FAIL;
     }
 
-    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 (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)
+						&& cctx->ctx_skip != SKIP_YES)
     {
-	if (oplen > 1 && !heredoc)
-	{
-	    // +=, /=, etc. require an existing variable
-	    semsg(_(e_cannot_use_operator_on_new_variable_str), lhs->lhs_name);
+	if (compile_lhs_new_local_var(cctx, lhs, var_start, cmdidx, oplen,
+					is_decl, has_cmd, heredoc) == FAIL)
 	    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)
-		&& 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;
     }
 
-    lhs->lhs_member_type = lhs->lhs_type;
-    if (lhs->lhs_has_index)
-    {
-	char_u	*after = var_start + lhs->lhs_varlen;
-	char_u	*p;
+    if (compile_lhs_set_member_type(cctx, lhs, var_start, is_decl, has_cmd)
+								== FAIL)
+	return FAIL;
 
-	// 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_varlen_total = p - var_start;
-		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;
-	}
-
-	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))
-	{
-	    lhs->lhs_member_type = &t_any;
-	}
-	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;
-	    }
-
-	    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;
-		}
-	    }
-
-	    // 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 (!IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc)
-						&& oc_var_check_ro(cl, m))
-		return FAIL;
-
-	    lhs->lhs_member_type = m->ocm_type;
-	}
-	else
-	    lhs->lhs_member_type = lhs->lhs_type->tt_member;
-    }
     return OK;
 }