Patch 8.2.3435
Problem:    Vim9: dict is not passed to dict function.
Solution:   Keep the dict used until a function call.
Files:      src/vim9compile.c, src/vim9execute.c, src/vim9.h,
            src/testdir/test_vim9_func.vim,
            src/testdir/test_vim9_disassemble.vim


*** ../vim-8.2.3434/src/vim9compile.c   2021-09-09 23:01:10.506519642 +0200
--- src/vim9compile.c   2021-09-13 15:48:42.500269599 +0200
***************
*** 2878,2886 ****
  /*
   * Compile getting a member from a list/dict/string/blob.  Stack has the
   * indexable value and the index or the two indexes of a slice.
   */
      static int
! compile_member(int is_slice, cctx_T *cctx)
  {
      type_T    **typep;
      garray_T  *stack = &cctx->ctx_type_stack;
--- 2878,2887 ----
  /*
   * Compile getting a member from a list/dict/string/blob.  Stack has the
   * indexable value and the index or the two indexes of a slice.
+  * "keeping_dict" is used for dict[func](arg) to pass dict to func.
   */
      static int
! compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
  {
      type_T    **typep;
      garray_T  *stack = &cctx->ctx_type_stack;
***************
*** 2935,2940 ****
--- 2936,2943 ----
            return FAIL;
        if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
            return FAIL;
+       if (keeping_dict != NULL)
+           *keeping_dict = TRUE;
      }
      else if (vartype == VAR_STRING)
      {
***************
*** 4314,4319 ****
--- 4317,4323 ----
        ppconst_T *ppconst)
  {
      char_u    *name_start = *end_leader;
+     int               keeping_dict = FALSE;
  
      for (;;)
      {
***************
*** 4360,4365 ****
--- 4364,4375 ----
                return FAIL;
            if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL)
                return FAIL;
+           if (keeping_dict)
+           {
+               keeping_dict = FALSE;
+               if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
+                   return FAIL;
+           }
        }
        else if (*p == '-' && p[1] == '>')
        {
***************
*** 4470,4475 ****
--- 4480,4491 ----
                if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL)
                    return FAIL;
            }
+           if (keeping_dict)
+           {
+               keeping_dict = FALSE;
+               if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
+                   return FAIL;
+           }
        }
        else if (**arg == '[')
        {
***************
*** 4537,4543 ****
            }
            *arg = *arg + 1;
  
!           if (compile_member(is_slice, cctx) == FAIL)
                return FAIL;
        }
        else if (*p == '.' && p[1] != '.')
--- 4553,4565 ----
            }
            *arg = *arg + 1;
  
!           if (keeping_dict)
!           {
!               keeping_dict = FALSE;
!               if (generate_instr(cctx, ISN_CLEARDICT) == NULL)
!                   return FAIL;
!           }
!           if (compile_member(is_slice, &keeping_dict, cctx) == FAIL)
                return FAIL;
        }
        else if (*p == '.' && p[1] != '.')
***************
*** 4562,4579 ****
                semsg(_(e_syntax_error_at_str), *arg);
                return FAIL;
            }
            if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL)
                return FAIL;
            *arg = p;
        }
        else
            break;
      }
  
-     // TODO - see handle_subscript():
      // Turn "dict.Func" into a partial for "Func" bound to "dict".
!     // Don't do this when "Func" is already a partial that was bound
!     // explicitly (pt_auto is FALSE).
  
      return OK;
  }
--- 4584,4604 ----
                semsg(_(e_syntax_error_at_str), *arg);
                return FAIL;
            }
+           if (keeping_dict && generate_instr(cctx, ISN_CLEARDICT) == NULL)
+               return FAIL;
            if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL)
                return FAIL;
+           keeping_dict = TRUE;
            *arg = p;
        }
        else
            break;
      }
  
      // Turn "dict.Func" into a partial for "Func" bound to "dict".
!     // This needs to be done at runtime to be able to check the type.
!     if (keeping_dict && generate_instr(cctx, ISN_USEDICT) == NULL)
!       return FAIL;
  
      return OK;
  }
***************
*** 6661,6667 ****
        }
  
        // Get the member.
!       if (compile_member(FALSE, cctx) == FAIL)
            return FAIL;
      }
      return OK;
--- 6686,6692 ----
        }
  
        // Get the member.
!       if (compile_member(FALSE, NULL, cctx) == FAIL)
            return FAIL;
      }
      return OK;
***************
*** 10406,10411 ****
--- 10431,10437 ----
        case ISN_CEXPR_AUCMD:
        case ISN_CHECKLEN:
        case ISN_CHECKNR:
+       case ISN_CLEARDICT:
        case ISN_CMDMOD_REV:
        case ISN_COMPAREANY:
        case ISN_COMPAREBLOB:
***************
*** 10482,10487 ****
--- 10508,10514 ----
        case ISN_UNLETINDEX:
        case ISN_UNLETRANGE:
        case ISN_UNPACK:
+       case ISN_USEDICT:
            // nothing allocated
            break;
      }
*** ../vim-8.2.3434/src/vim9execute.c   2021-09-09 23:01:10.506519642 +0200
--- src/vim9execute.c   2021-09-13 18:01:36.792768263 +0200
***************
*** 165,170 ****
--- 165,239 ----
      }
  }
  
+ static garray_T dict_stack = GA_EMPTY;
+ 
+ /*
+  * Put a value on the dict stack.  This consumes "tv".
+  */
+     static int
+ dict_stack_save(typval_T *tv)
+ {
+     if (dict_stack.ga_growsize == 0)
+       ga_init2(&dict_stack, (int)sizeof(typval_T), 10);
+     if (ga_grow(&dict_stack, 1) == FAIL)
+       return FAIL;
+     ((typval_T *)dict_stack.ga_data)[dict_stack.ga_len] = *tv;
+     ++dict_stack.ga_len;
+     return OK;
+ }
+ 
+ /*
+  * Get the typval at top of the dict stack.
+  */
+     static typval_T *
+ dict_stack_get_tv(void)
+ {
+     if (dict_stack.ga_len == 0)
+       return NULL;
+     return ((typval_T *)dict_stack.ga_data) + dict_stack.ga_len - 1;
+ }
+ 
+ /*
+  * Get the dict at top of the dict stack.
+  */
+     static dict_T *
+ dict_stack_get_dict(void)
+ {
+     typval_T *tv;
+ 
+     if (dict_stack.ga_len == 0)
+       return NULL;
+     tv = ((typval_T *)dict_stack.ga_data) + dict_stack.ga_len - 1;
+     if (tv->v_type == VAR_DICT)
+       return tv->vval.v_dict;
+     return NULL;
+ }
+ 
+ /*
+  * Drop an item from the dict stack.
+  */
+     static void
+ dict_stack_drop(void)
+ {
+     if (dict_stack.ga_len == 0)
+     {
+       iemsg("Dict stack underflow");
+       return;
+     }
+     --dict_stack.ga_len;
+     clear_tv(((typval_T *)dict_stack.ga_data) + dict_stack.ga_len);
+ }
+ 
+ /*
+  * Drop items from the dict stack until the length is equal to "len".
+  */
+     static void
+ dict_stack_clear(int len)
+ {
+     while (dict_stack.ga_len > len)
+       dict_stack_drop();
+ }
+ 
  /*
   * Call compiled function "cdf_idx" from compiled code.
   * This adds a stack frame and sets the instruction pointer to the start of 
the
***************
*** 765,771 ****
        partial_T   *pt,
        int         argcount,
        ectx_T      *ectx,
!       isn_T       *iptr)
  {
      typval_T  argvars[MAX_FUNC_ARGS];
      funcexe_T   funcexe;
--- 834,841 ----
        partial_T   *pt,
        int         argcount,
        ectx_T      *ectx,
!       isn_T       *iptr,
!       dict_T      *selfdict)
  {
      typval_T  argvars[MAX_FUNC_ARGS];
      funcexe_T   funcexe;
***************
*** 807,817 ****
        return FAIL;
      CLEAR_FIELD(funcexe);
      funcexe.evaluate = TRUE;
  
      // Call the user function.  Result goes in last position on the stack.
      // TODO: add selfdict if there is one
      error = call_user_func_check(ufunc, argcount, argvars,
!                                            STACK_TV_BOT(-1), &funcexe, NULL);
  
      // Clear the arguments.
      for (idx = 0; idx < argcount; ++idx)
--- 877,888 ----
        return FAIL;
      CLEAR_FIELD(funcexe);
      funcexe.evaluate = TRUE;
+     funcexe.selfdict = selfdict != NULL ? selfdict : dict_stack_get_dict();
  
      // Call the user function.  Result goes in last position on the stack.
      // TODO: add selfdict if there is one
      error = call_user_func_check(ufunc, argcount, argvars,
!                                STACK_TV_BOT(-1), &funcexe, funcexe.selfdict);
  
      // Clear the arguments.
      for (idx = 0; idx < argcount; ++idx)
***************
*** 864,870 ****
        char_u      *name,
        int         argcount,
        ectx_T      *ectx,
!       isn_T       *iptr)
  {
      ufunc_T *ufunc;
  
--- 935,942 ----
        char_u      *name,
        int         argcount,
        ectx_T      *ectx,
!       isn_T       *iptr,
!       dict_T      *selfdict)
  {
      ufunc_T *ufunc;
  
***************
*** 916,922 ****
            }
        }
  
!       return call_ufunc(ufunc, NULL, argcount, ectx, iptr);
      }
  
      return FAIL;
--- 988,994 ----
            }
        }
  
!       return call_ufunc(ufunc, NULL, argcount, ectx, iptr, selfdict);
      }
  
      return FAIL;
***************
*** 932,937 ****
--- 1004,1010 ----
      char_u    *name = NULL;
      int               called_emsg_before = called_emsg;
      int               res = FAIL;
+     dict_T    *selfdict = NULL;
  
      if (tv->v_type == VAR_PARTIAL)
      {
***************
*** 953,961 ****
            for (i = 0; i < pt->pt_argc; ++i)
                copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i));
        }
  
        if (pt->pt_func != NULL)
!           return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL);
  
        name = pt->pt_name;
      }
--- 1026,1035 ----
            for (i = 0; i < pt->pt_argc; ++i)
                copy_tv(&pt->pt_argv[i], STACK_TV_BOT(-argcount + i));
        }
+       selfdict = pt->pt_dict;
  
        if (pt->pt_func != NULL)
!           return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL, selfdict);
  
        name = pt->pt_name;
      }
***************
*** 973,979 ****
        if (error != FCERR_NONE)
            res = FAIL;
        else
!           res = call_by_name(fname, argcount, ectx, NULL);
        vim_free(tofree);
      }
  
--- 1047,1053 ----
        if (error != FCERR_NONE)
            res = FAIL;
        else
!           res = call_by_name(fname, argcount, ectx, NULL, selfdict);
        vim_free(tofree);
      }
  
***************
*** 1325,1331 ****
      int           called_emsg_before = called_emsg;
      int           res;
  
!     res = call_by_name(name, argcount, ectx, iptr);
      if (res == FAIL && called_emsg == called_emsg_before)
      {
        dictitem_T      *v;
--- 1399,1405 ----
      int           called_emsg_before = called_emsg;
      int           res;
  
!     res = call_by_name(name, argcount, ectx, iptr, NULL);
      if (res == FAIL && called_emsg == called_emsg_before)
      {
        dictitem_T      *v;
***************
*** 1570,1575 ****
--- 1644,1650 ----
  {
      int               ret = FAIL;
      int               save_trylevel_at_start = ectx->ec_trylevel_at_start;
+     int               dict_stack_len_at_start = dict_stack.ga_len;
  
      // Start execution at the first instruction.
      ectx->ec_iidx = 0;
***************
*** 4022,4028 ****
                    dict_T      *dict;
                    char_u      *key;
                    dictitem_T  *di;
-                   typval_T    temp_tv;
  
                    // dict member: dict is at stack-2, key at stack-1
                    tv = STACK_TV_BOT(-2);
--- 4097,4102 ----
***************
*** 4041,4063 ****
                        semsg(_(e_dictkey), key);
  
                        // If :silent! is used we will continue, make sure the
!                       // stack contents makes sense.
                        clear_tv(tv);
                        --ectx->ec_stack.ga_len;
                        tv = STACK_TV_BOT(-1);
!                       clear_tv(tv);
                        tv->v_type = VAR_NUMBER;
                        tv->vval.v_number = 0;
                        goto on_fatal_error;
                    }
                    clear_tv(tv);
                    --ectx->ec_stack.ga_len;
!                   // Clear the dict only after getting the item, to avoid
!                   // that it makes the item invalid.
                    tv = STACK_TV_BOT(-1);
!                   temp_tv = *tv;
                    copy_tv(&di->di_tv, tv);
-                   clear_tv(&temp_tv);
                }
                break;
  
--- 4115,4138 ----
                        semsg(_(e_dictkey), key);
  
                        // If :silent! is used we will continue, make sure the
!                       // stack contents makes sense and the dict stack is
!                       // updated.
                        clear_tv(tv);
                        --ectx->ec_stack.ga_len;
                        tv = STACK_TV_BOT(-1);
!                       (void) dict_stack_save(tv);
                        tv->v_type = VAR_NUMBER;
                        tv->vval.v_number = 0;
                        goto on_fatal_error;
                    }
                    clear_tv(tv);
                    --ectx->ec_stack.ga_len;
!                   // Put the dict used on the dict stack, it might be used by
!                   // a dict function later.
                    tv = STACK_TV_BOT(-1);
!                   if (dict_stack_save(tv) == FAIL)
!                       goto on_fatal_error;
                    copy_tv(&di->di_tv, tv);
                }
                break;
  
***************
*** 4066,4072 ****
                {
                    dict_T      *dict;
                    dictitem_T  *di;
-                   typval_T    temp_tv;
  
                    tv = STACK_TV_BOT(-1);
                    if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
--- 4141,4146 ----
***************
*** 4084,4094 ****
                        semsg(_(e_dictkey), iptr->isn_arg.string);
                        goto on_error;
                    }
!                   // Clear the dict after getting the item, to avoid that it
!                   // make the item invalid.
!                   temp_tv = *tv;
                    copy_tv(&di->di_tv, tv);
!                   clear_tv(&temp_tv);
                }
                break;
  
--- 4158,4194 ----
                        semsg(_(e_dictkey), iptr->isn_arg.string);
                        goto on_error;
                    }
!                   // Put the dict used on the dict stack, it might be used by
!                   // a dict function later.
!                   if (dict_stack_save(tv) == FAIL)
!                       goto on_fatal_error;
! 
                    copy_tv(&di->di_tv, tv);
!               }
!               break;
! 
!           case ISN_CLEARDICT:
!               dict_stack_drop();
!               break;
! 
!           case ISN_USEDICT:
!               {
!                   typval_T *dict_tv = dict_stack_get_tv();
! 
!                   // Turn "dict.Func" into a partial for "Func" bound to
!                   // "dict".  Don't do this when "Func" is already a partial
!                   // that was bound explicitly (pt_auto is FALSE).
!                   tv = STACK_TV_BOT(-1);
!                   if (dict_tv != NULL
!                           && dict_tv->v_type == VAR_DICT
!                           && dict_tv->vval.v_dict != NULL
!                           && (tv->v_type == VAR_FUNC
!                               || (tv->v_type == VAR_PARTIAL
!                                   && (tv->vval.v_partial->pt_auto
!                                    || tv->vval.v_partial->pt_dict == NULL))))
!                   dict_tv->vval.v_dict =
!                                       make_partial(dict_tv->vval.v_dict, tv);
!                   dict_stack_drop();
                }
                break;
  
***************
*** 4478,4483 ****
--- 4578,4584 ----
  done:
      ret = OK;
  theend:
+     dict_stack_clear(dict_stack_len_at_start);
      ectx->ec_trylevel_at_start = save_trylevel_at_start;
      return ret;
  }
***************
*** 5568,5573 ****
--- 5669,5677 ----
            case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break;
            case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current,
                                                  iptr->isn_arg.string); break;
+           case ISN_CLEARDICT: smsg("%s%4d CLEARDICT", pfx, current); break;
+           case ISN_USEDICT: smsg("%s%4d USEDICT", pfx, current); break;
+ 
            case ISN_NEGATENR: smsg("%s%4d NEGATENR", pfx, current); break;
  
            case ISN_CHECKNR: smsg("%s%4d CHECKNR", pfx, current); break;
*** ../vim-8.2.3434/src/vim9.h  2021-09-02 18:49:02.748932320 +0200
--- src/vim9.h  2021-09-13 15:44:13.008598382 +0200
***************
*** 162,167 ****
--- 162,170 ----
      ISN_CHECKLEN,   // check list length is isn_arg.checklen.cl_min_len
      ISN_SETTYPE,    // set dict type to isn_arg.type.ct_type
  
+     ISN_CLEARDICT,  // clear dict saved by ISN_MEMBER/ISN_STRINGMEMBER
+     ISN_USEDICT,    // use or clear dict saved by ISN_MEMBER/ISN_STRINGMEMBER
+ 
      ISN_PUT,      // ":put", uses isn_arg.put
  
      ISN_CMDMOD,           // set cmdmod
*** ../vim-8.2.3434/src/testdir/test_vim9_func.vim      2021-09-09 
22:30:48.128865561 +0200
--- src/testdir/test_vim9_func.vim      2021-09-13 17:46:39.853631142 +0200
***************
*** 2557,2562 ****
--- 2557,2593 ----
    endfor
  enddef
  
+ def Test_call_legacy_with_dict()
+   var lines =<< trim END
+       vim9script
+       func Legacy() dict
+         let g:result = self.value
+       endfunc
+       def TestDirect()
+         var d = {value: 'yes', func: Legacy}
+         d.func()
+       enddef
+       TestDirect()
+       assert_equal('yes', g:result)
+       unlet g:result
+ 
+       def TestIndirect()
+         var d = {value: 'foo', func: Legacy}
+         var Fi = d.func
+         Fi()
+       enddef
+       TestIndirect()
+       assert_equal('foo', g:result)
+       unlet g:result
+ 
+       var d = {value: 'bar', func: Legacy}
+       d.func()
+       assert_equal('bar', g:result)
+       unlet g:result
+   END
+   CheckScriptSuccess(lines)
+ enddef
+ 
  def DoFilterThis(a: string): list<string>
    # closure nested inside another closure using argument
    var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)
*** ../vim-8.2.3434/src/testdir/test_vim9_disassemble.vim       2021-08-22 
13:34:23.423960112 +0200
--- src/testdir/test_vim9_disassemble.vim       2021-09-13 17:40:46.541970193 
+0200
***************
*** 412,418 ****
          '\d PUSHNR 0\_s*' ..
          '\d LOAD $0\_s*' ..
          '\d MEMBER dd\_s*' ..
!         '\d STOREINDEX any\_s*' ..
          '\d\+ RETURN void',
          res)
  enddef
--- 412,419 ----
          '\d PUSHNR 0\_s*' ..
          '\d LOAD $0\_s*' ..
          '\d MEMBER dd\_s*' ..
!         '\d\+ USEDICT\_s*' ..
!         '\d\+ STOREINDEX any\_s*' ..
          '\d\+ RETURN void',
          res)
  enddef
***************
*** 1625,1635 ****
--- 1626,1638 ----
          'var res = d.item\_s*' ..
          '\d\+ LOAD $0\_s*' ..
          '\d\+ MEMBER item\_s*' ..
+         '\d\+ USEDICT\_s*' ..
          '\d\+ STORE $1\_s*' ..
          'res = d\["item"\]\_s*' ..
          '\d\+ LOAD $0\_s*' ..
          '\d\+ PUSHS "item"\_s*' ..
          '\d\+ MEMBER\_s*' ..
+         '\d\+ USEDICT\_s*' ..
          '\d\+ STORE $1\_s*',
          instr)
    assert_equal(1, DictMember())
***************
*** 2302,2307 ****
--- 2305,2339 ----
          res)
  enddef
  
+ func Legacy() dict
+   echo 'legacy'
+ endfunc
+ 
+ def s:UseMember()
+   var d = {func: Legacy}
+   var v = d.func()
+ enddef
+ 
+ def Test_disassemble_dict_stack()
+   var res = execute('disass s:UseMember')
+   assert_match('<SNR>\d*_UseMember\_s*' ..
+           'var d = {func: Legacy}\_s*' ..
+           '\d PUSHS "func"\_s*' ..
+           '\d PUSHFUNC "Legacy"\_s*' ..
+           '\d NEWDICT size 1\_s*' ..
+           '\d STORE $0\_s*' ..
+ 
+           'var v = d.func()\_s*' ..
+           '\d LOAD $0\_s*' ..
+           '\d MEMBER func\_s*' ..
+           '\d PCALL top (argc 0)\_s*' ..
+           '\d PCALL end\_s*' ..
+           '\d CLEARDICT\_s*' ..
+           '\d\+ STORE $1\_s*' ..
+           '\d\+ RETURN void*',
+         res)
+ enddef
+ 
  def s:EchoMessages()
    echohl ErrorMsg | echom v:exception | echohl NONE
  enddef
***************
*** 2363,2366 ****
--- 2395,2399 ----
  enddef
  
  
+ 
  " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
*** ../vim-8.2.3434/src/version.c       2021-09-12 21:00:10.625837682 +0200
--- src/version.c       2021-09-13 15:41:27.616798274 +0200
***************
*** 757,758 ****
--- 757,760 ----
  {   /* Add new patch number below this line */
+ /**/
+     3435,
  /**/

-- 
How To Keep A Healthy Level Of Insanity:
4. Put your garbage can on your desk and label it "in".

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202109131626.18DGQYTP1167157%40masaka.moolenaar.net.

Raspunde prin e-mail lui