patch 9.1.0933: Vim9: vim9compile.c can be further improved
Problem: Vim9: vim9compile.c can be further improved
Solution: further refactor the compile assignment code
(Yegappan Lakshmanan)
closes: #16230
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 776e6d6..b812989 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2584,43 +2584,6 @@
}
/*
- * Compile an object member variable assignment in the argument passed to a
- * class new() method.
- *
- * Instruction format:
- *
- * ifargisset <n> this.<varname> = <value>
- *
- * where <n> is the object member variable index.
- *
- * Generates the ISN_JUMP_IF_ARG_NOT_SET instruction to skip the assignment if
- * the value is passed as an argument to the new() method call.
- *
- * Returns OK on success.
- */
- static int
-compile_assign_obj_new_arg(char_u **argp, cctx_T *cctx)
-{
- char_u *arg = *argp;
-
- arg += 11; // skip "ifargisset"
- int def_idx = getdigits(&arg);
- arg = skipwhite(arg);
-
- // Use a JUMP_IF_ARG_NOT_SET instruction to skip if the value was not
- // given and the default value is "v:none".
- int off = STACK_FRAME_SIZE + (cctx->ctx_ufunc->uf_va_name != NULL
- ? 1 : 0);
- int count = cctx->ctx_ufunc->uf_def_args.ga_len;
- if (generate_JUMP_IF_ARG(cctx, ISN_JUMP_IF_ARG_NOT_SET,
- def_idx - count - off) == FAIL)
- return FAIL;
-
- *argp = arg;
- return OK;
-}
-
-/*
* Compile assignment context. Used when compiling an assignment statement.
*/
typedef struct cac_S cac_T;
@@ -2665,6 +2628,45 @@
}
/*
+ * Compile an object member variable assignment in the arguments passed to a
+ * class new() method.
+ *
+ * Instruction format:
+ *
+ * ifargisset <n> this.<varname> = <value>
+ *
+ * where <n> is the index of the default argument.
+ *
+ * Generates the ISN_JUMP_IF_ARG_NOT_SET instruction to skip the assignment if
+ * the value is passed as an argument to the new() method call.
+ *
+ * Returns OK on success.
+ */
+ static int
+compile_assign_obj_new_arg(char_u **argp, cctx_T *cctx)
+{
+ char_u *arg = *argp;
+
+ arg += 11; // skip "ifargisset"
+ int def_arg_idx = getdigits(&arg);
+ arg = skipwhite(arg);
+
+ // Use a JUMP_IF_ARG_NOT_SET instruction to skip if the value was not
+ // given and the default value is "v:none".
+ int stack_offset = STACK_FRAME_SIZE +
+ (cctx->ctx_ufunc->uf_va_name != NULL ? 1 : 0);
+ int def_arg_count = cctx->ctx_ufunc->uf_def_args.ga_len;
+ int arg_offset = def_arg_idx - def_arg_count - stack_offset;
+
+ if (generate_JUMP_IF_ARG(cctx, ISN_JUMP_IF_ARG_NOT_SET,
+ arg_offset) == FAIL)
+ return FAIL;
+
+ *argp = arg;
+ return OK;
+}
+
+/*
* Translate the increment (++) and decrement (--) operators to the
* corresponding compound operators (+= or -=).
*
@@ -2700,40 +2702,47 @@
*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
+ return translate_incdec_op(eap, cac);
+
+ char_u *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)
{
- char_u *sp;
+ // can be something like "[1, 2]->func()"
+ *retstr = arg;
+ return FAIL;
+ }
- 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;
- }
+ // 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;
}
/*
+ * Find the start of an assignment statement.
+ */
+ static char_u *
+compile_assign_compute_start(char_u *arg, int var_count)
+{
+ if (var_count > 0)
+ // [var1, var2] = [val1, val2]
+ // skip over the "["
+ return skipwhite(arg + 1);
+
+ return arg;
+}
+
+/*
* Parse a heredoc assignment starting at "p". Returns a pointer to the
* beginning of the heredoc content.
*/
@@ -2755,61 +2764,60 @@
}
/*
- * Evaluate the expression for "[var, var] = expr" assignment.
- * A line break may follow the assignment operator "=".
+ * Check the type of a RHS expression in a list assignment statement.
+ * The RHS expression is already compiled. So the type is on the stack.
*/
- static char_u *
-compile_list_assignment_expr(cctx_T *cctx, cac_T *cac)
+ static int
+compile_assign_list_check_rhs_type(cctx_T *cctx, cac_T *cac)
{
- char_u *wp;
-
- wp = cac->cac_op + cac->cac_oplen;
-
- if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
- return NULL;
-
- if (compile_expr0(&cac->cac_nextc, cctx) == FAIL)
- return NULL;
-
- 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
: get_type_on_stack(cctx, 0);
if (stacktype->tt_type == VAR_VOID)
{
emsg(_(e_cannot_use_void_value));
- return NULL;
+ return FAIL;
}
if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx,
FALSE, FALSE) == FAIL)
- return NULL;
+ return FAIL;
- // If a constant list was used we can check the length right here.
+ if (stacktype->tt_member != NULL)
+ cac->cac_rhs_type = stacktype->tt_member;
+
+ return OK;
+}
+
+/*
+ * In a list assignment statement, if a constant list was used, check the
+ * length. Returns OK if the length check succeeds. Returns FAIL otherwise.
+ */
+ static int
+compile_assign_list_check_length(cctx_T *cctx, cac_T *cac)
+{
+ int needed_list_len;
+ int did_check = FALSE;
+
needed_list_len = cac->cac_semicolon
- ? cac->cac_var_count - 1
- : cac->cac_var_count;
+ ? 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;
+ 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)
+ if (cac->cac_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;
+ return FAIL;
}
}
}
@@ -2817,8 +2825,37 @@
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 OK;
+}
+
+/*
+ * Evaluate the expression for "[var, var] = expr" assignment.
+ * A line break may follow the assignment operator "=".
+ */
+ static char_u *
+compile_assign_list_expr(cctx_T *cctx, cac_T *cac)
+{
+ char_u *whitep;
+
+ whitep = cac->cac_op + cac->cac_oplen;
+
+ if (may_get_next_line_error(whitep, &cac->cac_nextc, cctx) == FAIL)
+ return NULL;
+
+ // compile RHS expression
+ if (compile_expr0(&cac->cac_nextc, cctx) == FAIL)
+ return NULL;
+
+ if (cctx->ctx_skip == SKIP_YES)
+ // no need to parse more when skipping
+ return cac->cac_nextc;
+
+ if (compile_assign_list_check_rhs_type(cctx, cac) == FAIL)
+ return NULL;
+
+ // If a constant list was used we can check the length right here.
+ if (compile_assign_list_check_length(cctx, cac) == FAIL)
+ return FAIL;
return cac->cac_nextc;
}
@@ -2841,12 +2878,13 @@
cac->cac_nextc = parse_heredoc_assignment(eap, cctx, cac);
return cac->cac_nextc;
}
- else if (cac->cac_var_count > 0)
+
+ 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);
+ cac->cac_nextc = compile_assign_list_expr(cctx, cac);
return cac->cac_nextc;
}
@@ -2860,7 +2898,7 @@
compile_assign_single_eval_expr(cctx_T *cctx, cac_T *cac)
{
int ret = OK;
- char_u *wp;
+ char_u *whitep;
lhs_T *lhs = &cac->cac_lhs;
// Compile the expression.
@@ -2871,9 +2909,9 @@
// not available to this expression.
if (lhs->lhs_new_local)
--cctx->ctx_locals.ga_len;
- wp = cac->cac_op + cac->cac_oplen;
+ whitep = cac->cac_op + cac->cac_oplen;
- if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
+ if (may_get_next_line_error(whitep, &cac->cac_nextc, cctx) == FAIL)
{
if (lhs->lhs_new_local)
++cctx->ctx_locals.ga_len;
@@ -2888,6 +2926,79 @@
}
/*
+ * When compiling an assignment, set the LHS type to the RHS type.
+ */
+ static int
+compile_assign_set_lhs_type_from_rhs(
+ cctx_T *cctx,
+ cac_T *cac,
+ lhs_T *lhs,
+ type_T *rhs_type)
+{
+ if (rhs_type->tt_type == VAR_VOID)
+ {
+ emsg(_(e_cannot_use_void_value));
+ return FAIL;
+ }
+
+ 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);
+
+ return OK;
+}
+
+/*
+ * Returns TRUE if the "rhs_type" can be assigned to the "lhs" variable.
+ * Used when compiling an assignment statement.
+ */
+ static int
+compile_assign_valid_rhs_type(
+ cctx_T *cctx,
+ cac_T *cac,
+ lhs_T *lhs,
+ type_T *rhs_type)
+{
+ 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 FALSE;
+
+ return TRUE;
+}
+
+/*
* Compare the LHS type with the RHS type in an assignment.
*/
static int
@@ -2896,8 +3007,9 @@
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);
+ 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)
@@ -2905,77 +3017,95 @@
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 (rhs_type->tt_type == VAR_FUNC
+ || rhs_type->tt_type == VAR_PARTIAL)
+ {
+ // Make sure the variable name can be used as a funcref
+ if (!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));
+ // The LHS variable doesn't have a type. Set it to the RHS type.
+ if (compile_assign_set_lhs_type_from_rhs(cctx, cac, lhs, rhs_type)
+ == FAIL)
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)
+ if (!compile_assign_valid_rhs_type(cctx, cac, lhs, rhs_type))
return FAIL;
}
}
else
{
+ // Assigning to a register using @r = "abc"
+
type_T *lhs_type = lhs->lhs_member_type;
- // Special case: assigning to @# can use a number or a
- // string.
+ // 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)
+ && 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)
+
+ if (*cac->cac_nextc != '=')
+ {
+ if (need_type(rhs_type, lhs_type, FALSE, -1, 0, cctx, FALSE,
+ FALSE) == FAIL)
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/*
+ * Compile the RHS expression in an assignment statement and generate the
+ * instructions.
+ */
+ static int
+compile_assign_rhs_expr(cctx_T *cctx, cac_T *cac)
+{
+ 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.
+ int with_op = *cac->cac_op != '=';
+ if (generate_GETITEM(cctx, cac->cac_var_idx, with_op) == FAIL)
+ return FAIL;
+ }
+
+ if (compile_assign_check_type(cctx, cac) == FAIL)
+ return FAIL;
return OK;
}
@@ -2999,50 +3129,9 @@
return OK;
}
+ // If RHS is specified, then generate instructions for RHS expression
if (cac->cac_oplen > 0)
- {
- 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;
- }
+ return compile_assign_rhs_expr(cctx, cac);
if (cac->cac_cmdidx == CMD_final)
{
@@ -3186,6 +3275,7 @@
}
cctx->ctx_lnum = save_lnum;
+
return OK;
}
@@ -3202,6 +3292,10 @@
int has_argisset_prefix,
int jump_instr_idx)
{
+ /*
+ * Loop over variables in "[var, var] = expr".
+ * For "name = expr" and "var name: type" this is done only once.
+ */
for (cac->cac_var_idx = 0; cac->cac_var_idx == 0 ||
cac->cac_var_idx < cac->cac_var_count; cac->cac_var_idx++)
{
@@ -3286,14 +3380,14 @@
*/
static char_u *
compile_assignment(
- char_u *arg_start,
- exarg_T *eap,
- cmdidx_T cmdidx,
- cctx_T *cctx)
+ 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;
+ char_u *retstr = NULL;
int heredoc = FALSE;
int jump_instr_idx;
@@ -3313,25 +3407,18 @@
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 start of the assignment
+ cac.cac_var_start = compile_assign_compute_start(arg, cac.cac_var_count);
+
// 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) */
@@ -3349,11 +3436,11 @@
goto theend;
}
- ret = skipwhite(cac.cac_var_end);
+ retstr = skipwhite(cac.cac_var_end);
theend:
vim_free(cac.cac_lhs.lhs_name);
- return ret;
+ return retstr;
}
/*