Skip to content

Commit d0200c8

Browse files
committed
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)
1 parent 192e24d commit d0200c8

10 files changed

Lines changed: 236 additions & 39 deletions

File tree

src/proto/vim9class.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* vim9class.c */
2-
int object_index_from_itf_index(class_T *itf, int idx, class_T *cl);
2+
int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl);
33
void ex_class(exarg_T *eap);
44
type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx);
55
void ex_enum(exarg_T *eap);

src/proto/vim9instr.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int metho
5757
int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
5858
int generate_LISTAPPEND(cctx_T *cctx);
5959
int generate_BLOBAPPEND(cctx_T *cctx);
60-
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
60+
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int mi, int pushed_argcount);
6161
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
6262
int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name);
6363
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);

src/structs.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,15 +1484,17 @@ typedef struct {
14841484
char_u *ocm_init; // allocated
14851485
} ocmember_T;
14861486

1487-
// used for the lookup table of a class member index
1487+
// used for the lookup table of a class member index and object method index
14881488
typedef struct itf2class_S itf2class_T;
14891489
struct itf2class_S {
14901490
itf2class_T *i2c_next;
14911491
class_T *i2c_class;
1492+
int i2c_is_method; // TRUE for method indexes
14921493
// array with ints follows
14931494
};
14941495

1495-
#define CLASS_INTERFACE 1
1496+
#define CLASS_INTERFACE 1
1497+
#define CLASS_EXTENDED 2 // another class extends this one
14961498

14971499
// "class_T": used for v_class of typval of VAR_CLASS
14981500
// Also used for an interface (class_flags has CLASS_INTERFACE).

src/testdir/test_vim9_class.vim

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,56 @@ def Test_class_implements_interface()
10011001
v9.CheckScriptSuccess(lines)
10021002
enddef
10031003

1004+
def Test_call_interface_method()
1005+
var lines =<< trim END
1006+
vim9script
1007+
interface Base
1008+
def Enter(): void
1009+
endinterface
1010+
1011+
class Child implements Base
1012+
def Enter(): void
1013+
g:result ..= 'child'
1014+
enddef
1015+
endclass
1016+
1017+
def F(obj: Base)
1018+
obj.Enter()
1019+
enddef
1020+
1021+
g:result = ''
1022+
F(Child.new())
1023+
assert_equal('child', g:result)
1024+
unlet g:result
1025+
END
1026+
v9.CheckScriptSuccess(lines)
1027+
1028+
lines =<< trim END
1029+
vim9script
1030+
class Base
1031+
def Enter(): void
1032+
g:result ..= 'base'
1033+
enddef
1034+
endclass
1035+
1036+
class Child extends Base
1037+
def Enter(): void
1038+
g:result ..= 'child'
1039+
enddef
1040+
endclass
1041+
1042+
def F(obj: Base)
1043+
obj.Enter()
1044+
enddef
1045+
1046+
g:result = ''
1047+
F(Child.new())
1048+
assert_equal('child', g:result)
1049+
unlet g:result
1050+
END
1051+
v9.CheckScriptSuccess(lines)
1052+
enddef
1053+
10041054
def Test_class_used_as_type()
10051055
var lines =<< trim END
10061056
vim9script

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ static char *(features[]) =
695695

696696
static int included_patches[] =
697697
{ /* Add new patch number below this line */
698+
/**/
699+
1254,
698700
/**/
699701
1253,
700702
/**/

src/vim9.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ typedef enum {
112112
// function call
113113
ISN_BCALL, // call builtin function isn_arg.bfunc
114114
ISN_DCALL, // call def function isn_arg.dfunc
115+
ISN_METHODCALL, // call method on interface, uses isn_arg.mfunc
115116
ISN_UCALL, // call user function or funcref/partial isn_arg.ufunc
116117
ISN_PCALL, // call partial, use isn_arg.pfunc
117118
ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set
@@ -234,6 +235,13 @@ typedef struct {
234235
int cdf_argcount; // number of arguments on top of stack
235236
} cdfunc_T;
236237

238+
// arguments to ISN_METHODCALL
239+
typedef struct {
240+
class_T *cmf_itf; // interface used
241+
int cmf_idx; // index in "def_functions" for ISN_DCALL
242+
int cmf_argcount; // number of arguments on top of stack
243+
} cmfunc_T;
244+
237245
// arguments to ISN_PCALL
238246
typedef struct {
239247
int cpf_top; // when TRUE partial is above the arguments
@@ -517,6 +525,7 @@ struct isn_S {
517525
trycont_T trycont;
518526
cbfunc_T bfunc;
519527
cdfunc_T dfunc;
528+
cmfunc_T *mfunc;
520529
cpfunc_T pfunc;
521530
cufunc_T ufunc;
522531
echo_T echo;

src/vim9class.c

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,17 @@ add_members_to_class(
201201
* "cl" implementing that interface.
202202
*/
203203
int
204-
object_index_from_itf_index(class_T *itf, int idx, class_T *cl)
204+
object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl)
205205
{
206-
if (idx > itf->class_obj_member_count)
206+
if (idx > (is_method ? itf->class_obj_method_count
207+
: itf->class_obj_member_count))
207208
{
208209
siemsg("index %d out of range for interface %s", idx, itf->class_name);
209210
return 0;
210211
}
211212
itf2class_T *i2c;
212213
for (i2c = itf->class_itf2class; i2c != NULL; i2c = i2c->i2c_next)
213-
if (i2c->i2c_class == cl)
214+
if (i2c->i2c_class == cl && i2c->i2c_is_method == is_method)
214215
break;
215216
if (i2c == NULL)
216217
{
@@ -789,7 +790,11 @@ ex_class(exarg_T *eap)
789790
if (cl->class_name == NULL)
790791
goto cleanup;
791792

792-
cl->class_extends = extends_cl;
793+
if (extends_cl != NULL)
794+
{
795+
cl->class_extends = extends_cl;
796+
extends_cl->class_flags |= CLASS_EXTENDED;
797+
}
793798

794799
// Add class and object members to "cl".
795800
if (add_members_to_class(&classmembers,
@@ -820,34 +825,91 @@ ex_class(exarg_T *eap)
820825
VIM_CLEAR(ga_impl.ga_data);
821826
ga_impl.ga_len = 0;
822827

828+
cl->class_interfaces_cl = intf_classes;
829+
intf_classes = NULL;
830+
}
831+
832+
if (cl->class_interface_count > 0 || extends_cl != NULL)
833+
{
823834
// For each interface add a lookuptable for the member index on the
824835
// interface to the member index in this class.
825-
for (int i = 0; i < cl->class_interface_count; ++i)
836+
// And a lookuptable for the object method index on the interface
837+
// to the object method index in this class.
838+
// Also do this for the extended class, if any.
839+
for (int i = 0; i <= cl->class_interface_count; ++i)
826840
{
827-
class_T *ifcl = intf_classes[i];
841+
class_T *ifcl = i < cl->class_interface_count
842+
? cl->class_interfaces_cl[i]
843+
: extends_cl;
844+
if (ifcl == NULL)
845+
continue;
846+
847+
// Table for members.
828848
itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T)
829849
+ ifcl->class_obj_member_count * sizeof(int));
830850
if (if2cl == NULL)
831851
goto cleanup;
832852
if2cl->i2c_next = ifcl->class_itf2class;
833853
ifcl->class_itf2class = if2cl;
834854
if2cl->i2c_class = cl;
855+
if2cl->i2c_is_method = FALSE;
835856

836857
for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i)
837-
for (int cl_i = 0; cl_i < cl->class_obj_member_count; ++cl_i)
858+
for (int cl_i = 0; cl_i < cl->class_obj_member_count;
859+
++cl_i)
838860
{
839861
if (STRCMP(ifcl->class_obj_members[if_i].ocm_name,
840-
cl->class_obj_members[cl_i].ocm_name) == 0)
862+
cl->class_obj_members[cl_i].ocm_name) == 0)
841863
{
842864
int *table = (int *)(if2cl + 1);
843865
table[if_i] = cl_i;
844866
break;
845867
}
846868
}
847-
}
848869

849-
cl->class_interfaces_cl = intf_classes;
850-
intf_classes = NULL;
870+
// Table for methods.
871+
if2cl = alloc_clear(sizeof(itf2class_T)
872+
+ ifcl->class_obj_method_count * sizeof(int));
873+
if (if2cl == NULL)
874+
goto cleanup;
875+
if2cl->i2c_next = ifcl->class_itf2class;
876+
ifcl->class_itf2class = if2cl;
877+
if2cl->i2c_class = cl;
878+
if2cl->i2c_is_method = TRUE;
879+
880+
for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i)
881+
{
882+
int done = FALSE;
883+
for (int cl_i = 0; cl_i < objmethods.ga_len; ++cl_i)
884+
{
885+
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
886+
((ufunc_T **)objmethods.ga_data)[cl_i]->uf_name)
887+
== 0)
888+
{
889+
int *table = (int *)(if2cl + 1);
890+
table[if_i] = cl_i;
891+
done = TRUE;
892+
break;
893+
}
894+
}
895+
896+
if (!done && extends_cl != NULL)
897+
{
898+
for (int cl_i = 0;
899+
cl_i < extends_cl->class_obj_member_count; ++cl_i)
900+
{
901+
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
902+
extends_cl->class_obj_methods[cl_i]->uf_name)
903+
== 0)
904+
{
905+
int *table = (int *)(if2cl + 1);
906+
table[if_i] = cl_i;
907+
break;
908+
}
909+
}
910+
}
911+
}
912+
}
851913
}
852914

853915
if (is_class && cl->class_class_member_count > 0)

src/vim9execute.c

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,7 +2262,8 @@ execute_storeindex(isn_T *iptr, ectx_T *ectx)
22622262
class_T *itf = iptr->isn_arg.storeindex.si_class;
22632263
if (itf != NULL)
22642264
// convert interface member index to class member index
2265-
idx = object_index_from_itf_index(itf, idx, obj->obj_class);
2265+
idx = object_index_from_itf_index(itf, FALSE,
2266+
idx, obj->obj_class);
22662267

22672268
clear_tv(&otv[idx]);
22682269
otv[idx] = *tv;
@@ -2950,6 +2951,20 @@ load_namespace_var(ectx_T *ectx, isntype_T isn_type, isn_T *iptr)
29502951
return OK;
29512952
}
29522953

2954+
2955+
static void
2956+
object_required_error(typval_T *tv)
2957+
{
2958+
garray_T type_list;
2959+
ga_init2(&type_list, sizeof(type_T *), 10);
2960+
type_T *type = typval2type(tv, get_copyID(), &type_list, TVTT_DO_MEMBER);
2961+
char *tofree = NULL;
2962+
char *typename = type_name(type, &tofree);
2963+
semsg(_(e_object_required_found_str), typename);
2964+
vim_free(tofree);
2965+
clear_type_list(&type_list);
2966+
}
2967+
29532968
/*
29542969
* Execute instructions in execution context "ectx".
29552970
* Return OK or FAIL;
@@ -4125,6 +4140,30 @@ exec_instructions(ectx_T *ectx)
41254140
goto on_error;
41264141
break;
41274142

4143+
// call a method on an interface
4144+
case ISN_METHODCALL:
4145+
{
4146+
SOURCING_LNUM = iptr->isn_lnum;
4147+
tv = STACK_TV_BOT(-1);
4148+
if (tv->v_type != VAR_OBJECT)
4149+
{
4150+
object_required_error(tv);
4151+
goto on_error;
4152+
}
4153+
object_T *obj = tv->vval.v_object;
4154+
class_T *cl = obj->obj_class;
4155+
4156+
// convert the interface index to the object index
4157+
cmfunc_T *mfunc = iptr->isn_arg.mfunc;
4158+
int idx = object_index_from_itf_index(mfunc->cmf_itf,
4159+
TRUE, mfunc->cmf_idx, cl);
4160+
4161+
if (call_ufunc(cl->class_obj_methods[idx], NULL,
4162+
mfunc->cmf_argcount, ectx, NULL, NULL) == FAIL)
4163+
goto on_error;
4164+
}
4165+
break;
4166+
41284167
// call a builtin function
41294168
case ISN_BCALL:
41304169
SOURCING_LNUM = iptr->isn_lnum;
@@ -5213,15 +5252,7 @@ exec_instructions(ectx_T *ectx)
52135252
if (tv->v_type != VAR_OBJECT)
52145253
{
52155254
SOURCING_LNUM = iptr->isn_lnum;
5216-
garray_T type_list;
5217-
ga_init2(&type_list, sizeof(type_T *), 10);
5218-
type_T *type = typval2type(tv, get_copyID(),
5219-
&type_list, TVTT_DO_MEMBER);
5220-
char *tofree = NULL;
5221-
char *typename = type_name(type, &tofree);
5222-
semsg(_(e_object_required_found_str), typename);
5223-
vim_free(tofree);
5224-
clear_type_list(&type_list);
5255+
object_required_error(tv);
52255256
goto on_error;
52265257
}
52275258

@@ -5234,8 +5265,8 @@ exec_instructions(ectx_T *ectx)
52345265
idx = iptr->isn_arg.classmember.cm_idx;
52355266
// convert the interface index to the object index
52365267
idx = object_index_from_itf_index(
5237-
iptr->isn_arg.classmember.cm_class,
5238-
idx, obj->obj_class);
5268+
iptr->isn_arg.classmember.cm_class,
5269+
FALSE, idx, obj->obj_class);
52395270
}
52405271

52415272
// 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)
66376668
cdfunc->cdf_argcount);
66386669
}
66396670
break;
6671+
case ISN_METHODCALL:
6672+
{
6673+
cmfunc_T *mfunc = iptr->isn_arg.mfunc;
6674+
6675+
smsg("%s%4d METHODCALL %s.%s(argc %d)", pfx, current,
6676+
mfunc->cmf_itf->class_name,
6677+
mfunc->cmf_itf->class_obj_methods[
6678+
mfunc->cmf_idx]->uf_name,
6679+
mfunc->cmf_argcount);
6680+
}
6681+
break;
66406682
case ISN_UCALL:
66416683
{
66426684
cufunc_T *cufunc = &iptr->isn_arg.ufunc;

0 commit comments

Comments
 (0)