0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 8.2.1466: Vim9: cannot index or slice a variable with type "any"

Problem:    Vim9: cannot index or slice a variable with type "any".
Solution:   Add runtime index and slice.
This commit is contained in:
Bram Moolenaar
2020-08-16 17:33:35 +02:00
parent 56acb0943e
commit cc673e746a
11 changed files with 417 additions and 212 deletions

View File

@@ -21,6 +21,10 @@ EXTERN char e_invalid_command[]
#ifdef FEAT_EVAL
EXTERN char e_invalid_command_str[]
INIT(= N_("E476: Invalid command: %s"));
EXTERN char e_cannot_slice_dictionary[]
INIT(= N_("E719: cannot slice a Dictionary"));
EXTERN char e_cannot_index_special_variable[]
INIT(= N_("E909: Cannot index a special variable"));
EXTERN char e_missing_let_str[]
INIT(= N_("E1100: Missing :let: %s"));
EXTERN char e_variable_not_found_str[]
@@ -69,9 +73,9 @@ EXTERN char e_const_requires_a_value[]
INIT(= N_("E1021: const requires a value"));
EXTERN char e_type_or_initialization_required[]
INIT(= N_("E1022: type or initialization required"));
EXTERN char e_cannot_slice_dictionary[]
INIT(= N_("E1023: cannot slice a dictionary"));
// E1024 unused
// E1023 unused
EXTERN char e_using_number_as_string[]
INIT(= N_("E1024: Using a Number as a String"));
EXTERN char e_using_rcurly_outside_if_block_scope[]
INIT(= N_("E1025: using } outside of a block scope"));
EXTERN char e_missing_rcurly[]
@@ -146,7 +150,8 @@ EXTERN char e_expected_dot_after_name_str[]
INIT(= N_("E1060: expected dot after name: %s"));
EXTERN char e_cannot_find_function_str[]
INIT(= N_("E1061: Cannot find function %s"));
// E1062 unused
EXTERN char e_cannot_index_number[]
INIT(= N_("E1062: Cannot index a Number"));
EXTERN char e_type_mismatch_for_v_variable[]
INIT(= N_("E1063: type mismatch for v: variable"));
// E1064 unused

View File

@@ -20,8 +20,6 @@
# include <float.h>
#endif
static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
#define NAMESPACE_CHAR (char_u *)"abglstvw"
/*
@@ -928,7 +926,7 @@ get_lval(
if (lp->ll_tv->v_type == VAR_DICT)
{
if (!quiet)
emsg(_(e_dictrange));
emsg(_(e_cannot_slice_dictionary));
clear_tv(&var1);
return NULL;
}
@@ -3549,47 +3547,12 @@ eval_index(
&& (evalarg->eval_flags & EVAL_EVALUATE);
int empty1 = FALSE, empty2 = FALSE;
typval_T var1, var2;
long i;
long n1, n2 = 0;
long len = -1;
int range = FALSE;
char_u *s;
char_u *key = NULL;
int keylen = -1;
switch (rettv->v_type)
{
case VAR_FUNC:
case VAR_PARTIAL:
if (verbose)
emsg(_("E695: Cannot index a Funcref"));
if (check_can_index(rettv, evaluate, verbose) == FAIL)
return FAIL;
case VAR_FLOAT:
#ifdef FEAT_FLOAT
if (verbose)
emsg(_(e_float_as_string));
return FAIL;
#endif
case VAR_BOOL:
case VAR_SPECIAL:
case VAR_JOB:
case VAR_CHANNEL:
if (verbose)
emsg(_("E909: Cannot index a special variable"));
return FAIL;
case VAR_UNKNOWN:
case VAR_ANY:
case VAR_VOID:
if (evaluate)
return FAIL;
// FALLTHROUGH
case VAR_STRING:
case VAR_NUMBER:
case VAR_LIST:
case VAR_DICT:
case VAR_BLOB:
break;
}
init_tv(&var1);
init_tv(&var2);
@@ -3599,11 +3562,11 @@ eval_index(
* dict.name
*/
key = *arg + 1;
for (len = 0; eval_isdictc(key[len]); ++len)
for (keylen = 0; eval_isdictc(key[keylen]); ++keylen)
;
if (len == 0)
if (keylen == 0)
return FAIL;
*arg = skipwhite(key + len);
*arg = skipwhite(key + keylen);
}
else
{
@@ -3666,21 +3629,102 @@ eval_index(
if (evaluate)
{
n1 = 0;
if (!empty1 && rettv->v_type != VAR_DICT)
{
n1 = tv_get_number(&var1);
int res = eval_index_inner(rettv, range,
empty1 ? NULL : &var1, empty2 ? NULL : &var2,
key, keylen, verbose);
if (!empty1)
clear_tv(&var1);
}
if (range)
clear_tv(&var2);
return res;
}
return OK;
}
/*
* Check if "rettv" can have an [index] or [sli:ce]
*/
int
check_can_index(typval_T *rettv, int evaluate, int verbose)
{
switch (rettv->v_type)
{
if (empty2)
case VAR_FUNC:
case VAR_PARTIAL:
if (verbose)
emsg(_("E695: Cannot index a Funcref"));
return FAIL;
case VAR_FLOAT:
#ifdef FEAT_FLOAT
if (verbose)
emsg(_(e_float_as_string));
return FAIL;
#endif
case VAR_BOOL:
case VAR_SPECIAL:
case VAR_JOB:
case VAR_CHANNEL:
if (verbose)
emsg(_(e_cannot_index_special_variable));
return FAIL;
case VAR_UNKNOWN:
case VAR_ANY:
case VAR_VOID:
if (evaluate)
{
emsg(_(e_cannot_index_special_variable));
return FAIL;
}
// FALLTHROUGH
case VAR_STRING:
case VAR_LIST:
case VAR_DICT:
case VAR_BLOB:
break;
case VAR_NUMBER:
if (in_vim9script())
emsg(_(e_cannot_index_number));
break;
}
return OK;
}
/*
* Apply index or range to "rettv".
* "var1" is the first index, NULL for [:expr].
* "var2" is the second index, NULL for [expr] and [expr: ]
* Alternatively, "key" is not NULL, then key[keylen] is the dict index.
*/
int
eval_index_inner(
typval_T *rettv,
int is_range,
typval_T *var1,
typval_T *var2,
char_u *key,
int keylen,
int verbose)
{
long n1, n2 = 0;
long len;
n1 = 0;
if (var1 != NULL && rettv->v_type != VAR_DICT)
n1 = tv_get_number(var1);
if (is_range)
{
if (rettv->v_type == VAR_DICT)
{
if (verbose)
emsg(_(e_cannot_slice_dictionary));
return FAIL;
}
if (var2 == NULL)
n2 = -1;
else
{
n2 = tv_get_number(&var2);
clear_tv(&var2);
}
n2 = tv_get_number(var2);
}
switch (rettv->v_type)
@@ -3699,16 +3743,18 @@ eval_index(
case VAR_NUMBER:
case VAR_STRING:
s = tv_get_string(rettv);
{
char_u *s = tv_get_string(rettv);
len = (long)STRLEN(s);
if (in_vim9script())
{
if (range)
if (is_range)
s = string_slice(s, n1, n2);
else
s = char_from_string(s, n1);
}
else if (range)
else if (is_range)
{
// The resulting variable is a substring. If the indexes
// are out of range the result is empty.
@@ -3740,11 +3786,12 @@ eval_index(
clear_tv(rettv);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = s;
}
break;
case VAR_BLOB:
len = blob_len(rettv->vval.v_blob);
if (range)
if (is_range)
{
// The resulting variable is a sub-blob. If the indexes
// are out of range the result is empty.
@@ -3767,6 +3814,7 @@ eval_index(
else
{
blob_T *blob = blob_alloc();
long i;
if (blob != NULL)
{
@@ -3805,54 +3853,40 @@ eval_index(
break;
case VAR_LIST:
if (empty1)
if (var1 == NULL)
n1 = 0;
if (empty2)
if (var2 == NULL)
n2 = -1;
if (list_slice_or_index(rettv->vval.v_list,
range, n1, n2, rettv, verbose) == FAIL)
is_range, n1, n2, rettv, verbose) == FAIL)
return FAIL;
break;
case VAR_DICT:
if (range)
{
if (verbose)
emsg(_(e_dictrange));
if (len == -1)
clear_tv(&var1);
return FAIL;
}
{
dictitem_T *item;
typval_T tmp;
if (len == -1)
{
key = tv_get_string_chk(&var1);
if (key == NULL)
{
clear_tv(&var1);
key = tv_get_string_chk(var1);
if (key == NULL)
return FAIL;
}
}
item = dict_find(rettv->vval.v_dict, key, (int)len);
item = dict_find(rettv->vval.v_dict, key, (int)keylen);
if (item == NULL && verbose)
semsg(_(e_dictkey), key);
if (len == -1)
clear_tv(&var1);
if (item == NULL)
return FAIL;
copy_tv(&item->di_tv, &var1);
copy_tv(&item->di_tv, &tmp);
clear_tv(rettv);
*rettv = var1;
*rettv = tmp;
}
break;
}
}
return OK;
}
@@ -5292,9 +5326,9 @@ char_from_string(char_u *str, varnumber_T index)
* "str_len".
* If going over the end return "str_len".
* If "idx" is negative count from the end, -1 is the last character.
* When going over the start return zero.
* When going over the start return -1.
*/
static size_t
static long
char_idx2byte(char_u *str, size_t str_len, varnumber_T idx)
{
varnumber_T nchar = idx;
@@ -5317,8 +5351,10 @@ char_idx2byte(char_u *str, size_t str_len, varnumber_T idx)
nbyte -= mb_head_off(str, str + nbyte);
++nchar;
}
if (nchar < 0)
return -1;
}
return nbyte;
return (long)nbyte;
}
/*
@@ -5328,24 +5364,26 @@ char_idx2byte(char_u *str, size_t str_len, varnumber_T idx)
char_u *
string_slice(char_u *str, varnumber_T first, varnumber_T last)
{
size_t start_byte, end_byte;
long start_byte, end_byte;
size_t slen;
if (str == NULL)
return NULL;
slen = STRLEN(str);
start_byte = char_idx2byte(str, slen, first);
if (start_byte < 0)
start_byte = 0; // first index very negative: use zero
if (last == -1)
end_byte = slen;
else
{
end_byte = char_idx2byte(str, slen, last);
if (end_byte < slen)
if (end_byte >= 0 && end_byte < (long)slen)
// end index is inclusive
end_byte += MB_CPTR2LEN(str + end_byte);
}
if (start_byte >= slen || end_byte <= start_byte)
if (start_byte >= (long)slen || end_byte <= start_byte)
return NULL;
return vim_strnsave(str + start_byte, end_byte - start_byte);
}

View File

@@ -914,7 +914,7 @@ list_slice_or_index(
semsg(_(e_listidx), n1);
return FAIL;
}
n1 = len;
n1 = n1 < 0 ? 0 : len;
}
if (range)
{

View File

@@ -37,6 +37,8 @@ int eval0(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg);
int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
void eval_addblob(typval_T *tv1, typval_T *tv2);
int eval_addlist(typval_T *tv1, typval_T *tv2);
int check_can_index(typval_T *rettv, int evaluate, int verbose);
int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, char_u *key, int keylen, int verbose);
char_u *partial_name(partial_T *pt);
void partial_unref(partial_T *pt);
int get_copyID(void);

View File

@@ -1091,6 +1091,50 @@ def Test_disassemble_dict_member()
call assert_equal(1, DictMember())
enddef
let somelist = [1, 2, 3, 4, 5]
def AnyIndex(): number
let res = g:somelist[2]
return res
enddef
def Test_disassemble_any_index()
let instr = execute('disassemble AnyIndex')
assert_match('AnyIndex\_s*' ..
'let res = g:somelist\[2\]\_s*' ..
'\d LOADG g:somelist\_s*' ..
'\d PUSHNR 2\_s*' ..
'\d ANYINDEX\_s*' ..
'\d STORE $0\_s*' ..
'return res\_s*' ..
'\d LOAD $0\_s*' ..
'\d CHECKTYPE number stack\[-1\]\_s*' ..
'\d RETURN',
instr)
assert_equal(3, AnyIndex())
enddef
def AnySlice(): list<number>
let res = g:somelist[1:3]
return res
enddef
def Test_disassemble_any_slice()
let instr = execute('disassemble AnySlice')
assert_match('AnySlice\_s*' ..
'let res = g:somelist\[1:3\]\_s*' ..
'\d LOADG g:somelist\_s*' ..
'\d PUSHNR 1\_s*' ..
'\d PUSHNR 3\_s*' ..
'\d ANYSLICE\_s*' ..
'\d STORE $0\_s*' ..
'return res\_s*' ..
'\d LOAD $0\_s*' ..
'\d CHECKTYPE list stack\[-1\]\_s*' ..
'\d RETURN',
instr)
assert_equal([2, 3, 4], AnySlice())
enddef
def NegateNumber(): number
let nr = 9
let plus = +nr

View File

@@ -1457,7 +1457,7 @@ def Test_expr7_list()
4]
call CheckDefFailure(["let x = 1234[3]"], 'E1107:')
call CheckDefExecFailure(["let x = g:anint[3]"], 'E1029:')
call CheckDefExecFailure(["let x = g:anint[3]"], 'E1062:')
call CheckDefFailure(["let x = g:list_mixed[xxx]"], 'E1001:')
@@ -1768,9 +1768,91 @@ def Test_expr_member()
call CheckDefExecFailure(["let d: dict<number>", "d = g:list_empty"], 'E1029: Expected dict but got list')
enddef
def Test_expr_index()
def Test_expr7_any_index_slice()
let lines =<< trim END
# getting the one member should clear the list only after getting the item
assert_equal('bbb', ['aaa', 'bbb', 'ccc'][1])
# string is permissive, index out of range accepted
g:teststring = 'abcdef'
assert_equal('b', g:teststring[1])
assert_equal('', g:teststring[-1])
assert_equal('', g:teststring[99])
assert_equal('b', g:teststring[1:1])
assert_equal('bcdef', g:teststring[1:])
assert_equal('abcd', g:teststring[:3])
assert_equal('cdef', g:teststring[-4:])
assert_equal('abcdef', g:teststring[-9:])
assert_equal('abcd', g:teststring[:-3])
assert_equal('', g:teststring[:-9])
# blob index cannot be out of range
g:testblob = 0z01ab
assert_equal(0x01, g:testblob[0])
assert_equal(0xab, g:testblob[1])
assert_equal(0xab, g:testblob[-1])
assert_equal(0x01, g:testblob[-2])
# blob slice accepts out of range
assert_equal(0z01ab, g:testblob[0:1])
assert_equal(0z01, g:testblob[0:0])
assert_equal(0z01, g:testblob[-2:-2])
assert_equal(0zab, g:testblob[1:1])
assert_equal(0zab, g:testblob[-1:-1])
assert_equal(0z, g:testblob[2:2])
assert_equal(0z, g:testblob[0:-3])
# list index cannot be out of range
g:testlist = [0, 1, 2, 3]
assert_equal(0, g:testlist[0])
assert_equal(1, g:testlist[1])
assert_equal(3, g:testlist[3])
assert_equal(3, g:testlist[-1])
assert_equal(0, g:testlist[-4])
assert_equal(1, g:testlist[g:theone])
# list slice accepts out of range
assert_equal([0], g:testlist[0:0])
assert_equal([3], g:testlist[3:3])
assert_equal([0, 1], g:testlist[0:1])
assert_equal([0, 1, 2, 3], g:testlist[0:3])
assert_equal([0, 1, 2, 3], g:testlist[0:9])
assert_equal([], g:testlist[-1:1])
assert_equal([1], g:testlist[-3:1])
assert_equal([0, 1], g:testlist[-4:1])
assert_equal([0, 1], g:testlist[-9:1])
assert_equal([1, 2, 3], g:testlist[1:-1])
assert_equal([1], g:testlist[1:-3])
assert_equal([], g:testlist[1:-4])
assert_equal([], g:testlist[1:-9])
g:testdict = #{a: 1, b: 2}
assert_equal(1, g:testdict['a'])
assert_equal(2, g:testdict['b'])
END
CheckDefSuccess(lines)
CheckScriptSuccess(['vim9script'] + lines)
CheckDefExecFailure(['echo g:testblob[2]'], 'E979:')
CheckScriptFailure(['vim9script', 'echo g:testblob[2]'], 'E979:')
CheckDefExecFailure(['echo g:testblob[-3]'], 'E979:')
CheckScriptFailure(['vim9script', 'echo g:testblob[-3]'], 'E979:')
CheckDefExecFailure(['echo g:testlist[4]'], 'E684:')
CheckScriptFailure(['vim9script', 'echo g:testlist[4]'], 'E684:')
CheckDefExecFailure(['echo g:testlist[-5]'], 'E684:')
CheckScriptFailure(['vim9script', 'echo g:testlist[-5]'], 'E684:')
CheckDefExecFailure(['echo g:testdict["a":"b"]'], 'E719:')
CheckScriptFailure(['vim9script', 'echo g:testdict["a":"b"]'], 'E719:')
CheckDefExecFailure(['echo g:testdict[1]'], 'E716:')
CheckScriptFailure(['vim9script', 'echo g:testdict[1]'], 'E716:')
unlet g:teststring
unlet g:testblob
unlet g:testlist
enddef
def Test_expr_member_vim9script()

View File

@@ -793,8 +793,8 @@ def Test_try_catch()
endtry
assert_equal(99, n)
# TODO: this will change when index on "any" works
try
# string slice returns a string, not a number
n = g:astring[3]
catch /E1029:/
n = 77

View File

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

View File

@@ -120,6 +120,8 @@ typedef enum {
ISN_STRSLICE, // [expr:expr] string slice
ISN_LISTINDEX, // [expr] list index
ISN_LISTSLICE, // [expr:expr] list slice
ISN_ANYINDEX, // [expr] runtime index
ISN_ANYSLICE, // [expr:expr] runtime slice
ISN_SLICE, // drop isn_arg.number items from start of list
ISN_GETITEM, // push list item, isn_arg.number is the index
ISN_MEMBER, // dict[member]

View File

@@ -3179,20 +3179,20 @@ compile_subscript(
}
else if (vtype == VAR_LIST || *typep == &t_any)
{
// TODO: any requires runtime code
if (*typep == &t_any && need_type(*typep, &t_list_any,
is_slice ? -3 : -2, cctx, FALSE) == FAIL)
return FAIL;
if (is_slice)
{
if (generate_instr_drop(cctx, ISN_LISTSLICE, 2) == FAIL)
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 (generate_instr_drop(cctx, ISN_LISTINDEX, 1) == FAIL)
if (generate_instr_drop(cctx,
vtype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX,
1) == FAIL)
return FAIL;
}
}
@@ -7085,11 +7085,13 @@ delete_instr(isn_T *isn)
case ISN_2STRING_ANY:
case ISN_ADDBLOB:
case ISN_ADDLIST:
case ISN_ANYINDEX:
case ISN_ANYSLICE:
case ISN_BCALL:
case ISN_CATCH:
case ISN_CHECKLEN:
case ISN_CHECKNR:
case ISN_CHECKTYPE:
case ISN_CHECKLEN:
case ISN_COMPAREANY:
case ISN_COMPAREBLOB:
case ISN_COMPAREBOOL:
@@ -7102,7 +7104,6 @@ delete_instr(isn_T *isn)
case ISN_COMPARESTRING:
case ISN_CONCAT:
case ISN_DCALL:
case ISN_SHUFFLE:
case ISN_DROP:
case ISN_ECHO:
case ISN_ECHOERR:
@@ -7111,14 +7112,10 @@ delete_instr(isn_T *isn)
case ISN_EXECCONCAT:
case ISN_EXECUTE:
case ISN_FOR:
case ISN_GETITEM:
case ISN_JUMP:
case ISN_LISTINDEX:
case ISN_LISTSLICE:
case ISN_STRINDEX:
case ISN_STRSLICE:
case ISN_GETITEM:
case ISN_SLICE:
case ISN_MEMBER:
case ISN_JUMP:
case ISN_LOAD:
case ISN_LOADBDICT:
case ISN_LOADGDICT:
@@ -7128,27 +7125,32 @@ delete_instr(isn_T *isn)
case ISN_LOADTDICT:
case ISN_LOADV:
case ISN_LOADWDICT:
case ISN_MEMBER:
case ISN_NEGATENR:
case ISN_NEWDICT:
case ISN_NEWLIST:
case ISN_OPNR:
case ISN_OPFLOAT:
case ISN_OPANY:
case ISN_OPFLOAT:
case ISN_OPNR:
case ISN_PCALL:
case ISN_PCALL_END:
case ISN_PUSHBOOL:
case ISN_PUSHF:
case ISN_PUSHNR:
case ISN_PUSHBOOL:
case ISN_PUSHSPEC:
case ISN_RETURN:
case ISN_SHUFFLE:
case ISN_SLICE:
case ISN_STORE:
case ISN_STOREOUTER:
case ISN_STOREV:
case ISN_STORENR:
case ISN_STOREREG:
case ISN_STORESCRIPT:
case ISN_STOREDICT:
case ISN_STORELIST:
case ISN_STORENR:
case ISN_STOREOUTER:
case ISN_STOREREG:
case ISN_STORESCRIPT:
case ISN_STOREV:
case ISN_STRINDEX:
case ISN_STRSLICE:
case ISN_THROW:
case ISN_TRY:
// nothing allocated

View File

@@ -2297,6 +2297,32 @@ call_def_function(
}
break;
case ISN_ANYINDEX:
case ISN_ANYSLICE:
{
int is_slice = iptr->isn_type == ISN_ANYSLICE;
typval_T *var1, *var2;
int res;
// index: composite is at stack-2, index at stack-1
// slice: composite is at stack-3, indexes at stack-2 and
// stack-1
tv = is_slice ? STACK_TV_BOT(-3) : STACK_TV_BOT(-2);
if (check_can_index(tv, TRUE, TRUE) == FAIL)
goto on_error;
var1 = is_slice ? STACK_TV_BOT(-2) : STACK_TV_BOT(-1);
var2 = is_slice ? STACK_TV_BOT(-1) : NULL;
res = eval_index_inner(tv, is_slice,
var1, var2, NULL, -1, TRUE);
clear_tv(var1);
if (is_slice)
clear_tv(var2);
ectx.ec_stack.ga_len -= is_slice ? 2 : 1;
if (res == FAIL)
goto on_error;
}
break;
case ISN_SLICE:
{
list_T *list;
@@ -3133,6 +3159,8 @@ ex_disassemble(exarg_T *eap)
case ISN_STRSLICE: smsg("%4d STRSLICE", current); break;
case ISN_LISTINDEX: smsg("%4d LISTINDEX", current); break;
case ISN_LISTSLICE: smsg("%4d LISTSLICE", current); break;
case ISN_ANYINDEX: smsg("%4d ANYINDEX", current); break;
case ISN_ANYSLICE: smsg("%4d ANYSLICE", current); break;
case ISN_SLICE: smsg("%4d SLICE %lld",
current, iptr->isn_arg.number); break;
case ISN_GETITEM: smsg("%4d ITEM %lld",