# HG changeset patch # User Christian Brabandt # Date 1695564007 -7200 # Node ID 4e531adb3fac37e97163e93c91f7d580af639b3a # Parent 46a93a71d4ee9e652105f7596a54805242afa31f patch 9.0.1928: Vim9: constructor type checking bug Commit: https://github.com/vim/vim/commit/b895b0fabce7d952a6617eb69fc1e1597ece8b00 Author: h-east Date: Sun Sep 24 15:46:31 2023 +0200 patch 9.0.1928: Vim9: constructor type checking bug Problem: Vim9: constructor type checking bug Solution: Fix class constructor regression closes: #13102 closes: #13113 Signed-off-by: Christian Brabandt Co-authored-by: h-east diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -47,7 +47,7 @@ char_u *get_scriptlocal_funcname(char_u char_u *alloc_printable_func_name(char_u *fname); char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi); void list_functions(regmatch_T *regmatch); -ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags); +ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags, ocmember_T *obj_members, int obj_member_count); void ex_function(exarg_T *eap); ufunc_T *find_func_by_name(char_u *name, compiletype_T *compile_type); void ex_defcompile(exarg_T *eap); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -58,7 +58,7 @@ int check_internal_func_args(cctx_T *cct int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call); int generate_LISTAPPEND(cctx_T *cctx); int generate_BLOBAPPEND(cctx_T *cctx); -int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int mi, type_T *mtype, int pushed_argcount); +int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int mi, int pushed_argcount); int generate_UCALL(cctx_T *cctx, char_u *name, int argcount); int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name); int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top); diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -921,6 +921,27 @@ def Test_class_new_with_object_member() Check() END v9.CheckSourceFailure(lines, 'E1013:') + + lines =<< trim END + vim9script + + class C + this.str: string + def new(str: any) + enddef + endclass + + def Check() + try + var c = C.new(1) + catch + assert_report($'Unexpected exception was caught: {v:exception}') + endtry + enddef + + Check() + END + v9.CheckSourceSuccess(lines) enddef def Test_class_object_member_inits() diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -65,6 +65,7 @@ one_function_arg( garray_T *newargs, garray_T *argtypes, int types_optional, + garray_T *arg_objm, evalarg_T *evalarg, exarg_T *eap, int is_vararg, @@ -136,7 +137,8 @@ one_function_arg( } // get any type from "arg: type" - if (argtypes != NULL && (skip || ga_grow(argtypes, 1) == OK)) + if (argtypes != NULL && (skip || ga_grow(argtypes, 1) == OK) + && arg_objm != NULL && (skip || ga_grow(arg_objm, 1) == OK)) { char_u *type = NULL; @@ -172,6 +174,7 @@ one_function_arg( type = vim_strsave((char_u *) (is_vararg ? "list" : "any")); ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type; + ((int8_T *)arg_objm->ga_data)[arg_objm->ga_len++] = FALSE; } } @@ -221,6 +224,7 @@ get_function_args( garray_T *newargs, garray_T *argtypes, // NULL unless using :def int types_optional, // types optional if "argtypes" is not NULL + garray_T *arg_objm, // NULL unless using :def evalarg_T *evalarg, // context or NULL int *varargs, garray_T *default_args, @@ -241,6 +245,8 @@ get_function_args( ga_init2(newargs, sizeof(char_u *), 3); if (argtypes != NULL) ga_init2(argtypes, sizeof(char_u *), 3); + if (arg_objm != NULL) + ga_init2(arg_objm, sizeof(int8_T), 3); if (!skip && default_args != NULL) ga_init2(default_args, sizeof(char_u *), 3); @@ -295,7 +301,7 @@ get_function_args( arg = p; p = one_function_arg(p, newargs, argtypes, types_optional, - evalarg, eap, TRUE, skip); + arg_objm, evalarg, eap, TRUE, skip); if (p == arg) break; if (*skipwhite(p) == '=') @@ -350,11 +356,13 @@ get_function_args( vim_strnsave(arg, argend - arg); newargs->ga_len++; - if (argtypes != NULL && ga_grow(argtypes, 1) == OK) + if (argtypes != NULL && ga_grow(argtypes, 1) == OK + && arg_objm != NULL && ga_grow(arg_objm, 1) == OK) { // TODO: use the actual type ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = vim_strsave((char_u *)"any"); + ((int8_T *)arg_objm->ga_data)[arg_objm->ga_len++] = TRUE; // Add a line to the function body for the assignment. if (ga_grow(newlines, 1) == OK) @@ -391,7 +399,7 @@ get_function_args( arg = p; p = one_function_arg(p, newargs, argtypes, types_optional, - evalarg, eap, FALSE, skip); + arg_objm, evalarg, eap, FALSE, skip); if (p == arg) break; @@ -490,7 +498,13 @@ err_ret: * Return OK or FAIL. */ static int -parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs) +parse_argument_types( + ufunc_T *fp, + garray_T *argtypes, + int varargs, + garray_T *arg_objm, + ocmember_T *obj_members, + int obj_member_count) { int len = 0; @@ -516,7 +530,24 @@ parse_argument_types(ufunc_T *fp, garray // will get the type from the default value type = &t_unknown; else - type = parse_type(&p, &fp->uf_type_list, TRUE); + { + if (arg_objm != NULL && ((int8_T *)arg_objm->ga_data)[i]) + { + char_u *aname = ((char_u **)fp->uf_args.ga_data)[i]; + + type = &t_any; + for (int om = 0; om < obj_member_count; ++om) + { + if (STRCMP(aname, obj_members[om].ocm_name) == 0) + { + type = obj_members[om].ocm_type; + break; + } + } + } + else + type = parse_type(&p, &fp->uf_type_list, TRUE); + } if (type == NULL) return FAIL; fp->uf_arg_types[i] = type; @@ -1395,7 +1426,7 @@ lambda_function_body( SOURCING_LNUM = sourcing_lnum_top; // parse argument types - if (parse_argument_types(ufunc, argtypes, varargs) == FAIL) + if (parse_argument_types(ufunc, argtypes, varargs, NULL, NULL, 0) == FAIL) { SOURCING_LNUM = lnum_save; goto erret; @@ -1464,6 +1495,7 @@ get_lambda_tv( garray_T *pnewargs; garray_T argtypes; garray_T default_args; + garray_T arg_objm; ufunc_T *fp = NULL; partial_T *pt = NULL; int varargs; @@ -1490,12 +1522,16 @@ get_lambda_tv( // be found after the arguments. s = *arg + 1; ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL, - types_optional ? &argtypes : NULL, types_optional, evalarg, - NULL, &default_args, TRUE, NULL, FALSE, NULL, NULL); + types_optional ? &argtypes : NULL, types_optional, + types_optional ? &arg_objm : NULL, evalarg, + NULL, &default_args, TRUE, NULL, FALSE, NULL, NULL); if (ret == FAIL || skip_arrow(s, equal_arrow, &ret_type, NULL) == NULL) { if (types_optional) + { ga_clear_strings(&argtypes); + ga_clear(&arg_objm); + } return called_emsg == called_emsg_start ? NOTDONE : FAIL; } @@ -1506,7 +1542,8 @@ get_lambda_tv( pnewargs = NULL; *arg += 1; ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs, - types_optional ? &argtypes : NULL, types_optional, evalarg, + types_optional ? &argtypes : NULL, types_optional, + types_optional ? &arg_objm : NULL, evalarg, &varargs, &default_args, FALSE, NULL, FALSE, NULL, NULL); if (ret == FAIL @@ -1514,7 +1551,10 @@ get_lambda_tv( equal_arrow || vim9script ? &white_error : NULL)) == NULL) { if (types_optional) + { ga_clear_strings(&argtypes); + ga_clear(&arg_objm); + } ga_clear_strings(&newargs); return white_error ? FAIL : NOTDONE; } @@ -1631,7 +1671,7 @@ get_lambda_tv( if (types_optional) { if (parse_argument_types(fp, &argtypes, - vim9script && varargs) == FAIL) + vim9script && varargs, NULL, NULL, 0) == FAIL) goto errret; if (ret_type != NULL) { @@ -4591,7 +4631,9 @@ define_function( exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, - int class_flags) + int class_flags, + ocmember_T *obj_members, + int obj_member_count) { int j; int c; @@ -4604,6 +4646,7 @@ define_function( char_u *line_arg = NULL; garray_T newargs; garray_T argtypes; + garray_T arg_objm; garray_T default_args; garray_T newlines; int varargs = FALSE; @@ -4662,6 +4705,7 @@ define_function( ga_init(&newargs); ga_init(&argtypes); + ga_init(&arg_objm); ga_init(&default_args); /* @@ -4909,6 +4953,7 @@ define_function( ++p; if (get_function_args(&p, ')', &newargs, eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE, + eap->cmdidx == CMD_def ? &arg_objm : NULL, NULL, &varargs, &default_args, eap->skip, eap, class_flags, &newlines, lines_to_free) == FAIL) goto errret_2; @@ -5252,7 +5297,8 @@ define_function( // The function may use script variables from the context. function_using_block_scopes(fp, cstack); - if (parse_argument_types(fp, &argtypes, varargs) == FAIL) + if (parse_argument_types(fp, &argtypes, varargs, &arg_objm, + obj_members, obj_member_count) == FAIL) { SOURCING_LNUM = lnum_save; free_fp = fp_allocated; @@ -5352,6 +5398,7 @@ errret_2: VIM_CLEAR(fp); ret_free: ga_clear_strings(&argtypes); + ga_clear(&arg_objm); vim_free(fudi.fd_newkey); if (name != name_arg) vim_free(name); @@ -5370,7 +5417,7 @@ ex_function(exarg_T *eap) garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - (void)define_function(eap, NULL, &lines_to_free, 0); + (void)define_function(eap, NULL, &lines_to_free, 0, NULL, 0); ga_clear_strings(&lines_to_free); } diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1928, +/**/ 1927, /**/ 1926, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -1200,7 +1200,8 @@ add_default_constructor( garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, CF_CLASS); + ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, CF_CLASS, + cl->class_obj_members, cl->class_obj_member_count); ga_clear_strings(&lines_to_free); vim_free(fga.ga_data); @@ -1679,7 +1680,7 @@ early_ret: else class_flags = CF_INTERFACE; ufunc_T *uf = define_function(&ea, NULL, &lines_to_free, - class_flags); + class_flags, objmembers.ga_data, objmembers.ga_len); ga_clear_strings(&lines_to_free); if (uf != NULL) diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1101,7 +1101,7 @@ compile_nested_function(exarg_T *eap, cc int save_KeyTyped = KeyTyped; KeyTyped = FALSE; - ufunc = define_function(eap, lambda_name, lines_to_free, 0); + ufunc = define_function(eap, lambda_name, lines_to_free, 0, NULL, 0); KeyTyped = save_KeyTyped; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -4470,7 +4470,7 @@ exec_instructions(ectx_T *ectx) ea.cmd = ea.arg = iptr->isn_arg.string; ga_init2(&lines_to_free, sizeof(char_u *), 50); SOURCING_LNUM = iptr->isn_lnum; - define_function(&ea, NULL, &lines_to_free, 0); + define_function(&ea, NULL, &lines_to_free, 0, NULL, 0); ga_clear_strings(&lines_to_free); } break; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -395,8 +395,8 @@ compile_class_object_index(cctx_T *cctx, if (type->tt_type == VAR_OBJECT && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) - return generate_CALL(cctx, ufunc, cl, fi, type, argcount); - return generate_CALL(cctx, ufunc, NULL, 0, type, argcount); + return generate_CALL(cctx, ufunc, cl, fi, argcount); + return generate_CALL(cctx, ufunc, NULL, 0, argcount); } if (type->tt_type == VAR_OBJECT) @@ -977,7 +977,6 @@ compile_call( int has_g_namespace; ca_special_T special_fn; imported_T *import; - type_T *type; if (varlen >= sizeof(namebuf)) { @@ -1061,7 +1060,6 @@ compile_call( if (compile_arguments(arg, cctx, &argcount, special_fn) == FAIL) goto theend; - type = get_decl_type_on_stack(cctx, 1); is_autoload = vim_strchr(name, AUTOLOAD_CHAR) != NULL; if (ASCII_ISLOWER(*name) && name[1] != ':' && !is_autoload) { @@ -1079,6 +1077,8 @@ compile_call( if (STRCMP(name, "add") == 0 && argcount == 2) { + type_T *type = get_decl_type_on_stack(cctx, 1); + // add() can be compiled to instructions if we know the type if (type->tt_type == VAR_LIST) { @@ -1128,7 +1128,7 @@ compile_call( { if (!func_is_global(ufunc)) { - res = generate_CALL(cctx, ufunc, NULL, 0, type, argcount); + res = generate_CALL(cctx, ufunc, NULL, 0, argcount); goto theend; } if (!has_g_namespace @@ -1147,7 +1147,7 @@ compile_call( if (cctx->ctx_ufunc->uf_defclass == cl) { res = generate_CALL(cctx, cl->class_class_functions[mi], NULL, - 0, type, argcount); + 0, argcount); } else { @@ -1175,7 +1175,7 @@ compile_call( // If we can find a global function by name generate the right call. if (ufunc != NULL) { - res = generate_CALL(cctx, ufunc, NULL, 0, type, argcount); + res = generate_CALL(cctx, ufunc, NULL, 0, argcount); goto theend; } diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -1784,7 +1784,6 @@ generate_CALL( ufunc_T *ufunc, class_T *cl, int mi, - type_T *mtype, // method type int pushed_argcount) { isn_T *isn; @@ -1810,8 +1809,6 @@ generate_CALL( { int i; compiletype_T compile_type; - int class_constructor = (mtype->tt_type == VAR_CLASS - && STRNCMP(ufunc->uf_name, "new", 3) == 0); for (i = 0; i < argcount; ++i) { @@ -1830,18 +1827,6 @@ generate_CALL( if (ufunc->uf_arg_types == NULL) continue; expected = ufunc->uf_arg_types[i]; - - // When the method is a class constructor and the formal - // argument is an object member, the type check is performed on - // the object member type. - if (class_constructor && expected->tt_type == VAR_ANY) - { - class_T *clp = mtype->tt_class; - char_u *aname = ((char_u **)ufunc->uf_args.ga_data)[i]; - ocmember_T *m = object_member_lookup(clp, aname, 0, NULL); - if (m != NULL) - expected = m->ocm_type; - } } else if (ufunc->uf_va_type == NULL || ufunc->uf_va_type == &t_list_any)