To: vim_dev@googlegroups.com Subject: Patch 9.0.1254 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1254 Problem: Calling a method on an interface does not work. Solution: At runtime figure out what method to call. (closes #11901) Files: src/vim9expr.c, src/vim9instr.c, src/proto/vim9instr.pro, src/vim9.h, src/vim9execute.c, src/vim9class.c, src/structs.h, src/proto/vim9class.pro, src/testdir/test_vim9_class.vim *** ../vim-9.0.1253/src/vim9expr.c 2023-01-27 20:13:58.432454836 +0000 --- src/vim9expr.c 2023-01-28 14:24:19.423670412 +0000 *************** *** 321,329 **** } ufunc_T *ufunc = NULL; ! for (int i = is_super ? child_count : 0; i < function_count; ++i) { ! ufunc_T *fp = functions[i]; // Use a separate pointer to avoid that ASAN complains about // uf_name[] only being 4 characters. char_u *ufname = (char_u *)fp->uf_name; --- 321,330 ---- } ufunc_T *ufunc = NULL; ! int fi; ! for (fi = is_super ? child_count : 0; fi < function_count; ++fi) { ! 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,353 **** 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) --- 348,358 ---- int argcount = 0; if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) return FAIL; ! ! 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,370 **** } *arg = name_end; ! if (cl->class_flags & CLASS_INTERFACE) return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type); return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type); } --- 369,375 ---- } *arg = name_end; ! 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,1069 **** { if (!func_is_global(ufunc)) { ! res = generate_CALL(cctx, ufunc, argcount); goto theend; } if (!has_g_namespace --- 1068,1074 ---- { if (!func_is_global(ufunc)) { ! res = generate_CALL(cctx, ufunc, NULL, 0, argcount); goto theend; } if (!has_g_namespace *************** *** 1092,1098 **** // If we can find a global function by name generate the right call. if (ufunc != NULL) { ! res = generate_CALL(cctx, ufunc, argcount); goto theend; } --- 1097,1103 ---- // If we can find a global function by name generate the right call. if (ufunc != NULL) { ! res = generate_CALL(cctx, ufunc, NULL, 0, argcount); goto theend; } *** ../vim-9.0.1253/src/vim9instr.c 2023-01-27 20:13:58.436454834 +0000 --- src/vim9instr.c 2023-01-28 12:00:01.225053225 +0000 *************** *** 1709,1719 **** } /* ! * Generate an ISN_DCALL or ISN_UCALL instruction. * Return FAIL if the number of arguments is wrong. */ int ! generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) { isn_T *isn; int regular_args = ufunc->uf_args.ga_len; --- 1709,1726 ---- } /* ! * 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, ! class_T *cl, ! int mi, ! int pushed_argcount) { isn_T *isn; int regular_args = ufunc->uf_args.ga_len; *************** *** 1783,1793 **** return FAIL; } ! if ((isn = generate_instr(cctx, ! ufunc->uf_def_status != UF_NOT_COMPILED ? ISN_DCALL ! : ISN_UCALL)) == NULL) return FAIL; ! if (isn->isn_type == ISN_DCALL) { isn->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; isn->isn_arg.dfunc.cdf_argcount = argcount; --- 1790,1810 ---- return FAIL; } ! 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_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,2488 **** --- 2500,2513 ---- } 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; *** ../vim-9.0.1253/src/proto/vim9instr.pro 2023-01-27 20:13:58.436454834 +0000 --- src/proto/vim9instr.pro 2023-01-28 12:00:12.625044316 +0000 *************** *** 57,63 **** 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_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); --- 57,63 ---- 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, 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); *** ../vim-9.0.1253/src/vim9.h 2023-01-27 20:13:58.436454834 +0000 --- src/vim9.h 2023-01-28 11:55:00.541324743 +0000 *************** *** 112,117 **** --- 112,118 ---- // 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,239 **** --- 235,247 ---- 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,522 **** --- 525,531 ---- trycont_T trycont; cbfunc_T bfunc; cdfunc_T dfunc; + cmfunc_T *mfunc; cpfunc_T pfunc; cufunc_T ufunc; echo_T echo; *** ../vim-9.0.1253/src/vim9execute.c 2023-01-27 20:13:58.436454834 +0000 --- src/vim9execute.c 2023-01-28 13:50:40.552626610 +0000 *************** *** 2262,2268 **** 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); clear_tv(&otv[idx]); otv[idx] = *tv; --- 2262,2269 ---- 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, FALSE, ! idx, obj->obj_class); clear_tv(&otv[idx]); otv[idx] = *tv; *************** *** 2950,2955 **** --- 2951,2970 ---- 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,4130 **** --- 4140,4169 ---- 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,5227 **** 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); goto on_error; } --- 5252,5258 ---- if (tv->v_type != VAR_OBJECT) { SOURCING_LNUM = iptr->isn_lnum; ! object_required_error(tv); goto on_error; } *************** *** 5234,5241 **** 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); } // the members are located right after the object struct --- 5265,5272 ---- 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, ! FALSE, idx, obj->obj_class); } // the members are located right after the object struct *************** *** 6637,6642 **** --- 6668,6684 ---- 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; *** ../vim-9.0.1253/src/vim9class.c 2023-01-27 13:16:14.674850404 +0000 --- src/vim9class.c 2023-01-28 14:51:08.079957103 +0000 *************** *** 201,216 **** * "cl" implementing that interface. */ int ! object_index_from_itf_index(class_T *itf, int idx, class_T *cl) { ! if (idx > 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) break; if (i2c == NULL) { --- 201,217 ---- * "cl" implementing that interface. */ int ! object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl) { ! 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 && i2c->i2c_is_method == is_method) break; if (i2c == NULL) { *************** *** 789,795 **** if (cl->class_name == NULL) goto cleanup; ! cl->class_extends = extends_cl; // Add class and object members to "cl". if (add_members_to_class(&classmembers, --- 790,800 ---- if (cl->class_name == NULL) goto cleanup; ! 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,830 **** VIM_CLEAR(ga_impl.ga_data); ga_impl.ga_len = 0; // 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) ! { ! class_T *ifcl = intf_classes[i]; itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T) + ifcl->class_obj_member_count * sizeof(int)); if (if2cl == NULL) --- 825,850 ---- 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. ! // 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 = 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,853 **** if2cl->i2c_next = ifcl->class_itf2class; ifcl->class_itf2class = if2cl; if2cl->i2c_class = cl; 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) { if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, ! cl->class_obj_members[cl_i].ocm_name) == 0) { int *table = (int *)(if2cl + 1); table[if_i] = cl_i; break; } } - } ! cl->class_interfaces_cl = intf_classes; ! intf_classes = NULL; } if (is_class && cl->class_class_member_count > 0) --- 852,915 ---- 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) { if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, ! 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; ! ! 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) *** ../vim-9.0.1253/src/structs.h 2023-01-20 18:49:42.763170966 +0000 --- src/structs.h 2023-01-28 14:21:34.103761488 +0000 *************** *** 1484,1498 **** char_u *ocm_init; // allocated } ocmember_T; ! // used for the lookup table of a class member index typedef struct itf2class_S itf2class_T; struct itf2class_S { itf2class_T *i2c_next; class_T *i2c_class; // array with ints follows }; ! #define CLASS_INTERFACE 1 // "class_T": used for v_class of typval of VAR_CLASS // Also used for an interface (class_flags has CLASS_INTERFACE). --- 1484,1500 ---- char_u *ocm_init; // allocated } ocmember_T; ! // 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_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). *** ../vim-9.0.1253/src/proto/vim9class.pro 2023-01-24 15:07:00.428562536 +0000 --- src/proto/vim9class.pro 2023-01-28 13:40:21.620461706 +0000 *************** *** 1,5 **** /* vim9class.c */ ! int object_index_from_itf_index(class_T *itf, 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); --- 1,5 ---- /* vim9class.c */ ! 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); *** ../vim-9.0.1253/src/testdir/test_vim9_class.vim 2023-01-27 20:13:58.436454834 +0000 --- src/testdir/test_vim9_class.vim 2023-01-28 14:02:38.060674983 +0000 *************** *** 1001,1006 **** --- 1001,1056 ---- 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 *** ../vim-9.0.1253/src/version.c 2023-01-28 10:43:47.157153173 +0000 --- src/version.c 2023-01-28 11:42:34.685721166 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1254, /**/ -- "I don’t know how to make a screenshot" - Richard Stallman, July 2002 (when asked to send a screenshot of his desktop for unix.se) /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///