# HG changeset patch # User Bram Moolenaar # Date 1674919804 -3600 # Node ID ffa11e2757e76fbb2f11dcf877682e3775e984eb # Parent 1b6543beb5606eb505eb33c20325c2f0287ae0fe patch 9.0.1254: calling a method on an interface does not work Commit: https://github.com/vim/vim/commit/d0200c8631582bbb16a9b585e2ca7adccc84ccdd Author: Bram Moolenaar Date: Sat Jan 28 15:19:40 2023 +0000 patch 9.0.1254: calling a method on an interface does not work Problem: Calling a method on an interface does not work. Solution: At runtime figure out what method to call. (closes https://github.com/vim/vim/issues/11901) diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -1,5 +1,5 @@ /* vim9class.c */ -int object_index_from_itf_index(class_T *itf, int idx, class_T *cl); +int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl); void ex_class(exarg_T *eap); type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx); void ex_enum(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 @@ -57,7 +57,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, 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/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1484,15 +1484,17 @@ typedef struct { char_u *ocm_init; // allocated } ocmember_T; -// used for the lookup table of a class member index +// used for the lookup table of a class member index and object method index typedef struct itf2class_S itf2class_T; struct itf2class_S { itf2class_T *i2c_next; class_T *i2c_class; + int i2c_is_method; // TRUE for method indexes // array with ints follows }; -#define CLASS_INTERFACE 1 +#define CLASS_INTERFACE 1 +#define CLASS_EXTENDED 2 // another class extends this one // "class_T": used for v_class of typval of VAR_CLASS // Also used for an interface (class_flags has CLASS_INTERFACE). 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 @@ -1001,6 +1001,56 @@ def Test_class_implements_interface() v9.CheckScriptSuccess(lines) enddef +def Test_call_interface_method() + var lines =<< trim END + vim9script + interface Base + def Enter(): void + endinterface + + class Child implements Base + def Enter(): void + g:result ..= 'child' + enddef + endclass + + def F(obj: Base) + obj.Enter() + enddef + + g:result = '' + F(Child.new()) + assert_equal('child', g:result) + unlet g:result + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + class Base + def Enter(): void + g:result ..= 'base' + enddef + endclass + + class Child extends Base + def Enter(): void + g:result ..= 'child' + enddef + endclass + + def F(obj: Base) + obj.Enter() + enddef + + g:result = '' + F(Child.new()) + assert_equal('child', g:result) + unlet g:result + END + v9.CheckScriptSuccess(lines) +enddef + def Test_class_used_as_type() var lines =<< trim END vim9script diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1254, +/**/ 1253, /**/ 1252, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -112,6 +112,7 @@ typedef enum { // function call ISN_BCALL, // call builtin function isn_arg.bfunc ISN_DCALL, // call def function isn_arg.dfunc + ISN_METHODCALL, // call method on interface, uses isn_arg.mfunc ISN_UCALL, // call user function or funcref/partial isn_arg.ufunc ISN_PCALL, // call partial, use isn_arg.pfunc ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set @@ -234,6 +235,13 @@ typedef struct { int cdf_argcount; // number of arguments on top of stack } cdfunc_T; +// arguments to ISN_METHODCALL +typedef struct { + class_T *cmf_itf; // interface used + int cmf_idx; // index in "def_functions" for ISN_DCALL + int cmf_argcount; // number of arguments on top of stack +} cmfunc_T; + // arguments to ISN_PCALL typedef struct { int cpf_top; // when TRUE partial is above the arguments @@ -517,6 +525,7 @@ struct isn_S { trycont_T trycont; cbfunc_T bfunc; cdfunc_T dfunc; + cmfunc_T *mfunc; cpfunc_T pfunc; cufunc_T ufunc; echo_T echo; diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -201,16 +201,17 @@ add_members_to_class( * "cl" implementing that interface. */ int -object_index_from_itf_index(class_T *itf, int idx, class_T *cl) +object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl) { - if (idx > itf->class_obj_member_count) + if (idx > (is_method ? itf->class_obj_method_count + : itf->class_obj_member_count)) { siemsg("index %d out of range for interface %s", idx, itf->class_name); return 0; } itf2class_T *i2c; for (i2c = itf->class_itf2class; i2c != NULL; i2c = i2c->i2c_next) - if (i2c->i2c_class == cl) + if (i2c->i2c_class == cl && i2c->i2c_is_method == is_method) break; if (i2c == NULL) { @@ -789,7 +790,11 @@ early_ret: if (cl->class_name == NULL) goto cleanup; - cl->class_extends = extends_cl; + if (extends_cl != NULL) + { + cl->class_extends = extends_cl; + extends_cl->class_flags |= CLASS_EXTENDED; + } // Add class and object members to "cl". if (add_members_to_class(&classmembers, @@ -820,11 +825,26 @@ early_ret: VIM_CLEAR(ga_impl.ga_data); ga_impl.ga_len = 0; + cl->class_interfaces_cl = intf_classes; + intf_classes = NULL; + } + + if (cl->class_interface_count > 0 || extends_cl != NULL) + { // For each interface add a lookuptable for the member index on the // interface to the member index in this class. - for (int i = 0; i < cl->class_interface_count; ++i) + // And a lookuptable for the object method index on the interface + // to the object method index in this class. + // Also do this for the extended class, if any. + for (int i = 0; i <= cl->class_interface_count; ++i) { - class_T *ifcl = intf_classes[i]; + class_T *ifcl = i < cl->class_interface_count + ? cl->class_interfaces_cl[i] + : extends_cl; + if (ifcl == NULL) + continue; + + // Table for members. itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T) + ifcl->class_obj_member_count * sizeof(int)); if (if2cl == NULL) @@ -832,22 +852,64 @@ early_ret: if2cl->i2c_next = ifcl->class_itf2class; ifcl->class_itf2class = if2cl; if2cl->i2c_class = cl; + if2cl->i2c_is_method = FALSE; for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i) - for (int cl_i = 0; cl_i < cl->class_obj_member_count; ++cl_i) + for (int cl_i = 0; cl_i < cl->class_obj_member_count; + ++cl_i) { if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, - cl->class_obj_members[cl_i].ocm_name) == 0) + cl->class_obj_members[cl_i].ocm_name) == 0) { int *table = (int *)(if2cl + 1); table[if_i] = cl_i; break; } } - } + + // Table for methods. + if2cl = alloc_clear(sizeof(itf2class_T) + + ifcl->class_obj_method_count * sizeof(int)); + if (if2cl == NULL) + goto cleanup; + if2cl->i2c_next = ifcl->class_itf2class; + ifcl->class_itf2class = if2cl; + if2cl->i2c_class = cl; + if2cl->i2c_is_method = TRUE; - cl->class_interfaces_cl = intf_classes; - intf_classes = NULL; + for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i) + { + int done = FALSE; + for (int cl_i = 0; cl_i < objmethods.ga_len; ++cl_i) + { + if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, + ((ufunc_T **)objmethods.ga_data)[cl_i]->uf_name) + == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = cl_i; + done = TRUE; + break; + } + } + + if (!done && extends_cl != NULL) + { + for (int cl_i = 0; + cl_i < extends_cl->class_obj_member_count; ++cl_i) + { + if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name, + extends_cl->class_obj_methods[cl_i]->uf_name) + == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = cl_i; + break; + } + } + } + } + } } if (is_class && cl->class_class_member_count > 0) diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2262,7 +2262,8 @@ execute_storeindex(isn_T *iptr, ectx_T * class_T *itf = iptr->isn_arg.storeindex.si_class; if (itf != NULL) // convert interface member index to class member index - idx = object_index_from_itf_index(itf, idx, obj->obj_class); + idx = object_index_from_itf_index(itf, FALSE, + idx, obj->obj_class); clear_tv(&otv[idx]); otv[idx] = *tv; @@ -2950,6 +2951,20 @@ load_namespace_var(ectx_T *ectx, isntype return OK; } + + static void +object_required_error(typval_T *tv) +{ + garray_T type_list; + ga_init2(&type_list, sizeof(type_T *), 10); + type_T *type = typval2type(tv, get_copyID(), &type_list, TVTT_DO_MEMBER); + char *tofree = NULL; + char *typename = type_name(type, &tofree); + semsg(_(e_object_required_found_str), typename); + vim_free(tofree); + clear_type_list(&type_list); +} + /* * Execute instructions in execution context "ectx". * Return OK or FAIL; @@ -4125,6 +4140,30 @@ exec_instructions(ectx_T *ectx) goto on_error; break; + // call a method on an interface + case ISN_METHODCALL: + { + SOURCING_LNUM = iptr->isn_lnum; + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_OBJECT) + { + object_required_error(tv); + goto on_error; + } + object_T *obj = tv->vval.v_object; + class_T *cl = obj->obj_class; + + // convert the interface index to the object index + cmfunc_T *mfunc = iptr->isn_arg.mfunc; + int idx = object_index_from_itf_index(mfunc->cmf_itf, + TRUE, mfunc->cmf_idx, cl); + + if (call_ufunc(cl->class_obj_methods[idx], NULL, + mfunc->cmf_argcount, ectx, NULL, NULL) == FAIL) + goto on_error; + } + break; + // call a builtin function case ISN_BCALL: SOURCING_LNUM = iptr->isn_lnum; @@ -5213,15 +5252,7 @@ exec_instructions(ectx_T *ectx) if (tv->v_type != VAR_OBJECT) { SOURCING_LNUM = iptr->isn_lnum; - garray_T type_list; - ga_init2(&type_list, sizeof(type_T *), 10); - type_T *type = typval2type(tv, get_copyID(), - &type_list, TVTT_DO_MEMBER); - char *tofree = NULL; - char *typename = type_name(type, &tofree); - semsg(_(e_object_required_found_str), typename); - vim_free(tofree); - clear_type_list(&type_list); + object_required_error(tv); goto on_error; } @@ -5234,8 +5265,8 @@ exec_instructions(ectx_T *ectx) idx = iptr->isn_arg.classmember.cm_idx; // convert the interface index to the object index idx = object_index_from_itf_index( - iptr->isn_arg.classmember.cm_class, - idx, obj->obj_class); + iptr->isn_arg.classmember.cm_class, + FALSE, idx, obj->obj_class); } // the members are located right after the object struct @@ -6637,6 +6668,17 @@ list_instructions(char *pfx, isn_T *inst cdfunc->cdf_argcount); } break; + case ISN_METHODCALL: + { + cmfunc_T *mfunc = iptr->isn_arg.mfunc; + + smsg("%s%4d METHODCALL %s.%s(argc %d)", pfx, current, + mfunc->cmf_itf->class_name, + mfunc->cmf_itf->class_obj_methods[ + mfunc->cmf_idx]->uf_name, + mfunc->cmf_argcount); + } + break; case ISN_UCALL: { cufunc_T *cufunc = &iptr->isn_arg.ufunc; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -321,9 +321,10 @@ compile_class_object_index(cctx_T *cctx, } ufunc_T *ufunc = NULL; - for (int i = is_super ? child_count : 0; i < function_count; ++i) + int fi; + for (fi = is_super ? child_count : 0; fi < function_count; ++fi) { - ufunc_T *fp = functions[i]; + ufunc_T *fp = functions[fi]; // Use a separate pointer to avoid that ASAN complains about // uf_name[] only being 4 characters. char_u *ufname = (char_u *)fp->uf_name; @@ -347,7 +348,11 @@ compile_class_object_index(cctx_T *cctx, int argcount = 0; if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) return FAIL; - return generate_CALL(cctx, ufunc, argcount); + + if (type->tt_type == VAR_OBJECT + && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) + return generate_CALL(cctx, ufunc, cl, fi, argcount); + return generate_CALL(cctx, ufunc, NULL, 0, argcount); } if (type->tt_type == VAR_OBJECT) @@ -364,7 +369,7 @@ compile_class_object_index(cctx_T *cctx, } *arg = name_end; - if (cl->class_flags & CLASS_INTERFACE) + if (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)) return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type); return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type); } @@ -1063,7 +1068,7 @@ compile_call( { if (!func_is_global(ufunc)) { - res = generate_CALL(cctx, ufunc, argcount); + res = generate_CALL(cctx, ufunc, NULL, 0, argcount); goto theend; } if (!has_g_namespace @@ -1092,7 +1097,7 @@ compile_call( // If we can find a global function by name generate the right call. if (ufunc != NULL) { - res = generate_CALL(cctx, ufunc, 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 @@ -1709,11 +1709,18 @@ generate_BLOBAPPEND(cctx_T *cctx) } /* - * Generate an ISN_DCALL or ISN_UCALL instruction. + * Generate an ISN_DCALL, ISN_UCALL or ISN_METHODCALL instruction. + * When calling a method on an object, of which we know the interface only, + * then "cl" is the interface and "mi" the method index on the interface. * Return FAIL if the number of arguments is wrong. */ int -generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) +generate_CALL( + cctx_T *cctx, + ufunc_T *ufunc, + class_T *cl, + int mi, + int pushed_argcount) { isn_T *isn; int regular_args = ufunc->uf_args.ga_len; @@ -1783,11 +1790,21 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufu return FAIL; } - if ((isn = generate_instr(cctx, - ufunc->uf_def_status != UF_NOT_COMPILED ? ISN_DCALL - : ISN_UCALL)) == NULL) + if ((isn = generate_instr(cctx, cl != NULL ? ISN_METHODCALL + : ufunc->uf_def_status != UF_NOT_COMPILED + ? ISN_DCALL : ISN_UCALL)) == NULL) return FAIL; - if (isn->isn_type == ISN_DCALL) + if (isn->isn_type == ISN_METHODCALL) + { + isn->isn_arg.mfunc = ALLOC_ONE(cmfunc_T); + if (isn->isn_arg.mfunc == NULL) + return FAIL; + isn->isn_arg.mfunc->cmf_itf = cl; + ++cl->class_refcount; + isn->isn_arg.mfunc->cmf_idx = mi; + isn->isn_arg.mfunc->cmf_argcount = argcount; + } + else if (isn->isn_type == ISN_DCALL) { isn->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; isn->isn_arg.dfunc.cdf_argcount = argcount; @@ -2483,6 +2500,14 @@ delete_instr(isn_T *isn) } break; + case ISN_METHODCALL: + { + cmfunc_T *mfunc = isn->isn_arg.mfunc; + class_unref(mfunc->cmf_itf); + vim_free(mfunc); + } + break; + case ISN_NEWFUNC: { newfuncarg_T *arg = isn->isn_arg.newfunc.nf_arg;