# HG changeset patch # User Christian Brabandt # Date 1696010407 -7200 # Node ID 016d8f8632304e94c15d870081f83c570b84712f # Parent 3d3d0492824e372912f79dbb0091285d97e48c2d patch 9.0.1955: Vim9: lockvar issues with objects/classes Commit: https://github.com/vim/vim/commit/ee865f37acab6cac2cee6a171d60e1b365f852b0 Author: Ernie Rael Date: Fri Sep 29 19:53:55 2023 +0200 patch 9.0.1955: Vim9: lockvar issues with objects/classes Problem: Vim9: lockvar issues with objects/classes Solution: fix `get_lhs()` object/class access and avoid `SEGV`, make error messages more accurate. - `get_lval()` detects/returns object/class access - `compile_lock_unlock()` generate code for bare static and obj_arg access - `do_lock_var()` check lval for `ll_object`/`ll_class` and fail if so. Details: - Add `ll_object`/`ll_class`/`ll_oi` to `lval_T`. - Add `lockunlock_T` to `isn_T` for `is_arg` to specify handling of `lval_root` in `get_lval()`. - In `get_lval()`, fill in `ll_object`/`ll_class`/`ll_oi` as needed; when no `[idx] or .key`, check lval_root on the way out. - In `do_lock_var()` check for `ll_object`/`ll_class`; also bullet proof ll_dict case and give `Dictionay required` if problem. (not needed to avoid lockvar crash anymore) - In `compile_lock_unlock()` compile for the class variable and func arg cases. closes: #13174 Signed-off-by: Christian Brabandt Co-authored-by: Ernie Rael diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3534,8 +3534,12 @@ EXTERN char e_missing_name_after_impleme INIT(= N_("E1389: Missing name after implements")); EXTERN char e_cannot_use_an_object_variable_except_with_the_new_method_str[] INIT(= N_("E1390: Cannot use an object variable \"this.%s\" except with the \"new\" method")); -#endif -// E1391 - E1499 unused (reserved for Vim9 class support) +EXTERN char e_cannot_lock_object_variable_str[] + INIT(= N_("E1391: Cannot (un)lock variable \"%s\" in class \"%s\"")); +EXTERN char e_cannot_lock_class_variable_str[] + INIT(= N_("E1392: Cannot (un)lock class variable \"%s\" in class \"%s\"")); +#endif +// E1393 - E1499 unused (reserved for Vim9 class support) EXTERN char e_cannot_mix_positional_and_non_positional_str[] INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s")); EXTERN char e_fmt_arg_nr_unused_str[] diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -986,6 +986,62 @@ eval_foldexpr(win_T *wp, int *cp) #endif /* + * Fill in "lp" using "root". This is used in a special case when + * "get_lval()" parses a bare word when "lval_root" is not NULL. + * + * This is typically called with "lval_root" as "root". For a class, find + * the name from lp in the class from root, fill in lval_T if found. For a + * complex type, list/dict use it as the result; just put the root into + * ll_tv. + * + * "lval_root" is a hack used during run-time/instr-execution to provide the + * starting point for "get_lval()" to traverse a chain of indexes. In some + * cases get_lval sees a bare name and uses this function to populate the + * lval_T. + * + * For setting up "lval_root" (currently only used with lockvar) + * compile_lock_unlock - pushes object on stack (which becomes lval_root) + * execute_instructions: ISN_LOCKUNLOCK - sets lval_root from stack. + */ + static void +get_lval_root(lval_T *lp, typval_T *root, int is_arg) +{ +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lvalroot(): name %s", lp->ll_name); +#endif + if (!is_arg && root->v_type == VAR_CLASS) + { + if (root->vval.v_class != NULL) + { + // Special special case. Look for a bare class variable reference. + class_T *cl = root->vval.v_class; + int m_idx; + ocmember_T *m = class_member_lookup(cl, lp->ll_name, + lp->ll_name_end - lp->ll_name, &m_idx); + if (m != NULL) + { + // Assuming "inside class" since bare reference. + lp->ll_class = root->vval.v_class; + lp->ll_oi = m_idx; + lp->ll_valtype = m->ocm_type; + lp->ll_tv = &lp->ll_class->class_members_tv[m_idx]; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lvalroot() class member: name %s", + lp->ll_name); +#endif + return; + } + } + } + +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lvalroot() any type"); +#endif + lp->ll_tv = root; + lp->ll_is_root = TRUE; +} + +/* * Get an lval: variable, Dict item or List item that can be assigned a value * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", * "name.key", "name.key[expr]" etc. @@ -1028,6 +1084,11 @@ get_lval( int writing = 0; int vim9script = in_vim9script(); +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lval(): name %s, lval_root %p", + name, (void*)lval_root); +#endif + // Clear everything in "lp". CLEAR_POINTER(lp); @@ -1122,6 +1183,7 @@ get_lval( return NULL; lp->ll_name_end = tp; } + // TODO: check inside class? } } if (lp->ll_name == NULL) @@ -1157,7 +1219,11 @@ get_lval( // Without [idx] or .key we are done. if ((*p != '[' && *p != '.')) + { + if (lval_root != NULL) + get_lval_root(lp, lval_root, lval_root_is_arg); return p; + } if (vim9script && lval_root != NULL) { @@ -1350,6 +1416,8 @@ get_lval( } } lp->ll_list = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; // a NULL dict is equivalent with an empty dict if (lp->ll_tv->vval.v_dict == NULL) @@ -1482,6 +1550,8 @@ get_lval( clear_tv(&var1); lp->ll_dict = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; lp->ll_list = lp->ll_tv->vval.v_list; lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, (flags & GLV_ASSIGN_WITH_OP) == 0, quiet); @@ -1516,10 +1586,22 @@ get_lval( } else // v_type == VAR_CLASS || v_type == VAR_OBJECT { - class_T *cl = (v_type == VAR_OBJECT - && lp->ll_tv->vval.v_object != NULL) - ? lp->ll_tv->vval.v_object->obj_class - : lp->ll_tv->vval.v_class; + lp->ll_dict = NULL; + lp->ll_list = NULL; + + class_T *cl; + if (v_type == VAR_OBJECT && lp->ll_tv->vval.v_object != NULL) + { + cl = lp->ll_tv->vval.v_object->obj_class; + lp->ll_object = lp->ll_tv->vval.v_object; + } + else + { + cl = lp->ll_tv->vval.v_class; + lp->ll_object = NULL; + } + lp->ll_class = cl; + // TODO: what if class is NULL? if (cl != NULL) { @@ -1539,6 +1621,7 @@ get_lval( fp = method_lookup(cl, round == 1 ? VAR_CLASS : VAR_OBJECT, key, p - key, &m_idx); + lp->ll_oi = m_idx; if (fp != NULL) { lp->ll_ufunc = fp; @@ -1548,12 +1631,16 @@ get_lval( } } + // TODO: dont' check access if inside class + // TODO: is GLV_READ_ONLY the right thing to use + // for class/object member access? + // Probably in some cases. Need inside class check if (lp->ll_valtype == NULL) { int m_idx; - ocmember_T *om; - - om = member_lookup(cl, v_type, key, p - key, &m_idx); + ocmember_T *om + = member_lookup(cl, v_type, key, p - key, &m_idx); + lp->ll_oi = m_idx; if (om != NULL) { switch (om->ocm_access) diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2123,6 +2123,30 @@ do_unlet(char_u *name, int forceit) return FAIL; } + static void +report_lockvar_member(char *msg, lval_T *lp) +{ + int did_alloc = FALSE; + char_u *vname = (char_u *)""; + char_u *class_name = lp->ll_class != NULL + ? lp->ll_class->class_name : (char_u *)""; + if (lp->ll_name != NULL) + { + if (lp->ll_name_end == NULL) + vname = lp->ll_name; + else + { + vname = vim_strnsave(lp->ll_name, lp->ll_name_end - lp->ll_name); + if (vname == NULL) + return; + did_alloc = TRUE; + } + } + semsg(_(msg), vname, class_name); + if (did_alloc) + vim_free(vname); +} + /* * Lock or unlock variable indicated by "lp". * "deep" is the levels to go (-1 for unlimited); @@ -2141,6 +2165,10 @@ do_lock_var( int cc; dictitem_T *di; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: do_lock_var(): name %s, is_root %d", lp->ll_name, lp->ll_is_root); +#endif + if (lp->ll_tv == NULL) { cc = *name_end; @@ -2201,10 +2229,13 @@ do_lock_var( } *name_end = cc; } - else if (deep == 0) + else if (deep == 0 && lp->ll_object == NULL && lp->ll_class == NULL) { // nothing to do } + else if (lp->ll_is_root) + // (un)lock the item. + item_lock(lp->ll_tv, deep, lock, FALSE); else if (lp->ll_range) { listitem_T *li = lp->ll_li; @@ -2220,13 +2251,57 @@ do_lock_var( else if (lp->ll_list != NULL) // (un)lock a List item. item_lock(&lp->ll_li->li_tv, deep, lock, FALSE); + else if (lp->ll_object != NULL) // This check must be before ll_class. + { + // (un)lock an object variable. + report_lockvar_member(e_cannot_lock_object_variable_str, lp); + ret = FAIL; + } + else if (lp->ll_class != NULL) + { + // (un)lock a class variable. + report_lockvar_member(e_cannot_lock_class_variable_str, lp); + ret = FAIL; + } else + { // (un)lock a Dictionary item. - item_lock(&lp->ll_di->di_tv, deep, lock, FALSE); + if (lp->ll_di == NULL) + { + emsg(_(e_dictionary_required)); + ret = FAIL; + } + else + item_lock(&lp->ll_di->di_tv, deep, lock, FALSE); + } return ret; } +#ifdef LOG_LOCKVAR + static char * +vartype_tostring(vartype_T vartype) +{ + return + vartype == VAR_BOOL ? "v_number" + : vartype == VAR_SPECIAL ? "v_number" + : vartype == VAR_NUMBER ? "v_number" + : vartype == VAR_FLOAT ? "v_float" + : vartype == VAR_STRING ? "v_string" + : vartype == VAR_BLOB ? "v_blob" + : vartype == VAR_FUNC ? "v_string" + : vartype == VAR_PARTIAL ? "v_partial" + : vartype == VAR_LIST ? "v_list" + : vartype == VAR_DICT ? "v_dict" + : vartype == VAR_JOB ? "v_job" + : vartype == VAR_CHANNEL ? "v_channel" + : vartype == VAR_INSTR ? "v_instr" + : vartype == VAR_CLASS ? "v_class" + : vartype == VAR_OBJECT ? "v_object" + : ""; +} +#endif + /* * Lock or unlock an item. "deep" is nr of levels to go. * When "check_refcount" is TRUE do not lock a list or dict with a reference @@ -2243,6 +2318,10 @@ item_lock(typval_T *tv, int deep, int lo hashitem_T *hi; int todo; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_tostring(tv->v_type)); +#endif + if (recurse >= DICT_MAXNEST) { emsg(_(e_variable_nested_too_deep_for_unlock)); diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1954,6 +1954,7 @@ EXTERN int timer_busy INIT(= 0); // w EXTERN int input_busy INIT(= 0); // when inside get_user_input() then > 0 EXTERN typval_T *lval_root INIT(= NULL); +EXTERN int lval_root_is_arg INIT(= 0); #endif #ifdef FEAT_BEVAL_TERM diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -69,6 +69,7 @@ int generate_MULT_EXPR(cctx_T *cctx, isn int generate_ECHOWINDOW(cctx_T *cctx, int count, long time); int generate_SOURCE(cctx_T *cctx, int sid); int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum); +int generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg); int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line); int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str); int generate_LEGACY_EVAL(cctx_T *cctx, char_u *line); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -4535,6 +4535,11 @@ typedef struct * "tv" points to the (first) list item value * "li" points to the (first) list item * "range", "n1", "n2" and "empty2" indicate what items are used. + * For a member in a class/object: TODO: verify fields + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the (first) list item value + * "oi" index into member array, see _type to determine which array * For an existing Dict item: * "name" points to the (expanded) variable name. * "exp_name" NULL or non-NULL, to be freed later. @@ -4571,6 +4576,11 @@ typedef struct lval_S type_T *ll_valtype; // type expected for the value or NULL blob_T *ll_blob; // The Blob or NULL ufunc_T *ll_ufunc; // The function or NULL + object_T *ll_object; // The object or NULL, class is not NULL + class_T *ll_class; // The class or NULL, object may be NULL + int ll_oi; // The object/class member index + int ll_is_root; // Special case. ll_tv is lval_root, + // ignore the rest. } lval_T; // Structure used to save the current state. Used when executing Normal mode diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -3480,7 +3480,7 @@ enddef " Test for locking a variable referring to an object and reassigning to another " object. -def Test_object_lockvar() +def Test_lockvar_object() var lines =<< trim END vim9script @@ -3515,6 +3515,480 @@ def Test_object_lockvar() v9.CheckSourceSuccess(lines) enddef +" Test trying to lock an object variable from various places +def Test_lockvar_object_variable() + # An object variable lockvar has several cases: + # object method, scriptlevel, scriplevel from :def, :def arg + # method arg, static method arg. + # Also different depths + + # TODO: handle inside_class in vim9class + # lockvar of a read-only currently fails even if inside + + # + # lockvar of read-only object variable + # + + # read-only lockvar from object method + var lines =<< trim END + vim9script + + class C + this.val1: number + def Lock() + lockvar this.val1 + enddef + endclass + var o = C.new(3) + o.Lock() + END + # TODO: wrong error + v9.CheckSourceFailure(lines, 'E1335: Variable "val1" in class "C" is not writable') + + # read-only lockvar from scriptlevel + lines =<< trim END + vim9script + + class C + this.val2: number + endclass + var o = C.new(3) + lockvar o.val2 + END + v9.CheckSourceFailure(lines, 'E1335: Variable "val2" in class "C" is not writable') + + # read-only lockvar of scriptlevel variable from def + lines =<< trim END + vim9script + + class C + this.val3: number + endclass + var o = C.new(3) + def Lock() + lockvar o.val3 + enddef + Lock() + END + v9.CheckSourceFailure(lines, 'E1335: Variable "val3" in class "C" is not writable') + + # read-only lockvar of def argument variable + lines =<< trim END + vim9script + + class C + this.val4: number + endclass + def Lock(o: C) + lockvar o.val4 + enddef + Lock(C.new(3)) + END + v9.CheckSourceFailure(lines, 'E1335: Variable "val4" in class "C" is not writable') + + # TODO: the following tests use type "any" for argument. Need a run time + # check for access. Probably OK as is for now. + + # read-only lockvar from object method arg + lines =<< trim END + vim9script + + class C + this.val5: number + def Lock(o_any: any) + lockvar o_any.val5 + enddef + endclass + var o = C.new(3) + o.Lock(C.new(5)) + END + # TODO: wrong error, tricky since type "any" + v9.CheckSourceFailure(lines, 'E1335: Variable "val5" in class "C" is not writable') + + # read-only lockvar from class method arg + lines =<< trim END + vim9script + + class C + this.val6: number + static def Lock(o_any: any) + lockvar o_any.val6 + enddef + endclass + var o = C.new(3) + C.Lock(o) + END + # TODO: wrong error, tricky since type "any" + v9.CheckSourceFailure(lines, 'E1335: Variable "val6" in class "C" is not writable') + + # + # lockvar of public object variable + # + + # lockvar from object method + lines =<< trim END + vim9script + + class C + public this.val1: number + def Lock() + lockvar this.val1 + enddef + endclass + var o = C.new(3) + o.Lock() + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "this.val1" in class "C"', 1) + + # lockvar from scriptlevel + lines =<< trim END + vim9script + + class C + public this.val2: number + endclass + var o = C.new(3) + lockvar o.val2 + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val2" in class "C"', 7) + + # lockvar of scriptlevel variable from def + lines =<< trim END + vim9script + + class C + public this.val3: number + endclass + var o = C.new(3) + def Lock() + lockvar o.val3 + enddef + Lock() + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val3" in class "C"', 1) + + # lockvar of def argument variable + lines =<< trim END + vim9script + + class C + public this.val4: number + endclass + def Lock(o: C) + lockvar o.val4 + enddef + Lock(C.new(3)) + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val4" in class "C"', 1) + + # lockvar from object method arg + lines =<< trim END + vim9script + + class C + public this.val5: number + def Lock(o_any: any) + lockvar o_any.val5 + enddef + endclass + var o = C.new(3) + o.Lock(C.new(5)) + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val5" in class "C"', 1) + + # lockvar from class method arg + lines =<< trim END + vim9script + + class C + public this.val6: number + static def Lock(o_any: any) + lockvar o_any.val6 + enddef + endclass + var o = C.new(3) + C.Lock(o) + END + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val6" in class "C"', 1) +enddef + +" Test trying to lock a class variable from various places +def Test_lockvar_class_variable() + + # lockvar bare static from object method + var lines =<< trim END + vim9script + + class C + public static sval1: number + def Lock() + lockvar sval1 + enddef + endclass + var o = C.new() + o.Lock() + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval1" in class "C"', 1) + + # lockvar C.static from object method + lines =<< trim END + vim9script + + class C + public static sval2: number + def Lock() + lockvar C.sval2 + enddef + endclass + var o = C.new() + o.Lock() + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval2" in class "C"', 1) + + # lockvar bare static from class method + lines =<< trim END + vim9script + + class C + public static sval3: number + static def Lock() + lockvar sval3 + enddef + endclass + C.Lock() + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval3" in class "C"', 1) + + # lockvar C.static from class method + lines =<< trim END + vim9script + + class C + public static sval4: number + static def Lock() + lockvar C.sval4 + enddef + endclass + C.Lock() + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval4" in class "C"', 1) + + # lockvar C.static from script level + lines =<< trim END + vim9script + + class C + public static sval5: number + endclass + lockvar C.sval5 + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval5" in class "C"', 6) + + # lockvar o.static from script level + lines =<< trim END + vim9script + + class C + public static sval6: number + endclass + var o = C.new() + lockvar o.sval6 + END + v9.CheckSourceFailure(lines, 'E1375: Class variable "sval6" accessible only using class "C"', 7) +enddef + +" Test locking an argument to :def +def Test_lockvar_argument() + # Lockvar a function arg + var lines =<< trim END + vim9script + + def Lock(val: any) + lockvar val + enddef + + var d = {a: 1, b: 2} + Lock(d) + + d->extend({c: 3}) + END + v9.CheckSourceFailure(lines, 'E741: Value is locked: extend() argument') + + # Lockvar a function arg. Verify "sval" is interpreted as argument and not a + # class member in "C". This tests lval_root_is_arg. + lines =<< trim END + vim9script + + class C + public static sval: list + endclass + + def Lock2(sval: any) + lockvar sval + enddef + + var o = C.new() + Lock2(o) + END + v9.CheckSourceSuccess(lines) + + # Lock a class. + lines =<< trim END + vim9script + + class C + public static sval: list + endclass + + def Lock2(sval: any) + lockvar sval + enddef + + Lock2(C) + END + v9.CheckSourceSuccess(lines) + + # Lock an object. + lines =<< trim END + vim9script + + class C + public static sval: list + endclass + + def Lock2(sval: any) + lockvar sval + enddef + + Lock2(C.new()) + END + v9.CheckSourceSuccess(lines) + + # In this case (unlike previous) "lockvar sval" is a class member. + lines =<< trim END + vim9script + + class C + public static sval: list + def Lock2() + lockvar sval + enddef + endclass + + + var o = C.new() + o.Lock2() + END + v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval" in class "C"', 1) +enddef + +" Test that this can be locked without error +def Test_lockvar_this() + # lockvar this + var lines =<< trim END + vim9script + class C + def TLock() + lockvar this + enddef + endclass + var o = C.new() + o.TLock() + END + v9.CheckSourceSuccess(lines) + + # lockvar four (four letter word, but not this) + lines =<< trim END + vim9script + class C + def TLock4() + var four: number + lockvar four + enddef + endclass + var o = C.new() + o.TLock4() + END + v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable') + + # lockvar this5; "this" + one char, 5 letter word, starting with "this" + lines =<< trim END + vim9script + class C + def TLock5() + var this5: number + lockvar this5 + enddef + endclass + var o = C.new() + o.TLock5() + END + v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable') +enddef + +" Test some general lockvar cases +def Test_lockvar_general() + # lockvar an object and a class. It does nothing + var lines =<< trim END + vim9script + class C + endclass + var o = C.new() + lockvar o + lockvar C + END + v9.CheckSourceSuccess(lines) + + # Lock a list element that's nested in an object variable from a :def + lines =<< trim END + vim9script + + class C + public this.val: list> = [ [1], [2], [3] ] + endclass + def Lock2(obj: any) + lockvar obj.val[1] + enddef + + var o = C.new() + Lock2(o) + o.val[0] = [9] + assert_equal([ [9], [2], [3] ], o.val) + try + o.val[1] = [999] + call assert_false(true, 'assign should have failed') + catch + assert_exception('E741:') + endtry + o.val[2] = [8] + assert_equal([ [9], [2], [8] ], o.val) + END + v9.CheckSourceSuccess(lines) + + # Lock a list element that's nested in an object variable from scriptlevel + lines =<< trim END + vim9script + + class C + public this.val: list> = [ [1], [2], [3] ] + endclass + + var o = C.new() + lockvar o.val[1] + o.val[0] = [9] + assert_equal([ [9], [2], [3] ], o.val) + try + o.val[1] = [999] + call assert_false(true, 'assign should have failed') + catch + assert_exception('E741:') + endtry + o.val[2] = [8] + assert_equal([ [9], [2], [8] ], o.val) + END + v9.CheckSourceSuccess(lines) +enddef + " Test for a private object method def Test_private_object_method() # Try calling a private method using an object (at the script level) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1955, +/**/ 1954, /**/ 1953, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -499,12 +499,19 @@ typedef struct { class_T *cm_class; int cm_idx; } classmember_T; + // arguments to ISN_STOREINDEX typedef struct { vartype_T si_vartype; class_T *si_class; } storeindex_T; +// arguments to ISN_LOCKUNLOCK +typedef struct { + char_u *string; // for exec_command + int is_arg; // is lval_root a function arg +} lockunlock_T; + /* * Instruction */ @@ -561,6 +568,7 @@ struct isn_S { construct_T construct; classmember_T classmember; storeindex_T storeindex; + lockunlock_T lockunlock; } isn_arg; }; diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -189,10 +189,15 @@ compile_lock_unlock( int cc = *name_end; char_u *p = lvp->ll_name; int ret = OK; - size_t len; char_u *buf; isntype_T isn = ISN_EXEC; char *cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar"; + int is_arg = FALSE; + +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile_lock_unlock(): cookie %p, name %s", + coookie, p); +#endif if (cctx->ctx_skip == SKIP_YES) return OK; @@ -207,20 +212,68 @@ compile_lock_unlock( if (p[1] != ':') { char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START); + // If name is is locally accessible, except for local var, + // then put it on the stack to use with ISN_LOCKUNLOCK. + // This could be v.memb, v[idx_key]; bare class variable, + // function arg. The local variable on the stack, will be passed + // to ex_lockvar() indirectly. - if (lookup_local(p, end - p, NULL, cctx) == OK) + char_u *name = NULL; + int len = end - p; + + if (lookup_local(p, len, NULL, cctx) == OK) { - char_u *s = p; - - if (*end != '.' && *end != '[') + // Handle "this", "this.val", "anyvar[idx]" + if (*end != '.' && *end != '[' + && (len != 4 || STRNCMP("this", p, len) != 0)) { emsg(_(e_cannot_lock_unlock_local_variable)); return FAIL; } - - // For "d.member" put the local variable on the stack, it will be - // passed to ex_lockvar() indirectly. - if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL) + // Push the local on the stack, could be "this". + name = p; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile... lookup_local: name %s", name); +#endif + } + if (name == NULL) + { + class_T *cl; + if (cctx_class_member_idx(cctx, p, len, &cl) >= 0) + { + if (*end != '.' && *end != '[') + { + // Push the class of the bare class variable name + name = cl->class_name; + len = STRLEN(name); +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile... cctx_class_member: name %s", + name); +#endif + } + } + } + if (name == NULL) + { + int idx; + type_T *type; + // Can lockvar any function arg. + // TODO: test arg[idx]/arg.member + if (arg_exists(p, len, &idx, &type, NULL, cctx) == OK) + { + name = p; + is_arg = TRUE; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile... arg_exists: name %s", name); +#endif + } + } + if (name != NULL) + { +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile... INS_LOCKUNLOCK %s", name); +#endif + if (compile_load(&name, name + len, cctx, FALSE, FALSE) == FAIL) return FAIL; isn = ISN_LOCKUNLOCK; } @@ -228,7 +281,7 @@ compile_lock_unlock( // Checking is done at runtime. *name_end = NUL; - len = name_end - p + 20; + size_t len = name_end - p + 20; buf = alloc(len); if (buf == NULL) ret = FAIL; @@ -238,7 +291,13 @@ compile_lock_unlock( vim_snprintf((char *)buf, len, "%s! %s", cmd, p); else vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, p); - ret = generate_EXEC_copy(cctx, isn, buf); +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: compile... buf %s", buf); +#endif + if (isn == ISN_LOCKUNLOCK) + ret = generate_LOCKUNLOCK(cctx, buf, is_arg); + else + ret = generate_EXEC_copy(cctx, isn, buf); vim_free(buf); *name_end = cc; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1957,7 +1957,7 @@ fill_partial_and_closure( * Execute iptr->isn_arg.string as an Ex command. */ static int -exec_command(isn_T *iptr) +exec_command(isn_T *iptr, char_u *cmd_string) { source_cookie_T cookie; @@ -1965,8 +1965,7 @@ exec_command(isn_T *iptr) // Pass getsourceline to get an error for a missing ":end" command. CLEAR_FIELD(cookie); cookie.sourcing_lnum = iptr->isn_lnum - 1; - if (do_cmdline(iptr->isn_arg.string, - getsourceline, &cookie, + if (do_cmdline(cmd_string, getsourceline, &cookie, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED) == FAIL || did_emsg) return FAIL; @@ -3182,7 +3181,7 @@ exec_instructions(ectx_T *ectx) // execute Ex command line case ISN_EXEC: - if (exec_command(iptr) == FAIL) + if (exec_command(iptr, iptr->isn_arg.string) == FAIL) goto on_error; break; @@ -4179,16 +4178,24 @@ exec_instructions(ectx_T *ectx) case ISN_LOCKUNLOCK: { + // TODO: could put lval_root info in struct typval_T *lval_root_save = lval_root; + int lval_root_is_arg_save = lval_root_is_arg; int res; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: execute INS_LOCKUNLOCK isn_arg %s", + iptr->isn_arg.string); +#endif // Stack has the local variable, argument the whole :lock // or :unlock command, like ISN_EXEC. --ectx->ec_stack.ga_len; lval_root = STACK_TV_BOT(0); - res = exec_command(iptr); + lval_root_is_arg = iptr->isn_arg.lockunlock.is_arg; + res = exec_command(iptr, iptr->isn_arg.lockunlock.string); clear_tv(lval_root); lval_root = lval_root_save; + lval_root_is_arg = lval_root_is_arg_save; if (res == FAIL) goto on_error; } diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -2174,6 +2174,23 @@ generate_PUT(cctx_T *cctx, int regname, * A copy is made of "line". */ int +generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_LOCKUNLOCK)) == NULL) + return FAIL; + isn->isn_arg.lockunlock.string = vim_strsave(line); + isn->isn_arg.lockunlock.is_arg = is_arg; + return OK; +} + +/* + * Generate an EXEC instruction that takes a string argument. + * A copy is made of "line". + */ + int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line) { isn_T *isn;