0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -04:00

patch 8.2.2719: Vim9: appending to dict item doesn't work in a :def function

Problem:    Vim9: appending to dict item doesn't work in a :def function.
Solution:   Implement assignment with operator on indexed item.
This commit is contained in:
Bram Moolenaar
2021-04-05 17:11:17 +02:00
parent 0865b15bc6
commit e42939af87
3 changed files with 195 additions and 118 deletions

View File

@@ -1116,7 +1116,6 @@ enddef
def Test_assign_dict_with_op() def Test_assign_dict_with_op()
var lines =<< trim END var lines =<< trim END
vim9script
var ds: dict<string> = {a: 'x'} var ds: dict<string> = {a: 'x'}
ds['a'] ..= 'y' ds['a'] ..= 'y'
ds.a ..= 'z' ds.a ..= 'z'
@@ -1148,8 +1147,46 @@ def Test_assign_dict_with_op()
dn.a %= 6 dn.a %= 6
assert_equal(2, dn.a) assert_equal(2, dn.a)
END END
# TODO: this should also work with a :def function CheckDefAndScriptSuccess(lines)
CheckScriptSuccess(lines) enddef
def Test_assign_list_with_op()
var lines =<< trim END
var ls: list<string> = ['x']
ls[0] ..= 'y'
assert_equal('xy', ls[0])
var ln: list<number> = [9]
ln[0] += 2
assert_equal(11, ln[0])
ln[0] -= 3
assert_equal(8, ln[0])
ln[0] *= 2
assert_equal(16, ln[0])
ln[0] /= 3
assert_equal(5, ln[0])
ln[0] %= 3
assert_equal(2, ln[0])
END
CheckDefAndScriptSuccess(lines)
enddef
def Test_assign_with_op_fails()
var lines =<< trim END
var s = 'abc'
s[1] += 'x'
END
CheckDefAndScriptFailure2(lines, 'E1141:', 'E689:', 2)
lines =<< trim END
var s = 'abc'
s[1] ..= 'x'
END
CheckDefAndScriptFailure2(lines, 'E1141:', 'E689:', 2)
enddef enddef
def Test_assign_lambda() def Test_assign_lambda()

View File

@@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
2719,
/**/ /**/
2718, 2718,
/**/ /**/

View File

@@ -2649,6 +2649,112 @@ clear_ppconst(ppconst_T *ppconst)
ppconst->pp_used = 0; ppconst->pp_used = 0;
} }
/*
* Compile getting a member from a list/dict/string/blob. Stack has the
* indexable value and the index.
*/
static int
compile_member(int is_slice, cctx_T *cctx)
{
type_T **typep;
garray_T *stack = &cctx->ctx_type_stack;
vartype_T vtype;
type_T *valtype;
// We can index a list and a dict. If we don't know the type
// we can use the index value type.
// TODO: If we don't know use an instruction to figure it out at
// runtime.
typep = ((type_T **)stack->ga_data) + stack->ga_len
- (is_slice ? 3 : 2);
vtype = (*typep)->tt_type;
valtype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
// If the index is a string, the variable must be a Dict.
if (*typep == &t_any && valtype == &t_string)
vtype = VAR_DICT;
if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
{
if (need_type(valtype, &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL)
return FAIL;
if (is_slice)
{
valtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
if (need_type(valtype, &t_number, -2, 0, cctx,
FALSE, FALSE) == FAIL)
return FAIL;
}
}
if (vtype == VAR_DICT)
{
if (is_slice)
{
emsg(_(e_cannot_slice_dictionary));
return FAIL;
}
if ((*typep)->tt_type == VAR_DICT)
{
*typep = (*typep)->tt_member;
if (*typep == &t_unknown)
// empty dict was used
*typep = &t_any;
}
else
{
if (need_type(*typep, &t_dict_any, -2, 0, cctx,
FALSE, FALSE) == FAIL)
return FAIL;
*typep = &t_any;
}
if (may_generate_2STRING(-1, cctx) == FAIL)
return FAIL;
if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
return FAIL;
}
else if (vtype == VAR_STRING)
{
*typep = &t_string;
if ((is_slice
? generate_instr_drop(cctx, ISN_STRSLICE, 2)
: generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL)
return FAIL;
}
else if (vtype == VAR_BLOB)
{
emsg("Sorry, blob index and slice not implemented yet");
return FAIL;
}
else if (vtype == VAR_LIST || *typep == &t_any)
{
if (is_slice)
{
if (generate_instr_drop(cctx,
vtype == VAR_LIST ? ISN_LISTSLICE : ISN_ANYSLICE,
2) == FAIL)
return FAIL;
}
else
{
if ((*typep)->tt_type == VAR_LIST)
{
*typep = (*typep)->tt_member;
if (*typep == &t_unknown)
// empty list was used
*typep = &t_any;
}
if (generate_instr_drop(cctx,
vtype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX, 1) == FAIL)
return FAIL;
}
}
else
{
emsg(_(e_string_list_dict_or_blob_required));
return FAIL;
}
return OK;
}
/* /*
* Generate an instruction to load script-local variable "name", without the * Generate an instruction to load script-local variable "name", without the
* leading "s:". * leading "s:".
@@ -3934,10 +4040,6 @@ compile_subscript(
} }
else if (**arg == '[') else if (**arg == '[')
{ {
garray_T *stack = &cctx->ctx_type_stack;
type_T **typep;
type_T *valtype;
vartype_T vtype;
int is_slice = FALSE; int is_slice = FALSE;
// list index: list[123] // list index: list[123]
@@ -4004,99 +4106,8 @@ compile_subscript(
} }
*arg = *arg + 1; *arg = *arg + 1;
// We can index a list and a dict. If we don't know the type if (compile_member(is_slice, cctx) == FAIL)
// we can use the index value type.
// TODO: If we don't know use an instruction to figure it out at
// runtime.
typep = ((type_T **)stack->ga_data) + stack->ga_len
- (is_slice ? 3 : 2);
vtype = (*typep)->tt_type;
valtype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
// If the index is a string, the variable must be a Dict.
if (*typep == &t_any && valtype == &t_string)
vtype = VAR_DICT;
if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
{
if (need_type(valtype, &t_number, -1, 0, cctx,
FALSE, FALSE) == FAIL)
return FAIL;
if (is_slice)
{
valtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
if (need_type(valtype, &t_number, -2, 0, cctx,
FALSE, FALSE) == FAIL)
return FAIL;
}
}
if (vtype == VAR_DICT)
{
if (is_slice)
{
emsg(_(e_cannot_slice_dictionary));
return FAIL;
}
if ((*typep)->tt_type == VAR_DICT)
{
*typep = (*typep)->tt_member;
if (*typep == &t_unknown)
// empty dict was used
*typep = &t_any;
}
else
{
if (need_type(*typep, &t_dict_any, -2, 0, cctx,
FALSE, FALSE) == FAIL)
return FAIL;
*typep = &t_any;
}
if (may_generate_2STRING(-1, cctx) == FAIL)
return FAIL;
if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
return FAIL;
}
else if (vtype == VAR_STRING)
{
*typep = &t_string;
if ((is_slice
? generate_instr_drop(cctx, ISN_STRSLICE, 2)
: generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL)
return FAIL;
}
else if (vtype == VAR_BLOB)
{
emsg("Sorry, blob index and slice not implemented yet");
return FAIL; return FAIL;
}
else if (vtype == VAR_LIST || *typep == &t_any)
{
if (is_slice)
{
if (generate_instr_drop(cctx,
vtype == VAR_LIST ? ISN_LISTSLICE : ISN_ANYSLICE,
2) == FAIL)
return FAIL;
}
else
{
if ((*typep)->tt_type == VAR_LIST)
{
*typep = (*typep)->tt_member;
if (*typep == &t_unknown)
// empty list was used
*typep = &t_any;
}
if (generate_instr_drop(cctx,
vtype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX,
1) == FAIL)
return FAIL;
}
}
else
{
emsg(_(e_string_list_dict_or_blob_required));
return FAIL;
}
} }
else if (*p == '.' && p[1] != '.') else if (*p == '.' && p[1] != '.')
{ {
@@ -5905,9 +5916,11 @@ compile_lhs(
lhs->lhs_type = lhs->lhs_lvar->lv_type; lhs->lhs_type = lhs->lhs_lvar->lv_type;
} }
if (oplen == 3 && !heredoc && lhs->lhs_dest != dest_global if (oplen == 3 && !heredoc
&& lhs->lhs_type->tt_type != VAR_STRING && lhs->lhs_dest != dest_global
&& lhs->lhs_type->tt_type != VAR_ANY) && !lhs->lhs_has_index
&& lhs->lhs_type->tt_type != VAR_STRING
&& lhs->lhs_type->tt_type != VAR_ANY)
{ {
emsg(_(e_can_only_concatenate_to_string)); emsg(_(e_can_only_concatenate_to_string));
return FAIL; return FAIL;
@@ -5993,26 +6006,21 @@ compile_lhs(
} }
/* /*
* Assignment to a list or dict member, or ":unlet" for the item, using the * For an assignment with an index, compile the "idx" in "var[idx]" or "key" in
* information in "lhs". * "var.key".
* Returns OK or FAIL.
*/ */
static int static int
compile_assign_unlet( compile_assign_index(
char_u *var_start, char_u *var_start,
lhs_T *lhs, lhs_T *lhs,
int is_assign, int is_assign,
type_T *rhs_type, int *range,
cctx_T *cctx) cctx_T *cctx)
{ {
char_u *p;
int r;
vartype_T dest_type;
size_t varlen = lhs->lhs_varlen; size_t varlen = lhs->lhs_varlen;
garray_T *stack = &cctx->ctx_type_stack; char_u *p;
int range = FALSE; int r = OK;
// Compile the "idx" in "var[idx]" or "key" in "var.key".
p = var_start + varlen; p = var_start + varlen;
if (*p == '[') if (*p == '[')
{ {
@@ -6027,7 +6035,7 @@ compile_assign_unlet(
semsg(_(e_cannot_use_range_with_assignment_str), p); semsg(_(e_cannot_use_range_with_assignment_str), p);
return FAIL; return FAIL;
} }
range = TRUE; *range = TRUE;
p = skipwhite(p); p = skipwhite(p);
if (!IS_WHITE_OR_NUL(p[-1]) || !IS_WHITE_OR_NUL(p[1])) if (!IS_WHITE_OR_NUL(p[-1]) || !IS_WHITE_OR_NUL(p[1]))
{ {
@@ -6053,7 +6061,29 @@ compile_assign_unlet(
r = generate_PUSHS(cctx, key); r = generate_PUSHS(cctx, key);
} }
if (r == FAIL) return r;
}
/*
* Assignment to a list or dict member, or ":unlet" for the item, using the
* information in "lhs".
* Returns OK or FAIL.
*/
static int
compile_assign_unlet(
char_u *var_start,
lhs_T *lhs,
int is_assign,
type_T *rhs_type,
cctx_T *cctx)
{
char_u *p;
vartype_T dest_type;
size_t varlen = lhs->lhs_varlen;
garray_T *stack = &cctx->ctx_type_stack;
int range = FALSE;
if (compile_assign_index(var_start, lhs, is_assign, &range, cctx) == FAIL)
return FAIL; return FAIL;
if (lhs->lhs_type == &t_any) if (lhs->lhs_type == &t_any)
@@ -6330,9 +6360,17 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
if (lhs.lhs_has_index) if (lhs.lhs_has_index)
{ {
// TODO: get member from list or dict int range = FALSE;
emsg("Index with operation not supported yet");
goto theend; // Get member from list or dict. First compile the
// index value.
if (compile_assign_index(var_start, &lhs,
TRUE, &range, cctx) == FAIL)
goto theend;
// Get the member.
if (compile_member(FALSE, cctx) == FAIL)
goto theend;
} }
} }