Patch 8.2.1864
Problem: Vim9: no error for wrong list type.
Solution: Add flag to indicate a constant. (closes #7160)
Files: src/vim9compile.c, src/testdir/test_vim9_assign.vim
*** ../vim-8.2.1863/src/vim9compile.c 2020-10-18 23:32:10.107420617 +0200
--- src/vim9compile.c 2020-10-19 16:00:50.186323298 +0200
***************
*** 815,824 ****
--- 815,847 ----
}
/*
+ * Return TRUE if "actual" could be "expected" and a runtime typecheck is to be
+ * used. Return FALSE if the types will never match.
+ */
+ static int
+ use_typecheck(type_T *actual, type_T *expected)
+ {
+ if (actual->tt_type == VAR_ANY
+ || actual->tt_type == VAR_UNKNOWN
+ || (actual->tt_type == VAR_FUNC
+ && (expected->tt_type == VAR_FUNC
+ || expected->tt_type == VAR_PARTIAL)
+ && (actual->tt_member == &t_any || actual->tt_argcount < 0)))
+ return TRUE;
+ if ((actual->tt_type == VAR_LIST || actual->tt_type == VAR_DICT)
+ && actual->tt_type == expected->tt_type)
+ // This takes care of a nested list or dict.
+ return use_typecheck(actual->tt_member, expected->tt_member);
+ return FALSE;
+ }
+
+ /*
* Check that
* - "actual" matches "expected" type or
* - "actual" is a type that can be "expected" type: add a runtime check; or
* - return FAIL.
+ * If "actual_is_const" is TRUE then the type won't change at runtime, do not
+ * generate a TYPECHECK.
*/
static int
need_type(
***************
*** 826,832 ****
type_T *expected,
int offset,
cctx_T *cctx,
! int silent)
{
if (expected == &t_bool && actual != &t_bool
&& (actual->tt_flags & TTFLAG_BOOL_OK))
--- 849,856 ----
type_T *expected,
int offset,
cctx_T *cctx,
! int silent,
! int actual_is_const)
{
if (expected == &t_bool && actual != &t_bool
&& (actual->tt_flags & TTFLAG_BOOL_OK))
***************
*** 841,859 ****
return OK;
// If the actual type can be the expected type add a runtime check.
! // TODO: if it's a constant a runtime check makes no sense.
! if (actual->tt_type == VAR_ANY
! || actual->tt_type == VAR_UNKNOWN
! || (actual->tt_type == VAR_FUNC
! && (expected->tt_type == VAR_FUNC
! || expected->tt_type == VAR_PARTIAL)
! && (actual->tt_member == &t_any || actual->tt_argcount < 0))
! || (actual->tt_type == VAR_LIST
! && expected->tt_type == VAR_LIST
! && actual->tt_member == &t_any)
! || (actual->tt_type == VAR_DICT
! && expected->tt_type == VAR_DICT
! && actual->tt_member == &t_any))
{
generate_TYPECHECK(cctx, expected, offset);
return OK;
--- 865,872 ----
return OK;
// If the actual type can be the expected type add a runtime check.
! // If it's a constant a runtime check makes no sense.
! if (!actual_is_const && use_typecheck(actual, expected))
{
generate_TYPECHECK(cctx, expected, offset);
return OK;
***************
*** 1526,1532 ****
else
expected = ufunc->uf_va_type->tt_member;
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
! if (need_type(actual, expected, -argcount + i, cctx, TRUE) == FAIL)
{
arg_type_mismatch(expected, actual, i + 1);
return FAIL;
--- 1539,1546 ----
else
expected = ufunc->uf_va_type->tt_member;
actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i];
! if (need_type(actual, expected, -argcount + i, cctx,
! TRUE, FALSE) == FAIL)
{
arg_type_mismatch(expected, actual, i + 1);
return FAIL;
***************
*** 2061,2068 ****
--- 2075,2085 ----
typedef struct {
typval_T pp_tv[PPSIZE]; // stack of ppconst constants
int pp_used; // active entries in pp_tv[]
+ int pp_is_const; // all generated code was constants, used for a
+ // list or dict with constant members
} ppconst_T;
+ static int compile_expr0_ext(char_u **arg, cctx_T *cctx, int *is_const);
static int compile_expr0(char_u **arg, cctx_T *cctx);
static int compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst);
***************
*** 2629,2641 ****
/*
* parse a list: [expr, expr]
* "*arg" points to the '['.
*/
static int
! compile_list(char_u **arg, cctx_T *cctx)
{
char_u *p = skipwhite(*arg + 1);
char_u *whitep = *arg + 1;
int count = 0;
for (;;)
{
--- 2646,2661 ----
/*
* parse a list: [expr, expr]
* "*arg" points to the '['.
+ * ppconst->pp_is_const is set if all items are a constant.
*/
static int
! compile_list(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
char_u *p = skipwhite(*arg + 1);
char_u *whitep = *arg + 1;
int count = 0;
+ int is_const;
+ int is_all_const = TRUE; // reset when non-const encountered
for (;;)
{
***************
*** 2654,2661 ****
++p;
break;
}
! if (compile_expr0(&p, cctx) == FAIL)
return FAIL;
++count;
if (*p == ',')
{
--- 2674,2683 ----
++p;
break;
}
! if (compile_expr0_ext(&p, cctx, &is_const) == FAIL)
return FAIL;
+ if (!is_const)
+ is_all_const = FALSE;
++count;
if (*p == ',')
{
***************
*** 2671,2678 ****
}
*arg = p;
! generate_NEWLIST(cctx, count);
! return OK;
}
/*
--- 2693,2700 ----
}
*arg = p;
! ppconst->pp_is_const = is_all_const;
! return generate_NEWLIST(cctx, count);
}
/*
***************
*** 2772,2780 ****
/*
* parse a dict: {'key': val} or #{key: val}
* "*arg" points to the '{'.
*/
static int
! compile_dict(char_u **arg, cctx_T *cctx, int literal)
{
garray_T *instr = &cctx->ctx_instr;
garray_T *stack = &cctx->ctx_type_stack;
--- 2794,2803 ----
/*
* parse a dict: {'key': val} or #{key: val}
* "*arg" points to the '{'.
+ * ppconst->pp_is_const is set if all item values are a constant.
*/
static int
! compile_dict(char_u **arg, cctx_T *cctx, int literal, ppconst_T *ppconst)
{
garray_T *instr = &cctx->ctx_instr;
garray_T *stack = &cctx->ctx_type_stack;
***************
*** 2783,2788 ****
--- 2806,2813 ----
dictitem_T *item;
char_u *whitep = *arg;
char_u *p;
+ int is_const;
+ int is_all_const = TRUE; // reset when non-const encountered
if (d == NULL)
return FAIL;
***************
*** 2827,2833 ****
{
type_T *keytype = ((type_T **)stack->ga_data)
[stack->ga_len - 1];
! if (need_type(keytype, &t_string, -1, cctx, FALSE) == FAIL)
return FAIL;
}
}
--- 2852,2859 ----
{
type_T *keytype = ((type_T **)stack->ga_data)
[stack->ga_len - 1];
! if (need_type(keytype, &t_string, -1, cctx,
! FALSE, FALSE) == FAIL)
return FAIL;
}
}
***************
*** 2873,2880 ****
goto failret;
}
! if (compile_expr0(arg, cctx) == FAIL)
return FAIL;
++count;
whitep = *arg;
--- 2899,2908 ----
goto failret;
}
! if (compile_expr0_ext(arg, cctx, &is_const) == FAIL)
return FAIL;
+ if (!is_const)
+ is_all_const = FALSE;
++count;
whitep = *arg;
***************
*** 2908,2913 ****
--- 2936,2942 ----
*arg += STRLEN(*arg);
dict_unref(d);
+ ppconst->pp_is_const = is_all_const;
return generate_NEWDICT(cctx, count);
failret:
***************
*** 3245,3250 ****
--- 3274,3280 ----
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
+ ppconst->pp_is_const = FALSE;
// funcref(arg)
type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
***************
*** 3261,3266 ****
--- 3291,3297 ----
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
+ ppconst->pp_is_const = FALSE;
// something->method()
// Apply the '!', '-' and '+' first:
***************
*** 3316,3321 ****
--- 3347,3353 ----
// TODO: recognize list or dict at runtime
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
+ ppconst->pp_is_const = FALSE;
++p;
*arg = skipwhite(p);
***************
*** 3371,3382 ****
vtype = VAR_DICT;
if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
{
! if (need_type(valtype, &t_number, -1, cctx, FALSE) == FAIL)
return FAIL;
if (is_slice)
{
valtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
! if (need_type(valtype, &t_number, -2, cctx, FALSE) == FAIL)
return FAIL;
}
}
--- 3403,3416 ----
vtype = VAR_DICT;
if (vtype == VAR_STRING || vtype == VAR_LIST || vtype == VAR_BLOB)
{
! if (need_type(valtype, &t_number, -1, 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, cctx,
! FALSE, FALSE) == FAIL)
return FAIL;
}
}
***************
*** 3392,3398 ****
*typep = (*typep)->tt_member;
else
{
! if (need_type(*typep, &t_dict_any, -2, cctx, FALSE) == FAIL)
return FAIL;
*typep = &t_any;
}
--- 3426,3433 ----
*typep = (*typep)->tt_member;
else
{
! if (need_type(*typep, &t_dict_any, -2, cctx,
! FALSE, FALSE) == FAIL)
return FAIL;
*typep = &t_any;
}
***************
*** 3441,3448 ****
--- 3476,3485 ----
}
else if (*p == '.' && p[1] != '.')
{
+ // dictionary member:
dict.name
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
+ ppconst->pp_is_const = FALSE;
*arg = p + 1;
if (may_get_next_line(*arg, arg, cctx) == FAIL)
***************
*** 3450,3456 ****
emsg(_(e_missing_name_after_dot));
return FAIL;
}
- // dictionary member:
dict.name
p = *arg;
if (eval_isdictc(*p))
while (eval_isnamec(*p))
--- 3487,3492 ----
***************
*** 3480,3486 ****
* Compile an expression at "*arg" and add instructions to "cctx->ctx_instr".
* "arg" is advanced until after the expression, skipping white space.
*
! * If the value is a constant "ppconst->pp_ret" will be set.
* Before instructions are generated, any values in "ppconst" will generated.
*
* This is the compiling equivalent of eval1(), eval2(), etc.
--- 3516,3522 ----
* Compile an expression at "*arg" and add instructions to "cctx->ctx_instr".
* "arg" is advanced until after the expression, skipping white space.
*
! * If the value is a constant "ppconst->pp_used" will be non-zero.
* Before instructions are generated, any values in "ppconst" will generated.
*
* This is the compiling equivalent of eval1(), eval2(), etc.
***************
*** 3521,3526 ****
--- 3557,3564 ----
typval_T *rettv = &ppconst->pp_tv[ppconst->pp_used];
int used_before = ppconst->pp_used;
+ ppconst->pp_is_const = FALSE;
+
/*
* Skip '!', '-' and '+' characters. They are handled later.
*/
***************
*** 3610,3616 ****
/*
* List: [expr, expr]
*/
! case '[': ret = compile_list(arg, cctx);
break;
/*
--- 3648,3654 ----
/*
* List: [expr, expr]
*/
! case '[': ret = compile_list(arg, cctx, ppconst);
break;
/*
***************
*** 3619,3625 ****
case '#': if ((*arg)[1] == '{')
{
++*arg;
! ret = compile_dict(arg, cctx, TRUE);
}
else
ret = NOTDONE;
--- 3657,3663 ----
case '#': if ((*arg)[1] == '{')
{
++*arg;
! ret = compile_dict(arg, cctx, TRUE, ppconst);
}
else
ret = NOTDONE;
***************
*** 3638,3644 ****
if (ret != FAIL && *start == '>')
ret = compile_lambda(arg, cctx);
else
! ret = compile_dict(arg, cctx, FALSE);
}
break;
--- 3676,3682 ----
if (ret != FAIL && *start == '>')
ret = compile_lambda(arg, cctx);
else
! ret = compile_dict(arg, cctx, FALSE, ppconst);
}
break;
***************
*** 3807,3813 ****
actual = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (check_type(want_type, actual, FALSE, 0) == FAIL)
{
! if (need_type(actual, want_type, -1, cctx, FALSE) == FAIL)
return FAIL;
}
}
--- 3845,3851 ----
actual = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (check_type(want_type, actual, FALSE, 0) == FAIL)
{
! if (need_type(actual, want_type, -1, cctx, FALSE, FALSE) == FAIL)
return FAIL;
}
}
***************
*** 4420,4428 ****
/*
* Toplevel expression.
*/
static int
! compile_expr0(char_u **arg, cctx_T *cctx)
{
ppconst_T ppconst;
--- 4458,4468 ----
/*
* Toplevel expression.
+ * Sets "is_const" (if not NULL) to indicate the value is a constant.
+ * Returns OK or FAIL.
*/
static int
! compile_expr0_ext(char_u **arg, cctx_T *cctx, int *is_const)
{
ppconst_T ppconst;
***************
*** 4432,4443 ****
--- 4472,4494 ----
clear_ppconst(&ppconst);
return FAIL;
}
+ if (is_const != NULL)
+ *is_const = ppconst.pp_used > 0 || ppconst.pp_is_const;
if (generate_ppconst(cctx, &ppconst) == FAIL)
return FAIL;
return OK;
}
/*
+ * Toplevel expression.
+ */
+ static int
+ compile_expr0(char_u **arg, cctx_T *cctx)
+ {
+ return compile_expr0_ext(arg, cctx, NULL);
+ }
+
+ /*
* compile "return [expr]"
*/
static char_u *
***************
*** 4466,4472 ****
return NULL;
}
if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1,
! cctx, FALSE) == FAIL)
return NULL;
}
}
--- 4517,4523 ----
return NULL;
}
if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1,
! cctx, FALSE, FALSE) == FAIL)
return NULL;
}
}
***************
*** 4834,4840 ****
emsg(_(e_cannot_use_void_value));
goto theend;
}
! if (need_type(stacktype, &t_list_any, -1, cctx, FALSE) == FAIL)
goto theend;
// TODO: check the length of a constant list here
generate_CHECKLEN(cctx, semicolon ? var_count - 1 : var_count,
--- 4885,4892 ----
emsg(_(e_cannot_use_void_value));
goto theend;
}
! if (need_type(stacktype, &t_list_any, -1, cctx,
! FALSE, FALSE) == FAIL)
goto theend;
// TODO: check the length of a constant list here
generate_CHECKLEN(cctx, semicolon ? var_count - 1 : var_count,
***************
*** 5194,5199 ****
--- 5246,5252 ----
else if (oplen > 0)
{
type_T *stacktype;
+ int is_const = FALSE;
// For "var = expr" evaluate the expression.
if (var_count == 0)
***************
*** 5219,5225 ****
--cctx->ctx_locals.ga_len;
instr_count = instr->ga_len;
p = skipwhite(op + oplen);
! r = compile_expr0(&p, cctx);
if (new_local)
++cctx->ctx_locals.ga_len;
if (r == FAIL)
--- 5272,5278 ----
--cctx->ctx_locals.ga_len;
instr_count = instr->ga_len;
p = skipwhite(op + oplen);
! r = compile_expr0_ext(&p, cctx, &is_const);
if (new_local)
++cctx->ctx_locals.ga_len;
if (r == FAIL)
***************
*** 5281,5293 ****
// could be indexing "any"
use_type = &t_any;
}
! if (need_type(stacktype, use_type, -1, cctx, FALSE)
! == FAIL)
goto theend;
}
}
else if (*p != '=' && need_type(stacktype, member_type, -1,
! cctx, FALSE) == FAIL)
goto theend;
}
else if (cmdidx == CMD_final)
--- 5334,5346 ----
// could be indexing "any"
use_type = &t_any;
}
! if (need_type(stacktype, use_type, -1, cctx,
! FALSE, is_const) == FAIL)
goto theend;
}
}
else if (*p != '=' && need_type(stacktype, member_type, -1,
! cctx, FALSE, FALSE) == FAIL)
goto theend;
}
else if (cmdidx == CMD_final)
***************
*** 5374,5380 ****
// If variable is float operation with number is OK.
!(expected == &t_float && stacktype == &t_number) &&
#endif
! need_type(stacktype, expected, -1, cctx, FALSE) == FAIL)
goto theend;
if (*op == '.')
--- 5427,5434 ----
// If variable is float operation with number is OK.
!(expected == &t_float && stacktype == &t_number) &&
#endif
! need_type(stacktype, expected, -1, cctx,
! FALSE, FALSE) == FAIL)
goto theend;
if (*op == '.')
***************
*** 5768,5774 ****
type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (type != &t_bool && type != &t_number && type != &t_any
! && need_type(type, &t_bool, -1, cctx, FALSE) == FAIL)
return FAIL;
return OK;
}
--- 5822,5828 ----
type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (type != &t_bool && type != &t_number && type != &t_any
! && need_type(type, &t_bool, -1, cctx, FALSE, FALSE) == FAIL)
return FAIL;
return OK;
}
***************
*** 6105,6111 ****
// Now that we know the type of "var", check that it is a list, now or at
// runtime.
vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
! if (need_type(vartype, &t_list_any, -1, cctx, FALSE) == FAIL)
{
drop_scope(cctx);
return NULL;
--- 6159,6165 ----
// Now that we know the type of "var", check that it is a list, now or at
// runtime.
vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
! if (need_type(vartype, &t_list_any, -1, cctx, FALSE, FALSE) == FAIL)
{
drop_scope(cctx);
return NULL;
*** ../vim-8.2.1863/src/testdir/test_vim9_assign.vim 2020-10-17 22:04:04.118833463 +0200
--- src/testdir/test_vim9_assign.vim 2020-10-19 16:03:10.341906066 +0200
***************
*** 702,707 ****
--- 702,710 ----
nrl[i] = i
endfor
assert_equal([0, 1, 2, 3, 4], nrl)
+
+ CheckDefFailure(["var l: list<number> = ['', true]"], 'E1012: Type mismatch; expected list<number> but got list<any>', 1)
+ CheckDefFailure(["var l: list<list<number>> = [['', true]]"], 'E1012: Type mismatch; expected list<list<number>> but got list<list<any>>', 1)
enddef
def Test_assign_dict()
***************
*** 718,723 ****
--- 721,729 ----
nrd[i] = i
endfor
assert_equal({'0': 0, '1': 1, '2': 2}, nrd)
+
+ CheckDefFailure(["var d: dict<number> = #{a: '', b: true}"], 'E1012: Type mismatch; expected dict<number> but got dict<any>', 1)
+ CheckDefFailure(["var d: dict<dict<number>> = #{x: #{a: '', b: true}}"], 'E1012: Type mismatch; expected dict<dict<number>> but got dict<dict<any>>', 1)
enddef
def Test_assign_dict_unknown_type()
*** ../vim-8.2.1863/src/version.c 2020-10-19 13:12:29.844428375 +0200
--- src/version.c 2020-10-19 14:49:56.614641391 +0200
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 1864,
/**/
--
I AM THANKFUL...
...for the taxes that I pay because it means that I am employed.
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language --
http://www.Zimbu.org ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///