changeset 31843:ffa11e2757e7 v9.0.1254

patch 9.0.1254: calling a method on an interface does not work Commit: https://github.com/vim/vim/commit/d0200c8631582bbb16a9b585e2ca7adccc84ccdd Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jan 28 15:19:40 2023 +0000 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 https://github.com/vim/vim/issues/11901)
author Bram Moolenaar <Bram@vim.org>
date Sat, 28 Jan 2023 16:30:04 +0100
parents 1b6543beb560
children c049b6f34639
files src/proto/vim9class.pro src/proto/vim9instr.pro src/structs.h src/testdir/test_vim9_class.vim src/version.c src/vim9.h src/vim9class.c src/vim9execute.c src/vim9expr.c src/vim9instr.c
diffstat 10 files changed, 236 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- 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);
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -57,7 +57,7 @@ int check_internal_func_args(cctx_T *cct
 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);
--- 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).
--- 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
--- 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,
--- 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;
--- 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;
 			}
 		    }
-	    }
+
+		// 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;
 
-	    cl->class_interfaces_cl = intf_classes;
-	    intf_classes = NULL;
+		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)
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2262,7 +2262,8 @@ execute_storeindex(isn_T *iptr, ectx_T *
 	    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
     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 *inst
 							 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;
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -321,9 +321,10 @@ compile_class_object_index(cctx_T *cctx,
 	}
 
 	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,
 	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,
 		}
 
 		*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;
     }
 
--- 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 *ufu
 	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;