summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2023-01-28 15:19:40 +0000
committerBram Moolenaar <Bram@vim.org>2023-01-28 15:19:40 +0000
commitd0200c8631582bbb16a9b585e2ca7adccc84ccdd (patch)
tree91de748be127428bb62bbd295c41ad52b4b7336a
parent192e24d974522852bdca67db6d76ead87f57e677 (diff)
downloadvim-git-d0200c8631582bbb16a9b585e2ca7adccc84ccdd.tar.gz
patch 9.0.1254: calling a method on an interface does not workv9.0.1254
Problem: Calling a method on an interface does not work. Solution: At runtime figure out what method to call. (closes #11901)
-rw-r--r--src/proto/vim9class.pro2
-rw-r--r--src/proto/vim9instr.pro2
-rw-r--r--src/structs.h6
-rw-r--r--src/testdir/test_vim9_class.vim50
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h9
-rw-r--r--src/vim9class.c84
-rw-r--r--src/vim9execute.c66
-rw-r--r--src/vim9expr.c17
-rw-r--r--src/vim9instr.c37
10 files changed, 236 insertions, 39 deletions
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index 13a643dd5..707f4ec61 100644
--- 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
index 304131748..ff132831c 100644
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -57,7 +57,7 @@ int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int metho
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
index 3b5a233f6..1c7a52788 100644
--- 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
index a59cdc4c4..3a434d1ba 100644
--- 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
index 08947caac..a6534c209 100644
--- 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
index faf2650ba..fd98bb502 100644
--- 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
index d64e35ec0..ecf5de711 100644
--- 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;
}
}
- }
- cl->class_interfaces_cl = intf_classes;
- intf_classes = NULL;
+ // 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)
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 726b2d65f..3498e547e 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2262,7 +2262,8 @@ execute_storeindex(isn_T *iptr, ectx_T *ectx)
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_T isn_type, isn_T *iptr)
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 *instr, int instr_count, ufunc_T *ufunc)
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
index 06d1657ec..c65fc79fd 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -321,9 +321,10 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
}
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, char_u **arg, type_T *type)
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, char_u **arg, type_T *type)
}
*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
index ae0304435..cc0d8ad8b 100644
--- 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 *ufunc, int pushed_argcount)
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;