To: vim_dev@googlegroups.com Subject: Patch 8.2.1864 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 = ['', true]"], 'E1012: Type mismatch; expected list but got list', 1) + CheckDefFailure(["var l: list> = [['', true]]"], 'E1012: Type mismatch; expected list> but got list>', 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 = #{a: '', b: true}"], 'E1012: Type mismatch; expected dict but got dict', 1) + CheckDefFailure(["var d: dict> = #{x: #{a: '', b: true}}"], 'E1012: Type mismatch; expected dict> but got dict>', 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 -- Bram@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 ///