]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0920: Vim9: compile_assignment() too long v9.1.0920
authorYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 13 Dec 2024 10:54:54 +0000 (11:54 +0100)
committerChristian Brabandt <cb@256bit.org>
Fri, 13 Dec 2024 10:54:54 +0000 (11:54 +0100)
Problem:  Vim9: compile_assignment() too long
Solution: refactor compile_assignment() function and split up into
          smaller parts (Yegappan Lakshmanan)

closes: #16209

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/version.c
src/vim9compile.c

index 50596585c3025f6e289a81663ae1d1741d65ef72..1c38b6211e8d8d791ec04b0163e2fb5de650f5dc 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    920,
 /**/
     919,
 /**/
index e75462f0b4b109ac0a0101a73b420e9605c2e461..64e2195997902754fa4cc0ac8dfaac4b9d836228 100644 (file)
@@ -2391,11 +2391,11 @@ compile_load_lhs_with_index(lhs_T *lhs, char_u *var_start, cctx_T *cctx)
  */
     int
 compile_assign_unlet(
-       char_u  *var_start,
-       lhs_T   *lhs,
-       int     is_assign,
-       type_T  *rhs_type,
-       cctx_T  *cctx)
+    char_u     *var_start,
+    lhs_T      *lhs,
+    int                is_assign,
+    type_T     *rhs_type,
+    cctx_T     *cctx)
 {
     vartype_T  dest_type;
     int                range = FALSE;
@@ -2599,7 +2599,7 @@ push_default_value(
  * Returns OK on success.
  */
     static int
-compile_assignment_obj_new_arg(char_u **argp, cctx_T *cctx)
+compile_assign_obj_new_arg(char_u **argp, cctx_T *cctx)
 {
     char_u *arg = *argp;
 
@@ -2621,17 +2621,57 @@ compile_assignment_obj_new_arg(char_u **argp, cctx_T *cctx)
 }
 
 /*
- * Convert the increment (++) or decrement (--) operator to the corresponding
- * compound operator.
+ * Compile assignment context.  Used when compiling an assignment statement.
+ */
+typedef struct cac_S cac_T;
+struct cac_S
+{
+    cmdidx_T   cac_cmdidx;             // assignment command
+    char_u     *cac_nextc;             // next character to parse
+    lhs_T      cac_lhs;                // lhs of the assignment
+    type_T     *cac_rhs_type;          // rhs type of an assignment
+    char_u     *cac_op;                // assignment operator
+    int                cac_oplen;              // assignment operator length
+    char_u     *cac_var_start;         // start of the variable names
+    char_u     *cac_var_end;           // end of the variable names
+    int                cac_var_count;          // number of variables in assignment
+    int                cac_var_idx;            // variable index in a list
+    int                cac_semicolon;          // semicolon in [var1, var2; var3]
+    garray_T   *cac_instr;
+    int                cac_instr_count;
+    int                cac_incdec;
+    int                cac_did_generate_slice;
+    int                cac_is_decl;
+    int                cac_is_const;
+    int                cac_start_lnum;
+    type_T     *cac_inferred_type;
+    int                cac_skip_store;
+};
+
+/*
+ * Initialize the compile assignment context.
+ */
+    static void
+compile_assign_context_init(cac_T *cac, cctx_T *cctx, int cmdidx, char_u *arg)
+{
+    CLEAR_FIELD(*cac);
+    cac->cac_cmdidx = cmdidx;
+    cac->cac_instr = &cctx->ctx_instr;
+    cac->cac_rhs_type = &t_any;
+    cac->cac_is_decl = is_decl_command(cmdidx);
+    cac->cac_start_lnum = SOURCING_LNUM;
+    cac->cac_instr_count = -1;
+    cac->cac_var_end = arg;
+}
+
+/*
+ * Translate the increment (++) and decrement (--) operators to the
+ * corresponding compound operators (+= or -=).
  *
  * Returns OK on success and FAIL on syntax error.
  */
     static int
-incdec_op_translate(
-    exarg_T    *eap,
-    char_u     **op,
-    int                *oplen,
-    int                *incdec)
+translate_incdec_op(exarg_T *eap, cac_T *cac)
 {
     if (VIM_ISWHITE(eap->cmd[2]))
     {
@@ -2639,9 +2679,56 @@ incdec_op_translate(
                eap->cmdidx == CMD_increment ? "++" : "--", eap->cmd);
        return FAIL;
     }
-    *op = (char_u *)(eap->cmdidx == CMD_increment ? "+=" : "-=");
-    *oplen = 2;
-    *incdec = TRUE;
+    cac->cac_op = (char_u *)(eap->cmdidx == CMD_increment ? "+=" : "-=");
+    cac->cac_oplen = 2;
+    cac->cac_incdec = TRUE;
+
+    return OK;
+}
+
+/*
+ * Process the operator in an assignment statement.
+ */
+    static int
+compile_assign_process_operator(
+    exarg_T    *eap,
+    char_u     *arg,
+    cac_T      *cac,
+    int                *heredoc,
+    char_u     **retstr)
+{
+    *retstr = NULL;
+
+    if (eap->cmdidx == CMD_increment || eap->cmdidx == CMD_decrement)
+    {
+       // Change an unary operator to a compound operator
+       if (translate_incdec_op(eap, cac) == FAIL)
+           return FAIL;
+    }
+    else
+    {
+       char_u  *sp;
+
+       sp = cac->cac_nextc;
+       cac->cac_nextc = skipwhite(cac->cac_nextc);
+       cac->cac_op = cac->cac_nextc;
+       cac->cac_oplen = assignment_len(cac->cac_nextc, heredoc);
+
+       if (cac->cac_var_count > 0 && cac->cac_oplen == 0)
+       {
+           // can be something like "[1, 2]->func()"
+           *retstr = arg;
+           return FAIL;
+       }
+
+       // need white space before and after the operator
+       if (cac->cac_oplen > 0 && (!VIM_ISWHITE(*sp)
+                   || !IS_WHITE_OR_NUL(cac->cac_op[cac->cac_oplen])))
+       {
+           error_white_both(cac->cac_op, cac->cac_oplen);
+           return FAIL;
+       }
+    }
 
     return OK;
 }
@@ -2651,519 +2738,621 @@ incdec_op_translate(
  * beginning of the heredoc content.
  */
     static char_u *
-heredoc_assign_stmt_end_get(char_u *p, exarg_T *eap, cctx_T *cctx)
+parse_heredoc_assignment(exarg_T *eap, cctx_T *cctx, cac_T *cac)
 {
     // [let] varname =<< [trim] {end}
     eap->ea_getline = exarg_getline;
     eap->cookie = cctx;
 
-    list_T *l = heredoc_get(eap, p + 3, FALSE, TRUE);
+    list_T *l = heredoc_get(eap, cac->cac_nextc + 3, FALSE, TRUE);
     if (l == NULL)
        return NULL;
 
     list_free(l);
-    p += STRLEN(p);
+    cac->cac_nextc += STRLEN(cac->cac_nextc);
 
-    return p;
+    return cac->cac_nextc;
 }
 
+/*
+ * Evaluate the expression for "[var, var] = expr" assignment.
+ * A line break may follow the assignment operator "=".
+ */
     static char_u *
-compile_list_assignment(
-    char_u     *p,
-    char_u     *op,
-    int                oplen,
-    int                var_count,
-    int                semicolon,
-    garray_T   *instr,
-    type_T     **rhs_type,
-    cctx_T     *cctx)
+compile_list_assignment_expr(cctx_T *cctx, cac_T *cac)
 {
     char_u *wp;
 
-    // for "[var, var] = expr" evaluate the expression here, loop over the
-    // list of variables below.
-    // A line break may follow the "=".
+    wp = cac->cac_op + cac->cac_oplen;
 
-    wp = op + oplen;
-    if (may_get_next_line_error(wp, &p, cctx) == FAIL)
+    if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
        return NULL;
-    if (compile_expr0(&p, cctx) == FAIL)
+
+    if (compile_expr0(&cac->cac_nextc, cctx) == FAIL)
        return NULL;
 
-    if (cctx->ctx_skip != SKIP_YES)
-    {
-       type_T  *stacktype;
-       int             needed_list_len;
-       int             did_check = FALSE;
+    if (cctx->ctx_skip == SKIP_YES)
+       // no need to parse more when skipping
+       return cac->cac_nextc;
+
+    type_T     *stacktype;
+    int        needed_list_len;
+    int        did_check = FALSE;
 
-       stacktype = cctx->ctx_type_stack.ga_len == 0 ? &t_void
+    stacktype = cctx->ctx_type_stack.ga_len == 0 ? &t_void
                                                : get_type_on_stack(cctx, 0);
-       if (stacktype->tt_type == VAR_VOID)
-       {
-           emsg(_(e_cannot_use_void_value));
-           return NULL;
-       }
-       if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx, FALSE,
-                                                       FALSE) == FAIL)
-           return NULL;
-       // If a constant list was used we can check the length right here.
-       needed_list_len = semicolon ? var_count - 1 : var_count;
-       if (instr->ga_len > 0)
-       {
-           isn_T       *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
+    if (stacktype->tt_type == VAR_VOID)
+    {
+       emsg(_(e_cannot_use_void_value));
+       return NULL;
+    }
 
-           if (isn->isn_type == ISN_NEWLIST)
+    if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx,
+                                               FALSE, FALSE) == FAIL)
+       return NULL;
+
+    // If a constant list was used we can check the length right here.
+    needed_list_len = cac->cac_semicolon
+                           ? cac->cac_var_count - 1
+                           : cac->cac_var_count;
+    if (cac->cac_instr->ga_len > 0)
+    {
+       isn_T   *isn = ((isn_T *)cac->cac_instr->ga_data) +
+           cac->cac_instr->ga_len - 1;
+
+       if (isn->isn_type == ISN_NEWLIST)
+       {
+           did_check = TRUE;
+           if (cac->cac_semicolon ? isn->isn_arg.number <
+                   needed_list_len
+                   : isn->isn_arg.number != needed_list_len)
            {
-               did_check = TRUE;
-               if (semicolon ? isn->isn_arg.number < needed_list_len
-                       : isn->isn_arg.number != needed_list_len)
-               {
-                   semsg(_(e_expected_nr_items_but_got_nr),
-                           needed_list_len, (int)isn->isn_arg.number);
-                   return NULL;
-               }
+               semsg(_(e_expected_nr_items_but_got_nr),
+                       needed_list_len, (int)isn->isn_arg.number);
+               return NULL;
            }
        }
-       if (!did_check)
-           generate_CHECKLEN(cctx, needed_list_len, semicolon);
-       if (stacktype->tt_member != NULL)
-           *rhs_type = stacktype->tt_member;
     }
 
-    return p;
+    if (!did_check)
+       generate_CHECKLEN(cctx, needed_list_len, cac->cac_semicolon);
+
+    if (stacktype->tt_member != NULL)
+       cac->cac_rhs_type = stacktype->tt_member;
+
+    return cac->cac_nextc;
 }
 
 /*
- * Compile declaration and assignment:
- * "let name"
- * "var name = expr"
- * "final name = expr"
- * "const name = expr"
- * "name = expr"
- * "arg" points to "name".
- * "++arg" and "--arg"
- * Return NULL for an error.
- * Return "arg" if it does not look like a variable list.
+ * Find and return the end of a heredoc or a list of variables assignment
+ * statement.  For a single variable assignment statement, returns the current
+ * end.
+ * Returns NULL on failure.
  */
     static char_u *
-compile_assignment(
-       char_u      *arg_start,
-       exarg_T     *eap,
-       cmdidx_T    cmdidx,
-       cctx_T      *cctx)
+compile_assign_compute_end(
+    exarg_T    *eap,
+    cctx_T     *cctx,
+    cac_T      *cac,
+    int                heredoc)
 {
-    char_u     *arg = arg_start;
-    char_u     *var_start;
-    char_u     *p;
-    char_u     *end = arg;
-    char_u     *ret = NULL;
-    int                var_count = 0;
-    int                var_idx;
-    int                semicolon = 0;
-    int                did_generate_slice = FALSE;
-    garray_T   *instr = &cctx->ctx_instr;
-    int                jump_instr_idx = instr->ga_len;
-    char_u     *op;
-    int                oplen = 0;
-    int                heredoc = FALSE;
-    int                incdec = FALSE;
-    type_T     *rhs_type = &t_any;
-    char_u     *sp;
-    int                is_decl = is_decl_command(cmdidx);
-    lhs_T      lhs;
-    CLEAR_FIELD(lhs);
-    long       start_lnum = SOURCING_LNUM;
+    if (heredoc)
+    {
+       cac->cac_nextc = parse_heredoc_assignment(eap, cctx, cac);
+       return cac->cac_nextc;
+    }
+    else if (cac->cac_var_count > 0)
+    {
+       // for "[var, var] = expr" evaluate the expression. The list of
+       // variables are processed later.
+       // A line break may follow the "=".
+       cac->cac_nextc = compile_list_assignment_expr(cctx, cac);
+       return cac->cac_nextc;
+    }
 
-    int        has_arg_is_set_prefix = STRNCMP(arg, "ifargisset ", 11) == 0;
-    if (has_arg_is_set_prefix &&
-           compile_assignment_obj_new_arg(&arg, cctx) == FAIL)
-       goto theend;
+    return cac->cac_var_end;
+}
 
-    // Skip over the "varname" or "[varname, varname]" to get to any "=".
-    p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
-    if (p == NULL)
-       return *arg == '[' ? arg : NULL;
+/*
+ * For "var = expr" evaluate the expression.
+ */
+    static int
+compile_assign_single_eval_expr(cctx_T *cctx, cac_T *cac)
+{
+    int                ret = OK;
+    char_u     *wp;
+    lhs_T      *lhs = &cac->cac_lhs;
 
+    // Compile the expression.
+    if (cac->cac_incdec)
+       return generate_PUSHNR(cctx, 1);
 
-    if (eap->cmdidx == CMD_increment || eap->cmdidx == CMD_decrement)
+    // Temporarily hide the new local variable here, it is
+    // not available to this expression.
+    if (lhs->lhs_new_local)
+       --cctx->ctx_locals.ga_len;
+    wp = cac->cac_op + cac->cac_oplen;
+
+    if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
     {
-       if (incdec_op_translate(eap, &op, &oplen, &incdec) == FAIL)
-           return NULL;
+       if (lhs->lhs_new_local)
+           ++cctx->ctx_locals.ga_len;
+       return FAIL;
+    }
+
+    ret = compile_expr0_ext(&cac->cac_nextc, cctx, &cac->cac_is_const);
+    if (lhs->lhs_new_local)
+       ++cctx->ctx_locals.ga_len;
+
+    return ret;
+}
+
+/*
+ * Compare the LHS type with the RHS type in an assignment.
+ */
+    static int
+compile_assign_check_type(cctx_T *cctx, cac_T *cac)
+{
+    lhs_T      *lhs = &cac->cac_lhs;
+    type_T     *rhs_type;
+
+    rhs_type = cctx->ctx_type_stack.ga_len == 0 ?
+                                       &t_void : get_type_on_stack(cctx, 0);
+    cac->cac_rhs_type = rhs_type;
+
+    if (check_type_is_value(rhs_type) == FAIL)
+       return FAIL;
+
+    if (lhs->lhs_lvar != NULL && (cac->cac_is_decl || !lhs->lhs_has_type))
+    {
+       if ((rhs_type->tt_type == VAR_FUNC
+                   || rhs_type->tt_type == VAR_PARTIAL)
+               && !lhs->lhs_has_index
+               && var_wrong_func_name(lhs->lhs_name, TRUE))
+           return FAIL;
+
+       if (lhs->lhs_new_local && !lhs->lhs_has_type)
+       {
+           if (rhs_type->tt_type == VAR_VOID)
+           {
+               emsg(_(e_cannot_use_void_value));
+               return FAIL;
+           }
+           else
+           {
+               type_T *type;
+
+               // An empty list or dict has a &t_unknown member,
+               // for a variable that implies &t_any.
+               if (rhs_type == &t_list_empty)
+                   type = &t_list_any;
+               else if (rhs_type == &t_dict_empty)
+                   type = &t_dict_any;
+               else if (rhs_type == &t_unknown)
+                   type = &t_any;
+               else
+               {
+                   type = rhs_type;
+                   cac->cac_inferred_type = rhs_type;
+               }
+               set_var_type(lhs->lhs_lvar, type, cctx);
+           }
+       }
+       else if (*cac->cac_op == '=')
+       {
+           type_T *use_type = lhs->lhs_lvar->lv_type;
+           where_T where = WHERE_INIT;
+
+           // Without operator check type here, otherwise below.
+           // Use the line number of the assignment.
+           SOURCING_LNUM = cac->cac_start_lnum;
+           if (cac->cac_var_count > 0)
+           {
+               where.wt_index = cac->cac_var_idx + 1;
+               where.wt_kind = WT_VARIABLE;
+           }
+           // If assigning to a list or dict member, use the
+           // member type.  Not for "list[:] =".
+           if (lhs->lhs_has_index &&
+                   !has_list_index(cac->cac_var_start +
+                       lhs->lhs_varlen, cctx))
+               use_type = lhs->lhs_member_type;
+           if (need_type_where(rhs_type, use_type, FALSE, -1,
+                       where, cctx, FALSE, cac->cac_is_const) == FAIL)
+               return FAIL;
+       }
     }
     else
     {
-       sp = p;
-       p = skipwhite(p);
-       op = p;
-       oplen = assignment_len(p, &heredoc);
+       type_T *lhs_type = lhs->lhs_member_type;
 
-       if (var_count > 0 && oplen == 0)
-           // can be something like "[1, 2]->func()"
-           return arg;
+       // Special case: assigning to @# can use a number or a
+       // string.
+       // Also: can assign a number to a float.
+       if ((lhs_type == &t_number_or_string || lhs_type == &t_float)
+                           && rhs_type->tt_type == VAR_NUMBER)
+           lhs_type = &t_number;
+       if (*cac->cac_nextc != '=' && need_type(rhs_type,
+                   lhs_type, FALSE, -1, 0, cctx, FALSE, FALSE) == FAIL)
+           return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ * Compile the RHS expression in an assignment
+ */
+    static int
+compile_assign_rhs(cctx_T *cctx, cac_T *cac)
+{
+    lhs_T      *lhs = &cac->cac_lhs;
 
-       if (oplen > 0 && (!VIM_ISWHITE(*sp) || !IS_WHITE_OR_NUL(op[oplen])))
+    if (cctx->ctx_skip == SKIP_YES)
+    {
+       if (cac->cac_oplen > 0 && cac->cac_var_count == 0)
        {
-           error_white_both(op, oplen);
-           return NULL;
+           // skip over the "=" and the expression
+           cac->cac_nextc = skipwhite(cac->cac_op + cac->cac_oplen);
+           (void)compile_expr0(&cac->cac_nextc, cctx);
        }
+       return OK;
     }
 
-    if (heredoc)
+    if (cac->cac_oplen > 0)
     {
-       p = heredoc_assign_stmt_end_get(p, eap, cctx);
-       if (p == NULL)
-           return NULL;
-       end = p;
+       cac->cac_is_const = FALSE;
+
+       // for "+=", "*=", "..=" etc. first load the current value
+       if (*cac->cac_op != '='
+               && compile_load_lhs_with_index(&cac->cac_lhs,
+                                               cac->cac_var_start,
+                                               cctx) == FAIL)
+           return FAIL;
+
+       // For "var = expr" evaluate the expression.
+       if (cac->cac_var_count == 0)
+       {
+           int ret;
+
+           // Compile the expression.
+           cac->cac_instr_count = cac->cac_instr->ga_len;
+           ret = compile_assign_single_eval_expr(cctx, cac);
+           if (ret == FAIL)
+               return FAIL;
+       }
+       else if (cac->cac_semicolon &&
+                               cac->cac_var_idx == cac->cac_var_count - 1)
+       {
+           // For "[var; var] = expr" get the rest of the list
+           cac->cac_did_generate_slice = TRUE;
+           if (generate_SLICE(cctx, cac->cac_var_count - 1) == FAIL)
+               return FAIL;
+       }
+       else
+       {
+           // For "[var, var] = expr" get the "var_idx" item from the
+           // list.
+           if (generate_GETITEM(cctx, cac->cac_var_idx,
+                                       *cac->cac_op != '=') == FAIL)
+               return FAIL;
+       }
+
+       if (compile_assign_check_type(cctx, cac) == FAIL)
+           return FAIL;
+
+       return OK;
     }
-    else if (var_count > 0)
+
+    if (cac->cac_cmdidx == CMD_final)
     {
-       // "[var, var] = expr"
-       p = compile_list_assignment(p, op, oplen, var_count, semicolon,
-                                               instr, &rhs_type, cctx);
-       if (p == NULL)
-           goto theend;
-       end = p;
+       emsg(_(e_final_requires_a_value));
+       return FAIL;
     }
 
-    /*
-     * Loop over variables in "[var, var] = expr".
-     * For "var = expr" and "let var: type" this is done only once.
-     */
-    if (var_count > 0)
-       var_start = skipwhite(arg + 1);  // skip over the "["
+    if (cac->cac_cmdidx == CMD_const)
+    {
+       emsg(_(e_const_requires_a_value));
+       return FAIL;
+    }
+
+    if (!lhs->lhs_has_type || lhs->lhs_dest == dest_option
+                                       || lhs->lhs_dest == dest_func_option)
+    {
+       emsg(_(e_type_or_initialization_required));
+       return FAIL;
+    }
+
+    // variables are always initialized
+    if (GA_GROW_FAILS(cac->cac_instr, 1))
+       return FAIL;
+
+    cac->cac_instr_count = cac->cac_instr->ga_len;
+
+    return push_default_value(cctx, lhs->lhs_member_type->tt_type,
+                             lhs->lhs_dest == dest_local,
+                             &cac->cac_skip_store);
+}
+
+/*
+ * Compile a compound op assignment statement (+=, -=, *=, %=, etc.)
+ */
+    static int
+compile_assign_compound_op(cctx_T *cctx, cac_T *cac)
+{
+    lhs_T          *lhs = &cac->cac_lhs;
+    type_T         *expected;
+    type_T         *stacktype = NULL;
+
+    if (*cac->cac_op == '.')
+    {
+       if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
+           return FAIL;
+    }
     else
-       var_start = arg;
-    for (var_idx = 0; var_idx == 0 || var_idx < var_count; var_idx++)
     {
-       int     instr_count = -1;
-       int     save_lnum;
-       int     skip_store = FALSE;
-       type_T  *inferred_type = NULL;
+       expected = lhs->lhs_member_type;
+       stacktype = get_type_on_stack(cctx, 0);
+       if (
+               // If variable is float operation with number is OK.
+               !(expected == &t_float && (stacktype == &t_number
+                                       || stacktype == &t_number_bool))
+               && need_type(stacktype, expected, TRUE, -1, 0, cctx,
+                                       FALSE, FALSE) == FAIL)
+           return FAIL;
+    }
+
+    if (*cac->cac_op == '.')
+    {
+       if (generate_CONCAT(cctx, 2) == FAIL)
+           return FAIL;
+    }
+    else if (*cac->cac_op == '+')
+    {
+       if (generate_add_instr(cctx,
+                   operator_type(lhs->lhs_member_type, stacktype),
+                   lhs->lhs_member_type, stacktype,
+                   EXPR_APPEND) == FAIL)
+           return FAIL;
+    }
+    else if (generate_two_op(cctx, cac->cac_op) == FAIL)
+       return FAIL;
+
+    return OK;
+}
+
+/*
+ * Generate the STORE and SETTYPE instructions for an assignment statement.
+ */
+    static int
+compile_assign_generate_store(cctx_T *cctx, cac_T *cac)
+{
+    lhs_T      *lhs = &cac->cac_lhs;
+    int                save_lnum;
+
+    // Use the line number of the assignment for store instruction.
+    save_lnum = cctx->ctx_lnum;
+    cctx->ctx_lnum = cac->cac_start_lnum - 1;
+
+    if (lhs->lhs_has_index)
+    {
+       // Use the info in "lhs" to store the value at the index in the
+       // list, dict or object.
+       if (compile_assign_unlet(cac->cac_var_start, &cac->cac_lhs,
+                                TRUE, cac->cac_rhs_type, cctx) == FAIL)
+       {
+           cctx->ctx_lnum = save_lnum;
+           return FAIL;
+       }
+    }
+    else
+    {
+       if (cac->cac_is_decl && cac->cac_cmdidx == CMD_const &&
+                       (lhs->lhs_dest == dest_script
+                        || lhs->lhs_dest == dest_script_v9
+                        || lhs->lhs_dest == dest_global
+                        || lhs->lhs_dest == dest_local))
+           // ":const var": lock the value, but not referenced variables
+           generate_LOCKCONST(cctx);
+
+       type_T  *inferred_type = cac->cac_inferred_type;
+
+       if ((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);
+       else if (inferred_type != NULL
+               && (inferred_type->tt_type == VAR_DICT
+                   || inferred_type->tt_type == VAR_LIST)
+               && inferred_type->tt_member != NULL
+               && inferred_type->tt_member != &t_unknown
+               && inferred_type->tt_member != &t_any)
+           // Set the type in the list or dict, so that it can be checked,
+           // also in legacy script.
+           generate_SETTYPE(cctx, inferred_type);
 
-       if (var_start[0] == '_' && !eval_isnamec(var_start[1]))
+       if (!cac->cac_skip_store &&
+               generate_store_lhs(cctx, &cac->cac_lhs,
+                                  cac->cac_instr_count,
+                                  cac->cac_is_decl) == FAIL)
+       {
+           cctx->ctx_lnum = save_lnum;
+           return FAIL;
+       }
+    }
+
+    cctx->ctx_lnum = save_lnum;
+    return OK;
+}
+
+/*
+ * Process the variable(s) in an assignment statement
+ */
+    static int
+compile_assign_process_variables(
+    cctx_T     *cctx,
+    cac_T      *cac,
+    int                cmdidx,
+    int                heredoc,
+    int                has_cmd,
+    int                has_argisset_prefix,
+    int                jump_instr_idx)
+{
+    for (cac->cac_var_idx = 0; cac->cac_var_idx == 0 ||
+           cac->cac_var_idx < cac->cac_var_count; cac->cac_var_idx++)
+    {
+       if (cac->cac_var_start[0] == '_'
+                               && !eval_isnamec(cac->cac_var_start[1]))
        {
            // Ignore underscore in "[a, _, b] = list".
-           if (var_count > 0)
+           if (cac->cac_var_count > 0)
            {
-               var_start = skipwhite(var_start + 2);
+               cac->cac_var_start = skipwhite(cac->cac_var_start + 2);
                continue;
            }
            emsg(_(e_cannot_use_underscore_here));
-           goto theend;
+           return FAIL;
        }
-       vim_free(lhs.lhs_name);
+       vim_free(cac->cac_lhs.lhs_name);
 
        /*
         * Figure out the LHS type and other properties.
         */
-       if (compile_assign_lhs(var_start, &lhs, cmdidx,
-                               is_decl, heredoc, var_start > eap->cmd,
-                               oplen, cctx) == FAIL)
-           goto theend;
+       if (compile_assign_lhs(cac->cac_var_start, &cac->cac_lhs, cmdidx,
+                   cac->cac_is_decl, heredoc, has_cmd,
+                   cac->cac_oplen, cctx) == FAIL)
+           return FAIL;
+
+       // Compile the RHS expression
        if (heredoc)
        {
-           SOURCING_LNUM = start_lnum;
-           if (lhs.lhs_has_type
-                   && need_type(&t_list_string, lhs.lhs_type, FALSE,
-                                           -1, 0, cctx, FALSE, FALSE) == FAIL)
-               goto theend;
+           SOURCING_LNUM = cac->cac_start_lnum;
+           if (cac->cac_lhs.lhs_has_type
+                   && need_type(&t_list_string, cac->cac_lhs.lhs_type,
+                       FALSE, -1, 0, cctx, FALSE, FALSE) == FAIL)
+               return FAIL;
        }
        else
        {
-           if (cctx->ctx_skip == SKIP_YES)
-           {
-               if (oplen > 0 && var_count == 0)
-               {
-                   // skip over the "=" and the expression
-                   p = skipwhite(op + oplen);
-                   (void)compile_expr0(&p, cctx);
-               }
-           }
-           else if (oplen > 0)
-           {
-               int     is_const = FALSE;
-               char_u  *wp;
-
-               // for "+=", "*=", "..=" etc. first load the current value
-               if (*op != '='
-                       && compile_load_lhs_with_index(&lhs, var_start,
-                                                                cctx) == FAIL)
-                   goto theend;
-
-               // For "var = expr" evaluate the expression.
-               if (var_count == 0)
-               {
-                   int r;
-
-                   // Compile the expression.
-                   instr_count = instr->ga_len;
-                   if (incdec)
-                   {
-                       r = generate_PUSHNR(cctx, 1);
-                   }
-                   else
-                   {
-                       // Temporarily hide the new local variable here, it is
-                       // not available to this expression.
-                       if (lhs.lhs_new_local)
-                           --cctx->ctx_locals.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;
-               }
-               else if (semicolon && var_idx == var_count - 1)
-               {
-                   // For "[var; var] = expr" get the rest of the list
-                   did_generate_slice = TRUE;
-                   if (generate_SLICE(cctx, var_count - 1) == FAIL)
-                       goto theend;
-               }
-               else
-               {
-                   // For "[var, var] = expr" get the "var_idx" item from the
-                   // list.
-                   if (generate_GETITEM(cctx, var_idx, *op != '=') == FAIL)
-                       goto theend;
-               }
-
-               rhs_type = cctx->ctx_type_stack.ga_len == 0 ? &t_void
-                                                 : get_type_on_stack(cctx, 0);
-               if (check_type_is_value(rhs_type) == FAIL)
-                   goto theend;
-               if (lhs.lhs_lvar != NULL && (is_decl || !lhs.lhs_has_type))
-               {
-                   if ((rhs_type->tt_type == VAR_FUNC
-                               || rhs_type->tt_type == VAR_PARTIAL)
-                           && !lhs.lhs_has_index
-                           && 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)
-                       {
-                           emsg(_(e_cannot_use_void_value));
-                           goto theend;
-                       }
-                       else
-                       {
-                           type_T *type;
-
-                           // An empty list or dict has a &t_unknown member,
-                           // for a variable that implies &t_any.
-                           if (rhs_type == &t_list_empty)
-                               type = &t_list_any;
-                           else if (rhs_type == &t_dict_empty)
-                               type = &t_dict_any;
-                           else if (rhs_type == &t_unknown)
-                               type = &t_any;
-                           else
-                           {
-                               type = rhs_type;
-                               inferred_type = rhs_type;
-                           }
-                           set_var_type(lhs.lhs_lvar, type, cctx);
-                       }
-                   }
-                   else if (*op == '=')
-                   {
-                       type_T *use_type = lhs.lhs_lvar->lv_type;
-                       where_T where = WHERE_INIT;
-
-                       // Without operator check type here, otherwise below.
-                       // Use the line number of the assignment.
-                       SOURCING_LNUM = start_lnum;
-                       if (var_count > 0)
-                       {
-                           where.wt_index = var_idx + 1;
-                           where.wt_kind = WT_VARIABLE;
-                       }
-                       // If assigning to a list or dict member, use the
-                       // member type.  Not for "list[:] =".
-                       if (lhs.lhs_has_index
-                               && !has_list_index(var_start + lhs.lhs_varlen,
-                                                                        cctx))
-                           use_type = lhs.lhs_member_type;
-                       if (need_type_where(rhs_type, use_type, FALSE, -1,
-                                        where, cctx, FALSE, is_const) == FAIL)
-                           goto theend;
-                   }
-               }
-               else
-               {
-                   type_T *lhs_type = lhs.lhs_member_type;
-
-                   // Special case: assigning to @# can use a number or a
-                   // string.
-                   // Also: can assign a number to a float.
-                   if ((lhs_type == &t_number_or_string
-                               || lhs_type == &t_float)
-                           && rhs_type->tt_type == VAR_NUMBER)
-                       lhs_type = &t_number;
-                   if (*p != '=' && need_type(rhs_type, lhs_type, FALSE,
-                                           -1, 0, cctx, FALSE, FALSE) == FAIL)
-                       goto theend;
-               }
-           }
-           else if (cmdidx == CMD_final)
-           {
-               emsg(_(e_final_requires_a_value));
-               goto theend;
-           }
-           else if (cmdidx == CMD_const)
-           {
-               emsg(_(e_const_requires_a_value));
-               goto theend;
-           }
-           else if (!lhs.lhs_has_type || lhs.lhs_dest == dest_option
-                                          || lhs.lhs_dest == dest_func_option)
-           {
-               emsg(_(e_type_or_initialization_required));
-               goto theend;
-           }
-           else
-           {
-               // variables are always initialized
-               if (GA_GROW_FAILS(instr, 1))
-                   goto theend;
-               instr_count = instr->ga_len;
-               int r = push_default_value(cctx, lhs.lhs_member_type->tt_type,
-                                     lhs.lhs_dest == dest_local, &skip_store);
-               if (r == FAIL)
-                   goto theend;
-           }
-           if (var_count == 0)
-               end = p;
+           if (compile_assign_rhs(cctx, cac) == FAIL)
+               return FAIL;
+           if (cac->cac_var_count == 0)
+               cac->cac_var_end = cac->cac_nextc;
        }
 
        // no need to parse more when skipping
        if (cctx->ctx_skip == SKIP_YES)
            break;
 
-       if (oplen > 0 && *op != '=')
+       if (cac->cac_oplen > 0 && *cac->cac_op != '=')
        {
-           type_T          *expected;
-           type_T          *stacktype = NULL;
-
-           if (*op == '.')
-           {
-               if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
-                   goto theend;
-           }
-           else
-           {
-               expected = lhs.lhs_member_type;
-               stacktype = get_type_on_stack(cctx, 0);
-               if (
-                   // If variable is float operation with number is OK.
-                   !(expected == &t_float && (stacktype == &t_number
-                           || stacktype == &t_number_bool))
-                   && need_type(stacktype, expected, TRUE, -1, 0, cctx,
-                                                        FALSE, FALSE) == FAIL)
-                   goto theend;
-           }
-
-           if (*op == '.')
-           {
-               if (generate_CONCAT(cctx, 2) == FAIL)
-                   goto theend;
-           }
-           else if (*op == '+')
-           {
-               if (generate_add_instr(cctx,
-                           operator_type(lhs.lhs_member_type, stacktype),
-                                      lhs.lhs_member_type, stacktype,
-                                                         EXPR_APPEND) == FAIL)
-                   goto theend;
-           }
-           else if (generate_two_op(cctx, op) == FAIL)
-               goto theend;
+           if (compile_assign_compound_op(cctx, cac) == FAIL)
+               return FAIL;
        }
 
-       // Use the line number of the assignment for store instruction.
-       save_lnum = cctx->ctx_lnum;
-       cctx->ctx_lnum = start_lnum - 1;
-
-       if (lhs.lhs_has_index)
-       {
-           // Use the info in "lhs" to store the value at the index in the
-           // list, dict or object.
-           if (compile_assign_unlet(var_start, &lhs, TRUE, rhs_type, cctx)
-                                                                      == FAIL)
-           {
-               cctx->ctx_lnum = save_lnum;
-               goto theend;
-           }
-       }
-       else
-       {
-           if (is_decl && cmdidx == CMD_const && (lhs.lhs_dest == dest_script
-                                           || lhs.lhs_dest == dest_script_v9
-                                           || lhs.lhs_dest == dest_global
-                                           || lhs.lhs_dest == dest_local))
-               // ":const var": lock the value, but not referenced variables
-               generate_LOCKCONST(cctx);
-
-           if ((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);
-           else if (inferred_type != NULL
-                   && (inferred_type->tt_type == VAR_DICT
-                                       || inferred_type->tt_type == VAR_LIST)
-                   && inferred_type->tt_member != NULL
-                   && inferred_type->tt_member != &t_unknown
-                   && inferred_type->tt_member != &t_any)
-               // Set the type in the list or dict, so that it can be checked,
-               // also in legacy script.
-               generate_SETTYPE(cctx, inferred_type);
-
-           if (!skip_store && generate_store_lhs(cctx, &lhs,
-                                                instr_count, is_decl) == FAIL)
-           {
-               cctx->ctx_lnum = save_lnum;
-               goto theend;
-           }
-       }
-       cctx->ctx_lnum = save_lnum;
+       // generate the store instructions
+       if (compile_assign_generate_store(cctx, cac) == FAIL)
+           return FAIL;
 
-       if (var_idx + 1 < var_count)
-           var_start = skipwhite(lhs.lhs_end + 1);
+       if (cac->cac_var_idx + 1 < cac->cac_var_count)
+           cac->cac_var_start = skipwhite(cac->cac_lhs.lhs_end + 1);
 
-       if (has_arg_is_set_prefix)
+       if (has_argisset_prefix)
        {
            // set instruction index in JUMP_IF_ARG_SET to here
-           isn_T *isn = ((isn_T *)instr->ga_data) + jump_instr_idx;
-           isn->isn_arg.jumparg.jump_where = instr->ga_len;
+           isn_T *isn = ((isn_T *)cac->cac_instr->ga_data) + jump_instr_idx;
+           isn->isn_arg.jumparg.jump_where = cac->cac_instr->ga_len;
        }
     }
 
+    return OK;
+}
+
+/*
+ * Compile declaration and assignment:
+ * "let name"
+ * "var name = expr"
+ * "final name = expr"
+ * "const name = expr"
+ * "name = expr"
+ * "arg" points to "name".
+ * "++arg" and "--arg"
+ * Return NULL for an error.
+ * Return "arg" if it does not look like a variable list.
+ */
+    static char_u *
+compile_assignment(
+       char_u      *arg_start,
+       exarg_T     *eap,
+       cmdidx_T    cmdidx,
+       cctx_T      *cctx)
+{
+    cac_T      cac;
+    char_u     *arg = arg_start;
+    char_u     *ret = NULL;
+    int                heredoc = FALSE;
+    int                jump_instr_idx;
+
+    compile_assign_context_init(&cac, cctx, cmdidx, arg);
+
+    jump_instr_idx = cac.cac_instr->ga_len;
+
+    // process object variable initialization in a new() constructor method
+    int        has_argisset_prefix = STRNCMP(arg, "ifargisset ", 11) == 0;
+    if (has_argisset_prefix &&
+                       compile_assign_obj_new_arg(&arg, cctx) == FAIL)
+       goto theend;
+
+    // Skip over the "varname" or "[varname, varname]" to get to any "=".
+    cac.cac_nextc = skip_var_list(arg, TRUE, &cac.cac_var_count,
+                                               &cac.cac_semicolon, TRUE);
+    if (cac.cac_nextc == NULL)
+       return *arg == '[' ? arg : NULL;
+
+    char_u *retstr;
+    if (compile_assign_process_operator(eap, arg, &cac, &heredoc,
+                                                       &retstr) == FAIL)
+       return retstr;
+
+    // Compute the end of the assignment
+    cac.cac_var_end = compile_assign_compute_end(eap, cctx, &cac, heredoc);
+    if (cac.cac_var_end == NULL)
+       return NULL;
+
+    /*
+     * Loop over variables in "[var, var] = expr".
+     * For "name = expr" and "var name: type" this is done only once.
+     */
+    if (cac.cac_var_count > 0)
+       cac.cac_var_start = skipwhite(arg + 1);  // skip over the "["
+    else
+       cac.cac_var_start = arg;
+
+    int has_cmd = cac.cac_var_start > eap->cmd;
+
+    /* process the variable(s) */
+    if (compile_assign_process_variables(cctx, &cac, cmdidx, heredoc,
+                                        has_cmd, has_argisset_prefix,
+                                        jump_instr_idx) == FAIL)
+       goto theend;
+
     // For "[var, var] = expr" drop the "expr" value.
     // Also for "[var, var; _] = expr".
-    if (var_count > 0 && (!semicolon || !did_generate_slice))
+    if (cac.cac_var_count > 0 &&
+           (!cac.cac_semicolon || !cac.cac_did_generate_slice))
     {
        if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
            goto theend;
     }
 
-    ret = skipwhite(end);
+    ret = skipwhite(cac.cac_var_end);
 
 theend:
-    vim_free(lhs.lhs_name);
+    vim_free(cac.cac_lhs.lhs_name);
     return ret;
 }