]> granicus.if.org Git - vim/commitdiff
patch 9.0.1250: cannot use an object method with :defer v9.0.1250
authorBram Moolenaar <Bram@vim.org>
Fri, 27 Jan 2023 20:14:02 +0000 (20:14 +0000)
committerBram Moolenaar <Bram@vim.org>
Fri, 27 Jan 2023 20:14:02 +0000 (20:14 +0000)
Problem:    Cannot use an object method with :defer. (Ernie Rael)
Solution:   Find the object method and generate code to call it.
            (closes #11886)

src/proto/vim9instr.pro
src/testdir/test_vim9_class.vim
src/version.c
src/vim9.h
src/vim9cmds.c
src/vim9execute.c
src/vim9expr.c
src/vim9instr.c

index 6b70362ba6e6987eb3254b06e39485c634cd810c..304131748243c82144863119724f6e0e5d0c1983 100644 (file)
@@ -5,7 +5,7 @@ isn_T *generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type);
 isn_T *generate_instr_debug(cctx_T *cctx);
 int generate_CONSTRUCT(cctx_T *cctx, class_T *cl);
 int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type);
-int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int itf_idx, type_T *type);
+int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type);
 int generate_STORE_THIS(cctx_T *cctx, int idx);
 int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx);
 int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type);
@@ -61,7 +61,7 @@ int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, 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);
-int generate_DEFER(cctx_T *cctx, int var_idx, int argcount);
+int generate_DEFER(cctx_T *cctx, int var_idx, int obj_method, int argcount);
 int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
 int generate_ECHO(cctx_T *cctx, int with_white, int count);
 int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
index a496b6d7d55f75d60d0621810a40be72ccbed30d..a59cdc4c4e994f3256164b231b38114ea4c22c4c 100644 (file)
@@ -1331,5 +1331,35 @@ def Test_closure_in_class()
   v9.CheckScriptSuccess(lines)
 enddef
 
+def Test_defer_with_object()
+  var lines =<< trim END
+      vim9script
+
+      class CWithEE
+        def Enter()
+          g:result ..= "entered/"
+        enddef
+        def Exit()
+          g:result ..= "exited"
+        enddef
+      endclass
+
+      def With(ee: CWithEE, F: func)
+        ee.Enter()
+        defer ee.Exit()
+        F()
+      enddef
+
+      g:result = ''
+      var obj = CWithEE.new()
+      obj->With(() => {
+        g:result ..= "called/"
+      })
+      assert_equal('entered/called/exited', g:result)
+  END
+  v9.CheckScriptSuccess(lines)
+  unlet g:result
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index cb304edec3e21d06a472af5d871996ec5fa606fc..0c31039c8926402fdc87bdc3b20b7b43e9635cef 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1250,
 /**/
     1249,
 /**/
index f1c453017532e3e37a69f852829910c970f018bd..faf2650baecaac16671cf24ebf2ab0c4d10f017b 100644 (file)
@@ -122,6 +122,7 @@ typedef enum {
     ISN_NEWFUNC,    // create a global function from a lambda function
     ISN_DEF,       // list functions
     ISN_DEFER,     // :defer  argument count is isn_arg.number
+    ISN_DEFEROBJ,   // idem, function is an object method
 
     // expression operations
     ISN_JUMP,      // jump if condition is matched isn_arg.jump
index 763d8b3b8a6d2c12f64acb9cefdd32083d999149..69c04aec9b22e657dc43febe271f0b0db2b82d61 100644 (file)
@@ -1910,6 +1910,7 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
     int                defer_var_idx;
     type_T     *type;
     int                func_idx;
+    int                obj_method = 0;
 
     // Get a funcref for the function name.
     // TODO: better way to find the "(".
@@ -1925,8 +1926,15 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
        // TODO: better type
        generate_PUSHFUNC(cctx, (char_u *)internal_func_name(func_idx),
                                                           &t_func_any, FALSE);
-    else if (compile_expr0(&arg, cctx) == FAIL)
-       return NULL;
+    else
+    {
+       int typecount = cctx->ctx_type_stack.ga_len;
+       if (compile_expr0(&arg, cctx) == FAIL)
+           return NULL;
+       if (cctx->ctx_type_stack.ga_len >= typecount + 2)
+           // must have seen "obj.Func", pushed an object and a function
+           obj_method = 1;
+    }
     *paren = '(';
 
     // check for function type
@@ -1958,7 +1966,7 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
     defer_var_idx = get_defer_var_idx(cctx);
     if (defer_var_idx == 0)
        return NULL;
-    if (generate_DEFER(cctx, defer_var_idx - 1, argcount) == FAIL)
+    if (generate_DEFER(cctx, defer_var_idx - 1, obj_method, argcount) == FAIL)
        return NULL;
 
     return skipwhite(arg);
index 22e1e58eb889b0ca093d79e8859e9ec4e9b06a67..726b2d65f68350ec496d70dc939e4e60a3b76c00 100644 (file)
@@ -973,9 +973,10 @@ add_defer_item(int var_idx, int argcount, ectx_T *ectx)
  * Returns OK or FAIL.
  */
     static int
-defer_command(int var_idx, int argcount, ectx_T *ectx)
+defer_command(int var_idx, int has_obj, int argcount, ectx_T *ectx)
 {
-    list_T     *l = add_defer_item(var_idx, argcount, ectx);
+    int                obj_off = has_obj ? 1 : 0;
+    list_T     *l = add_defer_item(var_idx, argcount + obj_off, ectx);
     int                i;
     typval_T   *func_tv;
 
@@ -983,23 +984,25 @@ defer_command(int var_idx, int argcount, ectx_T *ectx)
        return FAIL;
 
     func_tv = STACK_TV_BOT(-argcount - 1);
-    if (func_tv->v_type != VAR_FUNC && func_tv->v_type != VAR_PARTIAL)
+    if (has_obj ? func_tv->v_type != VAR_PARTIAL : func_tv->v_type != VAR_FUNC)
     {
        semsg(_(e_expected_str_but_got_str),
-               "function or partial",
+               has_obj ? "partial" : "function",
                vartype_name(func_tv->v_type));
        return FAIL;
     }
     list_set_item(l, 0, func_tv);
+    if (has_obj)
+       list_set_item(l, 1, STACK_TV_BOT(-argcount - 2));
 
     for (i = 0; i < argcount; ++i)
-       list_set_item(l, i + 1, STACK_TV_BOT(-argcount + i));
-    ectx->ec_stack.ga_len -= argcount + 1;
+       list_set_item(l, i + 1 + obj_off, STACK_TV_BOT(-argcount + i));
+    ectx->ec_stack.ga_len -= argcount + 1 + obj_off;
     return OK;
 }
 
 /*
- * Add a deferred function "name" with one argument "arg_tv".
+ * Add a deferred call for "name" with arguments "argvars[argcount]".
  * Consumes "name", also on failure.
  * Only to be called when in_def_function() returns TRUE.
  */
@@ -1056,19 +1059,31 @@ invoke_defer_funcs(ectx_T *ectx)
        typval_T    argvars[MAX_FUNC_ARGS];
        int         i;
        listitem_T  *arg_li = l->lv_first;
-       funcexe_T   funcexe;
+       typval_T    *functv = &l->lv_first->li_tv;
+       int         obj_off = functv->v_type == VAR_PARTIAL ? 1 : 0;
+       int         argcount = l->lv_len - 1 - obj_off;
 
-       for (i = 0; i < l->lv_len - 1; ++i)
+       if (obj_off == 1)
+           arg_li = arg_li->li_next;  // second list item is the object
+       for (i = 0; i < argcount; ++i)
        {
            arg_li = arg_li->li_next;
            argvars[i] = arg_li->li_tv;
        }
 
+       funcexe_T   funcexe;
        CLEAR_FIELD(funcexe);
        funcexe.fe_evaluate = TRUE;
        rettv.v_type = VAR_UNKNOWN;
-       (void)call_func(l->lv_first->li_tv.vval.v_string, -1,
-                                    &rettv, l->lv_len - 1, argvars, &funcexe);
+       if (functv->v_type == VAR_PARTIAL)
+       {
+           funcexe.fe_partial = functv->vval.v_partial;
+           funcexe.fe_object = l->lv_first->li_next->li_tv.vval.v_object;
+           if (funcexe.fe_object != NULL)
+               ++funcexe.fe_object->obj_refcount;
+       }
+       (void)call_func(functv->vval.v_string, -1,
+                                         &rettv, argcount, argvars, &funcexe);
        clear_tv(&rettv);
     }
 }
@@ -4170,7 +4185,9 @@ exec_instructions(ectx_T *ectx)
 
            // :defer func(arg)
            case ISN_DEFER:
+           case ISN_DEFEROBJ:
                if (defer_command(iptr->isn_arg.defer.defer_var_idx,
+                            iptr->isn_type == ISN_DEFEROBJ,
                             iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
                    goto on_error;
                break;
@@ -6640,7 +6657,9 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                smsg("%s%4d PCALL end", pfx, current);
                break;
            case ISN_DEFER:
-               smsg("%s%4d DEFER %d args", pfx, current,
+           case ISN_DEFEROBJ:
+               smsg("%s%4d %s %d args", pfx, current,
+                           iptr->isn_type == ISN_DEFER ? "DEFER" : "DEFEROBJ",
                                      (int)iptr->isn_arg.defer.defer_argcount);
                break;
            case ISN_RETURN:
index 1034f64b070deab058bd2d5ff59673c4787316b3..06d1657ec36462fc033444dd489abac67b0ae713 100644 (file)
@@ -370,6 +370,17 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
            }
        }
 
+       // Could be a function reference: "obj.Func".
+       for (int i = 0; i < cl->class_obj_method_count; ++i)
+       {
+           ufunc_T *fp = cl->class_obj_methods[i];
+           // Use a separate pointer to avoid that ASAN complains about
+           // uf_name[] only being 4 characters.
+           char_u *ufname = (char_u *)fp->uf_name;
+           if (STRNCMP(name, ufname, len) == 0 && ufname[len] == NUL)
+               return generate_FUNCREF(cctx, fp, NULL);
+       }
+
        semsg(_(e_member_not_found_on_object_str_str), cl->class_name, name);
     }
     else
index c86f4cf8d7ae7f116704e5d157ec40863dd655d8..ae030443569df0f3108b8460024dc71ee6f985b4 100644 (file)
@@ -1329,8 +1329,6 @@ generate_NEWDICT(cctx_T *cctx, int count, int use_null)
 /*
  * Generate an ISN_FUNCREF instruction.
  * "isnp" is set to the instruction, so that fr_dfunc_idx can be set later.
- * If variables were declared inside a loop "loop_var_idx" is the index of the
- * first one and "loop_var_count" the number of variables declared.
  */
     int
 generate_FUNCREF(
@@ -1362,7 +1360,12 @@ generate_FUNCREF(
     if (ufunc->uf_def_status == UF_NOT_COMPILED)
        extra->fre_func_name = vim_strsave(ufunc->uf_name);
     else
+    {
+       if (isnp == NULL && ufunc->uf_def_status == UF_TO_BE_COMPILED)
+           // compile the function now, we need the uf_dfunc_idx value
+           (void)compile_def_function(ufunc, FALSE, CT_NONE, NULL);
        isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx;
+    }
 
     // Reserve an extra variable to keep track of the number of closures
     // created.
@@ -1942,14 +1945,17 @@ generate_PCALL(
 
 /*
  * Generate an ISN_DEFER instruction.
+ * "obj_method" is one for "obj.Method()", zero otherwise.
  */
     int
-generate_DEFER(cctx_T *cctx, int var_idx, int argcount)
+generate_DEFER(cctx_T *cctx, int var_idx, int obj_method, int argcount)
 {
     isn_T *isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if ((isn = generate_instr_drop(cctx, ISN_DEFER, argcount + 1)) == NULL)
+    if ((isn = generate_instr_drop(cctx,
+                   obj_method == 0 ? ISN_DEFER : ISN_DEFEROBJ,
+                   argcount + 1)) == NULL)
        return FAIL;
     isn->isn_arg.defer.defer_var_idx = var_idx;
     isn->isn_arg.defer.defer_argcount = argcount;
@@ -2568,6 +2574,7 @@ delete_instr(isn_T *isn)
        case ISN_COND2BOOL:
        case ISN_DEBUG:
        case ISN_DEFER:
+       case ISN_DEFEROBJ:
        case ISN_DROP:
        case ISN_ECHO:
        case ISN_ECHOCONSOLE: