# HG changeset patch # User Christian Brabandt # Date 1712084404 -7200 # Node ID a14868d39709759330ed5e4ef648e0012e6cb1fd # Parent 8b2823d22946b2472235e244e05c15a521bf4e0c patch 9.1.0257: Vim9: :call may not find imported class members Commit: https://github.com/vim/vim/commit/f1750ca0c2ef91c6e4857655ca8fdf8dd8f5abb8 Author: Yegappan Lakshmanan Date: Tue Apr 2 20:41:04 2024 +0200 patch 9.1.0257: Vim9: :call may not find imported class members Problem: Vim9: :call may not find imported class members (mityu) Solution: Set the typval of an imported lval variable correctly (Yegappan Lakshmanan) fixes: #14334 closes: #14386 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1146,6 +1146,91 @@ get_lval_check_access( } /* + * Get lval information for a variable imported from script "imp_sid". On + * success, updates "lp" with the variable name, type, script ID and typval. + * The variable name starts at or after "p". + * If "rettv" is not NULL it points to the value to be assigned. This used to + * match the rhs and lhs types. + * Returns a pointer to the character after the variable name if the imported + * variable is valid and writable. + * Returns NULL if the variable is not exported or typval is not found or the + * rhs type doesn't match the lhs type or the variable is not writable. + */ + static char_u * +get_lval_imported( + lval_T *lp, + typval_T *rettv, + scid_T imp_sid, + char_u *p, + dictitem_T **dip, + int fne_flags, + int vim9script) +{ + ufunc_T *ufunc; + type_T *type = NULL; + int cc; + int rc = FAIL; + + p = skipwhite(p); + + import_check_sourced_sid(&imp_sid); + lp->ll_sid = imp_sid; + lp->ll_name = p; + p = find_name_end(lp->ll_name, NULL, NULL, fne_flags); + lp->ll_name_end = p; + + // check the item is exported + cc = *p; + *p = NUL; + if (find_exported(imp_sid, lp->ll_name, &ufunc, &type, NULL, NULL, + TRUE) == -1) + goto failed; + + if (vim9script && type != NULL) + { + where_T where = WHERE_INIT; + + // In a vim9 script, do type check and make sure the variable is + // writable. + if (check_typval_type(type, rettv, where) == FAIL) + goto failed; + } + + // Get the typval for the exported item + hashtab_T *ht = &SCRIPT_VARS(imp_sid); + if (ht == NULL) + goto failed; + + dictitem_T *di = find_var_in_ht(ht, 0, lp->ll_name, TRUE); + if (di == NULL) + // variable is not found + goto success; + + *dip = di; + + // Check whether the variable is writable. + svar_T *sv = find_typval_in_script(&di->di_tv, imp_sid, FALSE); + if (sv != NULL && sv->sv_const != 0) + { + semsg(_(e_cannot_change_readonly_variable_str), lp->ll_name); + goto failed; + } + + // check whether variable is locked + if (value_check_lock(di->di_tv.v_lock, lp->ll_name, FALSE)) + goto failed; + + lp->ll_tv = &di->di_tv; + +success: + rc = OK; + +failed: + *p = cc; + return rc == OK ? p : NULL; +} + +/* * 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. @@ -1177,7 +1262,7 @@ get_lval( char_u *p; char_u *expr_start, *expr_end; int cc; - dictitem_T *v; + dictitem_T *v = NULL; typval_T var1; typval_T var2; int empty1 = FALSE; @@ -1311,28 +1396,13 @@ get_lval( if (*p == '.') { imported_T *import = find_imported(lp->ll_name, p - lp->ll_name, TRUE); - if (import != NULL) { - ufunc_T *ufunc; - type_T *type; - - import_check_sourced_sid(&import->imp_sid); - lp->ll_sid = import->imp_sid; - lp->ll_name = skipwhite(p + 1); - p = find_name_end(lp->ll_name, NULL, NULL, fne_flags); - lp->ll_name_end = p; - - // check the item is exported - cc = *p; - *p = NUL; - if (find_exported(import->imp_sid, lp->ll_name, &ufunc, &type, - NULL, NULL, TRUE) == -1) - { - *p = cc; + p++; // skip '.' + p = get_lval_imported(lp, rettv, import->imp_sid, p, &v, + fne_flags, vim9script); + if (p == NULL) return NULL; - } - *p = cc; } } @@ -1352,7 +1422,7 @@ get_lval( lp->ll_tv = lval_root->lr_tv; v = NULL; } - else + else if (lp->ll_tv == NULL) { cc = *p; *p = NUL; 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 @@ -3121,6 +3121,28 @@ def Test_class_import() v9.CheckScriptSuccess(lines) enddef +" Test for importing a class into a legacy script and calling the class method +def Test_class_method_from_legacy_script() + var lines =<< trim END + vim9script + export class A + static var name: string = 'a' + static def SetName(n: string) + name = n + enddef + endclass + END + writefile(lines, 'Xvim9export.vim', 'D') + + lines =<< trim END + import './Xvim9export.vim' as vim9 + + call s:vim9.A.SetName('b') + call assert_equal('b', s:vim9.A.name) + END + v9.CheckScriptSuccess(lines) +enddef + " Test for implementing an imported interface def Test_implement_imported_interface() var lines =<< trim END @@ -3220,6 +3242,23 @@ def Test_abstract_class() endclass END v9.CheckSourceFailure(lines, 'E1359: Cannot define a "new" method in an abstract class', 4) + + # extending an abstract class with class methods and variables + lines =<< trim END + vim9script + abstract class A + static var s: string = 'vim' + static def Fn(): list + return [10] + enddef + endclass + class B extends A + endclass + var b = B.new() + assert_equal('vim', A.s) + assert_equal([10], A.Fn()) + END + v9.CheckScriptSuccess(lines) enddef def Test_closure_in_class() diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim --- a/src/testdir/test_vim9_import.vim +++ b/src/testdir/test_vim9_import.vim @@ -2054,6 +2054,13 @@ def Test_import_vim9_from_legacy() export def GetText(): string return 'text' enddef + export var exported_nr: number = 22 + def AddNum(n: number) + exported_nr += n + enddef + export var exportedDict: dict = {Fn: AddNum} + export const CONST = 10 + export final finalVar = 'abc' END writefile(vim9_lines, 'Xvim9_export.vim', 'D') @@ -2072,6 +2079,13 @@ def Test_import_vim9_from_legacy() " imported symbol is script-local call assert_equal('exported', s:vim9.exported) call assert_equal('text', s:vim9.GetText()) + call s:vim9.exportedDict.Fn(5) + call assert_equal(27, s:vim9.exported_nr) + call call(s:vim9.exportedDict.Fn, [3]) + call assert_equal(30, s:vim9.exported_nr) + call assert_fails('let s:vim9.CONST = 20', 'E46: Cannot change read-only variable "CONST"') + call assert_fails('let s:vim9.finalVar = ""', 'E46: Cannot change read-only variable "finalVar"') + call assert_fails('let s:vim9.non_existing_var = 20', 'E1048: Item not found in script: non_existing_var') END writefile(legacy_lines, 'Xlegacy_script.vim', 'D') diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 257, +/**/ 256, /**/ 255,