mirror of
https://github.com/vim/vim
synced 2025-07-16 01:01:58 +00: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:
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -695,6 +695,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1254,
|
||||
/**/
|
||||
1253,
|
||||
/**/
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user