Patch 9.0.1254

4 views
Skip to first unread message

Bram Moolenaar

unread,
Jan 28, 2023, 10:20:17 AM1/28/23
to vim...@googlegroups.com

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 -- Br...@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 ///
Reply all
Reply to author
Forward
0 new messages