patch 9.1.1146: Vim9: wrong context being used when evaluating class member
Commit: https://github.com/vim/vim/commit/16f2d3a46527094901080527dd3cade98cc877f9 Author: Yegappan Lakshmanan <yegap...@yahoo.com> Date: Mon Feb 24 19:23:43 2025 +0100 patch 9.1.1146: Vim9: wrong context being used when evaluating class member Problem: Vim9: wrong context being used when evaluating class member (lifepillar, Ernie Rael) Solution: Use the correct script context when evaluating a class member init expression(Yegappan Lakshmanan) fixes: #14011 fixes: #14402 closes: #15112 closes: #16660 Signed-off-by: Yegappan Lakshmanan <yegap...@yahoo.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro index c918aead2..080a2ffaa 100644 --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -12,7 +12,6 @@ int need_type(type_T *actual, type_T *expected, int number_ok, int offset, int a lvar_T *reserve_local(cctx_T *cctx, char_u *name, size_t len, int assign, type_T *type); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx, cstack_T *cstack); imported_T *find_imported(char_u *name, size_t len, int load); -imported_T *find_imported_from_extends(cctx_T *cctx, char_u *name, size_t len, int load); char_u *may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp); char_u *peek_next_line_from_context(cctx_T *cctx); char_u *next_line_from_context(cctx_T *cctx, int skip_comment); diff --git a/src/proto/vim9expr.pro b/src/proto/vim9expr.pro index d28908c40..f58e34625 100644 --- a/src/proto/vim9expr.pro +++ b/src/proto/vim9expr.pro @@ -2,7 +2,7 @@ int generate_ppconst(cctx_T *cctx, ppconst_T *ppconst); void clear_ppconst(ppconst_T *ppconst); int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx); -int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end, imported_T *import); +int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end); int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error); int compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, ca_special_T special_fn); char_u *to_name_end(char_u *arg, int use_namespace); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro index 8ee33b8cc..3fcf08c09 100644 --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -81,6 +81,7 @@ int generate_undo_cmdmods(cctx_T *cctx); int generate_store_var(cctx_T *cctx, assign_dest_T dest, int opt_flags, int vimvaridx, type_T *type, char_u *name, lhs_T *lhs); int inside_loop_scope(cctx_T *cctx); int generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count, int is_decl); +int generate_SCRIPTCTX_SET(cctx_T *cctx, sctx_T new_sctx); void may_generate_prof_end(cctx_T *cctx, int prof_lnum); void delete_instr(isn_T *isn); void clear_instr_ga(garray_T *gap); diff --git a/src/structs.h b/src/structs.h index 03c28e20a..ce98bcef5 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1568,6 +1568,7 @@ typedef struct { type_T *ocm_type; int ocm_flags; char_u *ocm_init; // allocated + sctx_T ocm_init_sctx; // script context of the initializer expression } ocmember_T; // used for the lookup table of a class member index and object method index diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index b4e25c39a..7a41b5d52 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -3633,4 +3633,33 @@ def Test_disassemble_using_script_local_funcref() unlet g:instr enddef +" Disassemble the code generated for using a script local variable +" in an instance variable initialization expression +def Test_disassemble_using_script_local_var_in_obj_init() + var lines =<< trim END + vim9script + const DEFAULT = 'default-obj_key' + export class ObjKey + const unique_object_id3 = DEFAULT + endclass + END + writefile(lines, 'Xscriptlocalobjinit.vim', 'D') + lines =<< trim END + vim9script + import './Xscriptlocalobjinit.vim' as obj_key + + class C1 extends obj_key.ObjKey + endclass + g:instr = execute('disassemble C1.new') + END + v9.CheckScriptSuccess(lines) + assert_match('new\_s*' .. + '0 NEW C1 size \d\+\_s*' .. + '1 SCRIPTCTX_SET .*/Xscriptlocalobjinit.vim\_s*' .. + '2 LOADSCRIPT DEFAULT-0 from .*/Xscriptlocalobjinit.vim\_s*' .. + '3 SCRIPTCTX_SET .*\_s*' .. + '4 STORE_THIS 0', g:instr) + unlet g:instr +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim index 6937fb3c2..aa81851b4 100644 --- a/src/testdir/test_vim9_import.vim +++ b/src/testdir/test_vim9_import.vim @@ -3595,4 +3595,70 @@ enddef " &rtp = save_rtp " enddef +" Test for using a non-exported constant as an instance variable initiazer in an +" imported class +def Test_import_member_initializer() + var lines =<< trim END + vim9script + const DEFAULT = 'default' + export class Foo + public var x = DEFAULT + endclass + END + writefile(lines, 'Ximportclass.vim', 'D') + + # The initializer for Foo.x is evaluated in the context of Ximportclass.vim. + lines =<< trim END + vim9script + import './Ximportclass.vim' as X + class Bar extends X.Foo + endclass + var o = Bar.new() + assert_equal('default', o.x) + END + v9.CheckScriptSuccess(lines) + + # Another test + lines =<< trim END + vim9script + + export interface IObjKey + var unique_object_id: string + endinterface + + # helper sub-class. + export class ObjKey implements IObjKey + const unique_object_id = GenerateKey() + endclass + + export def GenerateKey(): string + return "SomeKey" + enddef + END + writefile(lines, 'XobjKey.vim', 'D') + + lines =<< trim END + vim9script + + import "./XobjKey.vim" as obj_key + + const GenKey = obj_key.GenerateKey + + class LocalObjKey implements obj_key.IObjKey + const unique_object_id = GenKey() + endclass + + type Key1 = obj_key.ObjKey + type Key2 = LocalObjKey + + class C1 extends Key1 + endclass + class C2 extends Key2 + endclass + assert_equal('SomeKey', C1.new().unique_object_id) + assert_equal('SomeKey', C2.new().unique_object_id) + END + v9.CheckScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index 0adb6521d..1d362718b 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1146, /**/ 1145, /**/ diff --git a/src/vim9.h b/src/vim9.h index c502ac462..63d7116fd 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -219,6 +219,8 @@ typedef enum { ISN_CEXPR_AUCMD, // first part of :cexpr isn_arg.number is cmdidx ISN_CEXPR_CORE, // second part of :cexpr, uses isn_arg.cexpr + ISN_SCRIPTCTX_SET, // set script context for expression evaluation + ISN_FINISH // end marker in list of instructions } isntype_T; @@ -570,6 +572,7 @@ struct isn_S { classmember_T classmember; storeindex_T storeindex; lockunlock_T lockunlock; + sctx_T setsctx; } isn_arg; }; diff --git a/src/vim9class.c b/src/vim9class.c index dc13c4b2e..cbb91f4fe 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -204,7 +204,13 @@ add_member( m->ocm_flags |= OCMFLAG_HAS_TYPE; m->ocm_type = type; if (init_expr != NULL) + { m->ocm_init = init_expr; + // Save the script context, we need it when evaluating or compiling the + // initializer expression. + m->ocm_init_sctx = current_sctx; + m->ocm_init_sctx.sc_lnum += SOURCING_LNUM; + } ++gap->ga_len; return OK; } @@ -1355,7 +1361,11 @@ add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap) typval_T *tv = &cl->class_members_tv[i]; if (m->ocm_init != NULL) { + sctx_T save_current_sctx = current_sctx; + + current_sctx = m->ocm_init_sctx; typval_T *etv = eval_expr(m->ocm_init, eap); + current_sctx = save_current_sctx; if (etv == NULL) return FAIL; diff --git a/src/vim9compile.c b/src/vim9compile.c index 3d8921ad8..f6764831a 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -836,30 +836,6 @@ find_imported(char_u *name, size_t len, int load) return ret; } -/* - * Find "name" in imported items of extended base class of the class to which - * the context :def function belongs. - */ - imported_T * -find_imported_from_extends(cctx_T *cctx, char_u *name, size_t len, int load) -{ - if (cctx == NULL || cctx->ctx_ufunc == NULL - || cctx->ctx_ufunc->uf_class == NULL) - return NULL; - - class_T *cl_extends = cctx->ctx_ufunc->uf_class->class_extends; - if (cl_extends == NULL - || cl_extends->class_class_function_count_child <= 0) - return NULL; - - sctx_T current_sctx_save = current_sctx; - current_sctx = cl_extends->class_class_functions[0]->uf_script_ctx; - imported_T *ret = find_imported(name, len, load); - current_sctx = current_sctx_save; - - return ret; -} - /* * Called when checking for a following operator at "arg". When the rest of * the line is empty or only a comment, peek the next line. If there is a next @@ -1398,7 +1374,7 @@ generate_loadvar(cctx_T *cctx, lhs_T *lhs) case dest_script: case dest_script_v9: res = compile_load_scriptvar(cctx, - name + (name[1] == ':' ? 2 : 0), NULL, NULL, NULL); + name + (name[1] == ':' ? 2 : 0), NULL, NULL); break; case dest_env: // Include $ in the name here @@ -3995,9 +3971,34 @@ obj_constructor_prologue(ufunc_T *ufunc, cctx_T *cctx) if (m->ocm_init != NULL) { - char_u *expr = m->ocm_init; + char_u *expr = m->ocm_init; + sctx_T save_current_sctx; + int change_sctx = FALSE; + + // If the member variable initialization script context is + // different from the current script context, then change it. + if (current_sctx.sc_sid != m->ocm_init_sctx.sc_sid) + change_sctx = TRUE; + + if (change_sctx) + { + // generate an instruction to change the script context to the + // member variable initialization script context. + save_current_sctx = current_sctx; + current_sctx = m->ocm_init_sctx; + generate_SCRIPTCTX_SET(cctx, current_sctx); + } + + int r = compile_expr0(&expr, cctx); + + if (change_sctx) + { + // restore the previous script context + current_sctx = save_current_sctx; + generate_SCRIPTCTX_SET(cctx, current_sctx); + } - if (compile_expr0(&expr, cctx) == FAIL) + if (r == FAIL) return FAIL; if (!ends_excmd2(m->ocm_init, expr)) diff --git a/src/vim9execute.c b/src/vim9execute.c index 65f6536fd..4b69dfafa 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -6132,6 +6132,13 @@ exec_instructions(ectx_T *ectx) clear_tv(STACK_TV_BOT(0)); ectx->ec_where = (where_T)WHERE_INIT; break; + + case ISN_SCRIPTCTX_SET: + // change the script context. Used to evaluate an object + // member variable initialization expression in the context of + // the script where the class is defined. + current_sctx = iptr->isn_arg.setsctx; + break; } continue; @@ -6716,6 +6723,7 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc) int prev_current = 0; int current; int def_arg_idx = 0; + sctx_T script_ctx = current_sctx; for (current = 0; current < instr_count; ++current) { @@ -6897,8 +6905,11 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc) scriptref_T *sref = iptr->isn_arg.script.scriptref; scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid); svar_T *sv; + sctx_T save_sctx = current_sctx; + current_sctx = script_ctx; sv = get_script_svar(sref, -1); + current_sctx = save_sctx; if (sv == NULL) smsg("%s%4d LOADSCRIPT [deleted] from %s", pfx, current, si->sn_name); @@ -7015,8 +7026,11 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc) scriptref_T *sref = iptr->isn_arg.script.scriptref; scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid); svar_T *sv; + sctx_T save_sctx = current_sctx; + current_sctx = script_ctx; sv = get_script_svar(sref, -1); + current_sctx = save_sctx; if (sv == NULL) smsg("%s%4d STORESCRIPT [deleted] in %s", pfx, current, si->sn_name); @@ -7650,6 +7664,15 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc) break; case ISN_DROP: smsg("%s%4d DROP", pfx, current); break; + case ISN_SCRIPTCTX_SET: + { + int sid = iptr->isn_arg.setsctx.sc_sid; + scriptitem_T *si = SCRIPT_ITEM(sid); + smsg("%s%4d SCRIPTCTX_SET %s", pfx, current, si->sn_name); + script_ctx = iptr->isn_arg.setsctx; + } + break; + case ISN_FINISH: // End of list of instructions for ISN_SUBSTITUTE. return; } diff --git a/src/vim9expr.c b/src/vim9expr.c index 76ce9c50b..af12fb69c 100644 --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -538,12 +538,11 @@ compile_load_scriptvar( cctx_T *cctx, char_u *name, // variable NUL terminated char_u *start, // start of variable - char_u **end, // end of variable, may be NULL - imported_T *import) // found imported item, can be NULL + char_u **end) // end of variable, may be NULL { scriptitem_T *si; int idx; - imported_T *imp; + imported_T *import; if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) return FAIL; @@ -558,14 +557,8 @@ compile_load_scriptvar( return OK; } - if (end == NULL) - imp = NULL; - else if (import == NULL) - imp = find_imported(name, 0, FALSE); - else - imp = import; - - if (imp != NULL) + import = end == NULL ? NULL : find_imported(name, 0, FALSE); + if (import != NULL) { char_u *p = skipwhite(*end); char_u *exp_name; @@ -575,8 +568,8 @@ compile_load_scriptvar( int done = FALSE; int res = OK; - check_script_symlink(imp->imp_sid); - import_check_sourced_sid(&imp->imp_sid); + check_script_symlink(import->imp_sid); + import_check_sourced_sid(&import->imp_sid); // Need to lookup the member. if (*p != '.') @@ -598,11 +591,11 @@ compile_load_scriptvar( cc = *p; *p = NUL; - si = SCRIPT_ITEM(imp->imp_sid); + si = SCRIPT_ITEM(import->imp_sid); if (si->sn_import_autoload && si->sn_state == SN_STATE_NOT_LOADED) // "import autoload './dir/script.vim'" or // "import autoload './autoload/script.vim'" - load script first - res = generate_SOURCE(cctx, imp->imp_sid); + res = generate_SOURCE(cctx, import->imp_sid); if (res == OK) { @@ -631,17 +624,17 @@ compile_load_scriptvar( { char_u sid_name[MAX_FUNC_NAME_LEN]; - func_name_with_sid(exp_name, imp->imp_sid, sid_name); + func_name_with_sid(exp_name, import->imp_sid, sid_name); res = generate_PUSHFUNC(cctx, sid_name, &t_func_any, TRUE); } else res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name, - imp->imp_sid, &t_any); + import->imp_sid, &t_any); done = TRUE; } else { - idx = find_exported(imp->imp_sid, exp_name, &ufunc, &type, + idx = find_exported(import->imp_sid, exp_name, &ufunc, &type, cctx, NULL, TRUE); } } @@ -663,7 +656,7 @@ compile_load_scriptvar( } generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, - imp->imp_sid, + import->imp_sid, idx, type); return OK; @@ -778,7 +771,7 @@ compile_load( res = generate_funcref(cctx, name, FALSE); else res = compile_load_scriptvar(cctx, name, - NULL, &end, NULL); + NULL, &end); break; case 'g': if (vim_strchr(name, AUTOLOAD_CHAR) == NULL) { @@ -894,15 +887,11 @@ compile_load( } else { - imported_T *imp = NULL; - // "var" can be script-local even without using "s:" if it // already exists in a Vim9 script or when it's imported. if (script_var_exists(*arg, len, cctx, NULL) == OK - || (imp = find_imported(name, 0, FALSE)) != NULL - || (imp = find_imported_from_extends(cctx, name, 0, FALSE)) - != NULL) - res = compile_load_scriptvar(cctx, name, *arg, &end, imp); + || find_imported(name, 0, FALSE) != NULL) + res = compile_load_scriptvar(cctx, name, *arg, &end); // When evaluating an expression and the name starts with an // uppercase letter it can be a user defined function. diff --git a/src/vim9instr.c b/src/vim9instr.c index b51903950..86fddd9f6 100644 --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -2506,6 +2506,23 @@ generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count, int is_decl) return OK; } +/* + * Generate instruction to set the script context. Used to evaluate an + * object member variable initialization expression in the context of the + * script where the class is defined. + */ + int +generate_SCRIPTCTX_SET(cctx_T *cctx, sctx_T new_sctx) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_SCRIPTCTX_SET)) == NULL) + return FAIL; + isn->isn_arg.setsctx = new_sctx; + return OK; +} + #if defined(FEAT_PROFILE) || defined(PROTO) void may_generate_prof_end(cctx_T *cctx, int prof_lnum) @@ -2821,6 +2838,7 @@ delete_instr(isn_T *isn) case ISN_UNPACK: case ISN_USEDICT: case ISN_WHILE: + case ISN_SCRIPTCTX_SET: // nothing allocated break; } -- -- 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 vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1tmdDQ-00AiNJ-DQ%40256bit.org.