patch 9.2.1140: Vim9: :def in legacy script can create a script variable
Problem: A :def function in a legacy script can create a script-local
variable by assigning to it, while declaring one with "var"
gives E1101.
Solution: Give E1089 for assigning to an unknown script-local variable
in any :def function, not only in one defined in a Vim9
script. Remove uf_script_ctx_version, which was only used
for this check. Do not give it with ":source ++dryrun".
runtime/menu.vim created s:bmenu_short, s:bmenu_count, s:suglist,
s:fromword, s:changeitem, s:additem and s:ignoreitem this way, define
them at the script level.
":source ++dryrun" does not execute ":let" in a legacy script, thus
every script variable would be unknown when compiling. Document that
reading or calling such a variable without "s:" still gives an error,
and separate the paragraphs of ":source-dryrun" with blank lines.
Test_unlet() created s:somevar this way, define it at the script level
instead. Test_using_s_var_in_function() checks that assigning to
s:scriptlevel in a :def function of a legacy script gives E1089 when
the variable does not exist. Test_source_dryrun() checks that
assigning to a script variable with "s:" in a :def function of a
legacy script gives no error with ":source ++dryrun".
closes: #21379
Assisted-by: Claude
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
1 parent
c77608d35e
commit
6175ce8762
10 files changed
+57
-11
No files matched your search
+12
-1
@@ -1,4 +1,4 @@
|
||||
*repeat.txt* For Vim version 9.2. Last change: 2026 Sep 25
|
||||
*repeat.txt* For Vim version 9.2. Last change: 2026 Sep 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -248,6 +248,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
|
||||
Read the script without running it: only what defines
|
||||
something is executed, then every |:def| function in
|
||||
it is compiled and the errors are reported as usual.
|
||||
|
||||
What is executed: `vim9script`, `import`, `def`,
|
||||
`function`, `class`, `interface`, `enum`, `type` and a
|
||||
declaration with `var`, `const` or `final`. The
|
||||
@@ -256,17 +257,27 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
|
||||
expression is not evaluated. A static class variable
|
||||
is treated the same way and an enum value is not
|
||||
created.
|
||||
|
||||
Everything else is skipped, also `finish` and the
|
||||
condition of an `if`, `while` or `for`; a definition
|
||||
inside such a block is executed whether the condition
|
||||
holds or not. A function may therefore be defined
|
||||
twice, the last definition counts.
|
||||
|
||||
In a legacy script `let` is not executed, thus a
|
||||
script variable created by it does not exist. A
|
||||
|:def| function can use it with "s:", but reading it
|
||||
or calling it without "s:" gives an error. Defining a
|
||||
function in a Dictionary it holds, such as
|
||||
`function s:obj.Method()`, fails as well.
|
||||
|
||||
A script that is imported is read the same way, but an
|
||||
autoload script is not loaded. No |SourceCmd|,
|
||||
|SourcePre| or |SourcePost| autocommand is triggered.
|
||||
A class or enum in an autoload script is defined again
|
||||
by a dry run, and after one by a normal load, since no
|
||||
object was made from it; see |vim9-reload|.
|
||||
|
||||
Note: An error in a definition does not stop the
|
||||
reading, the rest of the script is read as well. And
|
||||
an error in a line of a function does not stop
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 Sep 25
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 Sep 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -52719,6 +52719,8 @@ Changed ~
|
||||
- The highlighting groups |hl-CursorLineFold| and |hl-CursorLineSign| are
|
||||
always used when 'cursorline' is set.
|
||||
- Virtual text above a line is now counted by |screenpos()| and |virtcol()|.
|
||||
- A |:def| function in a legacy script can no longer create a script-local
|
||||
variable by assigning to it, this gives |E1089|.
|
||||
|
||||
*added-9.3*
|
||||
Added ~
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*vim9.txt* For Vim version 9.2. Last change: 2026 Sep 25
|
||||
*vim9.txt* For Vim version 9.2. Last change: 2026 Sep 28
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -3610,7 +3610,9 @@ or within a `:function`. The three rules are:
|
||||
regardless of the function's parent context. Similarly, it gives E1268 in
|
||||
the script-local scope of a Vim9 script.
|
||||
3. In a legacy Vim script, "s:" is optional in a `:def` function, regardless
|
||||
of the function's parent context.
|
||||
of the function's parent context. A `:def` function cannot create a
|
||||
script-local variable, assigning to one that does not exist when the
|
||||
function is compiled gives |E1089|.
|
||||
|
||||
The following three scripts demonstrate these rules:
|
||||
>vim
|
||||
|
||||
+9
-1
@@ -2,7 +2,7 @@
|
||||
" You can also use this as a start for your own set of menus.
|
||||
"
|
||||
" Maintainer: The Vim Project <https://github.com/vim/vim>
|
||||
" Last Change: 2026 Jan 19
|
||||
" Last Change: 2026 Sep 28
|
||||
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
|
||||
|
||||
" Note that ":an" (short for ":anoremenu") is often used to make a menu work
|
||||
@@ -677,6 +677,8 @@ if !exists("no_buffers_menu")
|
||||
" wait with building the menu until after loading 'session' files. Makes
|
||||
" startup faster.
|
||||
let s:bmenu_wait = 1
|
||||
let s:bmenu_short = 1
|
||||
let s:bmenu_count = 0
|
||||
|
||||
" Dictionary of buffer number to name. This helps prevent problems where a
|
||||
" buffer as renamed and we didn't keep track of that.
|
||||
@@ -1008,6 +1010,12 @@ cnoremenu <script> <silent> 1.100 PopUp.Select\ &All <C-U>call <SID>SelectAll()<
|
||||
if has("spell")
|
||||
" Spell suggestions in the popup menu. Note that this will slow down the
|
||||
" appearance of the menu!
|
||||
let s:suglist = []
|
||||
let s:fromword = ''
|
||||
let s:changeitem = ''
|
||||
let s:additem = ''
|
||||
let s:ignoreitem = ''
|
||||
|
||||
def s:SpellPopup()
|
||||
if exists("s:changeitem") && s:changeitem != ''
|
||||
call s:SpellDel()
|
||||
|
||||
@@ -2083,7 +2083,6 @@ struct ufunc_S
|
||||
sctx_T uf_script_ctx; // SCTX where function was defined,
|
||||
// used for s: variables; sc_version changed
|
||||
// for :function
|
||||
int uf_script_ctx_version; // original sc_version of SCTX
|
||||
int uf_refcount; // reference count, see func_name_refcount()
|
||||
|
||||
funccall_T *uf_scoped; // l: local variables for closure
|
||||
|
||||
@@ -859,6 +859,19 @@ func Test_source_dryrun()
|
||||
call assert_true(exists('*DryrunLegacy'))
|
||||
delfunc DryrunLegacy
|
||||
|
||||
" a :def function in a legacy script can assign to a script variable
|
||||
" with "s:", although the ":let" that creates it is not executed
|
||||
let lines =<< trim END
|
||||
let s:count = 0
|
||||
def DryrunLegacyDef()
|
||||
s:count += 1
|
||||
enddef
|
||||
END
|
||||
call writefile(lines, 'Xdryrun_legacydef.vim', 'D')
|
||||
source ++dryrun Xdryrun_legacydef.vim
|
||||
call assert_true(exists('*DryrunLegacyDef'))
|
||||
delfunc DryrunLegacyDef
|
||||
|
||||
" no SourceCmd, SourcePre or SourcePost autocommand, also not for the
|
||||
" imported script
|
||||
let g:dryrun_events = ''
|
||||
|
||||
@@ -2682,6 +2682,8 @@ def Test_cannot_use_let()
|
||||
v9.CheckDefAndScriptFailure(['let a = 34'], 'E1126:', 1)
|
||||
enddef
|
||||
|
||||
let s:somevar = ''
|
||||
|
||||
def Test_unlet()
|
||||
g:somevar = 'yes'
|
||||
assert_true(exists('g:somevar'))
|
||||
@@ -3141,6 +3143,15 @@ def Test_using_s_var_in_function()
|
||||
call assert_equal(456, s:scriptlevel)
|
||||
END
|
||||
v9.CheckScriptSuccess(lines)
|
||||
|
||||
# but a :def function cannot create one
|
||||
lines =<< trim END
|
||||
def s:SomeFunc()
|
||||
s:scriptlevel = 456
|
||||
enddef
|
||||
call s:SomeFunc()
|
||||
END
|
||||
v9.CheckScriptFailure(lines, 'E1089: Unknown variable: s:scriptlevel', 1)
|
||||
enddef
|
||||
|
||||
" Test for specifying a type in assignment
|
||||
|
||||
@@ -1624,7 +1624,6 @@ lambda_function_body(
|
||||
if (!ASCII_ISUPPER(*ufunc->uf_name))
|
||||
ufunc->uf_flags |= FC_VIM9;
|
||||
ufunc->uf_script_ctx = current_sctx;
|
||||
ufunc->uf_script_ctx_version = current_sctx.sc_version;
|
||||
ufunc->uf_script_ctx.sc_lnum += sourcing_lnum_top;
|
||||
set_function_type(ufunc);
|
||||
|
||||
@@ -5739,7 +5738,6 @@ define_function(
|
||||
fp->uf_calls = 0;
|
||||
fp->uf_cleared = FALSE;
|
||||
fp->uf_script_ctx = current_sctx;
|
||||
fp->uf_script_ctx_version = current_sctx.sc_version;
|
||||
fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
|
||||
if (is_export)
|
||||
{
|
||||
|
||||
@@ -763,6 +763,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1140,
|
||||
/**/
|
||||
1139,
|
||||
/**/
|
||||
|
||||
+3
-3
@@ -1905,9 +1905,9 @@ compile_lhs_script_var(
|
||||
lhs->lhs_name);
|
||||
return FAIL;
|
||||
}
|
||||
else if (cctx->ctx_ufunc->uf_script_ctx_version == SCRIPT_VERSION_VIM9
|
||||
&& script_namespace
|
||||
&& !script_var && import == NULL)
|
||||
// With ":source ++dryrun" the ":let" that creates it was not executed.
|
||||
else if (script_namespace && !script_var && import == NULL
|
||||
&& !source_dryrun)
|
||||
{
|
||||
semsg(_(e_unknown_variable_str), lhs->lhs_name);
|
||||
return FAIL;
|
||||
|
||||
Reference in new issue
Block a user