diff options
author | Yegappan Lakshmanan <yegappan@yahoo.com> | 2023-10-14 11:18:50 +0200 |
---|---|---|
committer | Christian Brabandt <cb@256bit.org> | 2023-10-14 11:18:50 +0200 |
commit | 29bb67f1beefc7fd393dbfd9ee77d92f1db3a3c0 (patch) | |
tree | edaeb40abcb04a2d89e2886cd583ef497f2ad469 | |
parent | 69fb5afb3bc9da24c2fb0eafb0027ba9c6502fc2 (diff) |
patch 9.0.2019: Vim9: no support for funcrefsv9.0.2019
Problem: Vim9: no support for funcrefs
Solution: Add support for object/class funcref members
closes: #11981 #12417 #12960 #12324 #13333
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
-rw-r--r-- | src/eval.c | 11 | ||||
-rw-r--r-- | src/evalfunc.c | 3 | ||||
-rw-r--r-- | src/proto/vim9class.pro | 3 | ||||
-rw-r--r-- | src/proto/vim9instr.pro | 2 | ||||
-rw-r--r-- | src/structs.h | 1 | ||||
-rw-r--r-- | src/testdir/test_vim9_class.vim | 476 | ||||
-rw-r--r-- | src/testdir/test_vim9_func.vim | 11 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9.h | 1 | ||||
-rw-r--r-- | src/vim9class.c | 115 | ||||
-rw-r--r-- | src/vim9compile.c | 2 | ||||
-rw-r--r-- | src/vim9execute.c | 69 | ||||
-rw-r--r-- | src/vim9expr.c | 89 | ||||
-rw-r--r-- | src/vim9instr.c | 2 | ||||
-rw-r--r-- | src/vim9type.c | 4 |
15 files changed, 741 insertions, 50 deletions
diff --git a/src/eval.c b/src/eval.c index 4da6246ace..80ff5a3cba 100644 --- a/src/eval.c +++ b/src/eval.c @@ -5256,6 +5256,7 @@ partial_free(partial_T *pt) } else func_ptr_unref(pt->pt_func); + object_unref(pt->pt_obj); // "out_up" is no longer used, decrement refcount on partial that owns it. partial_unref(pt->pt_outer.out_up_partial); @@ -5578,6 +5579,7 @@ free_unref_items(int copyID) /* * PASS 2: free the items themselves. */ + object_free_items(copyID); dict_free_items(copyID); list_free_items(copyID); @@ -5818,6 +5820,15 @@ set_ref_in_item_partial( set_ref_in_item(&dtv, copyID, ht_stack, list_stack); } + if (pt->pt_obj != NULL) + { + typval_T objtv; + + objtv.v_type = VAR_OBJECT; + objtv.vval.v_object = pt->pt_obj; + set_ref_in_item(&objtv, copyID, ht_stack, list_stack); + } + for (int i = 0; i < pt->pt_argc; ++i) abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, ht_stack, list_stack); diff --git a/src/evalfunc.c b/src/evalfunc.c index 5fccf5270c..85c64a23a2 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -4801,6 +4801,9 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref) pt->pt_auto = arg_pt->pt_auto; if (pt->pt_dict != NULL) ++pt->pt_dict->dv_refcount; + pt->pt_obj = arg_pt->pt_obj; + if (pt->pt_obj != NULL) + ++pt->pt_obj->obj_refcount; } pt->pt_refcount = 1; diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro index 362e2cac5e..62d1b7d0f3 100644 --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -17,13 +17,14 @@ void emsg_var_cl_define(char *msg, char_u *name, size_t len, class_T *cl); ufunc_T *method_lookup(class_T *cl, vartype_T v_type, char_u *name, size_t namelen, int *idx); int inside_class(cctx_T *cctx_arg, class_T *cl); void copy_object(typval_T *from, typval_T *to); -void object_unref(object_T *obj); void copy_class(typval_T *from, typval_T *to); void class_unref(class_T *cl); int class_free_nonref(int copyID); int set_ref_in_classes(int copyID); void object_created(object_T *obj); +void object_unref(object_T *obj); int object_free_nonref(int copyID); +void object_free_items(int copyID); void method_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len); void member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len); void f_instanceof(typval_T *argvars, typval_T *rettv); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro index 898eb9751c..a236b75612 100644 --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -45,7 +45,7 @@ int generate_OLDSCRIPT(cctx_T *cctx, isntype_T isn_type, char_u *name, int sid, int generate_VIM9SCRIPT(cctx_T *cctx, isntype_T isn_type, int sid, int idx, type_T *type); int generate_NEWLIST(cctx_T *cctx, int count, int use_null); int generate_NEWDICT(cctx_T *cctx, int count, int use_null); -int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int fi, int *isn_idx); +int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int object_method, int fi, int *isn_idx); int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name); int generate_DEF(cctx_T *cctx, char_u *name, size_t len); int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); diff --git a/src/structs.h b/src/structs.h index 5131858741..a221a0406d 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2316,6 +2316,7 @@ struct partial_S int pt_copyID; // funcstack may contain pointer to partial dict_T *pt_dict; // dict for "self" + object_T *pt_obj; // object method }; typedef struct { diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim index 1aa9de7b99..3af9a1f87a 100644 --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -7477,4 +7477,480 @@ def Test_op_and_assignment() v9.CheckSourceSuccess(lines) enddef +" Test for using an object method as a funcref +def Test_object_funcref() + # Using object method funcref from a def function + var lines =<< trim END + vim9script + class A + def Foo(): list<number> + return [3, 2, 1] + enddef + endclass + def Bar() + var a = A.new() + var Fn = a.Foo + assert_equal([3, 2, 1], Fn()) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using object method funcref at the script level + lines =<< trim END + vim9script + class A + def Foo(): dict<number> + return {a: 1, b: 2} + enddef + endclass + var a = A.new() + var Fn = a.Foo + assert_equal({a: 1, b: 2}, Fn()) + END + v9.CheckSourceSuccess(lines) + + # Using object method funcref from another object method + lines =<< trim END + vim9script + class A + def Foo(): list<number> + return [3, 2, 1] + enddef + def Bar() + var Fn = this.Foo + assert_equal([3, 2, 1], Fn()) + enddef + endclass + var a = A.new() + a.Bar() + END + v9.CheckSourceSuccess(lines) + + # Using function() to get a object method funcref + lines =<< trim END + vim9script + class A + def Foo(l: list<any>): list<any> + return l + enddef + endclass + var a = A.new() + var Fn = function(a.Foo, [[{a: 1, b: 2}, [3, 4]]]) + assert_equal([{a: 1, b: 2}, [3, 4]], Fn()) + END + v9.CheckSourceSuccess(lines) + + # Use an object method with a function returning a funcref and then call the + # funcref. + lines =<< trim END + vim9script + + def Map(F: func(number): number): func(number): number + return (n: number) => F(n) + enddef + + class Math + def Double(n: number): number + return 2 * n + enddef + endclass + + const math = Math.new() + assert_equal(48, Map(math.Double)(24)) + END + v9.CheckSourceSuccess(lines) + + # Try using a private object method funcref from a def function + lines =<< trim END + vim9script + class A + def _Foo() + enddef + endclass + def Bar() + var a = A.new() + var Fn = a._Foo + enddef + Bar() + END + v9.CheckSourceFailure(lines, 'E1366: Cannot access private method: _Foo', 2) + + # Try using a private object method funcref at the script level + lines =<< trim END + vim9script + class A + def _Foo() + enddef + endclass + var a = A.new() + var Fn = a._Foo + END + v9.CheckSourceFailure(lines, 'E1366: Cannot access private method: _Foo', 7) + + # Using a private object method funcref from another object method + lines =<< trim END + vim9script + class A + def _Foo(): list<number> + return [3, 2, 1] + enddef + def Bar() + var Fn = this._Foo + assert_equal([3, 2, 1], Fn()) + enddef + endclass + var a = A.new() + a.Bar() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a class method as a funcref +def Test_class_funcref() + # Using class method funcref in a def function + var lines =<< trim END + vim9script + class A + static def Foo(): list<number> + return [3, 2, 1] + enddef + endclass + def Bar() + var Fn = A.Foo + assert_equal([3, 2, 1], Fn()) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using class method funcref at script level + lines =<< trim END + vim9script + class A + static def Foo(): dict<number> + return {a: 1, b: 2} + enddef + endclass + var Fn = A.Foo + assert_equal({a: 1, b: 2}, Fn()) + END + v9.CheckSourceSuccess(lines) + + # Using function() to get a class method funcref + lines =<< trim END + vim9script + class A + static def Foo(l: list<any>): list<any> + return l + enddef + endclass + var Fn = function(A.Foo, [[{a: 1, b: 2}, [3, 4]]]) + assert_equal([{a: 1, b: 2}, [3, 4]], Fn()) + END + v9.CheckSourceSuccess(lines) + + # Using a class method funcref from another class method + lines =<< trim END + vim9script + class A + static def Foo(): list<number> + return [3, 2, 1] + enddef + static def Bar() + var Fn = Foo + assert_equal([3, 2, 1], Fn()) + enddef + endclass + A.Bar() + END + v9.CheckSourceSuccess(lines) + + # Use a class method with a function returning a funcref and then call the + # funcref. + lines =<< trim END + vim9script + + def Map(F: func(number): number): func(number): number + return (n: number) => F(n) + enddef + + class Math + static def StaticDouble(n: number): number + return 2 * n + enddef + endclass + + assert_equal(48, Map(Math.StaticDouble)(24)) + END + v9.CheckSourceSuccess(lines) + + # Try using a private class method funcref in a def function + lines =<< trim END + vim9script + class A + static def _Foo() + enddef + endclass + def Bar() + var Fn = A._Foo + enddef + Bar() + END + v9.CheckSourceFailure(lines, 'E1366: Cannot access private method: _Foo', 1) + + # Try using a private class method funcref at script level + lines =<< trim END + vim9script + class A + static def _Foo() + enddef + endclass + var Fn = A._Foo + END + v9.CheckSourceFailure(lines, 'E1366: Cannot access private method: _Foo', 6) + + # Using a private class method funcref from another class method + lines =<< trim END + vim9script + class A + static def _Foo(): list<number> + return [3, 2, 1] + enddef + static def Bar() + var Fn = _Foo + assert_equal([3, 2, 1], Fn()) + enddef + endclass + A.Bar() + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using an object member as a funcref +def Test_object_member_funcref() + # Using a funcref object variable in an object method + var lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + this.Cb: func(number): number = Foo + def Bar() + assert_equal(200, this.Cb(20)) + enddef + endclass + + var a = A.new() + a.Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref object variable in a def method + lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + this.Cb: func(number): number = Foo + endclass + + def Bar() + var a = A.new() + assert_equal(200, a.Cb(20)) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref object variable at script level + lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + this.Cb: func(number): number = Foo + endclass + + var a = A.new() + assert_equal(200, a.Cb(20)) + END + v9.CheckSourceSuccess(lines) + + # Using a funcref object variable pointing to an object method in an object + # method. + lines =<< trim END + vim9script + class A + this.Cb: func(number): number = this.Foo + def Foo(n: number): number + return n * 10 + enddef + def Bar() + assert_equal(200, this.Cb(20)) + enddef + endclass + + var a = A.new() + a.Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref object variable pointing to an object method in a def + # method. + lines =<< trim END + vim9script + class A + this.Cb: func(number): number = this.Foo + def Foo(n: number): number + return n * 10 + enddef + endclass + + def Bar() + var a = A.new() + assert_equal(200, a.Cb(20)) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref object variable pointing to an object method at script + # level. + lines =<< trim END + vim9script + class A + this.Cb = this.Foo + def Foo(n: number): number + return n * 10 + enddef + endclass + + var a = A.new() + assert_equal(200, a.Cb(20)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using a class member as a funcref +def Test_class_member_funcref() + # Using a funcref class variable in a class method + var lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + static Cb = Foo + static def Bar() + assert_equal(200, Cb(20)) + enddef + endclass + + A.Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref class variable in a def method + lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + public static Cb = Foo + endclass + + def Bar() + assert_equal(200, A.Cb(20)) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref class variable at script level + lines =<< trim END + vim9script + def Foo(n: number): number + return n * 10 + enddef + + class A + public static Cb = Foo + endclass + + assert_equal(200, A.Cb(20)) + END + v9.CheckSourceSuccess(lines) + + # Using a funcref class variable pointing to a class method in a class + # method. + lines =<< trim END + vim9script + class A + static Cb: func(number): number + static def Foo(n: number): number + return n * 10 + enddef + static def Init() + Cb = Foo + enddef + static def Bar() + assert_equal(200, Cb(20)) + enddef + endclass + + A.Init() + A.Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref class variable pointing to a class method in a def method. + lines =<< trim END + vim9script + class A + static Cb: func(number): number + static def Foo(n: number): number + return n * 10 + enddef + static def Init() + Cb = Foo + enddef + endclass + + def Bar() + A.Init() + assert_equal(200, A.Cb(20)) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + + # Using a funcref class variable pointing to a class method at script level. + lines =<< trim END + vim9script + class A + static Cb: func(number): number + static def Foo(n: number): number + return n * 10 + enddef + static def Init() + Cb = Foo + enddef + endclass + + A.Init() + assert_equal(200, A.Cb(20)) + END + v9.CheckSourceSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 852f3a7ede..597a10c581 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -3663,6 +3663,17 @@ def Test_partial_call() const Call = Foo(Expr) END v9.CheckScriptFailure(lines, 'E1031:') + + # Test for calling a partial that takes a single argument. + # This used to produce a "E340: Internal error" message. + lines =<< trim END + def Foo(n: number): number + return n * 2 + enddef + var Fn = function(Foo, [10]) + assert_equal(20, Fn()) + END + v9.CheckDefAndScriptSuccess(lines) enddef def Test_partial_double_nested() diff --git a/src/version.c b/src/version.c index d05016389a..9147441a40 100644 --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2019, +/**/ 2018, /**/ 2017, diff --git a/src/vim9.h b/src/vim9.h index 785486963a..6bfbd9ee8f 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -383,6 +383,7 @@ typedef struct { char_u *fre_func_name; // function name for legacy function loopvarinfo_T fre_loopvar_info; // info about variables inside loops class_T *fre_class; // class for a method + int fre_object_method; // class or object method int fre_method_idx; // method index on "fre_class" } funcref_extra_T; diff --git a/src/vim9class.c b/src/vim9class.c index 5dda700fa3..bfa6149602 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -2167,15 +2167,35 @@ call_oc_method( ufunc_T *fp; typval_T argvars[MAX_FUNC_ARGS + 1]; int argcount = 0; + ocmember_T *ocm = NULL; + int m_idx; fp = method_lookup(cl, rettv->v_type, name, len, NULL); if (fp == NULL) { - method_not_found_msg(cl, rettv->v_type, name, len); - return FAIL; + // could be an object or class funcref variable + ocm = member_lookup(cl, rettv->v_type, name, len, &m_idx); + if (ocm == NULL || ocm->ocm_type->tt_type != VAR_FUNC) + { + method_not_found_msg(cl, rettv->v_type, name, len); + return FAIL; + } + + if (rettv->v_type == VAR_OBJECT) + { + // funcref object variable + object_T *obj = rettv->vval.v_object; + typval_T *tv = (typval_T *)(obj + 1) + m_idx; + copy_tv(tv, rettv); + } + else + // funcref class variable + copy_tv(&cl->class_members_tv[m_idx], rettv); + *arg = name_end; + return OK; } - if (*fp->uf_name == '_') + if (ocm == NULL && *fp->uf_name == '_') { // Cannot access a private method outside of a class semsg(_(e_cannot_access_private_method_str), fp->uf_name); @@ -2288,6 +2308,37 @@ class_object_index( return OK; } + // could be a class method or an object method + int fidx; + ufunc_T *fp = method_lookup(cl, rettv->v_type, name, len, &fidx); + if (fp != NULL) + { + // Private methods are not accessible outside the class + if (*name == '_') + { + semsg(_(e_cannot_access_private_method_str), fp->uf_name); + return FAIL; + } + + partial_T *pt = ALLOC_CLEAR_ONE(partial_T); + if (pt == NULL) + return FAIL; + + pt->pt_refcount = 1; + if (is_object) + { + pt->pt_obj = rettv->vval.v_object; + ++pt->pt_obj->obj_refcount; + } + pt->pt_auto = TRUE; + pt->pt_func = fp; + func_ptr_ref(pt->pt_func); + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + *arg = name_end; + return OK; + } + if (did_emsg == did_emsg_save) member_not_found_msg(cl, is_object, name, len); } @@ -2774,8 +2825,6 @@ object_created(object_T *obj) first_object = obj; } -static object_T *next_nonref_obj = NULL; - /* * Call this function when an object has been cleared and is about to be freed. * It is removed from the list headed by "first_object". @@ -2789,30 +2838,35 @@ object_cleared(object_T *obj) obj->obj_prev_used->obj_next_used = obj->obj_next_used; else if (first_object == obj) first_object = obj->obj_next_used; - - // update the next object to check if needed - if (obj == next_nonref_obj) - next_nonref_obj = obj->obj_next_used; } /* - * Free an object. + * Free the contents of an object ignoring the reference count. */ static void -object_clear(object_T *obj) +object_free_contents(object_T *obj) { - // Avoid a recursive call, it can happen if "obj" has a circular reference. - obj->obj_refcount = INT_MAX; - class_T *cl = obj->obj_class; if (!cl) return; + // Avoid a recursive call, it can happen if "obj" has a circular reference. + obj->obj_refcount = INT_MAX; + // the member values are just after the object structure typval_T *tv = (typval_T *)(obj + 1); for (int i = 0; i < cl->class_obj_member_count; ++i) clear_tv(tv + i); +} + + static void +object_free_object(object_T *obj) +{ + class_T *cl = obj->obj_class; + + if (!cl) + return; // Remove from the list headed by "first_object". object_cleared(obj); @@ -2821,6 +2875,16 @@ object_clear(object_T *obj) class_unref(cl); } + static void +object_free(object_T *obj) +{ + if (in_free_unref_items) + return; + + object_free_contents(obj); + object_free_object(obj); +} + /* * Unreference an object. */ @@ -2828,7 +2892,7 @@ object_clear(object_T *obj) object_unref(object_T *obj) { if (obj != NULL && --obj->obj_refcount <= 0) - object_clear(obj); + object_free(obj); } /* @@ -2839,21 +2903,32 @@ object_free_nonref(int copyID) { int did_free = FALSE; - for (object_T *obj = first_object; obj != NULL; obj = next_nonref_obj) + for (object_T *obj = first_object; obj != NULL; obj = obj->obj_next_used) { - next_nonref_obj = obj->obj_next_used; if ((obj->obj_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { - // Free the object and items it contains. - object_clear(obj); + // Free the object contents. Object itself will be freed later. + object_free_contents(obj); did_free = TRUE; } } - next_nonref_obj = NULL; return did_free; } + void +object_free_items(int copyID) +{ + object_T *obj_next; + + for (object_T *obj = first_object; obj != NULL; obj = obj_next) + { + obj_next = obj->obj_next_used; + if ((obj->obj_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) + object_free_object(obj); + } +} + /* * Output message which takes a variable name and the class that defines it. * "cl" is that class where the name was found. Search "cl"'s hierarchy to diff --git a/src/vim9compile.c b/src/vim9compile.c index 544ad17e10..03e79f5655 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1148,7 +1148,7 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) ASSIGN_CONST, ufunc->uf_func_type); if (lvar == NULL) goto theend; - if (generate_FUNCREF(cctx, ufunc, NULL, 0, &funcref_isn_idx) == FAIL) + if (generate_FUNCREF(cctx, ufunc, NULL, FALSE, 0, &funcref_isn_idx) == FAIL) goto theend; r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); } diff --git a/src/vim9execute.c b/src/vim9execute.c index a89bcd19b4..d8087bf08d 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -616,6 +616,12 @@ call_dfunc( // the first local variable. if (IS_OBJECT_METHOD(ufunc)) { + if (obj->v_type != VAR_OBJECT) + { + semsg(_(e_internal_error_str), "type in stack is not an object"); + return FAIL; + } + *STACK_TV_VAR(0) = *obj; obj->v_type = VAR_UNKNOWN; } @@ -1497,6 +1503,23 @@ call_partial( partial_T *pt = tv->vval.v_partial; int i; + if (pt->pt_obj != NULL) + { + // partial with an object method. Push the object before the + // function arguments. + if (GA_GROW_FAILS(&ectx->ec_stack, 1)) + return FAIL; + for (i = 1; i <= argcount; ++i) + *STACK_TV_BOT(-i + 1) = *STACK_TV_BOT(-i); + + typval_T *obj_tv = STACK_TV_BOT(-argcount); + obj_tv->v_type = VAR_OBJECT; + obj_tv->v_lock = 0; + obj_tv->vval.v_object = pt->pt_obj; + ++pt->pt_obj->obj_refcount; + ++ectx->ec_stack.ga_len; + } + if (pt->pt_argc > 0) { // Make space for arguments from the partial, shift the "argcount" @@ -4447,20 +4470,44 @@ exec_instructions(ectx_T *ectx) } if (extra != NULL && extra->fre_class != NULL) { - tv = STACK_TV_BOT(-1); - if (tv->v_type != VAR_OBJECT) + class_T *cl; + if (extra->fre_object_method) { - object_required_error(tv); - vim_free(pt); - goto on_error; + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_OBJECT) + { + object_required_error(tv); + vim_free(pt); + goto on_error; + } + + object_T *obj = tv->vval.v_object; + cl = obj->obj_class; + // drop the value from the stack + clear_tv(tv); + --ectx->ec_stack.ga_len; + + pt->pt_obj = obj; + ++obj->obj_refcount; } - object_T *obj = tv->vval.v_object; - class_T *cl = obj->obj_class; + else + cl = extra->fre_class; - // convert the interface index to the object index - int idx = object_index_from_itf_index(extra->fre_class, - TRUE, extra->fre_method_idx, cl); - ufunc = cl->class_obj_methods[idx]; + if (extra->fre_object_method) + { + // object method + // convert the interface index to the object index + int idx = + object_index_from_itf_index(extra->fre_class, + TRUE, extra->fre_method_idx, cl); + ufunc = cl->class_obj_methods[idx]; + } + else + { + // class method + ufunc = + cl->class_class_functions[extra->fre_method_idx]; + } } else if (extra == NULL || extra->fre_func_name == NULL) { diff --git a/src/vim9expr.c b/src/vim9expr.c index c15021e82a..c91ca9325b 100644 --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -281,6 +281,8 @@ inside_class_hierarchy(cctx_T *cctx_arg, class_T *cl) static int compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) { + int m_idx; + if (VIM_ISWHITE((*arg)[1])) { semsg(_(e_no_white_space_allowed_after_str_str), ".", *arg); @@ -365,17 +367,34 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) break; } } + ocmember_T *ocm = NULL; if (ufunc == NULL) { - method_not_found_msg(cl, type->tt_type, name, len); - return FAIL; + // could be a funcref in a member variable + ocm = member_lookup(cl, type->tt_type, name, len, &m_idx); + if (ocm == NULL || ocm->ocm_type->tt_type != VAR_FUNC) + { + method_not_found_msg(cl, type->tt_type, name, len); + return FAIL; + } + if (type->tt_type == VAR_CLASS) + { + if (generate_CLASSMEMBER(cctx, TRUE, cl, m_idx) == FAIL) + return FAIL; + } + else + { + if (generate_GET_OBJ_MEMBER(cctx, m_idx, ocm->ocm_type) == + FAIL) + return FAIL; + } } // A private object method can be used only inside the class where it // is defined or in one of the child classes. // A private class method can be used only in the class where it is // defined. - if (*ufunc->uf_name == '_' && + if (ocm == NULL && *ufunc->uf_name == '_' && ((type->tt_type == VAR_OBJECT && !inside_class_hierarchy(cctx, cl)) || (type->tt_type == VAR_CLASS @@ -393,6 +412,8 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) return FAIL; + if (ocm != NULL) + return generate_PCALL(cctx, argcount, name, ocm->ocm_type, TRUE); if (type->tt_type == VAR_OBJECT && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) return generate_CALL(cctx, ufunc, cl, fi, argcount); @@ -401,7 +422,6 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) if (type->tt_type == VAR_OBJECT) { - int m_idx; ocmember_T *m = object_member_lookup(cl, name, len, &m_idx); if (m_idx >= 0) { @@ -418,15 +438,21 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) return generate_GET_OBJ_MEMBER(cctx, m_idx, m->ocm_type); } - // Could be a function reference: "obj.Func". + // Could be an object method reference: "obj.Func". m_idx = object_method_idx(cl, name, len); if (m_idx >= 0) { ufunc_T *fp = cl->class_obj_methods[m_idx]; - if (type->tt_type == VAR_OBJECT - && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) - return generate_FUNCREF(cctx, fp, cl, m_idx, NULL); - return generate_FUNCREF(cctx, fp, NULL, 0, NULL); + // Private methods are not accessible outside the class + if (*name == '_' && !inside_class(cctx, cl)) + { + semsg(_(e_cannot_access_private_method_str), fp->uf_name); + return FAIL; + } + *arg = name_end; + if (type->tt_type == VAR_OBJECT) + return generate_FUNCREF(cctx, fp, cl, TRUE, m_idx, NULL); + return generate_FUNCREF(cctx, fp, NULL, FALSE, 0, NULL); } member_not_found_msg(cl, VAR_OBJECT, name, len); @@ -451,6 +477,24 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) *arg = name_end; return generate_CLASSMEMBER(cctx, TRUE, cl, idx); } + + // Could be a class method reference: "class.Func". + m_idx = class_method_idx(cl, name, len); + if (m_idx >= 0) + { + ufunc |