0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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 #11901)
This commit is contained in:
Bram Moolenaar 2023-01-28 15:19:40 +00:00
parent 192e24d974
commit d0200c8631
10 changed files with 236 additions and 39 deletions

View File

@ -1,5 +1,5 @@
/* vim9class.c */ /* 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); void ex_class(exarg_T *eap);
type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx); type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx);
void ex_enum(exarg_T *eap); void ex_enum(exarg_T *eap);

View File

@ -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_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
int generate_LISTAPPEND(cctx_T *cctx); int generate_LISTAPPEND(cctx_T *cctx);
int generate_BLOBAPPEND(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 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 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); int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);

View File

@ -1484,15 +1484,17 @@ typedef struct {
char_u *ocm_init; // allocated char_u *ocm_init; // allocated
} ocmember_T; } 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; typedef struct itf2class_S itf2class_T;
struct itf2class_S { struct itf2class_S {
itf2class_T *i2c_next; itf2class_T *i2c_next;
class_T *i2c_class; class_T *i2c_class;
int i2c_is_method; // TRUE for method indexes
// array with ints follows // 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 // "class_T": used for v_class of typval of VAR_CLASS
// Also used for an interface (class_flags has CLASS_INTERFACE). // Also used for an interface (class_flags has CLASS_INTERFACE).

View File

@ -1001,6 +1001,56 @@ def Test_class_implements_interface()
v9.CheckScriptSuccess(lines) v9.CheckScriptSuccess(lines)
enddef 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() def Test_class_used_as_type()
var lines =<< trim END var lines =<< trim END
vim9script vim9script

View File

@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1254,
/**/ /**/
1253, 1253,
/**/ /**/

View File

@ -112,6 +112,7 @@ typedef enum {
// function call // function call
ISN_BCALL, // call builtin function isn_arg.bfunc ISN_BCALL, // call builtin function isn_arg.bfunc
ISN_DCALL, // call def function isn_arg.dfunc 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_UCALL, // call user function or funcref/partial isn_arg.ufunc
ISN_PCALL, // call partial, use isn_arg.pfunc ISN_PCALL, // call partial, use isn_arg.pfunc
ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set 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 int cdf_argcount; // number of arguments on top of stack
} cdfunc_T; } 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 // arguments to ISN_PCALL
typedef struct { typedef struct {
int cpf_top; // when TRUE partial is above the arguments int cpf_top; // when TRUE partial is above the arguments
@ -517,6 +525,7 @@ struct isn_S {
trycont_T trycont; trycont_T trycont;
cbfunc_T bfunc; cbfunc_T bfunc;
cdfunc_T dfunc; cdfunc_T dfunc;
cmfunc_T *mfunc;
cpfunc_T pfunc; cpfunc_T pfunc;
cufunc_T ufunc; cufunc_T ufunc;
echo_T echo; echo_T echo;

View File

@ -201,16 +201,17 @@ add_members_to_class(
* "cl" implementing that interface. * "cl" implementing that interface.
*/ */
int 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); siemsg("index %d out of range for interface %s", idx, itf->class_name);
return 0; return 0;
} }
itf2class_T *i2c; itf2class_T *i2c;
for (i2c = itf->class_itf2class; i2c != NULL; i2c = i2c->i2c_next) 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; break;
if (i2c == NULL) if (i2c == NULL)
{ {
@ -789,7 +790,11 @@ early_ret:
if (cl->class_name == NULL) if (cl->class_name == NULL)
goto cleanup; 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". // Add class and object members to "cl".
if (add_members_to_class(&classmembers, if (add_members_to_class(&classmembers,
@ -820,11 +825,26 @@ early_ret:
VIM_CLEAR(ga_impl.ga_data); VIM_CLEAR(ga_impl.ga_data);
ga_impl.ga_len = 0; 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 // For each interface add a lookuptable for the member index on the
// interface to the member index in this class. // 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) itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T)
+ ifcl->class_obj_member_count * sizeof(int)); + ifcl->class_obj_member_count * sizeof(int));
if (if2cl == NULL) if (if2cl == NULL)
@ -832,22 +852,64 @@ early_ret:
if2cl->i2c_next = ifcl->class_itf2class; if2cl->i2c_next = ifcl->class_itf2class;
ifcl->class_itf2class = if2cl; ifcl->class_itf2class = if2cl;
if2cl->i2c_class = cl; 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 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, 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); int *table = (int *)(if2cl + 1);
table[if_i] = cl_i; table[if_i] = cl_i;
break; break;
} }
} }
}
cl->class_interfaces_cl = intf_classes; // Table for methods.
intf_classes = NULL; 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) if (is_class && cl->class_class_member_count > 0)

View File

@ -2262,7 +2262,8 @@ execute_storeindex(isn_T *iptr, ectx_T *ectx)
class_T *itf = iptr->isn_arg.storeindex.si_class; class_T *itf = iptr->isn_arg.storeindex.si_class;
if (itf != NULL) if (itf != NULL)
// convert interface member index to class member index // 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]); clear_tv(&otv[idx]);
otv[idx] = *tv; otv[idx] = *tv;
@ -2950,6 +2951,20 @@ load_namespace_var(ectx_T *ectx, isntype_T isn_type, isn_T *iptr)
return OK; 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". * Execute instructions in execution context "ectx".
* Return OK or FAIL; * Return OK or FAIL;
@ -4125,6 +4140,30 @@ exec_instructions(ectx_T *ectx)
goto on_error; goto on_error;
break; 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 // call a builtin function
case ISN_BCALL: case ISN_BCALL:
SOURCING_LNUM = iptr->isn_lnum; SOURCING_LNUM = iptr->isn_lnum;
@ -5213,15 +5252,7 @@ exec_instructions(ectx_T *ectx)
if (tv->v_type != VAR_OBJECT) if (tv->v_type != VAR_OBJECT)
{ {
SOURCING_LNUM = iptr->isn_lnum; SOURCING_LNUM = iptr->isn_lnum;
garray_T type_list; object_required_error(tv);
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; goto on_error;
} }
@ -5234,8 +5265,8 @@ exec_instructions(ectx_T *ectx)
idx = iptr->isn_arg.classmember.cm_idx; idx = iptr->isn_arg.classmember.cm_idx;
// convert the interface index to the object index // convert the interface index to the object index
idx = object_index_from_itf_index( idx = object_index_from_itf_index(
iptr->isn_arg.classmember.cm_class, iptr->isn_arg.classmember.cm_class,
idx, obj->obj_class); FALSE, idx, obj->obj_class);
} }
// the members are located right after the object struct // 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); cdfunc->cdf_argcount);
} }
break; 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: case ISN_UCALL:
{ {
cufunc_T *cufunc = &iptr->isn_arg.ufunc; cufunc_T *cufunc = &iptr->isn_arg.ufunc;

View File

@ -321,9 +321,10 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
} }
ufunc_T *ufunc = NULL; 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 // Use a separate pointer to avoid that ASAN complains about
// uf_name[] only being 4 characters. // uf_name[] only being 4 characters.
char_u *ufname = (char_u *)fp->uf_name; 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; int argcount = 0;
if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
return 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) 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; *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_ITF_MEMBER(cctx, cl, i, m->ocm_type);
return generate_GET_OBJ_MEMBER(cctx, 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)) if (!func_is_global(ufunc))
{ {
res = generate_CALL(cctx, ufunc, argcount); res = generate_CALL(cctx, ufunc, NULL, 0, argcount);
goto theend; goto theend;
} }
if (!has_g_namespace if (!has_g_namespace
@ -1092,7 +1097,7 @@ compile_call(
// If we can find a global function by name generate the right call. // If we can find a global function by name generate the right call.
if (ufunc != NULL) if (ufunc != NULL)
{ {
res = generate_CALL(cctx, ufunc, argcount); res = generate_CALL(cctx, ufunc, NULL, 0, argcount);
goto theend; goto theend;
} }

View File

@ -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. * Return FAIL if the number of arguments is wrong.
*/ */
int 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; isn_T *isn;
int regular_args = ufunc->uf_args.ga_len; 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; return FAIL;
} }
if ((isn = generate_instr(cctx, if ((isn = generate_instr(cctx, cl != NULL ? ISN_METHODCALL
ufunc->uf_def_status != UF_NOT_COMPILED ? ISN_DCALL : ufunc->uf_def_status != UF_NOT_COMPILED
: ISN_UCALL)) == NULL) ? ISN_DCALL : ISN_UCALL)) == NULL)
return FAIL; 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_idx = ufunc->uf_dfunc_idx;
isn->isn_arg.dfunc.cdf_argcount = argcount; isn->isn_arg.dfunc.cdf_argcount = argcount;
@ -2483,6 +2500,14 @@ delete_instr(isn_T *isn)
} }
break; break;
case ISN_METHODCALL:
{
cmfunc_T *mfunc = isn->isn_arg.mfunc;
class_unref(mfunc->cmf_itf);
vim_free(mfunc);
}
break;
case ISN_NEWFUNC: case ISN_NEWFUNC:
{ {
newfuncarg_T *arg = isn->isn_arg.newfunc.nf_arg; newfuncarg_T *arg = isn->isn_arg.newfunc.nf_arg;