]> granicus.if.org Git - vim/commitdiff
patch 9.0.1254: calling a method on an interface does not work v9.0.1254
authorBram Moolenaar <Bram@vim.org>
Sat, 28 Jan 2023 15:19:40 +0000 (15:19 +0000)
committerBram Moolenaar <Bram@vim.org>
Sat, 28 Jan 2023 15:19:40 +0000 (15:19 +0000)
Problem:    Calling a method on an interface does not work.
Solution:   At runtime figure out what method to call. (closes #11901)

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

index 13a643dd5cc57a4a10ddcbe3188c82f70088b1c3..707f4ec61c7483ea18ddfd646e9633a421e6a2f3 100644 (file)
@@ -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);
index 304131748243c82144863119724f6e0e5d0c1983..ff132831cc401968b3fe9bb9c6921226f52ac874 100644 (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_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);
index 3b5a233f631d421c43cbd9ee49df7b00cf491b28..1c7a5278842a725377771fa23b414badf7a25874 100644 (file)
@@ -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).
index a59cdc4c4e994f3256164b231b38114ea4c22c4c..3a434d1bacbf19f8e39d448fd5e57358aa8d79ff 100644 (file)
@@ -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
index 08947caac946204db2629a1b63a7a013c7d5803b..a6534c209cfc8e9588c9edcac3c4278e63325eaa 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1254,
 /**/
     1253,
 /**/
index faf2650baecaac16671cf24ebf2ab0c4d10f017b..fd98bb502c3ab3322bcbbb02b23a72d2484a5d0c 100644 (file)
@@ -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;
index d64e35ec012437fde1f24d0c82ace1691afebc4b..ecf5de7118312e02f9650a66eaca3d7c61f8a8b6 100644 (file)
@@ -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)
index 726b2d65f68350ec496d70dc939e4e60a3b76c00..3498e547e2a5638055853cc013de8f3de7ecd52e 100644 (file)
@@ -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;
index 06d1657ec36462fc033444dd489abac67b0ae713..c65fc79fd591b1d85825c89394e65a06cd671dc5 100644 (file)
@@ -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;
     }
 
index ae030443569df0f3108b8460024dc71ee6f985b4..cc0d8ad8b44f29f8ce3bd3212522b0ed289b939a 100644 (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.
  */
     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;