Problem: Error E1016 is used for 2 different things
(Ivan Pešić)
Solution: Use E1000 for the environment variable error.
fixes: #21522
closes: #21538
Signed-off-by: Christian Brabandt <cb@256bit.org>
6246 lines
214 KiB
Plaintext
6246 lines
214 KiB
Plaintext
*vim9.txt* For Vim version 9.2. Last change: 2026 Oct 25
|
||
|
||
|
||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||
|
||
|
||
Vim9 script commands and expressions. *Vim9* *vim9*
|
||
|
||
Most expression help is in |eval.txt|. This file is about the new syntax and
|
||
features in Vim9 script, including more than 200 sourceable scripts.
|
||
|
||
For a short primer on Vim9 script, other resources may be helpful too, e.g.,
|
||
https://learnxinyminutes.com/vim9script/.
|
||
|
||
|
||
1. What is Vim9 script? |Vim9-script|
|
||
2. Differences |vim9-differences|
|
||
3. New style functions |fast-functions|
|
||
4. Types |vim9-types|
|
||
5. Generic functions |generic-functions|
|
||
6. Namespace, Import and Export |vim9script|
|
||
7. Classes and interfaces |vim9-classes|
|
||
8. Rationale |vim9-rationale|
|
||
|
||
|
||
------------------------------------------------------------------------------
|
||
|
||
NOTE: In this vim9.txt help file, the Vim9 script code blocks beginning with
|
||
`vim9script` (and individual lines starting with `vim9cmd`) are Vim9 script
|
||
syntax highlighted. Also, they are sourceable, meaning you can run them to
|
||
see what they output. To source them, use `:'<,'>source` (see |:source-range|),
|
||
which is done by visually selecting the line(s) with |V| and typing `:so`.
|
||
For example, try it on the following Vim9 script:
|
||
>vim9
|
||
vim9script
|
||
echowindow "Welcome to Vim9 script!"
|
||
<
|
||
There are also code examples that should not be sourced - they explain
|
||
concepts that don't require a sourceable example. Such code blocks appear
|
||
in generic code syntax highlighting, like this:
|
||
>
|
||
def ThisFunction() # script-local
|
||
def g:ThatFunction() # global
|
||
export def Function() # for import and import autoload
|
||
<
|
||
|
||
==============================================================================
|
||
|
||
|
||
1. What is Vim9 script? *Vim9-script*
|
||
|
||
Vim script has been growing over time, while preserving backwards
|
||
compatibility. That means bad choices from the past often can't be changed
|
||
and compatibility with Vi restricts possible solutions. Execution is quite
|
||
slow, each line is parsed every time it is executed.
|
||
|
||
The main goal of Vim9 script is to drastically improve performance. This is
|
||
accomplished by compiling commands into instructions that can be efficiently
|
||
executed. An increase in execution speed of 10 to 100 times can be expected.
|
||
|
||
A secondary goal is to avoid Vim-specific constructs and get closer to
|
||
commonly used programming languages, such as JavaScript, TypeScript and Java.
|
||
|
||
The performance improvements can only be achieved by not being 100% backwards
|
||
compatible. For example, making function arguments available in the "a:"
|
||
dictionary adds quite a lot of overhead. In a Vim9 function this dictionary
|
||
is not available. Other differences are more subtle, such as how errors are
|
||
handled.
|
||
|
||
Vim9 script syntax, semantics, and behavior apply in:
|
||
- a function defined with the `:def` command
|
||
- a script file where the first command is `vim9script`
|
||
- an autocommand defined in the context of the above
|
||
- a command prefixed with the `vim9cmd` command modifier
|
||
|
||
When using `:function` in a Vim9 script file the legacy syntax is used, with
|
||
the highest |scriptversion|. However, this can be confusing and is therefore
|
||
discouraged.
|
||
|
||
Vim9 script and legacy Vim script can be mixed. There is no requirement to
|
||
rewrite old scripts, they keep working as before. You may want to use a few
|
||
`:def` functions for code that needs to be fast.
|
||
|
||
:vim9[cmd] {cmd} *:vim9* *:vim9cmd*
|
||
Evaluate and execute {cmd} using Vim9 script syntax,
|
||
semantics, and behavior. Useful when typing a command,
|
||
in a `:function`, or a legacy Vim script.
|
||
|
||
The following short example shows how a legacy Vim script
|
||
command and a :vim9cmd (so Vim9 script context) may appear
|
||
similar, though may differ not just syntactically, but also
|
||
semantically and behaviorally.
|
||
>vim
|
||
call popup_notification('entrée'[5:]
|
||
\ ->str2list()->string(), #{time: 7000})
|
||
vim9cmd popup_notification('entrée'[5 :]
|
||
->str2list()->string(), {time: 7000})
|
||
<
|
||
Notes: 1. The reason for the different output is Vim9 script
|
||
uses character indexing whereas legacy Vim script uses byte
|
||
indexing - see |vim9-string-index|.
|
||
2. Syntax is different too. In Vim9 script:
|
||
- The space in "[5 :]" is mandatory (see |vim9-white-space|).
|
||
- Line continuation with "\" is not required.
|
||
- The "#" (to avoid putting quotes around dictionary keys) is
|
||
neither required nor allowed - see |#{}|.
|
||
|
||
*E1164*
|
||
`:vim9cmd` cannot stand alone; it must be followed by a command.
|
||
|
||
:leg[acy] {cmd} *:leg* *:legacy*
|
||
Evaluate and execute {cmd} using legacy Vim script syntax,
|
||
semantics, and behavior. It is only applicable in a Vim9
|
||
script or a `:def` function. Using an equivalent script to
|
||
the one, above (see its notes for why the output differs):
|
||
>vim9
|
||
vim9script
|
||
# In legacy Vim script context the popup is [769, 101]
|
||
legacy call popup_notification('entrée'[5:]
|
||
\ ->str2list()->string(), #{time: 7000})
|
||
# In Vim9 script context the popup is [101]
|
||
popup_notification('entrée'[5 :]
|
||
->str2list()->string(), {time: 7000})
|
||
<
|
||
Vim9 script's script-local variables may be used by prefixing
|
||
"s:", like in legacy Vim script. This example shows the
|
||
difference in syntax: "k" for the script-local variable in
|
||
Vim9 script, "s:k" in the legacy Vim script context.
|
||
>vim9
|
||
vim9script
|
||
var k: string = "Okay"
|
||
echo k
|
||
legacy echo s:k
|
||
<
|
||
*E1189*
|
||
Using `:legacy` is not allowed in compiled Vim9 script
|
||
control flow contexts. For example:
|
||
>vim9
|
||
vim9script
|
||
def F_1189()
|
||
if v:version == 900
|
||
# E1189: Cannot use :legacy with this command: endif
|
||
legacy endif
|
||
enddef
|
||
F_1189()
|
||
< *E1234*
|
||
`:legacy` cannot stand alone; it must be followed by a command.
|
||
|
||
|
||
==============================================================================
|
||
|
||
2. Differences from legacy Vim script *vim9-differences*
|
||
|
||
Overview ~
|
||
*E1146*
|
||
The following list is a brief summary of some key differences between legacy
|
||
Vim script and Vim9 script, including `:def` functions:
|
||
|
||
- Comments start with #, not ":
|
||
>vim9
|
||
vim9script
|
||
# Comments start with a number sign (#), not a quotation mark (")
|
||
<
|
||
- Using a backslash (i.e., \, a reverse solidus) for line continuation is
|
||
rarely needed:
|
||
>vim9
|
||
vim9script
|
||
echo $"Your $HOME directory is {$HOME}\n" ..
|
||
$"Your $VIMRUNTIME environment variable is {$VIMRUNTIME}"
|
||
<
|
||
- White space is required in many places, improving readability.
|
||
See |vim9-white-space|.
|
||
*E1126*
|
||
- Declare variables with `:var` and assign values without `:let` (which is
|
||
not allowed):
|
||
>vim9
|
||
vim9script
|
||
var count = 0
|
||
count += 3 # 3
|
||
echo count
|
||
let E1126 = count # E1126: Cannot use :let in Vim9 script
|
||
<
|
||
- Constants can be declared with `:final` and `:const`:
|
||
>vim9
|
||
vim9script
|
||
final matches = ['Peter'] # adding to this list later is okay
|
||
const NAMES = ['Paul', 'Mary'] # NAMES constant cannot be changed
|
||
matches->extend(NAMES)
|
||
echo matches # ['Peter', 'Paul', 'Mary']
|
||
<
|
||
- Variables and functions are script-local by default (see |vim9-scopes|).
|
||
|
||
- Functions are declared with argument types and return type:
|
||
>vim9
|
||
vim9script
|
||
def Cubed(n: float): string # float arg, string returned
|
||
return $"\n{n} cubed is {n->pow(3)}"
|
||
enddef
|
||
echo Cubed(input("Enter a float to cube: ")->str2float())
|
||
<
|
||
- Calling functions does not require `:call`. Although it is deprecated, using
|
||
it does not give an error:
|
||
>vim9
|
||
vim9script
|
||
popup_notification("Builtin function called WITHOUT :call", {})
|
||
call popup_notification("Builtin function called WITH :call", {})
|
||
<
|
||
*vim9-invalid-Ex-commands*
|
||
- You cannot use these Ex commands:
|
||
|
||
`:Print` (or shortened forms like `:Pr`)
|
||
`:append` (or shortened forms like `:a`)
|
||
`:change` (or shortened forms like `:c`)
|
||
`:dp`
|
||
`:insert` (or shortened forms like `:i`)
|
||
`:k`
|
||
`:mode` (or the shortened form `:mod`)
|
||
`:open` (or shortened forms like `:o`)
|
||
`:sce` `:scg` `:sci` `:scI` `:scl` `:scn` `:scp`
|
||
`:sg` `:sgc` `:sge` `:sgi` `:sgI` `:sgl` `:sgn` `:sgp` `:sgr`
|
||
`:sic` `:sie` `:siI` `:sin` `:sip` `:sir`
|
||
`:sI` `:sIc` `:sIe` `:sIg` `:sIi` `:sIl` `:sIn` `:sIp` `:sIr`
|
||
`:src` `:srg` `:sri` `:srI` `:srl` `:srn` `:srp`
|
||
`:t`
|
||
`:xit` (or shortened forms `:x` and `:xi`)
|
||
|
||
Note: There are some commands that do not give errors but behave
|
||
differently in Vim9 script. One is `:dl`, which is an abbreviation of
|
||
`:dlist` (whereas it is `:delete` in legacy Vim script). Others are
|
||
`:sc`, `:si`, and `:sr`, which in legacy Vim script are short substitute
|
||
commands (like the 38 others listed above). However, in Vim9 script
|
||
they are shortened forms of `:scriptnames`, `:simalt`, and `:srewind`
|
||
respectively.
|
||
*vim9-no-shorten* *E1065*
|
||
- Many commands cannot be shortened. Trying to use a shortened command will
|
||
give E1065. Specifically, these commands' full names must be used:
|
||
|
||
- Flow control commands: `:break`, `:catch`, `:continue`, `:else`, `:elseif`, `:endfor`,
|
||
`:endif`, `:endtry`, `:endwhile`, `:finally`, `:finish`, `:return`, `:throw`, `:while`
|
||
- Declaration keywords: `:abstract`, `:class`, `:const`, `:def`, `:endclass`, `:enddef`,
|
||
`:endenum`, `:endinterface`, `:enum`, `:export`, `:final`, `:import`, `:interface`,
|
||
`:public`, `:static`, `:this`, `:type`, `:var`
|
||
|
||
- `:final` cannot be used as a shortened form of `:finally`. That is because
|
||
it means |:final| in Vim9 script. By itself, `:final` lists all variables,
|
||
the same as a bare `:let` in legacy Vim script.
|
||
|
||
- You cannot use |curly-braces-names| like `my_{&background}_message`.
|
||
|
||
- A range before a command must be prefixed with a colon, for example:
|
||
>vim9
|
||
vim9script
|
||
# The following line displays lines containing the word 'colon'
|
||
:-3,+2g/colon
|
||
# The following line gives E1050: colon required before a range
|
||
-3,+2g/colon
|
||
<
|
||
- Executing a register with "@" doesn't work; prepend either a colon or
|
||
use `:execute`:
|
||
>vim9
|
||
vim9script
|
||
@a = 'echo "Works"'
|
||
:@a # Works
|
||
execute @a # Works
|
||
@a # E1207: Expression without an effect @a
|
||
<
|
||
- Unless mentioned specifically, the highest |scriptversion| is used.
|
||
|
||
- When defining an expression mapping, the expression will be evaluated in
|
||
the context of the script where it was defined.
|
||
|
||
- When indexing a string, the index is counted in characters, not bytes.
|
||
For implications and examples, see |vim9-string-index|.
|
||
|
||
- There are some possibly unexpected differences - see |vim9-gotchas|.
|
||
|
||
|
||
Comments starting with # ~
|
||
*vim9-comments*
|
||
In legacy Vim script, comments start with a quotation mark ("). In Vim9
|
||
script, comments start with a number sign (#).
|
||
>vim9
|
||
vim9script
|
||
# This is a comment
|
||
'Vim9 script uses #, not "'->popup_notification({}) # Another comment
|
||
<
|
||
The reason is that a quotation mark can also start a string. In many places,
|
||
especially halfway through an expression with a line break, it is hard to tell
|
||
what the meaning is because both a string and a comment can be followed by
|
||
arbitrary text. To avoid confusion, only # comments are recognized. This is
|
||
the same as in shell scripts and Python.
|
||
|
||
In Vi, # is a command to list text with numbers. In Vim9 script, `:number`
|
||
may be used. For example, the following one-line script prints this
|
||
paragraph.
|
||
>vim9
|
||
vim9cmd :-4,-2number
|
||
<
|
||
To improve readability, there must be at least one space or tab between a
|
||
command and the # starting a comment. With 'syntax' set, syntax highlighting
|
||
also helps to show where an invalid comment is used. For example:
|
||
>vim9
|
||
vim9script
|
||
const OKAY: bool = true # A valid comment (white space before the #)
|
||
echo OKAY # true
|
||
try
|
||
const NO: bool = false# THIS IS AN INVALID COMMENT!
|
||
catch
|
||
echo v:exception # E121: Undefined variable: false#
|
||
endtry
|
||
< *E1170*
|
||
You cannot not start a comment with `#{` - it looks like the legacy dictionary
|
||
literal and produces an error where this might be confusing, for example:
|
||
>vim9
|
||
vim9script
|
||
#{ E1170: Cannot use #{ to start a comment:
|
||
<
|
||
A |fold-marker| (i.e., `#{{{`) used to start a fold, is still okay.
|
||
|
||
When reading a script, Vim doesn't know whether it is |Vim9| script before
|
||
finding the `vim9script` command. Before it, comments must use legacy
|
||
syntax, so a quotation mark:
|
||
>vim9
|
||
" legacy Vim script comment before vim9script
|
||
vim9script
|
||
# Vim9 script comment
|
||
<
|
||
That looks ugly (especially with the syntax highlighting not treating the
|
||
initial Vim script comment in Comment highlight group as a command).
|
||
It is better having the `vim9script` command as the first line:
|
||
>vim9
|
||
vim9script
|
||
# Vim9 script comment
|
||
<
|
||
In legacy Vim script, # is also used for the alternate file name. In Vim9
|
||
script you must use %% instead. Instead of ##, use %%%, which stands for
|
||
all arguments. See |c_%%| and |c_%%%#|.
|
||
|
||
|
||
Vim9 functions ~
|
||
*vim9-functions*
|
||
|
||
A function defined with `:def` is compiled. Execution is many times faster,
|
||
often 10 to 100 times.
|
||
|
||
Many errors are already found when compiling, before the function is executed.
|
||
The syntax is strict, to enforce code that is easy to read and understand.
|
||
|
||
Compilation is done when any of these is encountered:
|
||
- the first time the function is called
|
||
- when the `:defcompile` command is encountered in the script after the
|
||
function was defined
|
||
- `:disassemble` is used for the function, or
|
||
- a function that is compiled calls the function or uses it as a function
|
||
reference (so that the argument and return types can be checked).
|
||
|
||
*E1099*
|
||
"Unknown error", E1099, may occur while executing. If reproducible, it may be
|
||
reported at https://github.com/vim/vim/issues as it is now likely to be a
|
||
rare, unhandled error worth reporting.
|
||
*E1091*
|
||
If compilation fails it is not tried again on the next call, instead this
|
||
error is given: "E1091: Function is not compiled: {name}". For example:
|
||
>vim9
|
||
vim9script
|
||
def g:Broken(): string
|
||
echo 'a' .. []
|
||
enddef
|
||
g:Broken() # E1105: Cannot convert list to string
|
||
<
|
||
Compilation failed. Now, attempting to call "g:Broken" gives E1091:
|
||
>vim9
|
||
vim9cmd g:Broken() # E1091: Function is not compiled: Broken
|
||
<
|
||
*E1191*
|
||
Trying to call a function which failed to compile may also return:
|
||
"E1191: Call to function that failed to compile". For example:
|
||
>vim9
|
||
vim9script
|
||
def Set_hlsearch()
|
||
&hlsearch = 9 # NB: 'hlsearch' is bool, so would give E1012
|
||
enddef
|
||
def D_Set_hlsearch()
|
||
Set_hlsearch()
|
||
enddef
|
||
# With 'silent!', this call does not abort, despite the E1012 error
|
||
silent! Set_hlsearch()
|
||
# Trying to use the function that failed to compile gives E1191:
|
||
D_Set_hlsearch() # E1191: Call to function that failed to compile
|
||
<
|
||
Compilation will fail when attempting to execute a user command that
|
||
does not exist at either compile or execution time. So, this fails:
|
||
>vim9
|
||
vim9cmd YeahNah() # E117: Unknown function: YeahNah
|
||
<
|
||
However, a user command contained in a function, which itself is not yet
|
||
compiled, does not fail. For example, this script can be sourced
|
||
without error even though neither command in either of the functions exists:
|
||
>vim9
|
||
vim9script
|
||
def g:Late1()
|
||
DefinedLate1
|
||
enddef
|
||
def g:Late2()
|
||
execute('DefinedLate2')
|
||
enddef
|
||
<
|
||
Further, if the functions, above, are subsequently called before the commands
|
||
DefinedLate1 and DefinedLate2 exist, compilation fails. So, after sourcing
|
||
the script, above, when the following script is sourced, either |E476| or
|
||
|E492| are given:
|
||
>vim9
|
||
vim9cmd g:Late1() # E476: Invalid command: DefinedLate1
|
||
vim9cmd g:Late2() # E492: Not an editor command: DefinedLate2
|
||
<
|
||
Although those calls fail, and for different reasons, the point is that any
|
||
commands must exist when they are executed. So, the following script works
|
||
because the commands are defined prior to being executed (Note: The script
|
||
defining the two global functions must be re-sourced before sourcing this
|
||
script):
|
||
>vim9
|
||
vim9script
|
||
command DefinedLate1 @a = "One"
|
||
command DefinedLate2 @b = "Two"
|
||
g:Late1()
|
||
g:Late2()
|
||
echo (@a, @b) # ('One', 'Two')
|
||
<
|
||
A `:def` function has no options like `:function` (i.e., "abort", "range",
|
||
"closure", or "dict"). A `:def` function:
|
||
- always aborts on an error unless the error is caught by a `:try` block or
|
||
`:silent!` is used calling the function (for an example, see the first
|
||
script under |E1191|),
|
||
- may not have a range passed,
|
||
- automatically supports closures (for an example, see |vim9-closure|), and
|
||
- cannot be a "dict" function (see |vim9-no-dict-function|, which follows).
|
||
|
||
*vim9-no-dict-function*
|
||
Neither the "dict" option nor "dict" functions are supported in a `:def`
|
||
function. To illustrate, first consider this working legacy Vim script:
|
||
>vim
|
||
" legacy Vim script dict function
|
||
let dic = {'ln': [0, 1, 2, 3]}
|
||
function! dic.Len() dict
|
||
return len(self.ln)
|
||
endfunction
|
||
echo dic.Len() | " 4
|
||
< *E1182*
|
||
In Vim9 script, trying to do something similar gives E1182:
|
||
>vim9
|
||
vim9script
|
||
var dic: dict<list<number>> = {'ln': [0, 1, 2, 3]}
|
||
def dic.len(): number # E1182: Cannot define a dict function in Vim9…
|
||
return len(self.ln)
|
||
enddef
|
||
<
|
||
Instead of using a "dict" function, in Vim9 script, there are a few options:
|
||
|
||
- Use a Vim9 Class (see |Vim9-class|):
|
||
>vim9
|
||
vim9script
|
||
class MyClass
|
||
var ln: list<number>
|
||
def Len(): number
|
||
return this.ln->len()
|
||
enddef
|
||
endclass
|
||
var obj = MyClass.new([0, 1, 2, 3])
|
||
echo obj.Len() # 4
|
||
<
|
||
- Pass the dictionary explicitly:
|
||
>vim9
|
||
vim9script
|
||
def DicLen(self: dict<any>, arg: string): number
|
||
return self[arg]->len()
|
||
enddef
|
||
var da: dict<any> = {func: DicLen, item: [0, 1, 2, 3]}
|
||
echo da.func(da, 'item') # 4
|
||
<
|
||
- Call a legacy Vim script dict function (|Dictionary-function|):
|
||
>vim9
|
||
vim9script
|
||
function LegDicLen() dict
|
||
return self.item->len()
|
||
endfunc
|
||
def CallLegDicLen(): number
|
||
var da: dict<any> = {func: LegDicLen, item: [0, 1, 2, 3]}
|
||
return da.func()
|
||
enddef
|
||
echo CallLegDicLen() # 4
|
||
<
|
||
The argument types and return type need to be specified and match - various
|
||
errors may occur when they do not. See |fast-functions| and |type-checking|.
|
||
|
||
*vim9-any-type*
|
||
The "any" type can be used, type checking will then be done at runtime, like
|
||
with legacy functions, and allows the type to change. For example:
|
||
>vim9
|
||
vim9script
|
||
var avar: any
|
||
echo avar->typename() # number
|
||
avar = 'A string'
|
||
echo avar->typename() # string
|
||
avar = [['A', 'list'], ['of', 'lists']]
|
||
echo avar->typename() # list<list<string>>
|
||
<
|
||
Warning: The "any" type defaults to the number type and 0. Operations
|
||
demanding a specific type may produce unexpected results or errors if
|
||
that is not factored. Further, unlike a variable explicitly declared
|
||
as a number, direct assignment of any other type means the variable's
|
||
type is re-inferred, which can occur multiple times at runtime. For
|
||
example:
|
||
>vim9
|
||
vim9script
|
||
var n1: any
|
||
echo n1->typename() # number
|
||
n1 ..= 'zero'
|
||
echo n1 # 0zero (that is, the default 0 and "zero")
|
||
echo n1->typename() # string
|
||
n1 = true
|
||
echo n1->typename() # bool
|
||
var n2: any
|
||
n2->extend(['item']) # E712: Argument of extend() must be a List o…
|
||
<
|
||
Arguments are accessed by name, without "a:", just like any other language.
|
||
There is no "a:" dictionary or "a:000" list. This example shows not only "a:"
|
||
but also valid extreme white space minimization in a legacy Vim script:
|
||
>vim
|
||
function! MyFirst(s,d,n,...)
|
||
return a:s.a:d[a:n].a:000[1]
|
||
endfunction
|
||
let MyDict={1:'one',2:'two'}
|
||
echo MyFirst('The value of key 2 of MyDict is ',
|
||
\ MyDict,2,v:null,'!',9999)
|
||
<
|
||
The equivalent, in Vim9 script:
|
||
>vim9
|
||
vim9script
|
||
def MyFirst(s: string, d: dict<string>, n: number,
|
||
...l: list<any>): string
|
||
return s .. d[n] .. l[1]
|
||
enddef
|
||
var MyDict: dict<string> = {1: 'one', 2: 'two'}
|
||
echo MyFirst('The value of key 2 of MyDict is ',
|
||
MyDict, 2, null, '!', 9999)
|
||
<
|
||
Note: In this Vim9 script, an error would occur if omitting spaces:
|
||
- after any of the commas
|
||
- after any of the colons in 'key: value' or 'variable: type'
|
||
- before/after instances of '..', and
|
||
- before/after '='.
|
||
*vim9-variable-arguments*
|
||
The previous example shows variable arguments ("...l: list<any>") defined as
|
||
the last argument. In Vim9 script, variable arguments require a name and
|
||
list<type>, similar to TypeScript. This example iterates a variadic list
|
||
of numbers:
|
||
>vim9
|
||
vim9script
|
||
def MyProduct(...ln: list<number>): number
|
||
var prod: number = 1
|
||
for num in ln
|
||
prod *= num
|
||
endfor
|
||
return prod
|
||
enddef
|
||
echo MyProduct(10, 10) # 100
|
||
echo MyProduct(5, 4, 5) # 100
|
||
<
|
||
Errors may occur when variadic arguments are not declared or passed correctly:
|
||
|
||
*E1055*
|
||
- Failing to provide the name to a variable argument:
|
||
>vim9
|
||
vim9script
|
||
def F1055(n: number, ...): void # E1055: Missing name after ...
|
||
enddef
|
||
< *E1160*
|
||
- Trying to use a default for a variable argument:
|
||
>vim9
|
||
vim9script
|
||
def F1160(...l = []): void # E1160: Cannot use a default for variabl…
|
||
enddef
|
||
< *E1180*
|
||
- Failing to declare a variable argument as a list<type>:
|
||
>vim9
|
||
vim9script
|
||
def F1180(...l: string): void # E1180: Variable arguments type must…
|
||
enddef
|
||
<
|
||
When a function argument is optional (that is, it has a default value),
|
||
passing `v:none` as the argument results in using the default value. This is
|
||
useful when you want to specify a value for an argument that comes after an
|
||
argument that should use its default value. For example:
|
||
>vim9
|
||
vim9script
|
||
def F(pi: float = 3.14, ra: float = 1.0): float
|
||
return pi * ra->pow(2)
|
||
enddef
|
||
echo F(v:none, 2.0) # 12.56 (using default 'pi' value, 3.14)
|
||
<
|
||
*E1106*
|
||
When too many arguments are passed, either an |E176| or E1106 error occurs.
|
||
An E1106 example:
|
||
>vim9
|
||
vim9script
|
||
var ln: list<number> = [1, 2, 4, 8]
|
||
foreach(ln, (val) => {
|
||
echo val
|
||
}) # E1106: One argument too many (the Lambda has only one argument)
|
||
<
|
||
Note: `foreach()` passes two arguments to its callback: for a list,
|
||
they are the list's index and value. Vim9 lambdas must have both
|
||
(technically, "matching arity"), even when only one appears to be
|
||
required. So, "(val)" should be "(_, val)", explained below.
|
||
|
||
*vim9-ignored-argument*
|
||
The argument "_" (an underscore) can be used to ignore the argument. This is
|
||
most useful in callbacks where you don't need it, but do need to give an
|
||
argument to match the call. For example, when using |map()| with a list, two
|
||
arguments are passed, the index and the value. The following script
|
||
first demonstrates ignoring the indexes, then ignoring the values:
|
||
>vim9
|
||
vim9script
|
||
final nl: list<number> = [1, 2, 4, 8]
|
||
map(nl, (_, val) => val * 2) # '_' ignores the indexes
|
||
echo nl # [2, 4, 8, 16]
|
||
map(nl, (idx, _) => idx * 2) # '_' ignores the values
|
||
echo nl # [0, 2, 4, 6]
|
||
<
|
||
The "_" argument can be used multiple times, and no type is needed.
|
||
>vim9
|
||
vim9script
|
||
var count: number
|
||
def In20s(_, _, year: number): void
|
||
count += year >= 2020 ? 1 : 0
|
||
enddef
|
||
var data = [['Bo', 'A', 2026], ['Mo', 'B', 2018], ['Jo', 'A', 2022]]
|
||
for staff in data
|
||
In20s->call(staff)
|
||
endfor
|
||
echo $"There are {count} staff members in the 2020s."
|
||
<
|
||
Note: This script uses |call()|, not to be confused with |:call|.
|
||
|
||
*E1181*
|
||
Using "_" in a disallowed context gives error E1181. For example:
|
||
>vim9
|
||
vim9script
|
||
def F1181(_): string
|
||
return _ # E1181: Cannot use an underscore here
|
||
enddef
|
||
echo F1181("No")
|
||
<
|
||
|
||
Functions and variables are script-local by default ~
|
||
*vim9-scopes*
|
||
When using `:function` or `:def` to specify a new function at the script level
|
||
in a Vim9 script, the function is local to the script (like prefixing "s:" in
|
||
legacy Vim script). To define a global function or variable, the "g:" prefix
|
||
must be used. For functions in a script that is to be imported, and in an
|
||
autoload script, `:export` needs to be used for those to be used elsewhere.
|
||
>
|
||
def ThisFunction() # script-local
|
||
def g:ThatFunction() # global
|
||
export def Function() # for import and import autoload
|
||
<
|
||
*E1075*
|
||
Using "s:" (like in a legacy Vim script function), is not allowed. If used in
|
||
a script level `:def` function, |E1268| is given, and, if used in a nested `:def`
|
||
function, E1075. For example:
|
||
>vim9
|
||
vim9script
|
||
def F1075(): void
|
||
def s:Inner() # E1075: Namespace not supported: s:Inner()
|
||
enddef
|
||
enddef
|
||
F1075()
|
||
<
|
||
When using `:function` or `:def` to specify a nested function inside a `:def`
|
||
function and no namespace was given, the nested function is local to the code
|
||
block it is defined in. It cannot be used in a `function()` with a string
|
||
argument. Instead, pass the function reference itself:
|
||
>vim9
|
||
vim9script
|
||
def Outer(): void
|
||
def Inner(): string
|
||
return 'Inner() successfully called'
|
||
enddef
|
||
var Okay = function(Inner)
|
||
echo Okay() # Inner() successfully called
|
||
try
|
||
var Bad = function("Inner")
|
||
catch
|
||
echo v:exception # Vim:E700: Unknown function: Inner
|
||
endtry
|
||
enddef
|
||
Outer()
|
||
<
|
||
Note: Passing the string argument fails because "Inner" becomes a
|
||
function reference to a function with a generated internal name,
|
||
which could be shown with "funcref(Inner)".
|
||
|
||
It is not possible to define a script-local function in a function. You can
|
||
define a local function and assign it to a script-local |Funcref|, though it
|
||
must first have been declared at the script level.
|
||
>vim9
|
||
vim9script
|
||
var ScriptLocalFuncref: func
|
||
def Outer(): void
|
||
def Inner(): string
|
||
return "Hi from ScriptLocalFuncref!"
|
||
enddef
|
||
ScriptLocalFuncref = Inner
|
||
enddef
|
||
Outer()
|
||
echo ScriptLocalFuncref()
|
||
<
|
||
When referring to an unprefixed function (i.e., without either a "g:" or "s:"
|
||
prefix), Vim will search for the function in the function scope, in block
|
||
scopes, and in the script scope.
|
||
|
||
Imported functions are found with the prefix from the `:import` command.
|
||
Exporting and importing is explained at |vim9-import|.
|
||
|
||
In Vim9 script, a script-local function reference must start with an uppercase
|
||
letter. Consequently, even in scenarios where "s:" is required (within legacy
|
||
Vim script scopes), "s:Funcref" must be used, avoiding potential ambiguity
|
||
with builtin functions. Consider the following legacy Vim script and Vim9
|
||
script examples.
|
||
|
||
- First, legacy Vim script, showing that "s:" may be used in a legacy Vim
|
||
script to define a function, which would otherwise interfere with a
|
||
builtin function (in this instance, the builtin, |cos()|):
|
||
>vim
|
||
function! s:cos()
|
||
echo 's:cos() works'
|
||
endfunction
|
||
call s:cos() " s:cos() works
|
||
<
|
||
- Second, Vim9 scripts, showing that legacy functions:
|
||
1. Must start with an uppercase letter (otherwise |E1267| is given),
|
||
2. Cannot be defined with "s:" in a Vim9 script-local scope (|E1268|), and
|
||
3. Within the legacy scope of a function, "s:" is required but, for the
|
||
reasons, above, must be "s:" and an uppercase letter (|E117|):
|
||
>vim9
|
||
vim9script
|
||
execute ('function cos()') # E1267: Function name must start with a …
|
||
< >vim9
|
||
vim9script
|
||
execute ('function s:cos()') # E1268: Cannot use s: in Vim9 script: …
|
||
< >vim9
|
||
vim9script
|
||
function Cos()
|
||
echo 'This works: now we are in Cos().'
|
||
endfunction
|
||
function Call_Cos()
|
||
call s:Cos() " Requires the 's:'
|
||
call Cos() " E117: Unknown function: Cos
|
||
endfunction
|
||
Call_Cos()
|
||
< *vim9-s-namespace* *E1268*
|
||
Within a Vim9 script's `:def` function, "s:" cannot be used. Compare these
|
||
two scripts: first, legacy Vim script where "s:" may be used in a `:def` then,
|
||
second, Vim9 script where E1268 is given:
|
||
>vim
|
||
" Legacy Vim script - s: working in a :def, though it is optional
|
||
let s:leg = 'okay'
|
||
def Ok_def_s(): void
|
||
# Note that either leg or s:leg are valid here
|
||
echo leg .. ', ' .. s:leg # okay, okay
|
||
enddef
|
||
call Ok_def_s()
|
||
< >vim9
|
||
vim9script
|
||
var nine: string
|
||
def F1268(): void
|
||
echo s:nine # E1268: Cannot use s: in Vim9 script: s:nine
|
||
enddef
|
||
F1268()
|
||
<
|
||
The use of the "s:" prefix is not supported in the Vim9 script-local scope.
|
||
Functions and variables without a prefix are always script-local. This
|
||
includes calling legacy functions:
|
||
>vim9
|
||
vim9script
|
||
function ScriptLevel(str)
|
||
echo a:str
|
||
endfunction
|
||
ScriptLevel('okay')
|
||
# The following gives E1268: Cannot use s: in Vim9 script
|
||
s:ScriptLevel('not okay')
|
||
<
|
||
Within legacy functions, using "s:" for script-local variables is always
|
||
required:
|
||
>vim9
|
||
vim9script
|
||
var local: string = 'script-local :var'
|
||
function Legacy_requires_s()
|
||
echo s:local
|
||
" Without 's:', 'local' gives E121:
|
||
echo local
|
||
endfunction
|
||
Legacy_requires_s()
|
||
<
|
||
In all cases the function must be defined before it is used. That is, either
|
||
when it is called explicitly (including if `:defcompile` causes it to be
|
||
compiled), or when code that calls it is being compiled, inferring the return
|
||
type.
|
||
|
||
The result is that functions and variables without a namespace can usually be
|
||
found in the script, either defined there or imported. Global functions and
|
||
variables could be defined anywhere. (Good luck finding out where! You can
|
||
often see where it was last set using |:verbose|).
|
||
|
||
|
||
Deleting functions in a Vim9 script ~
|
||
*E1084*
|
||
In Vim9 script, script-local functions (either `:function` or `:def`) are
|
||
defined once when the script is sourced and cannot be deleted or replaced by
|
||
the script itself:
|
||
>vim9
|
||
vim9script
|
||
def MyDef(): void
|
||
enddef
|
||
delfunction MyDef # E1084: Cannot delete Vim9 script function MyDef
|
||
<
|
||
Note: A script-local function may be replaced by reloading the script.
|
||
See |vim9-reload|.
|
||
|
||
Global functions ("g:" prefixed) can still be defined and deleted at nearly
|
||
any time, though deleting a global function in Vim9 script has a distinction
|
||
between |function()| and |funcref()|. When a global function is deleted and
|
||
redefined, the replacement global function is updated dynamically in any
|
||
variables calling it whereas the function reference is persistent (unless the
|
||
variable itself is redeclared). This is an important distinction, differing
|
||
from legacy Vim script behavior, where deleting a function deletes the funcref
|
||
too. To illustrate:
|
||
>vim9
|
||
vim9script
|
||
def g:F(): string
|
||
return 'one'
|
||
enddef
|
||
var Function = function(g:F)
|
||
var Funcref = funcref(g:F)
|
||
echo (Function(), Funcref()) # ('one', 'one')
|
||
delfunction g:F
|
||
def g:F(): string
|
||
return 'two'
|
||
enddef
|
||
echo (Function(), Funcref()) # ('two', 'one')
|
||
Funcref = funcref(g:F) # Redeclare Funcref
|
||
echo (Function(), Funcref()) # ('two', 'two')
|
||
<
|
||
Note: This persistent behavior of |funcref()| may be regarded as a
|
||
feature, or may be unexpected if not understood.
|
||
|
||
When compiling a function, and a function call is encountered for a function
|
||
that is not (yet) defined, the |FuncUndefined| autocommand is not triggered.
|
||
You can use an autoload function if needed, or call a legacy function and have
|
||
|FuncUndefined| triggered there.
|
||
|
||
|
||
Reloading a Vim9 script clears functions and variables by default ~
|
||
*vim9-reload* *vim9-noclear*
|
||
When loading a legacy Vim script a second or subsequent time, nothing is
|
||
removed. Commands will replace existing variables and functions, create new
|
||
ones, and leave removed things hanging around.
|
||
|
||
When loading a Vim9 script a second or subsequent time, the default is that
|
||
all existing script-local functions and variables are deleted. So, you start
|
||
with a clean slate. This is useful if you are developing a plugin and want to
|
||
try a new version. If you renamed something you don't have to worry about the
|
||
old name persisting.
|
||
|
||
The exported functions and variables of an autoload script live in the global
|
||
namespace with the autoload prefix. When such a script is sourced again they
|
||
are likewise given a clean slate, so an autoload script can be sourced more
|
||
than once. A class or enum in an autoload script is an exception: it cannot
|
||
be redefined this way and |E1041| is given, because objects created from the
|
||
previous definition would keep referring to it. Restart Vim to load a changed
|
||
class or enum in an autoload script. In other scripts a class or enum is
|
||
cleared like everything else, so it can be redefined.
|
||
|
||
If you do want to keep script-local functions and variables, use "noclear".
|
||
To illustrate, source this script a few times; every time it is sourced,
|
||
another 9 is added to MyList:
|
||
>vim9
|
||
vim9script noclear
|
||
var MyList: list<number> = !exists('MyList') ? [9] : MyList->add(9)
|
||
echo MyList
|
||
<
|
||
You want to use this in scripts that use a `finish` command to bail out at
|
||
some point when loaded again. For example, when a buffer local option is set
|
||
to a function, the function does not need to be defined more than once:
|
||
>vim9
|
||
vim9script noclear
|
||
# 'We are in SomeFunc()' is echoed only the first time this script is
|
||
# sourced. Subsequent times, you are told, 'SomeFunc exists already'.
|
||
setlocal completefunc=SomeFunc
|
||
if exists('*SomeFunc')
|
||
popup_notification('SomeFunc() exists already', {time: 4000})
|
||
finish
|
||
endif
|
||
def SomeFunc()
|
||
popup_notification('We are in SomeFunc()', {time: 4000})
|
||
enddef
|
||
SomeFunc()
|
||
< *E1149*
|
||
An important consequence of cleared script-local functions and variables is
|
||
that idiomatic practices, such as finishing when "g:loaded_{plugin_name}"
|
||
exists, means it's important to remember that variables and functions do not
|
||
persist. To illustrate, source the following Vim9 script. Then source it
|
||
again. The first time in it will echo "Okay...." The second/subsequent times
|
||
it gives an E1149 error:
|
||
>vim9
|
||
vim9script
|
||
:+7,+15source
|
||
echo g:GetName()
|
||
# The second and subsequent times it is sourced, this error is given:
|
||
# E1149: Script variable is invalid after reload in function GetName
|
||
< >
|
||
" DO NOT SOURCE THIS SCRIPT: TO FOLLOW THIS DEMO, SOURCE THE ONE ABOVE
|
||
vim9script
|
||
if exists('g:loaded_E1149')
|
||
finish
|
||
endif
|
||
g:loaded_E1149 = true
|
||
var name: string = 'Okay (declared only the first time sourced!)'
|
||
def g:GetName(): string
|
||
return name
|
||
enddef
|
||
<
|
||
Note: If you want to re-run this, use `:unlet` g:loaded_E1149 to
|
||
remove the global variable.
|
||
|
||
*E1150*
|
||
Variables' types are not cleared when using `noclear` (|vim9-noclear|). If you
|
||
try to change a variable's type, E1150 may be given. For example, if you
|
||
source the following script it echoes "string". If you then source the second
|
||
script it gives E1150:
|
||
>vim9
|
||
vim9script
|
||
var my_var: string = "string"
|
||
def g:Get_myvar(): void
|
||
echo my_var
|
||
enddef
|
||
g:Get_myvar()
|
||
< >vim9
|
||
vim9script noclear
|
||
my_var = false # This changes my_var to a bool
|
||
echo my_var # false
|
||
echo my_var->typename() # bool
|
||
g:Get_myvar() # E1150: Script variable type changed
|
||
<
|
||
*E1190*
|
||
When reloading with `noclear`, compiled function calls are preserved. If a
|
||
called function's signature changes, argument mismatches will occur. For
|
||
example, when sourced for the first time, the following script echoes "9".
|
||
However, when sourced subsequently, the changed function signature change
|
||
means there is one argument too few and E1190 is given:
|
||
>vim9
|
||
vim9script noclear
|
||
if !exists('g:loaded_E1190')
|
||
def Echo(n: number): void
|
||
echo n
|
||
enddef
|
||
def CallEcho(n: number): void
|
||
Echo(n)
|
||
enddef
|
||
else
|
||
# Redefine Echo() with a second argument
|
||
def Echo(n: number, x: number)
|
||
echo n * x
|
||
enddef
|
||
endif
|
||
CallEcho(9) # First time, 9. Second, E1190: One argument too few.
|
||
g:loaded_E1190 = true
|
||
<
|
||
|
||
Variable declarations with :var, :final, and :const ~
|
||
*vim9-declaration* *:var*
|
||
Local variables need to be declared with `:var`. Local constants need to be
|
||
declared with either `:final` or `:const`. Collectively they are referred to
|
||
as "variables" in this section. Shortening `:var`, `:final`, or `:const` is not
|
||
allowed - see |vim9-no-shorten|.
|
||
|
||
Variables can be local to a script, function, or code block. The following
|
||
example demonstrates all three:
|
||
>vim9
|
||
vim9script
|
||
var s: string = 'script'
|
||
def Func(): void
|
||
var f: string = 'function'
|
||
{
|
||
var b: string = 'block'
|
||
echo $"Visible: {s}, {f}, and {b}"
|
||
}
|
||
echo $"Visible: {s} and {f}" # b: invisible outside the {...} block
|
||
enddef
|
||
Func()
|
||
echo $"Visible: {s} only" # f and b: invisible outside the :def
|
||
<
|
||
Note: See |vim9-exists()| for limitations in determining whether a
|
||
variable exists.
|
||
*vim9-block*
|
||
Variables are only visible in the block where they are defined, including any
|
||
nested blocks. Once the block ends the variable is no longer accessible and,
|
||
if you try to use it, gives E121. So, for example, to intentionally "hide" a
|
||
variable from code which follows it, a block may be used to make the variable
|
||
only accessible within the block's scope:
|
||
>vim9
|
||
vim9script
|
||
{
|
||
var inblock: string = 'inblock is not visible outside the block!'
|
||
}
|
||
echo inblock # E121: Undefined variable: inblock
|
||
<
|
||
For the variable "inblock" to be visible, it needs to be declared earlier:
|
||
>vim9
|
||
vim9script
|
||
var inblock: string
|
||
{
|
||
inblock = 'when defined earlier, inblock is visible outside'
|
||
}
|
||
echo inblock
|
||
<
|
||
A block is especially useful in a user command (see also |command-block|). In
|
||
the following example, the SAVE constant is assigned the value of the unnamed
|
||
register and later it is used to revert the unnamed register. The SAVE
|
||
constant is invisible outside the user command:
|
||
>vim9
|
||
vim9script
|
||
# Create the YankHelpGrep command (to :helpgrep a visual selection)
|
||
command -register YankHelpGrep {
|
||
const SAVE = @"
|
||
normal! y
|
||
execute $"helpgrep {getreg('<register>')}"
|
||
@" = SAVE
|
||
copen
|
||
}
|
||
# Map YH in Visual mode to YankLhelpGrep
|
||
xnoremap YH <ScriptCmd>YankHelpGrep "<CR>
|
||
<
|
||
A block can also be useful with autocommands (see |:autocmd-block|).
|
||
|
||
Although a block may be useful and terse, using a `:def` function works better
|
||
in many instances, and often is more readable.
|
||
*E1025* *E1128*
|
||
A block with a missing left curly bracket may give E1025 or E1128. Examples:
|
||
>vim9
|
||
vim9script
|
||
def F1025()
|
||
}
|
||
enddef
|
||
defcompile F1025 # E1025: Using } outside of a block scope
|
||
<and >vim9
|
||
vim9cmd } # E1128: } without {
|
||
<
|
||
Errors given when incorrectly declaring or initializing a variable include:
|
||
|
||
*E1017* >vim9
|
||
vim9script
|
||
var F1017: func = (): void => {
|
||
var a: any
|
||
var a = 'error' # E1017: Variable already declared: a
|
||
}
|
||
< *E1020* >vim9
|
||
vim9script
|
||
var x += 4 # E1020: Cannot use an operator on a new variable: x += 4
|
||
<
|
||
*E1022* >vim9
|
||
vim9script
|
||
var x # E1022: Type or initialization required
|
||
<
|
||
*E1034* >vim9
|
||
vim9script
|
||
var this = 'reserved' # E1034: Cannot use reserved name this
|
||
<
|
||
*E1054* >vim9
|
||
vim9script
|
||
var x: bool
|
||
var F1054: func = (): void => {
|
||
var x = 'no' # E1054: Variable already declared in the script: x
|
||
}
|
||
< *E1087* >vim9
|
||
vim9script
|
||
var F1087: func = (): void => {
|
||
var x.y = 0 # E1087: Cannot use an index when declaring a variable
|
||
}
|
||
< *E1124* >vim9
|
||
vim9script
|
||
function F1124()
|
||
var x = 'E1124: ":var" cannot be used in legacy Vim script'
|
||
endfunction
|
||
F1124()
|
||
< *E1079*
|
||
A variable cannot be declared in Command-line mode or Ex mode. For example:
|
||
>vim9
|
||
vim9script
|
||
feedkeys(":vim9cmd var x = 0\<CR>")
|
||
# E1079: Cannot declare a variable on the command line
|
||
<
|
||
Declaring a variable with a type, but without an initializer, defaults to:
|
||
- `false` for bool
|
||
- 0 for number and "any"
|
||
- 0.0 for float, and
|
||
- empty (for all other types that can be declared without an initializer).
|
||
To illustrate:
|
||
>vim9
|
||
vim9script
|
||
var a: any | var b: bool | var n: number | var f: float
|
||
echo [a, b, f, n] # [0, false, 0.0, 0]
|
||
var s: string | var F: func | var l: list<any> | var d: dict<any> |
|
||
\ var j: job | var c: channel | var z: blob | var t: tuple<any>
|
||
echo empty(s) && empty(F) && empty(l) && empty(d) && empty(j) &&
|
||
\ empty(c) && empty(z) && empty(t) # true
|
||
<
|
||
Note: Take care using the "any" type given its default is 0. For
|
||
implications, see |vim9-any-type|.
|
||
|
||
Uninitialized container types can be added to. For example:
|
||
>vim9
|
||
vim9script
|
||
var myDict: dict<list<number>>
|
||
var myList: list<number>
|
||
myList->add(9)
|
||
myDict['version'] = myList
|
||
echo myDict # {'version': [9]}
|
||
<
|
||
Initializing a variable to a null value differs from initializing a variable
|
||
with "null_<type>". Particularly, `null_dict`, `null_list`, and `null_blob`
|
||
give errors when trying to add to/extend variables initialized to them:
|
||
|
||
- `null_dict`:
|
||
*E1103* >vim9
|
||
vim9script
|
||
var nd = null_dict
|
||
def F1103(): void
|
||
nd['a'] = 'fail' # E1103: Dictionary not set
|
||
enddef
|
||
F1103()
|
||
< *E1133* >vim9
|
||
vim9script
|
||
var ed: dict<string> # An empty dict can be extended
|
||
ed->extend({'a': 'okay'})
|
||
echo ed # {'a': 'okay'}
|
||
var nd: dict<string> = null_dict
|
||
nd->extend({'a': 'fail'}) # E1133: Cannot extend a null_dict
|
||
<
|
||
- `null_list`:
|
||
*E1147* >vim9
|
||
vim9script
|
||
var nl = null_list
|
||
def F1147(): void
|
||
nl[0] = 'fail' # E1147: List not set
|
||
enddef
|
||
F1147()
|
||
< *E1130* >vim9
|
||
vim9script
|
||
var el: list<string> # An empty list can be added to
|
||
el->add('okay')
|
||
echo el # ['okay']
|
||
var nl: list<string> = null_list
|
||
nl->add('fail') # E1130: Cannot add to null_list
|
||
<
|
||
*E1134* >vim9
|
||
vim9script
|
||
var el: list<string> # An empty list can be extended
|
||
el->extend(['okay'])
|
||
echo el # ['okay']
|
||
var nl: list<string> = null_list
|
||
nl->extend(['fail']) # E1134: Cannot extend a null_list
|
||
<
|
||
- `null_blob`:
|
||
*E1581* >vim9
|
||
vim9script
|
||
var eb: blob # An empty blob can be extended
|
||
eb->extend(0zF09F988A)
|
||
echo eb->blob2str() # ['😊']
|
||
var nb: blob = null_blob
|
||
nb->extend(0zF09F988A) # E1581: Cannot extend a null_blob
|
||
<
|
||
*E1131* >vim9
|
||
vim9script
|
||
var eb: blob # An empty blob can be added to
|
||
eb->add(0xF0)->add(0x9F)->add(0x98)->add(0x8A)
|
||
echo eb->blob2str() # ['😊']
|
||
var nb: blob = null_blob
|
||
nb->add(0xF0) # E1131: Cannot add to null_blob
|
||
<
|
||
Note: Similar errors should not be encountered with:
|
||
- `null_tuple` (because |Tuples| are immutable, neither adding to nor
|
||
extending them is permitted), and
|
||
- `null_string` (which is one of the |null-anomalies|), for example:
|
||
>vim9
|
||
vim9script
|
||
var ns: string = null_string
|
||
ns ..= 'Okay!'
|
||
echo ns # Okay!
|
||
<
|
||
Using |:let| is not allowed in Vim9 script. Trying to do so gives |E1226|.
|
||
An existing variable is assigned to without any command. The same applies to
|
||
global, window, tab, buffer and Vim variables, because they are not really
|
||
declared. They can also be deleted with |:unlet|, for example:
|
||
>vim9
|
||
vim9script
|
||
g:v = "global"
|
||
b:v = "buffer local"
|
||
t:v = "tab local"
|
||
w:v = "window local"
|
||
echo [g:v, b:v, w:v, t:v]
|
||
unlet g:v | unlet b:v | unlet w:v | unlet t:v
|
||
# Now none of the 'v' variables exist, so this echoes [0, 0, 0, 0]
|
||
echo [exists('g:v'), exists('b:v'), exists('w:v'), exists('t:v')]
|
||
<
|
||
Declaring any of the following with `:var` gives an error:
|
||
- a global, buffer, tab, or window variable
|
||
- an environment variable
|
||
- an option, or
|
||
- a register.
|
||
Examples:
|
||
*E1000* >vim9
|
||
vim9script
|
||
var $X = 1 # E1000: Cannot declare an environment variable: $X
|
||
<
|
||
*E1016* >vim9
|
||
vim9script
|
||
var t:err = 'err' # E1016: Cannot declare a tab variable: t:err
|
||
<
|
||
*E1052* >vim9
|
||
vim9script
|
||
var &ts = 8 # E1052: Cannot declare an option: &ts = 8
|
||
<
|
||
*E1066* >vim9
|
||
vim9script
|
||
var @a = 'no' # E1066: Cannot declare a register: @a = 'no'
|
||
<
|
||
*E1178*
|
||
Use `:const` or `:final` instead of `:lockvar`, which does not work on local
|
||
variables.
|
||
>vim9
|
||
vim9script
|
||
def F1178(): void
|
||
var x: any
|
||
lockvar x
|
||
enddef
|
||
F1178() # E1178: Cannot lock or unlock a local variable
|
||
<
|
||
Even though `:lockvar` works with script-local variables, using `:const` or
|
||
`:final` usually is better for those too, except where locked/unlocked
|
||
toggling is wanted, which is shown in this example:
|
||
>vim9
|
||
vim9script
|
||
var n: number = 8
|
||
lockvar n
|
||
try
|
||
n = 9
|
||
catch
|
||
echo v:exception # E741: Value is locked: n
|
||
finally
|
||
unlockvar n
|
||
endtry
|
||
n = 9
|
||
echo n # 9
|
||
<
|
||
*vim9-exists()*
|
||
The `exists()` (and `exists_compiled()`) function does not work on local
|
||
arguments or variables declared in a compiled function. It always returns 0.
|
||
However, `exists()` works in both non-compiled Vim9 script and on variables
|
||
tested from within a compiled function where the variable is declared
|
||
already in a non-compiled scope. To illustrate:
|
||
>vim9
|
||
vim9script
|
||
var script: any
|
||
var MyCompiled: func = (arg: any): void => {
|
||
var compiled: any
|
||
echo exists('arg') # 0 (compiled, local argument)
|
||
echo exists('compiled') # 0 (compiled, local variable)
|
||
echo exists('script') # 1 (compiled, script-local variable)
|
||
}
|
||
MyCompiled(true)
|
||
echo exists('script') # 1 (script-local scope and variable)
|
||
<
|
||
Also, `exists()` works in a Vim9 script for arguments and variables in a
|
||
legacy function, including those declared with `:var`. For example:
|
||
>vim9
|
||
vim9script
|
||
function Legacy(arg = 'yes')
|
||
let local = 'yes'
|
||
vim9cmd var vlocal: string = 'yes'
|
||
return [exists('a:arg'), exists('local'), exists('vlocal')]
|
||
endfunction
|
||
echo Legacy() # [1, 1, 1]
|
||
< *vim9-no-shadowing*
|
||
Variables, functions and function arguments cannot shadow previously defined
|
||
or imported variables and functions in the same script file. However,
|
||
variables can shadow Ex commands, so rename the variable if necessary.
|
||
The following are examples of errors given when trying to shadow:
|
||
|
||
*E1006* >vim9
|
||
vim9script
|
||
def F1006(n: number): void
|
||
var n: number = 10 # E1006: n is used as an argument
|
||
enddef
|
||
F1006(9)
|
||
< *E1041* >vim9
|
||
vim9script
|
||
def X(): void
|
||
enddef
|
||
var X: number # E1041: Redefining script item: "X"
|
||
<
|
||
*E1167* >vim9
|
||
vim9script
|
||
var ll: list<number> = [2, 3, 1]
|
||
def F1167(): void
|
||
var x: number = 1
|
||
echo ll->sort((x, y) => x - y)
|
||
enddef
|
||
F1167() # E1167: Argument name shadows existing variable: x
|
||
<
|
||
*E1168* >vim9
|
||
vim9script
|
||
var ll: list<number> = [2, 3, 1]
|
||
var x: number
|
||
echo ll->sort((x, y) => x - y) # E1168: Argument already declared in…
|
||
<
|
||
*E1213*
|
||
Attempting to redefine an imported script's name will give E1213 (see also
|
||
|:import|). For example, the following script will write a temporary file,
|
||
which is then imported as "Imp" and its variable, "y", echoed. Subsequently,
|
||
E1213 is given because "Imp" is the {name} of the imported script:
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = tempname()
|
||
var lines: list<string> = ['vim9script', 'export var y = "Success!"']
|
||
lines->writefile(TMP)
|
||
import TMP as Imp
|
||
echo Imp.y # Success!
|
||
var Imp: string # E1213: Redefining imported item "Imp"
|
||
<
|
||
Global variables and global functions must be prefixed with "g:", including at
|
||
the script level. That is because a variable or function is script-local when
|
||
it does not have a prefix in a Vim9 script-local scope. An example (noting
|
||
this will appear to do nothing until you source the subsequent script):
|
||
>vim9
|
||
vim9script
|
||
var scriptvar: string = 'scriptvar is a script-local variable'
|
||
def Sfunc(): string
|
||
return 'Sfunc() is a script-local function'
|
||
enddef
|
||
g:globalvar = 'g:globalvar is a global variable'
|
||
def g:Gfunc(): string
|
||
return 'g:Gfunc() is a global function'
|
||
enddef
|
||
<
|
||
Now source the following script. The first two will echo the
|
||
"g:...is a global..." strings whereas the unprefixed variable and
|
||
function will give |E121| (undefined variable) and |E117| (unknown
|
||
function) respectively.
|
||
>vim
|
||
echo g:globalvar
|
||
echo g:Gfunc()
|
||
echo scriptvar
|
||
echo Sfunc()
|
||
<
|
||
The "g:" prefix is not needed for |autoload| functions.
|
||
|
||
*vim9-function-defined-later*
|
||
In a Vim9 script, it is possible to call a global function before it exists
|
||
provided it is called from within an `exists()` conditional. For example:
|
||
>vim9
|
||
vim9script
|
||
def Later(arg: number): void
|
||
if exists('g:ExLater')
|
||
g:ExLater(arg)
|
||
endif
|
||
enddef
|
||
Later(1)
|
||
def g:ExLater(arg: number): void
|
||
popup_notification($'Called g:ExLater() - {arg}', {})
|
||
enddef
|
||
Later(2)
|
||
<
|
||
When sourced initially, "Later(1)" does nothing because the `exists()`
|
||
conditional skips calling "g:ExLater(arg)" (since it doesn't exist, yet).
|
||
When sourced again, the "g:ExLater()" function exists, and both popups,
|
||
"Called g:ExLater() - 1" and "Called g:ExLater() - 2", are generated.
|
||
|
||
You could use `exists_compiled()` to avoid the error, however, then the
|
||
function would not be called, even when it is defined later. To illustrate,
|
||
in the following script neither of the "LaterCompiled()" calls generate
|
||
the popup when sourced initially but, when sourced a second time, two popups
|
||
are generated:
|
||
>vim9
|
||
vim9script
|
||
def LaterCompiled(arg: number): void
|
||
if exists_compiled('g:ExCompLater')
|
||
g:ExCompLater(arg)
|
||
endif
|
||
enddef
|
||
LaterCompiled(1)
|
||
def g:ExCompLater(arg: number): void
|
||
popup_notification($'Called g:ExCompLater() with {arg}', {})
|
||
enddef
|
||
LaterCompiled(2)
|
||
<
|
||
Since `&opt = value` is now assigning a value to option "opt", "&" by itself
|
||
cannot be used to repeat a `:substitute` command. A ":" needs to precede the
|
||
"&" to distinguish it as the repeat command. The following script
|
||
demonstrates this. It creates a modifiable buffer in a new split, appends
|
||
"princess", then replaces the "s" twice, resulting in "prince":
|
||
>vim9
|
||
vim9script
|
||
:sp | enew
|
||
append(0, 'princess')
|
||
:1substitute/s//
|
||
:&
|
||
<
|
||
Note: If you did use "&" instead of ":&" in this script it would give |E112|.
|
||
>
|
||
< *vim9-unpack-ignore*
|
||
For unpack assignment (destructuring), the underscore can be used to ignore
|
||
a list item, similar to how a function argument can be ignored:
|
||
>vim9
|
||
vim9script
|
||
var theList: list<string> = ['A', 'M', 'Z']
|
||
var [first, _, last] = theList # ignore the second item
|
||
echo $"{first} to {last}"
|
||
<
|
||
To ignore any remaining items, use "; _":
|
||
>vim9
|
||
vim9script
|
||
def GetNameCountry(): list<string>
|
||
return ['Kamoga', 'Kibaale', 'Uganda', '9', 'M']
|
||
enddef
|
||
# Unpack, ignoring second and all items after the third:
|
||
var [name, _, country; _] = GetNameCountry()
|
||
echo $"{name} lives in {country}"
|
||
<
|
||
Aside from declaring more than one variable at a time using unpack notation,
|
||
each variable can either have a declared type or infer it from its value:
|
||
>vim9
|
||
vim9script
|
||
var one: tuple<...list<any>> = (1, 'I', 'one', 'tahi', '١', '๑')
|
||
var [d: number, _, _, m; _] = one
|
||
echo $'Digit {d} is "{m}" in Māori.'
|
||
<
|
||
This approach should be used only where there is a list with values.
|
||
Declaring one variable per line usually is easier to understand.
|
||
|
||
The type of a variable that does not have a declared type is the type of the
|
||
value it gets. The variable after the ";" gets the remaining items: for a
|
||
list this is a list of the same member type, for a tuple this is a tuple of
|
||
the types of the remaining items:
|
||
>vim9
|
||
vim9script
|
||
var [v1; v2] = [8, 9, 10] # v2 has type list<number>
|
||
var [v3; v4] = (null, 'a', true) # v4 has type tuple<string, bool>
|
||
echo " Variable has type\n -------- --------"
|
||
echo $"{v1->printf('%12S')} {v1->typename()}"
|
||
echo $"{v2->printf('%12S')} {v2->typename()}"
|
||
echo $"{v3->printf('%12S')} {v3->typename()}"
|
||
echo $"{v4->printf('%12S')} {v4->typename()}"
|
||
<
|
||
When the value has type "any" the item types are not known and every variable
|
||
gets type "any".
|
||
|
||
*E1163*
|
||
When unpacking, type mismatches give E1163. For example, the builtin function
|
||
`getpos()` returns four numbers (bufnum, lnum, col, off) so "col" in this
|
||
script is a type mismatch:
|
||
>vim9
|
||
vim9script
|
||
def F1163(): void
|
||
var lin: number
|
||
var col: bool
|
||
[lin, col] = getpos('w0')[1 : 2] # E1163: Variable 2: type mismatc…
|
||
enddef
|
||
F1163()
|
||
< *E1080*
|
||
And E1080, which is the function-scope equivalent of |E452| at the script
|
||
level, is given in a `:def` function if ";" is used more than once:
|
||
>vim9
|
||
vim9script
|
||
def F1080(): void
|
||
var [b; l; c] = getpos('w0')[0 : 2] # E1080: Invalid assignment
|
||
enddef
|
||
F1080()
|
||
<
|
||
|
||
Constants ~
|
||
*vim9-const* *vim9-final*
|
||
How constants work varies between languages. Some consider a variable that
|
||
can't be assigned another value a constant. JavaScript is an example. Others
|
||
also make the value immutable, thus when a constant uses a list, the list
|
||
cannot be changed. Both can be used in Vim9 script.
|
||
|
||
*E1021* *E1307*
|
||
`:const` is used for making both the variable and the value a constant. Use
|
||
this for composite structures that you want to make sure will not be modified.
|
||
The following script shows:
|
||
- That a constant must have a value, otherwise E1021 is given, and
|
||
- Attempts to change a constant fail, with E1307.
|
||
>vim9
|
||
vim9script
|
||
try
|
||
const MISSING_VAL: any
|
||
catch
|
||
echo v:exception # E1021: Const requires a value
|
||
endtry
|
||
def F1307(): void
|
||
const L: list<number> = [7, 8]
|
||
L->add(9) # E1307: Argument 1: Trying to modify a const list<number>
|
||
enddef
|
||
F1307()
|
||
<
|
||
Errors |E46| and |E741| may also be given if you try to modify a constant in
|
||
the script-local scope.
|
||
|
||
Note: It is common to write constants in ALL_CAPS, as has been done in the
|
||
examples in this help file, though you do not have to.
|
||
|
||
*:final* *E1125*
|
||
`:final` is used for making only the variable a constant, but with the
|
||
variable's values mutable. This is well known from Java. This example shows
|
||
how a variable declared with final can have its values changed and concludes
|
||
with another variable declared with final giving E1125 because it has no
|
||
value:
|
||
>vim9
|
||
vim9script
|
||
final ln: list<number> = [7]
|
||
ln[1] = 9 # Adds an item to the list (okay with :final)
|
||
ln->add(10) # Alternative way to add to the list
|
||
ln->extend([11, 12]) # Extending the list with a list
|
||
ln[0] = 8 # Changing the values is fine too
|
||
echo ln # [8, 9, 10, 11, 12]
|
||
final f1125: list<any> # E1125: Final requires a value
|
||
<
|
||
The constant only applies to the value itself, not to what it refers to.
|
||
An example, showing that all items are constant/locked except for those within
|
||
the "females" list:
|
||
>vim9
|
||
vim9script
|
||
final females: list<string> = ['Martha']
|
||
const NAMES: list<list<string>> = [['Peter', 'Paul'], females]
|
||
NAMES[1][0] = 'Mary'
|
||
echo NAMES->flattennew() # ['Peter', 'Paul', 'Mary']
|
||
NAMES[1] = ["Mary"] # E741: Value is locked
|
||
<
|
||
|
||
Omitting :call and :eval ~
|
||
*vim9-omitting-:call*
|
||
Functions can be called without `:call`. Using `:call`, as the following
|
||
example shows, is allowed (though it's discouraged).
|
||
>vim9
|
||
vim9script
|
||
popup_create("Call functions without using :call", {time: 3000})
|
||
call popup_create(":call works, but isn't needed", {time: 6000})
|
||
<
|
||
*vim9-omitting-:eval*
|
||
Method calls can be made without `:eval` provided the evaluated expression is
|
||
unequivocally an expression. Each `->` must be followed by a function name,
|
||
and without a line break, otherwise |E260| is given. Likewise, the "(" of a
|
||
function call cannot have a line break before it, otherwise |E107| is given.
|
||
Consider the following script-local methods where in legacy script `:eval`
|
||
would be mandatory. In Vim9 script, it is unnecessary:
|
||
>vim9
|
||
vim9script
|
||
def Pop(arg: any): void
|
||
arg->popup_notification({time: 7000})
|
||
enddef
|
||
b:list = []
|
||
b:list->add('1')->add('2')->Pop() # 1 and 2 (on separate lines)
|
||
{a: 1, b: 2}->string()->Pop() # {'a': 1, 'b': 2}
|
||
(8, 9)->typename()->Pop() # tuple<number, number>
|
||
'single quoted ''string'''->Pop() # single quoted 'string'
|
||
"double quoted \"string\""->Pop() # double quoted "string"
|
||
(85 >> 1 == 42)->string()->Pop() # true
|
||
# The following gives E260: Missing name after ->
|
||
b:list->add('3')->
|
||
Pop()
|
||
<
|
||
In the rare case there is ambiguity between a function name and an Ex command,
|
||
prepend ":" to ensure the Ex command is executed. For example, there is the
|
||
`:substitute` command and `substitute()` function, the `:glob` shortened
|
||
command and `glob()` function, and so on. Consequently, when the ":" is not
|
||
present the "(" is inferred by Vim to be the left parenthesis before a
|
||
function's arguments, not a |pattern-delimiter|. So, prepending a colon will
|
||
ensure the command is executed with "(" as the delimiter. For example:
|
||
>vim9
|
||
vim9script
|
||
# Synonymous with ":glob/delimiter)" (and prints these lines)
|
||
:glob(delimiter)
|
||
# Literally calls the "glob()" function (and gives E121/E116)
|
||
glob(delimiter)
|
||
<
|
||
If an expression starts with "!" it is interpreted as a shell command, not
|
||
negation of a condition. So, this is interpreted as a shell command:
|
||
>vim9
|
||
vim9cmd !(900 == v:version)
|
||
<
|
||
Put the expression in parentheses to use the "!" for negation:
|
||
>vim9
|
||
vim9cmd (!(900 == v:version))->string()->popup_notification({})
|
||
<
|
||
Note that while variables need to be defined before they can be used,
|
||
functions may be called before being defined - |vim9-function-defined-later|.
|
||
This is required to allow for cyclic dependencies between functions. It is
|
||
slightly less efficient, since the function has to be looked up by name.
|
||
Also, a typographical error in the function name will only be found when the
|
||
function is called.
|
||
|
||
|
||
Omitting function() ~
|
||
|
||
A user defined function can be used as a function reference in an expression
|
||
without `function()`, though the function must already be defined. Argument
|
||
types and the return type will then be checked. An example, demonstrating
|
||
that `function()` is optional:
|
||
>vim9
|
||
vim9script
|
||
def IsASCII(c: string): string
|
||
return $"{c} is{char2nr(c) > 127 ? ' not' : ''} an ASCII character"
|
||
enddef
|
||
const ASCII: func(string): string = IsASCII
|
||
const F_ASCII: func(string): string = function(IsASCII)
|
||
popup_notification(['K'->ASCII(), '§'->F_ASCII()], {time: 7000})
|
||
<
|
||
In the script-local scope, as above, both direct assignment and using
|
||
`function()` require a function to be defined already when used in a function
|
||
reference.
|
||
|
||
Inside a `:def` function, both direct assignment and `function()` are allowed
|
||
as forward references because they are evaluated at runtime. For example:
|
||
>vim9
|
||
vim9script
|
||
# IsASCII forward reference
|
||
def ASCII(arg: string): string
|
||
var Forward_IsASCII: func(string): string = IsASCII
|
||
return Forward_IsASCII(arg)
|
||
enddef
|
||
# function(IsASCII) forward reference
|
||
def Func_ASCII(arg: string): string
|
||
var Forward_funcIsASCII: func(string): string = function(IsASCII)
|
||
return Forward_funcIsASCII(arg)
|
||
enddef
|
||
# The forward-referenced function from the two functions above
|
||
def IsASCII(c: string): string
|
||
return $"{c} is{char2nr(c) > 127 ? ' not' : ''} an ASCII character"
|
||
enddef
|
||
popup_notification(['K'->ASCII(), '§'->Func_ASCII()], {time: 7000})
|
||
<
|
||
|
||
Lambda using => instead of -> ~
|
||
*vim9-lambda*
|
||
In legacy Vim script there can be confusion between using "->" for method
|
||
calling/chaining and for a |lambda|. Also, when a "{" is found the parser
|
||
needs to figure out whether it is the start of a lambda or a dictionary, which
|
||
is more complicated in Vim9 script because of the use of argument types.
|
||
|
||
To avoid these problems, Vim9 script uses a different syntax for a lambda,
|
||
similar to JavaScript, which in its simplest form is:
|
||
>
|
||
(args) => expr1
|
||
<
|
||
This syntax produces a func type (|vim9-func-type|) with args of "any" type,
|
||
and return type "any". Examples:
|
||
>vim9
|
||
vim9script
|
||
# Vim9 lambda as expr2 of filter()
|
||
echo [8, -2, 9]->filter((_, val) => val > 0) # [8, 9]
|
||
# Vim9 lambda as a func type
|
||
var Increment = (n) => n + 1
|
||
echo Increment(8) # 9
|
||
echo Increment->typename() # func(any): any
|
||
<
|
||
A Vim9 script lambda can have its arguments and/or its return value strictly
|
||
typed. For example, using the filtering example, these all echo [8, 9]:
|
||
>vim9
|
||
vim9script
|
||
echo [8, -2, 9]->filter((_, val: number) => val > 0)
|
||
echo [8, -2, 9]->filter((_, val): bool => val > 0)
|
||
echo [8, -2, 9]->filter((_, val: number): bool => val > 0)
|
||
<
|
||
When used as a func type in a |Funcref| variable, fourteen valid forms are
|
||
possible. They are demonstrated in this example:
|
||
>vim9
|
||
vim9script
|
||
const I01 = (n) => n + 1
|
||
const I02 = (n): number => n + 1
|
||
const I03 = (n: number) => n + 1
|
||
const I04 = (n: number): number => n + 1
|
||
const I05: func = (n) => n + 1
|
||
const I06: func = (n: number) => n + 1
|
||
const I07: func = (n): number => n + 1
|
||
const I08: func = (n: number): number => n + 1
|
||
const I09: func: number = (n: number) => n + 1
|
||
const I10: func: number = (n): number => n + 1
|
||
const I11: func: number = (n: number): number => n + 1
|
||
const I12: func(number): number = (n): number => n + 1
|
||
const I13: func(number): number = (n: number) => n + 1
|
||
const I14: func(number): number = (n: number): number => n + 1
|
||
echo 0->I01()->I02()->I03()->I04()->I05()->I06()->I07()->I08()
|
||
->I09()->I10()->I11()->I12()->I13()->I14() # 14
|
||
<
|
||
Notes:
|
||
1. Return types use the most specific type from either the variable's
|
||
declaration or the lambda's return type (i.e., if ": {type}" appears
|
||
in either, that will be the lambda's return type). Further, the
|
||
variable's declaration cannot mismatch - it must be either the
|
||
lambda's explicit or inferred return type, be "any", or be omitted.
|
||
If the lambda's return type is not declared, it may be inferred from
|
||
the lambda's expression (e.g., I03 inferred as "number").
|
||
2. Arguments' types are determined from the lambda's parameter
|
||
declaration (not from the variable's type declaration, if specified).
|
||
So, the `typename()` of I02, I07, I10, and I12 is "func(any): number".
|
||
Those forms should be avoided because, although they appear to accept
|
||
"any" type arguments they don't (and consequently type mismatches can
|
||
easily occur).
|
||
3. The following are examples of invalid lambdas giving |E1012|.
|
||
They are invalid because either (a and b) the variable declaration is
|
||
for a number return type, but the lambda's return type is "any", or
|
||
(c) because the form "func({type}) =" is always invalid.
|
||
>vim9
|
||
vim9cmd var a: func: number = (n) => n + 1
|
||
# E1012: ...; expected func(...): number but got func(any): any
|
||
|
||
vim9cmd var b: func(number): number = (n) => n + 1
|
||
# E1012: ...; expected func(number): number but got func(any): any
|
||
|
||
vim9cmd var c: func(number) = (n) => n + 1
|
||
# E1012: ...; expected func(number) but got func(any): any
|
||
<
|
||
The "Increment" lambda, shown in the first example, with `func` declared and
|
||
the lambda's argument type and return type specified (like I08 in the
|
||
example above), is:
|
||
>vim9
|
||
vim9script
|
||
var Increment: func = (n: number): number => n + 1
|
||
echo Increment(0) # 1
|
||
echo Increment->typename() # func(number): number
|
||
<
|
||
Note: Specifying the func type explicitly may appear redundant, though it does
|
||
make it clearer up front that the variable is a func and, for reasons outlined
|
||
above, specifying a lambda's return type and arguments' types is prudent. It
|
||
is also more performant at runtime because "any" type checking is not required
|
||
(which can be verified using |:disassemble|). Many of the examples in this
|
||
help file use that form.
|
||
|
||
*E1157*
|
||
Although specifying a lambda's return type is not mandatory, including the
|
||
colon means the return type must be specified:
|
||
>vim9
|
||
vim9script
|
||
var Minimal = (n) => n / 2
|
||
var Missing = (n): => n / 2 # E1157: Missing return type
|
||
<
|
||
No line break is allowed in the arguments of a lambda up to and including the
|
||
"=>" (so that Vim can tell the difference between an expression in parentheses
|
||
and lambda arguments). This is okay:
|
||
>vim9
|
||
vim9script
|
||
var list: list<number> = [1, -2, 2, -3, -1, 3]
|
||
filter(list, (_, val): bool =>
|
||
val > 0)
|
||
echowindow list
|
||
<
|
||
This would not work (|E121| and |E116|): >
|
||
filter(list, (ind, val): bool
|
||
=> val > 0)
|
||
And this would not work (|E121| and |E116|): >
|
||
filter(list, (ind,
|
||
val): bool => val > 0)
|
||
Nor would this (|E1157|): >
|
||
filter(list, (ind, val):
|
||
bool => val > 0)
|
||
|
||
However, you can use a backslash to concatenate the lines before parsing.
|
||
This script also shows the lambda's "val" parameter typed:
|
||
>vim9
|
||
vim9script
|
||
var list: list<number> = [1, -2, 2, -3, -1, 3]
|
||
filter(list,
|
||
\ (_,
|
||
\ val: number):
|
||
\ bool
|
||
\ => val > 0)
|
||
echowindow list
|
||
< *E1172*
|
||
Default values are not allowed in a lambda:
|
||
>vim9
|
||
vim9script
|
||
var F_1172 = (n = 1) => n + 1 # E1172: Cannot use default values in…
|
||
<
|
||
*vim9-lambda-arguments*
|
||
In legacy Vim script, a lambda could be called with any number of extra
|
||
arguments and there was no way to warn for not using them. In Vim9 script the
|
||
number of arguments must match. If you want to accept either any arguments or
|
||
any additional arguments, use "..._", which enables |vim9-variable-arguments|.
|
||
For example:
|
||
>vim9
|
||
vim9script
|
||
b:fruits = ['apple', 'apricot', 'avocado']
|
||
def Complete(..._): list<string>
|
||
const K: string = getcmdline()[: getcmdpos() - 2]->matchstr('\S*$')
|
||
return b:fruits->filter((_, val) => val =~ K)
|
||
enddef
|
||
var fruit = input('> ', '', $"customlist,{Complete->string()}")
|
||
# (e.g., entering p<Tab> will present apple and apricot)
|
||
echowindow $"Your choice was {fruit}"
|
||
<
|
||
Note: Without "..._", the function will fail. The {completion}
|
||
argument of |input()| has three list items (ArgLead, CmdLine, and
|
||
CursorPos). None are required to build the output list, so it is
|
||
preferable to use "..._", though it works with "...l: list<any>" too).
|
||
See |:command-completion-custom|.
|
||
|
||
*inline-function*
|
||
A |lambda| can contain statements in a {} block. The following example
|
||
reports on three "local to buffer" options:
|
||
>vim9
|
||
vim9script
|
||
var Get_bv: func = (...args: list<string>): dict<any> => {
|
||
var opts: dict<any>
|
||
for arg in args
|
||
opts[arg] = getbufvar(bufname(), $'&{arg}')
|
||
endfor
|
||
return opts
|
||
}
|
||
echowindow Get_bv('modifiable', 'tabstop', 'textwidth')
|
||
<
|
||
This is also useful for a |Channel|, |Job|, or |timer|. This is a timer example:
|
||
>vim9
|
||
vim9script
|
||
echowindow "Handler calling..."
|
||
var count: number
|
||
var timer: number = timer_start(700, (_): void => {
|
||
count += 1
|
||
echowindow $'Handler called {count}'
|
||
}, {repeat: 3})
|
||
<
|
||
Note: In this example, |timer_start()| requires {callback}, the
|
||
function to call, as its second parameter. When the timer triggers,
|
||
Vim calls the callback function, passing the timer ID to it as a
|
||
mandatory argument. The "(_)" syntax in the lambda enables passing
|
||
that ID without needing explicitly either to name it or to reference
|
||
it - see |vim9-ignored-argument|.
|
||
|
||
The ending "}" of the block must be at the start of a line, excluding
|
||
leading space or tab characters. It can be followed by other characters too.
|
||
No command can follow the "{", though a comment can be used there.
|
||
These points are shown in this example:
|
||
>vim9
|
||
vim9script
|
||
var numbers: list<number> = [1, 2, 3, 4, 5]
|
||
var Square: func = (ln: list<number>): list<number> => ln
|
||
->mapnew((_, val) => { # Comment is okay after the { of the block
|
||
return val * val
|
||
}) # The } of the block starting a new line (excl. white space)
|
||
echowindow Square(numbers)
|
||
< *E1171*
|
||
Omitting the closing "}" of an inline-function's code block gives E1171:
|
||
>vim9
|
||
vim9script
|
||
var F_1171 = (): bool => {
|
||
return false # E1171: missing } after inline function
|
||
<
|
||
*command-block*
|
||
A block can also be used for defining a user command. Inside the block, Vim9
|
||
script syntax applies. This example uses a heredoc (see |:let-heredoc|):
|
||
>vim9
|
||
vim9script
|
||
command MyHeredoc {
|
||
var someVar: list<string> =<< trim eval END
|
||
Life, the universe, and everything
|
||
{6 * 7}
|
||
END
|
||
echowindow someVar
|
||
}
|
||
MyHeredoc
|
||
<
|
||
Note: "eval" is required after the "=<<" so that "{6 * 7}" is
|
||
evaluated and not treated as a |literal-string|. This is different to
|
||
|vim9-omitting-:eval|, even though the command block is Vim9 script.
|
||
|
||
If the statements include a dictionary, the dictionary's closing bracket must
|
||
not be written at the start of a line, otherwise it will be parsed as the end
|
||
of the block. So, this does not work (the last line gives |E1128|), because
|
||
the penultimate "}" is recognized as the end of the block:
|
||
>vim9
|
||
command FailingNewCommand {
|
||
g:mydict = {
|
||
'k1': 'v1',
|
||
'k2': 'v2'
|
||
}
|
||
}
|
||
<
|
||
To avoid this, place the dictionary's "}" after the last item:
|
||
>vim9
|
||
vim9script
|
||
command WorkingNewCommand {
|
||
g:mydict = {
|
||
'k1': 'v1',
|
||
'k2': 'v2' }
|
||
g:mydict->keys()->popup_notification({time: 4000})
|
||
}
|
||
WorkingNewCommand
|
||
<
|
||
Rationale: The "}" cannot be after a command because it would require parsing
|
||
the commands to find it. For consistency with that, no command can follow the
|
||
"{". Consequently, this means using "() => { command }" does not work and,
|
||
similarly, a line break (with optional leading spaces/tabs) is always required
|
||
before the "}" ending the command block too.
|
||
*E1026*
|
||
Omitting the closing "}" of a code block gives E1026 ("Missing }"):
|
||
>vim9
|
||
vim9script
|
||
command C1026 {
|
||
<
|
||
*vim9-curly*
|
||
To avoid the "{" of a dictionary being recognized as a statement block, wrap
|
||
the dictionary in parentheses:
|
||
>vim9
|
||
vim9script
|
||
var AgeDict: func = (arg: number): dict<number> => ({'age': arg})
|
||
echo 42->AgeDict()
|
||
<
|
||
Similarly, wrap a dictionary in parentheses to avoid confusion with the start
|
||
of a command block. For example, if a dictionary's opening curly bracket is
|
||
followed by a newline, wrap the dictionary in parentheses to avoid it being
|
||
interpreted as a command block:
|
||
>vim9
|
||
vim9script
|
||
command -nargs=+ PopDict {
|
||
var [key, val] = split(<q-args>)
|
||
({
|
||
[key]: val})
|
||
->string()->popup_notification({time: 5000})
|
||
}
|
||
PopDict one tahi
|
||
<
|
||
Note: The dictionary's "}" cannot be on a separate line, even when
|
||
within parentheses. Also, the dictionary's key must be in square
|
||
brackets to be evaluated as an expression - see |vim9-literal-dict|.
|
||
|
||
|
||
Automatic line continuation ~
|
||
*vim9-line-continuation*
|
||
In many cases it is obvious that an expression continues on the next line.
|
||
In such cases, there is no need in Vim9 script to prefix the line with a
|
||
backslash, which is required in legacy Vim script (see |line-continuation|)
|
||
when a list, dictionary, tuple, or function call spans multiple lines. The
|
||
following example illustrates all four:
|
||
>vim9
|
||
vim9script
|
||
var mylist: list<number> = [
|
||
1,
|
||
2,
|
||
]
|
||
var mydict: dict<number> = {
|
||
1: 1,
|
||
2: 2,
|
||
}
|
||
var mytuple: tuple<number, number> = (
|
||
1,
|
||
2,
|
||
)
|
||
var MyDef: func = (...l: list<any>): list<any> => l
|
||
echo MyDef(mylist,
|
||
mydict,
|
||
mytuple) # [[1, 2], {'1': 1, '2': 2}, (1, 2)]
|
||
<
|
||
For binary and ternary operators in expressions not within in a list,
|
||
dictionary, or tuple, a line break is possible either before or after the
|
||
operator. For example:
|
||
>vim9
|
||
vim9script
|
||
var text: string = 'V'
|
||
.. 'i'
|
||
.. 'm'
|
||
var version: number = 9 *
|
||
100
|
||
+ 2
|
||
var latest: string = v:version == 902
|
||
? 'current' :
|
||
'out-of-date'
|
||
echo (text, version, latest) # ('Vim', 902, 'current')
|
||
<
|
||
For a method call using "->", a line break is allowed before it. Likewise,
|
||
for a member using a dot. Both are illustrated in this interactive example,
|
||
which prompts for a min:sec pace and returns the total seconds and minutes and
|
||
seconds for a 5km run/walk:
|
||
>vim9
|
||
vim9script
|
||
def Run5(ms: string): dict<any>
|
||
const M: number = ms
|
||
->split('[:.]')[0]
|
||
->str2nr()
|
||
const S: number = ms
|
||
->split('[:.]')[1]
|
||
->str2nr()
|
||
var secs: number = (M * 5 * 60) + S * 5
|
||
var m_s: string = (secs / 60) .. ':' .. printf('%02d', (secs % 60))
|
||
return {seconds: secs, minutes_seconds: m_s}
|
||
enddef
|
||
var result: dict<any> = input('Pace of min:sec / km: ')
|
||
->Run5()
|
||
echo ' is a 5km completed in (seconds, mins:secs):'
|
||
echo (result
|
||
.seconds,
|
||
result
|
||
.minutes_seconds)
|
||
<
|
||
For commands that have an argument that is a list of commands, a | character
|
||
(see |:bar|) at the start of the line indicates line continuation:
|
||
>vim9
|
||
vim9script
|
||
command! ShowFileLine if !empty(bufname())
|
||
| echo $'File: {bufname()}'
|
||
| echo $'Line: {line(".")}'
|
||
| endif
|
||
ShowFileLine
|
||
<
|
||
Note: Consequently, a heredoc's first line usually cannot start with
|
||
a bar:
|
||
>vim9
|
||
vim9script
|
||
# E488: Trailing characters: | this doesn't work
|
||
var lines =<< trim END
|
||
| this doesn't work
|
||
END
|
||
<
|
||
Either use an empty line at the start or do not use heredoc.
|
||
Alternatively, ensure the "C" flag is temporarily in 'cpoptions':
|
||
>vim9
|
||
vim9script
|
||
const CPO: string = &cpoptions
|
||
execute CPO->match('C') == -1 ? 'set cpoptions+=C' : ''
|
||
var lines =<< trim END
|
||
| this works
|
||
END
|
||
echo lines[0]
|
||
&cpoptions = $'{CPO}'
|
||
<
|
||
If the heredoc is inside a function 'cpoptions' must be set before
|
||
`:def` and restored after the `:enddef`.
|
||
|
||
*E1097*
|
||
A continued, incomplete line will give E1097:
|
||
>vim9
|
||
vim9script
|
||
def F1097(): void
|
||
var x =
|
||
enddef
|
||
defcompile # E1097: Line incomplete
|
||
<
|
||
In places where line continuation with a backslash is still needed, such as
|
||
splitting up a long Ex command, comments can start with '#\ ' (like '"\ ' in
|
||
legacy Vim script):
|
||
>vim9
|
||
vim9script
|
||
syntax region IncSearch
|
||
\ oneline
|
||
#\ Sourcing this script applies the IncSearch highlight group
|
||
#\ to the text "Ex command" in this buffer.
|
||
\ start='Ex '
|
||
\ end='command'
|
||
<
|
||
This is also needed when line continuation is used without a backslash and a
|
||
line starts with a bar. For example:
|
||
>vim9
|
||
vim9script
|
||
command! BufferWords echo $'Buffer name: {bufname()}'
|
||
#\ Word count of the buffer
|
||
| echo $'Word count: {wordcount().words}'
|
||
BufferWords
|
||
< *E1050*
|
||
To make it possible for an operator at the start of a line to be recognized, a
|
||
colon must come before a range. This example will add "start" and "print"
|
||
(Note this involves shadowing the `:print` command):
|
||
>vim9
|
||
vim9script
|
||
var print: number = 1
|
||
# The following two lines are the same as declaring on one line
|
||
# var start: number = 1 + print
|
||
var start: number = 1
|
||
+ print
|
||
echo start
|
||
<
|
||
Whereas, this will assign "start" and print the second line of the script:
|
||
>vim9
|
||
vim9script
|
||
var start: number = 1
|
||
:+ print
|
||
<
|
||
And omitting a colon in this script gives E1050 because, unlike the initial
|
||
example, "print" is not a variable:
|
||
>vim9
|
||
vim9script
|
||
var start: number
|
||
+ print
|
||
# E1050: Colon required before a range: + print
|
||
<
|
||
After the range, an Ex command must follow. Without the colon you can call a
|
||
function without `:call`, but after a range you do need it:
|
||
>vim9
|
||
vim9script
|
||
const PRINT_W_DIVIDERS: func = (): void => {
|
||
echohl Statement
|
||
:print
|
||
echo "_"->repeat(70)
|
||
echohl None
|
||
}
|
||
# With no range, call the function without `call`; when sourced, this
|
||
# will print only the line with 'vim9script' preceded by a divider
|
||
PRINT_W_DIVIDERS()
|
||
# With the range, :call is required; when sourced, this prints lines
|
||
# from 'const...' to the closing '}', separated by dividers
|
||
:+,+6call PRINT_W_DIVIDERS()
|
||
<
|
||
However, the colon is not required for the |+cmd| argument. For example:
|
||
>vim9
|
||
vim9script
|
||
# Opens a new split of this help buffer and applies IncSearch
|
||
# highlight group to instances of 'MATCHME'.
|
||
split +/MATCHME
|
||
match IncSearch _MATCHME_
|
||
<
|
||
It is also possible to split a function's arguments over multiple lines.
|
||
For example:
|
||
>vim9
|
||
vim9script
|
||
def SubBlankHyphen(
|
||
text: string,
|
||
separator: string = ' '
|
||
): string
|
||
return text->substitute('[[:blank:]]', separator, 'g')
|
||
enddef
|
||
echo 'A multiple lines of arguments example'->SubBlankHyphen()
|
||
<
|
||
Since a continuation line cannot be easily recognized, parsing of commands is
|
||
stricter. In legacy Vim script, an error could result in unintended
|
||
interpretation of continuation lines. For example, consider the following,
|
||
working script:
|
||
>vim9
|
||
vim9script
|
||
def Msg(..._): void
|
||
popup_notification("Job finished successfully!", {time: 1500})
|
||
enddef
|
||
var myjob = job_start([&shell, &shellcmdflag, 'date'], {
|
||
exit_cb: Msg})
|
||
<
|
||
However, if there is an error in the command, like in the script below, Vim9
|
||
script will give errors.
|
||
>vim9
|
||
vim9script
|
||
var myjob = job_start([&shell, INVALID_LIST_ITEM_HERE, 'date'], {
|
||
exit_cb: Msg})
|
||
# Gives E121, E116 and the script stops executing
|
||
<
|
||
The equivalent legacy Vim script, below, fails on the `:let` command. However,
|
||
then it continues and interprets "exit" as the `:exit` command with argument
|
||
"_cb: Msg})", causing Vim to save changes to a file literally with that name
|
||
and exits: >
|
||
>
|
||
" *** DO NOT SOURCE this legacy Vim script ***
|
||
" If sourced, this will write the file '_cb: Msg})' and exit!
|
||
"
|
||
let myjob = job_start([&shell, INVALID_ITEM_HERE, 'date'], {
|
||
exit_cb: Msg})
|
||
<
|
||
*E1144*
|
||
To prevent unintended consequences, like the one above, Vim9 script
|
||
requires white space between most command names and their arguments.
|
||
(Note: Delimited commands, like `:global` and `:substitute`, are exceptions.)
|
||
For example:
|
||
>vim9
|
||
vim9script
|
||
exit_abc
|
||
# E1144: Command "exit" is not followed by white space: exit_abc
|
||
<
|
||
However, the argument of a command that is a command itself won't be
|
||
recognized consistently. For example, after "windo echo {expr}" a line break
|
||
inside the expression will only apply to the current window, not to subsequent
|
||
windows:
|
||
>vim9
|
||
vim9script
|
||
windo echo 'hi'
|
||
->toupper() # 'HI' (current window) and 'hi' (for others)
|
||
<
|
||
So, in instances like this, the "\" continuation character must still be used:
|
||
>vim9
|
||
vim9script
|
||
windo echo 'hi'
|
||
\ ->toupper() # 'HI' (for every window)
|
||
<
|
||
Note: This could have serious consequences if, for example, the
|
||
command was "windo execute ':global/something/'" and the continuation
|
||
line was ".. d". The lines with "something" would be deleted only in
|
||
the current window and the other windows would have only `:print`
|
||
(i.e., default ":p") performed on their "something" lines.
|
||
|
||
Other line continuation considerations:
|
||
|
||
- `:enddef` cannot be used at the start of a continuation line. It ends the
|
||
current `:def` function. See |E1057|.
|
||
|
||
- No line break is allowed in the LHS of an assignment. Specifically, when
|
||
unpacking a list (|:let-unpack|), this is okay:
|
||
>vim9
|
||
vim9script
|
||
const UNPACK: func = (a: string, b: string): list<string> => [a, b]
|
||
var [v1, v2] =
|
||
UNPACK('all', 'good')
|
||
echo $'{v1} {v2}'
|
||
<
|
||
whereas this would not work - it would give |E475|: >
|
||
|
||
var [v1,
|
||
v2] = UNPACK('all', 'good')
|
||
<
|
||
- No line break is allowed in between arguments of an `:echo`, `:execute` and
|
||
similar commands. This is okay:
|
||
>vim9
|
||
vim9script
|
||
echo [1,
|
||
2] [3,
|
||
4]
|
||
<
|
||
whereas this does not work (i.e., "[3, 4]" is not echoed):
|
||
>vim9
|
||
vim9script
|
||
echo [1, 2]
|
||
[3, 4]
|
||
<
|
||
- In some cases it is difficult for Vim to parse a command, especially when
|
||
commands are used as an argument to another command, such as `:windo`,
|
||
`:command` or `:autocmd`. In those cases the line continuation with a
|
||
backslash must be used. For example:
|
||
>
|
||
command! Foo call Bar('x', {
|
||
\ 'key': 'value',
|
||
\ })
|
||
autocmd BufWritePre * call Bar('x', {
|
||
\ 'key': 'value',
|
||
\ })
|
||
<
|
||
See also the "windo echo" example and note, above.
|
||
|
||
|
||
White space ~
|
||
*vim9-white-space*
|
||
Vim9 script enforces proper use of white space. There must be white space
|
||
before and after the "=" of variable assignment, for example:
|
||
>vim9
|
||
vim9script
|
||
var num = 234
|
||
<
|
||
White space is required:
|
||
*E1004*
|
||
- Before and after the "=" in variable declaration:
|
||
>vim9
|
||
vim9script
|
||
var num=234 # E1004: White space required before and after '=' at…
|
||
# Similarly, these would also give E1004:
|
||
var num= 234
|
||
var num =234
|
||
<
|
||
- Around most operators. So, the first example here works whereas the second
|
||
gives E1004:
|
||
>vim9
|
||
vim9cmd echo 'Yes' .. '!' # Yes!
|
||
vim9cmd echo 'F'..'ail' # E1004: White space required before …
|
||
<
|
||
- In a sublist (list slice) around the ":", except at the start and end:
|
||
>vim9
|
||
vim9script
|
||
var mylist: list<number> = [7, 8, 9]
|
||
echo mylist[:] # [7, 8, 9]
|
||
echo mylist[1 : 2] # [8, 9]
|
||
echo mylist[: 1] # [7, 8]
|
||
echo mylist[2 :] # [9]
|
||
echo mylist[1:2] # E1004: White space required before and after ":"…
|
||
<
|
||
*E1069*
|
||
- After a variable name and its ":" (preceding a type declaration):
|
||
>vim9
|
||
vim9script
|
||
var okay_list: list<any> = ['Is', 'good']
|
||
var fail_list:list<any> = ['Is', 'a', 'fail'] # E1069: White space …
|
||
<
|
||
- Before the '#' starting a comment. If it isn't present, errors such as
|
||
|E121| or |E488| are given:
|
||
>vim9
|
||
vim9cmd echo 'No'# E121: Undefined variable: #
|
||
vim9cmd var f: number = 99# E488: Trailing characters: …
|
||
<
|
||
White space is not allowed:
|
||
*E1068*
|
||
- Before the comma separating dictionary key-value pairs or list items (though
|
||
it is allowed with tuples). Examples:
|
||
>vim9
|
||
vim9cmd echo (1 , 2) # (1, 2) [Note: Vim normalizes the tuple]
|
||
vim9cmd echo {1: 1 , 2: 2} # E1068: No white space allowed before ','
|
||
vim9cmd echo [1 , 2] # E1068: No white space allowed before ','
|
||
<
|
||
- Before the comma separating function arguments:
|
||
>vim9
|
||
vim9script
|
||
def F1068(arg: string , arg2: bool): void
|
||
# E1068: No white space allowed before ',': , arg2: bool): void
|
||
enddef
|
||
<
|
||
- Before either the "<" or "(" in the declaration of a generic function:
|
||
>vim9
|
||
vim9script
|
||
def F1068<T> (): T # E1068: No white space allowed before '(': (): T
|
||
enddef
|
||
F1068()
|
||
< *E1074*
|
||
- After the '.' of an imported item. In this example, a temporary Vim9 script
|
||
is written, then imported. The script's exported "okay" variable is echoed
|
||
successfully, but the space after the '.' of the second "Imp" item gives
|
||
E1074:
|
||
>vim9
|
||
vim9script
|
||
var tmp: string = $'{tempname()}.vim'->substitute('\\', '/', 'g')
|
||
var temp_lines: list<string> = ['vim9script',
|
||
'export var okay: bool = true', 'export var err: bool']
|
||
temp_lines->writefile(tmp)
|
||
import tmp as Imp
|
||
echo Imp.okay # true
|
||
echo Imp. err # E1074: No white space allowed after dot
|
||
<
|
||
- Between a function name and the "(", though it is allowed before any
|
||
argument and after the last argument:
|
||
>vim9
|
||
vim9script
|
||
def MyN(...arg: list<number>): void
|
||
echo arg
|
||
enddef
|
||
MyN(1) # [1]
|
||
MyN( 2) # [2]
|
||
MyN(3 ) # [3]
|
||
MyN( 4, 5, 6 ) # [4, 5, 6]
|
||
MyN (7, 8, 9) # E492: Not an editor command
|
||
<
|
||
Note: The following are also not allowed, and give |E492|: >
|
||
MyN
|
||
\ (7, 8, 9)
|
||
MyN
|
||
(7, 8, 9)
|
||
< *E1202*
|
||
- After the '.' when accessing object properties, methods, or enum members.
|
||
An enum method example:
|
||
>vim9
|
||
vim9script
|
||
enum Metal
|
||
Au((79, 'Gold')),
|
||
Hg((80, 'Mercury'))
|
||
var data: tuple<number, string>
|
||
def Get_name(): string
|
||
return this.data[1]
|
||
enddef
|
||
endenum
|
||
echo Metal.Au.Get_name() # Gold
|
||
echo Metal.Hg. Get_name() # E1202: No white space allowed after '.':…
|
||
<
|
||
*E1205*
|
||
- In a `:set` command between the option name and a following "&", "!",
|
||
"<", "=", "+=", "-=" or "^=". For example:
|
||
>vim9
|
||
vim9script
|
||
# This is okay
|
||
set tabstop=8
|
||
echo &tabstop
|
||
# This is E1205: No white space allowed between option and: =8
|
||
set tabstop =8
|
||
<
|
||
- In a :set command between the option name and a following ':', which gives
|
||
|E518|. For example:
|
||
>vim9
|
||
vim9script
|
||
# This is okay
|
||
set tabstop:8
|
||
echo &tabstop
|
||
# This is E518: Unknown option :8
|
||
set tabstop :8
|
||
<
|
||
|
||
No curly braces expansion ~
|
||
*vim9-no-curly-braces-expansion*
|
||
Dynamic variable name construction using curly braces (|curly-braces-names|)
|
||
does not work in Vim9 script scopes. This example shows two curly braces
|
||
names expanding and working to form script level variable names in a legacy
|
||
Vim script `:function` but failing in a `:def` function:
|
||
>vim9
|
||
vim9script
|
||
var [O, O3] = ['oxygen', 'ozone']
|
||
function Al(arg, n = null_string) abort
|
||
return s:{a:arg}{a:n}
|
||
endfunction
|
||
def Al9(arg: string, n: any = null_string): string
|
||
return {arg}{n}
|
||
enddef
|
||
echo Al('O', 3) # ozone
|
||
echo Al9('O', 3) # E720: Missing colon in Dictionary: }{n}
|
||
<
|
||
Similarly, |curly-braces-function-names| are only possible in legacy Vim
|
||
script scopes:
|
||
>vim9
|
||
vim9script
|
||
var low_line: string = '_'
|
||
function F_curly()
|
||
echo 'A curly-braces-function-name: okay in legacy Vim script scope'
|
||
endfunction
|
||
legacy call s:F{s:low_line}curly()
|
||
F{low_line}curly() # E1144: Command "F" is not followed by white…
|
||
<
|
||
|
||
Command modifiers may not always be ignored and give an error ~
|
||
|
||
In some scenarios, using a command modifier for a command that does not use it
|
||
may give an error. However, some modifiers do not give an error (just like
|
||
how they do not error in legacy Vim script). For example, in this script the
|
||
meaningless |:vertical|, |:keepmarks|, and |:hide| modifiers are ignored:
|
||
>vim9
|
||
vim9script
|
||
vertical if 1 == 1
|
||
keepmarks echo 'Starting...'
|
||
try
|
||
echo ERROR
|
||
catch
|
||
hide echo 'Caught!'
|
||
endtry
|
||
endif
|
||
< *E1176*
|
||
However, if a modifier is applied to certain control flow commands, E1176
|
||
is given. An example is prepending |:silent| to |:endif|:
|
||
>vim9
|
||
vim9script
|
||
if 1
|
||
echo "Hi!"
|
||
silent endif
|
||
# E1176: Misplaced command modifier
|
||
<
|
||
Note: When lines 2 to 4 only of this script are sourced, you can see
|
||
it does not give E1176 (because then the script is executed in a
|
||
legacy Vim script context).
|
||
|
||
Similarly, adding modifiers to any of |:try|, |:endtry|, |:for|, |:endfor|,
|
||
|:while|, |:endwhile|, |:catch|, or |:finally| may result in an E1176 error,
|
||
though it depends on the redundant modifier used. For example, adding the
|
||
|:silent| modifier to |:for| is ignored whereas adding |:keepmarks| to
|
||
|:endfor| is an error. (Note the behavior of `:silent` is intentional. It works
|
||
like that so that error messages are suppressed when Vim does not support
|
||
the |+eval| feature.)
|
||
*E1082*
|
||
Also, using a command modifier without a following command gives E1082:
|
||
>vim9
|
||
vim9script
|
||
silent
|
||
# E1082: Command modifier without command
|
||
<
|
||
|
||
Dictionary literals ~
|
||
*vim9-literal-dict*
|
||
|
||
Traditionally Vim has supported dictionary literals with a {} syntax: >
|
||
let dict = {'key': value}
|
||
<
|
||
Later it became clear that using a simple text key is very common, thus
|
||
literal dictionaries were introduced in a backwards compatible way: >
|
||
let dict = #{key: value}
|
||
<
|
||
However, this #{} syntax is unlike any existing language. As it turns out,
|
||
using a literal key is much more common than using an expression, and
|
||
considering that JavaScript uses this syntax, using the {} form for dictionary
|
||
literals is considered a much more useful syntax. In Vim9 script the {} form
|
||
uses literal keys: >
|
||
var dict = {key: value}
|
||
<
|
||
For example:
|
||
>vim9
|
||
vim9script
|
||
var dict = {key: 9}
|
||
echo dict # {'key': 9}
|
||
<
|
||
Note: Vim normalizes the key, adding the ' characters. Dictionary
|
||
keys are always strings.
|
||
|
||
Literal keys work using alphanumeric characters, underscore, and dash. If you
|
||
want to use a character other than those, or even use an expression, you may:
|
||
- Use a single or double quoted string, or
|
||
- Use `extend()` and the literal key syntax, or
|
||
- For an expression, enclose the key in [] (like in a JavaScript computed
|
||
property).
|
||
All three are illustrated in this example:
|
||
>vim9
|
||
vim9script
|
||
var dict: dict<bool>
|
||
dict["key\twith\ttabs"] = true # double quoted key
|
||
dict->extend({[40.9 + 1.1]: true}) # evaluated expression key
|
||
dict['¡non–ASCII! w/ spaces'] = true # single quoted key
|
||
for k in dict->keys()
|
||
echo [k, dict[k]]
|
||
endfor
|
||
<
|
||
The key type can be string, number, bool, or float, though all keys are stored
|
||
as strings. Trying to use other types will give an error. For example:
|
||
>vim9
|
||
vim9script
|
||
var dict: dict<bool>
|
||
dict[9] = true # number key
|
||
dict[true] = true # bool key
|
||
dict[9.2] = true # float key
|
||
echo dict # {'true': true, '9.2': true, '9': true}
|
||
dict[(9, 2)] = false # E1522: Using a Tuple as a String
|
||
<
|
||
Without using [], the value is literal so retains any leading zeros. An
|
||
expression given with [] is evaluated and then converted to a string.
|
||
Expression evaluation means any leading zeros are omitted. For example:
|
||
>vim9
|
||
vim9script
|
||
var dict = {09: '09 is literal "09"', [09]: '[09] becomes "9"'}
|
||
echo dict['09'] # '09 is literal "09"'
|
||
echo dict['9'] # '[09] becomes "9"'
|
||
< *E1139*
|
||
If the key within [] is invalid, errors such as E1139 may be given, for
|
||
example:
|
||
>vim9
|
||
vim9script
|
||
var E1139 = {[0😢9]: 2} # E1139: Missing matching bracket after dict …
|
||
<
|
||
*E1014*
|
||
An invalid key gives E1014:
|
||
>vim9
|
||
vim9script
|
||
# This tries to use an unquoted control code character, U+0007 (ALERT)
|
||
var E1014: dict<string> = {: 'E1014'} # E1014: Invalid key: ^G
|
||
<
|
||
A float key must appear inside [], either in a dictionary literal or in a
|
||
subscript assignment. The '.' of a float outside of either context is an
|
||
invalid Vim9 script dictionary literal key:
|
||
>vim9
|
||
vim9script
|
||
var dict: dict<string> = {[.09]: 'ok'}
|
||
dict[0.10] = 'ok'
|
||
echo dict # {'0.09': 'ok', '0.1': 'ok'}
|
||
try
|
||
dict->extend({0.11: 'fail!'})
|
||
catch
|
||
echo v:exception # E720: Missing colon in Dictionary: .11: 'fail!'
|
||
endtry
|
||
< *E1127*
|
||
If the name after a '.' is omitted, E1127 is given:
|
||
>vim9
|
||
vim9script
|
||
def F1127(): void
|
||
var d = {x: 0}
|
||
echo d.
|
||
enddef
|
||
F1127() # E1127: Missing name after dot
|
||
<
|
||
|
||
No :xit, :t, :k, :Print, :append, :change, :insert, or :open ~
|
||
|
||
Some commands are too easily confused with local variable names, though they
|
||
have alternative commands that do the same thing:
|
||
|
||
Not allowed Instead use ~
|
||
`:k` |:mark|
|
||
`:Print` |:print|
|
||
`:t` |:copy|
|
||
`:xit` |:exit|
|
||
|
||
Note: Shortened forms like `:x` also are not allowed.
|
||
|
||
*E1100*
|
||
For example, using "mark x" in the following script would work, whereas using
|
||
"k x" gives E1100:
|
||
>vim9
|
||
vim9script
|
||
:+2
|
||
k x
|
||
# E1100: Command not supported in Vim9 script (missing :var?): k x
|
||
<
|
||
Some commands are not available at all in Vim9 script. These will give E1100
|
||
if they are used, including their shortened forms, like `:a`, `:o`, and `:ch`:
|
||
|
||
`:append`
|
||
`:change`
|
||
`:insert`
|
||
`:open`
|
||
|
||
See also |vim9-invalid-Ex-commands|.
|
||
|
||
|
||
Comparators ~
|
||
*vim9-comparators*
|
||
The 'ignorecase' option is not used for string comparators. Consequently,
|
||
"=~" and "=~#" work identically (i.e., comparisons are case sensitive):
|
||
>vim9
|
||
vim9script
|
||
var ic: bool = &ignorecase
|
||
set ignorecase
|
||
# Both of these echo 'false' because 'ignorecase' isn't used
|
||
echo 'a' =~# 'A'
|
||
echo 'a' =~ 'A'
|
||
# Case sensitive '=~#' echoes 0. Case insensitive '=~' echoes 1
|
||
legacy echo 'a' =~# 'A'
|
||
legacy echo 'a' =~ 'A'
|
||
# Revert 'ignorecase' to its setting before running this script
|
||
&ignorecase = ic
|
||
<
|
||
The expression "is" (|expr-is|), when used on strings, returns false (except
|
||
where the strings being compared are either explicitly null or uninitialized).
|
||
Similarly, the expression "isnot" (|expr-isnot|) returns true. This is
|
||
because, whereas in a legacy Vim script scope strings' content is compared,
|
||
in a |Vim9| script scope identity is compared. Consequently, because strings
|
||
are copied when used, two strings are not the same, though this might change
|
||
someday if strings are not copied but reference counted. For example:
|
||
>vim9
|
||
vim9script
|
||
var str: string = ''
|
||
echo str is str # false
|
||
legacy echo s:str is s:str | # 1
|
||
var x: string
|
||
var y: string = null_string
|
||
echo x is y # true
|
||
<
|
||
For boolean, number, and float types, in legacy Vim script "is" and "isnot"
|
||
work like string comparison. In Vim9 script, except when comparing a number
|
||
with a float, "is" will give either |E1037| or |E1072|. For example:
|
||
>vim
|
||
vim9cmd var [boo: bool, num: number, flo: float] = [true, 9, 9.2]
|
||
legacy echo s:boo is v:true | " 1
|
||
legacy echo s:num is 9 | " 1
|
||
legacy echo s:flo is 9.2 | " 1
|
||
legacy echo s:boo is 9 | " 0
|
||
legacy echo s:boo is 9.2 | " 0
|
||
legacy echo s:num is 9.2 | " 0
|
||
vim9cmd echo boo is true # E1037: Cannot use "is" with bool
|
||
vim9cmd echo num is 9 # E1037: Cannot use "is" with number
|
||
vim9cmd echo flo is 9.2 # E1037: Cannot use "is" with float
|
||
vim9cmd echo boo is 9 # E1072: Cannot compare bool with number
|
||
vim9cmd echo boo is 9.2 # E1072: Cannot compare bool with float
|
||
vim9cmd echo num is 9.2 # false
|
||
<
|
||
Similarly, "is" and "isnot" may not be used to compare job and channel types.
|
||
This example shows job comparison working with "is" in a legacy Vim script
|
||
scope but giving |E1072| in a Vim9 script scope:
|
||
>vim9
|
||
vim9script
|
||
var job1: job
|
||
var job2: job = job1
|
||
legacy echo s:job1 is s:job2 | # 1
|
||
echo job1 is job2 # E1072: Cannot compare job with job
|
||
<
|
||
Comparing container types list, dict, tuple, and blob using "is" and "isnot"
|
||
behaves the same in Vim9 script as it does in legacy Vim script. The
|
||
comparison checks instances, not content. Because they behave similarly,
|
||
only a Vim9 script list example is provided in this example, with legacy Vim
|
||
script scope comparisons to show the same behavior:
|
||
>vim9
|
||
vim9script
|
||
var l1: list<any>
|
||
var l2: list<any> = l1
|
||
echo l1 is l2 # true (same instance)
|
||
legacy echo s:l1 is s:l2 | # 1
|
||
echo l1 is [] # false (different instance)
|
||
legacy echo s:l1 is [] | # 0
|
||
echo l1 is null_list # false (different instance)
|
||
<
|
||
Comparing a `Funcref` variable is almost the same as comparing container
|
||
variables, though an uninitialized `Funcref` variable compared to Vim9
|
||
script's `null_function` behaves differently:
|
||
>vim9
|
||
vim9script
|
||
var F1: func
|
||
var F2: func = F1
|
||
echo F1 is F2 # true (same uninitialized instance)
|
||
echo F1 is null_function # true
|
||
<
|
||
In Vim9 script, comparing class objects behaves similarly:
|
||
>vim9
|
||
vim9script
|
||
class C
|
||
endclass
|
||
# Uninitialized class objects
|
||
var O1: C
|
||
var O2: C
|
||
echo O1 is O2 # true (same uninitialized class object)
|
||
echo O1 is null_object # true
|
||
# Initialized class objects
|
||
var O4: C = C.new()
|
||
var O5: C = O4
|
||
var O6: C = C.new()
|
||
echo O4 is O5 # true (same initialized class object)
|
||
echo O5 is O6 # false (different class objects)
|
||
<
|
||
Note: Classes themselves cannot be compared:
|
||
>vim9
|
||
vim9script
|
||
class C
|
||
endclass
|
||
echo C is null_class # E1401: Class "C" cannot be used as a value
|
||
<
|
||
Comparison of enum objects differs from class objects. Each enumvalue
|
||
is a singleton, so variables assigned the same enumvalue always reference
|
||
the identical instance. Even when a mutable instance variable of an enum
|
||
value is modified, the change affects all references to that enumvalue:
|
||
>vim9
|
||
vim9script
|
||
enum Switch
|
||
On(['active', true]),
|
||
Off(['inactive', false])
|
||
final state: list<any>
|
||
endenum
|
||
var on1: Switch = Switch.On
|
||
var on2: Switch = Switch.On
|
||
on2.state[0] = 'engaged'
|
||
# The change affects on1 and on2 because they're the same enumvalue
|
||
echo on1 is on2 # true
|
||
# Uninitialized enumvalue (and there is no null_enumvalue):
|
||
var uninitialized: Switch
|
||
echo uninitialized is null_object # true
|
||
<
|
||
In summary, in relation to the "is" and "isnot" comparison operators:
|
||
|
||
- Vim9 script introduces stricter rules than legacy Vim script and:
|
||
- Behaves differently for strings,
|
||
- Behaves the same for lists, dictionaries, tuples, and blobs,
|
||
- Does not allow comparison of booleans, numbers, floats, jobs, and
|
||
channels (except for comparing a number with a float), and
|
||
- Behaves the same for function references (though Vim9 script's
|
||
`null_function` behaves differently).
|
||
|
||
- Entirely exclusive to Vim9 script:
|
||
- Comparing class objects is similar to comparing function references,
|
||
and
|
||
- For enum objects, "is" and "isnot" always compare the same instance
|
||
of a given enumvalue.
|
||
|
||
|
||
Abort after error ~
|
||
|
||
In legacy Vim script, when an error is encountered, Vim continues to execute
|
||
the lines following the error. This can lead to a long sequence of errors
|
||
and need to type CTRL-C to stop it. For example, this script produces two
|
||
|E121| errors:
|
||
>vim
|
||
" legacy Vim script
|
||
let x = does_not_exist
|
||
let y = does_not_exist_too
|
||
<
|
||
However, in Vim9 script, execution of commands stops at the first error:
|
||
>vim9
|
||
vim9script
|
||
var x = does_not_exist # E121: Undefined variable: does_not_exist
|
||
# Execution stops and the following line is not executed
|
||
var y = does_not_exist_too
|
||
<
|
||
|
||
For loop ~
|
||
|
||
The loop variable must not be declared yet:
|
||
>vim9
|
||
vim9script
|
||
var it: list<number>
|
||
for it in [1, 2, 3] # E1041: Redefining script item: "it"
|
||
<
|
||
But it is possible to use a prefixed variable, e.g., a buffer local variable:
|
||
>vim9
|
||
vim9script
|
||
b:i = []
|
||
for b:i in [1, 2, 3]
|
||
echo b:i
|
||
endfor
|
||
< *E1254*
|
||
A loop variable in a `:def` function cannot be a s: variable:
|
||
>vim9
|
||
vim9script
|
||
def F()
|
||
for s:n in range(9) # E1254: Cannot use script variable in for loop
|
||
endfor
|
||
enddef
|
||
defcompile
|
||
<
|
||
Legacy Vim script has some tricks to make a for loop over a list handle
|
||
deleting items at the current or previous item. In Vim9 script, the same
|
||
trick applies in a non-compiled scope. However, in a compiled Vim9 script
|
||
scope, when an item is deleted the following item in the list is skipped
|
||
("iterator invalidation"), producing the same result as what you would see in
|
||
Python and Ruby. The following script demonstrates the differing behaviors in
|
||
non-compiled and compiled Vim9 script scopes:
|
||
>vim9
|
||
vim9script
|
||
echo 'Removing items from a list in a Vim9 script non-compiled scope:'
|
||
# This echoes 10, then 20, 30, 40, and []
|
||
var list_one: list<number> = [10, 20, 30, 40]
|
||
for n in list_one
|
||
echo n
|
||
list_one->remove(index(list_one, n))
|
||
endfor
|
||
echo list_one
|
||
echo 'Removing items from a list in a Vim9 script compiled scope:'
|
||
# This echoes 10, then 30, and the list [20, 40]
|
||
# This happens because when 10 is removed, 20 moves to index 0, but
|
||
# the iterator moves to index 1, which is now 30!
|
||
var list_two: list<number> = [10, 20, 30, 40]
|
||
var Remove_items: func = (): void => {
|
||
for n in list_two
|
||
echo n
|
||
list_two->remove(index(list_two, n))
|
||
endfor
|
||
echo list_two
|
||
}
|
||
Remove_items()
|
||
<
|
||
What this example shows is usually it is better not to change a list that is
|
||
iterated over. Making a copy first is often safer.
|
||
|
||
When looping over a list of lists, the nested lists can be changed. The loop
|
||
variable is "final" - that is, it cannot be changed but its value can be
|
||
changed. For example:
|
||
>vim9
|
||
vim9script
|
||
var lst: list<list<number>> = [[1, 2], [3, 4], [5, 6]]
|
||
for subl in lst
|
||
subl[0] = 9
|
||
endfor
|
||
echo lst # [[9, 2], [9, 4], [9, 6]]
|
||
for subl in lst
|
||
subl = [9, subl[1]] # E46: Cannot change read-only variable "subl"
|
||
endfor
|
||
< *E1306*
|
||
The depth of |:for| and |:while| loops added together, cannot exceed 10.
|
||
|
||
|
||
Conditions and expressions ~
|
||
|
||
Vim9 script has stricter type checking than legacy Vim script in boolean
|
||
contexts. Most operators require either a boolean, or 0/1, or a |Special| type.
|
||
The falsy (|??|) and logical NOT (|expr-!|) operators use truthiness rules.
|
||
|
||
– Strict Boolean Expressions ~
|
||
*vim9-boolean*
|
||
In Vim9 script, the conditionals |:if|, |ternary|, |:while|, `||` (|expr-barbar|),
|
||
and `&&` (|expr-&&|), require strict boolean types. This is different to
|
||
legacy Vim script, which treats any non-zero number as 1 and implicitly infers
|
||
a string or |Special| as a number equivalent. To illustrate, in legacy Vim
|
||
script, all these conditional expressions work:
|
||
>vim
|
||
" legacy Vim script
|
||
" These evaluate to 1:
|
||
echo 1 ? 1 : 0
|
||
echo 0 || 1
|
||
echo 1 || v:false
|
||
echo 1 && v:true
|
||
echo v:null || 1
|
||
echo 99 ? 1 : 0
|
||
echo "99" ? 1 : 0
|
||
" These evaluate to 0:
|
||
echo 0 || v:none
|
||
echo 0 && 1
|
||
echo "text" ? 1 : 0
|
||
<
|
||
In Vim9 script, conditional expressions (excluding `??` and `!`) have stricter
|
||
|type-checking|, which means:
|
||
- For numbers, only 0 (falsy) or 1 (truthy) are permitted, otherwise |E1023|
|
||
is given, and
|
||
- Strings are not permitted (|E1135|).
|
||
>vim
|
||
vim9cmd echo 1 ? 1 : 0 # 1
|
||
vim9cmd echo 0 || 1 # true
|
||
vim9cmd echo 1 || false # true
|
||
vim9cmd echo 1 && true # true
|
||
vim9cmd echo null || 1 # true
|
||
vim9cmd echo 0 || v:none # false
|
||
vim9cmd echo 0 && v:true # false
|
||
vim9cmd echo 9 ? 1 : 0 # E1023: Using a Number as a Bool: 9
|
||
vim9cmd echo "9" ? 1 : 0 # E1135: Using a String as a Bool: "9"
|
||
vim9cmd echo "x" ? 1 : 0 # E1135: Using a String as a Bool: "x"
|
||
<
|
||
– Falsiness Operator ~
|
||
*vim9-falsy*
|
||
For most types there is no error using `??` (the |falsy-operator|).
|
||
Values are either falsy or truthy, with falsy evaluated as follows:
|
||
|
||
Type Falsy when~
|
||
Number zero
|
||
String empty
|
||
Funcref null
|
||
List empty
|
||
Dictionary empty
|
||
Float zero
|
||
Boolean `false` (also |Special| `v:false`)
|
||
None always (`v:null` and `v:none`)
|
||
Job null
|
||
Channel null
|
||
Blob empty
|
||
Class not applicable (see |E1405|)
|
||
Object null
|
||
Typealias not applicable (see |E1403|)
|
||
Enum not applicable (see |E1421|)
|
||
EnumValue null
|
||
Tuple empty
|
||
void always
|
||
|
||
To illustrate, a script showing falsiness of all except error-giving types:
|
||
>vim9
|
||
vim9script
|
||
echo "Number: \t0\t\t" 0 ?? 'is falsy'
|
||
echo "String: \t''\t\t" '' ?? 'is falsy'
|
||
echo "Funcref:\tnull_function\t" null_function ?? 'is falsy'
|
||
echo "List: \t[]\t\t" [] ?? 'is falsy'
|
||
echo "Dictionary:\t{}\t\t" {} ?? 'is falsy'
|
||
echo "Float: \t0.0\t\t" 0.0 ?? 'is falsy'
|
||
echo "Boolean:\tfalse\t\t" false ?? 'is falsy'
|
||
echo " \tv:false\t\t" v:false ?? 'is falsy'
|
||
echo "None: \tv:none\t\t" v:none ?? 'is falsy'
|
||
echo " \tv:null\t\t" v:null ?? 'is falsy'
|
||
echo "Job: \tnull_job\t" null_job ?? 'is falsy'
|
||
echo "Channel:\tnull_channel\t" null_channel ?? 'is falsy'
|
||
echo "Blob: \t0z\t\t" 0z ?? 'is falsy'
|
||
echo "Object: \tnull_object\t" null_object ?? 'is falsy'
|
||
enum Enum
|
||
endenum
|
||
var null_enumval: Enum # NB: There is no inherent "null_enumvalue"
|
||
echo "EnumValue:\t'null_enumval'\t" null_enumval ?? 'is falsy'
|
||
echo "Tuple: \t()\t\t" () ?? 'is falsy'
|
||
echo "void: \t\t\t" test_void() ?? 'is falsy'
|
||
<
|
||
Note: Vim9 script's falsiness is much the same as Python's (e.g., "if []:" in
|
||
Python is falsy, the same as "if []" in Vim9 script. JavaScript is similar
|
||
too, though it differs in its unusual truthy evaluation of an empty
|
||
object/array.
|
||
|
||
– Type Conversions and Exceptions ~
|
||
*vim9-!*
|
||
When using the logical NOT operation, "!" (|expr-!|), for inverting, there is
|
||
no error (except with a class - |E1405|, enum - |E1421|, or typealias -
|
||
|E1403|) and the result is always a boolean. In this example, the falsy
|
||
values are inverted and all evaluations return "true":
|
||
>vim9
|
||
vim9script
|
||
echo [!0, !'', !null_function, ![], !{}, !0.0, !false,
|
||
!v:false, !v:none, !v:null, !null_job, !null_channel, !0z,
|
||
!null_object, !(), !test_void()]
|
||
< *vim9-!!*
|
||
Similarly, when using "!!" to turn a value into a boolean all the evaluations
|
||
return "false", as this example shows:
|
||
>vim9
|
||
vim9script
|
||
echo [!!0, !!'', !!null_function, !![], !!{}, !!0.0, !!false,
|
||
!!v:false, !!v:none, !!v:null, !!null_job, !!null_channel, !!0z,
|
||
!!null_object, !!(), !!test_void()]
|
||
<
|
||
Note: This is a rare instance where Vim9 script is more permissive than legacy
|
||
Vim script. As the following example demonstrates, legacy Vim script gives
|
||
errors when using "!" or "!!" with many types (including Funcref, List,
|
||
Dictionary, Blob, Job, and Tuple):
|
||
>vim
|
||
" legacy Vim script: !! erroring examples
|
||
let F = {x -> x}
|
||
let j = job_start([&shell, &shellcmdflag, 'date'], {})
|
||
echo !!F | " E703: Using a Funcref as a Number
|
||
echo !![] | " E745: Using a List as a Number
|
||
echo !!{} | " E728: Using a Dictionary as a Number
|
||
echo !!0z | " E974: Using a Blob as a Number
|
||
echo !!j | " E910: Using a Job as a Number
|
||
echo !!() | " E1520: Using a Tuple as a Number
|
||
<
|
||
When using ".." for string concatenation, number, float, bool and |Special|
|
||
types are always converted to strings:
|
||
>vim9
|
||
vim9script
|
||
var mystr = 8 .. ', ' .. 9.2 .. ', ' .. true .. ' and ' .. v:none
|
||
echo [mystr, mystr->typename()]
|
||
<
|
||
Notes: 1. Both `true` (and `v:true`, not shown) are stringified to
|
||
"true" whereas legacy Vim script stringifies `v:true` to "v:true".
|
||
2. In Vim9 script '..' string concatenation handles floats
|
||
consistently. Legacy Vim script doesn't, with the decimal point
|
||
being interpreted as a concatenation "." after the first "." or "..".
|
||
In the following legacy Vim script, 9.2 loses its decimal point:
|
||
>vim
|
||
let s:my8str = 8 .. ', ' .. 9.2 .. ', ' .. v:true .. ' and ' .. v:none
|
||
echo s:my8str | " 8, 92, v:true and v:none
|
||
<
|
||
This illustrates that primitives (|v:t_number|, |v:t_float|, and |v:t_bool|),
|
||
plus |Special| types may be compared directly with "==". For all other types,
|
||
|string()| must be used. For example:
|
||
>vim9
|
||
vim9script
|
||
echo 'This is a list: ' .. [1, 2, 3]->string()
|
||
# Whereas doing this gives E730: Using a List as a String:
|
||
echo 'This does not work! ' .. [1, 2, 3]
|
||
<
|
||
Similarly, |string()| should be used for "==" comparisons, otherwise |E1072|
|
||
is given. For example:
|
||
>vim9
|
||
vim9script
|
||
var l: list<any> = [9, '9.2']
|
||
echo string(l) == "[9, '9.2']" # true
|
||
echo l == "[9, '9.2']" # E1072: Cannot compare List with String
|
||
<
|
||
WARNING: Short-circuit evaluation may hide errors in boolean expressions when
|
||
an OR expression can be determined to be `true` without evaluating all
|
||
operands. Evaluation stops early, meaning invalid code may never execute,
|
||
masking errors that would otherwise occur at runtime, including Vim9 script
|
||
type-related errors. For example, a class itself cannot be used in a
|
||
comparison, but here it does not give an error in the first ternary expression
|
||
because "t" has already been evaluated to `true`:
|
||
>vim9
|
||
vim9script
|
||
var t: bool = true
|
||
echo t || null_class ? true : false # true
|
||
echo null_class || t ? true : false # E1405: Class "" cannot be used…
|
||
<
|
||
|
||
Predefined values ~
|
||
*false* *true* *null* *null_blob* *null_channel*
|
||
*null_class* *null_dict* *null_function* *null_job*
|
||
*null_list* *null_object* *null_partial* *null_string*
|
||
Vim9 script has predefined values representing true, false, and null states.
|
||
The following table lists those predefined values, along with their
|
||
|type()|, |typename()|, and |string()| representations:
|
||
|
||
Predefined value type() typename() string() ~
|
||
null_string |v:t_string| string ''
|
||
null_function |v:t_func| func(...): unknown function()
|
||
null_partial |v:t_func| func(...): unknown function('')
|
||
null_list |v:t_list| list<any> []
|
||
null_dict |v:t_dict| dict<any> {}
|
||
true |v:t_bool| bool true
|
||
false |v:t_bool| bool false
|
||
null |v:t_none| special null
|
||
null_job |v:t_job| job no process
|
||
null_channel |v:t_channel| channel channel fail
|
||
null_blob |v:t_blob| blob 0z
|
||
null_class |v:t_class| class<Unknown> class [unknown]
|
||
null_object |v:t_object| object<any> object of [unknown]
|
||
null_tuple |v:t_tuple| tuple<any> ()
|
||
|
||
The predefined value `true` is the same as `v:true`, `false` is the same as
|
||
`v:false`, and `null` is the same as `v:null`.
|
||
|
||
A "null_<type>" value is treated the same as an empty value only in some
|
||
cases. See |null-details|.
|
||
|
||
The following types do not have predefined "null_<type>" values:
|
||
- Number (instead use 0, though it is not `null`)
|
||
- Float (instead use 0.0, though, like the number 0, it also is not `null`)
|
||
- Typealias (which cannot be used as a value; it gives |E1403|)
|
||
- Enum (which cannot be used as a value; it gives |E1421|)
|
||
- EnumValue (however, it can be a `null_object` - see the "Switch" example in
|
||
|vim9-comparators|)
|
||
|
||
The "null_<type>" values can be useful for clearing script-local variables
|
||
because they cannot be deleted with `:unlet`. For example:
|
||
>vim9
|
||
vim9script
|
||
var myvar: string = "not null_string"
|
||
try
|
||
unlet myvar
|
||
catch
|
||
echo v:exception # E1081: Cannot unlet myvar
|
||
finally
|
||
myvar = null_string
|
||
echo $"'myvar' is {myvar->string()}"
|
||
endtry
|
||
<
|
||
The values can also be useful as the default value for an argument:
|
||
>vim9
|
||
vim9script
|
||
def CheckMyList(l: list<number> = null_list): string
|
||
if l == null
|
||
return 'No list was passed'
|
||
elseif l->empty()
|
||
return 'An empty list was passed'
|
||
else
|
||
return $"List {l->string()} was passed"
|
||
endif
|
||
enddef
|
||
echo CheckMyList()
|
||
echo CheckMyList([])
|
||
echo CheckMyList([8, 9])
|
||
<
|
||
Note: This examples shows comparing the list "l" against `null`, not
|
||
`null_list`. This is useful because it enables distinguishing the
|
||
default value, null_list, from an empty list []. See |null-compare|
|
||
and |null-anomalies| for more information testing against null.
|
||
|
||
It is possible to compare `null` with any value - it does not give a type
|
||
error. However, comparing `null` with a number, float or bool always results
|
||
in `false`. This is different than number and float in legacy Vim script
|
||
where comparing `v:null` with 0 or 0.0 returns 1. For example:
|
||
>vim9
|
||
vim9script
|
||
echo [0 == null, 0.0 == null] # [false, false]
|
||
legacy echo [0 == v:null, 0.0 == v:null] | # [1, 1]
|
||
<
|
||
*vim9-false-true*
|
||
When converting a boolean to a string, `false` and `true` are used. In Vim9
|
||
scripts, `v:false` is equal to `false` and `v:true` is equal to `true`.
|
||
>vim9
|
||
vim9script
|
||
echo $'{v:none} has no "none" equivalent, but'
|
||
echo $'"v:true" is stringified to "{v:true}" in a string, and'
|
||
echo 'Q: "v:false" and "false" can be used interchangeably?'
|
||
echo $'A: {v:false == false}'
|
||
<
|
||
Note: There is no "none" for `v:none` because it has no equivalent in other
|
||
languages.
|
||
|
||
*vim9-string-index*
|
||
Indexing a string with [idx] or taking a slice with [idx : idx] uses character
|
||
indexes instead of byte indexes. Combining/composing characters are included.
|
||
Example:
|
||
>vim9
|
||
vim9script
|
||
echo 'très'[2] # è (U+00E8)
|
||
legacy echo 'très'[2] | # <c3> (Illegal byte 0xc3)
|
||
<
|
||
A negative index is counting from the end, "[-1]" is the last character.
|
||
>vim9
|
||
vim9script
|
||
echo 'fenêtre'[-4 : -1] # être
|
||
legacy echo 'fenêtre'[-5 : -1] | # être (-5 because ê is two bytes)
|
||
<
|
||
Using the builtin function, |slice()|, can be a good choice because it uses
|
||
character indexes in both legacy Vim script scopes and Vim9 script scopes:
|
||
>vim9
|
||
vim9script
|
||
echo 'fenêtre'->slice(-4) # être
|
||
legacy echo 'fenêtre'->slice(-4) | # être
|
||
<
|
||
Use |strcharpart()| to count combining characters separately. The following
|
||
example uses "a" (U+0061) and a combining macron (U+0304):
|
||
>vim9
|
||
vim9script
|
||
echo 'Ngā mihi'->strcharpart(2, 1) # a (excluding combining macron)
|
||
echo 'Ngā mihi'[2] # ā (including combining macron)
|
||
<
|
||
If the index is out of range, an empty string is the result.
|
||
>vim9
|
||
vim9script
|
||
echo ['In range'[0 : 1]] # ['In']
|
||
echo ['Out of range'[19 : ]] # ['']
|
||
< *E1148*
|
||
Attempting to assign to or modify a string using an index gives an error.
|
||
For example:
|
||
>vim9
|
||
vim9script
|
||
def F1148(): void
|
||
b:s = 'Fails'
|
||
b:s[4] = '!' # E1148: Cannot index a string
|
||
enddef
|
||
F1148()
|
||
<
|
||
Note: Other errors may be given depending on the scope and whether the
|
||
variable is prefixed. For example, if the variable in this example
|
||
was declared with `:var` instead of being a buffer variable, it would
|
||
give |E1141| and, if the scope was script-local rather than a `:def`,
|
||
it would give |E689|.
|
||
|
||
In legacy Vim script, if either "++var" (|:++|) or "--var" (|:--|) are used in
|
||
a character index they are ignored. In Vim9 script, they are invalid:
|
||
>vim9
|
||
vim9script
|
||
var int: number = 1
|
||
var str: string = 'hear'
|
||
legacy echo s:str[++s:int : ] | # ear
|
||
echo str[++int : ] # E15: Invalid expression: "++int : ]
|
||
<
|
||
Numbers starting with zero are not considered to be octal, only numbers
|
||
starting with "0o" are octal: "0o744". (See |scriptversion-4|.) For example:
|
||
>vim9
|
||
vim9script
|
||
echo 017 # 17 (inferred decimal)
|
||
legacy echo 017 | # 15 (inferred octal)
|
||
echo 0o17 # 15 (explicit octal)
|
||
<
|
||
What to watch out for ~
|
||
*vim9-gotchas*
|
||
Vim9 script was designed to be closer to contemporary programming languages,
|
||
but at the same time tries to support legacy Vim commands. Some compromises
|
||
had to be made. Here is a summary of what might be unexpected.
|
||
|
||
Ex command ranges often need to be prefixed with a colon:
|
||
|
||
- A line beginning with "%" in legacy Vim script means "all lines" (|:%|)
|
||
whereas in Vim9 script it may mean modulo (|expr-%|) or give |E1050|.
|
||
For example:
|
||
>vim
|
||
" legacy Vim script
|
||
let g = 97
|
||
echo 1000
|
||
% g / 9.7
|
||
" 1000 (then prints all lines containing '9.7' to messages)
|
||
<
|
||
whereas:
|
||
>vim9
|
||
vim9script
|
||
var g = 97
|
||
echo 1000
|
||
% g / 9.7
|
||
# 3.092784 (which is 1000 % 97 / 9.7)
|
||
echo 1000
|
||
:%g/9.7
|
||
# 1000 (then prints all lines containing '9.7' to messages)
|
||
% g / 9.7
|
||
# E1050: Colon required before a range: % g / 9.7
|
||
<
|
||
- An initial apostrophe in legacy Vim script means go to mark (|'|). In Vim9
|
||
script it's either the start of a quoted string or it gives |E115|:
|
||
>vim
|
||
" legacy Vim script
|
||
:normal! mt
|
||
" Sets mark t at the line with the comment " legacy Vim script
|
||
't
|
||
" Jumps to mark t (which is at this line after sourcing this script)
|
||
<
|
||
whereas:
|
||
>vim9
|
||
vim9script
|
||
:normal! mt
|
||
# Sets mark t on the line with the vim9script command
|
||
't'->popup_notification({time: 4000}) # Vim9 script method chain
|
||
:'t
|
||
# Jumps to mark t (which is at this line after sourcing this script)
|
||
# 't by itself would be E115: Missing single quote
|
||
<
|
||
- In legacy Vim script, "->" shifts the prior line, which is prior to a range
|
||
if it is a sourced script, right by 'shiftwidth' spaces. In Vim9 script, it
|
||
is a continuation line of a chained method:
|
||
>vim9
|
||
vim9script
|
||
const mine: string = 'MINE'
|
||
->toupper()
|
||
echo mine # MINE
|
||
# In a modifiable buffer, using :-> on a line by itself would shift
|
||
# the line before vim9script to the right by 'shiftwidth' spaces when
|
||
# this script is sourced.
|
||
<
|
||
Some Ex commands can be confused with assignments in Vim9 script:
|
||
>vim
|
||
" legacy Vim script
|
||
let let = 8
|
||
echo let | " 8
|
||
:g:let = 8
|
||
" Prints lines matching 'let = 8' to messages
|
||
<whereas: >vim9
|
||
vim9script
|
||
g:let = 9
|
||
echo g:let # 9
|
||
:g:let = 9 # E1241: Separator not supported: :let = 9
|
||
<
|
||
To avoid confusion between a `:global` or `:substitute` command and an
|
||
expression or assignment, a few separators cannot be used when these commands
|
||
are abbreviated to a single character: '-', '.' and ':'. Examples using `:g`:
|
||
>vim9
|
||
vim9cmd g-pattern-cmd # E1241: Separator not supported: -pattern-cm…
|
||
vim9cmd g:pattern:cmd # E1069: White space required after ':': :cmd…
|
||
vim9cmd g.pattern.cmd # E121: Undefined variable: g
|
||
<
|
||
Also, there cannot be a space between the command and the separator, unlike in
|
||
legacy Vim script. For example:
|
||
>vim
|
||
g /[-:.]pattern/p | " This prints the three vim9cmd lines above
|
||
<
|
||
whereas in Vim9 script:
|
||
>vim9
|
||
vim9cmd g /[-:.]pattern/p # E1242: No whitespace allowed before sepa…
|
||
<
|
||
Functions defined with `:def` compile the whole function, so any syntax or
|
||
type errors will be detected during compilation, regardless of execution path.
|
||
Legacy functions have no static type checking so type-related issues (if any)
|
||
can only manifest for code that actually executes. For example, the following
|
||
legacy Vim script `:function` returns "yes" (except in the very unlikely
|
||
scenario that you source this in z/OS UNIX):
|
||
>vim9
|
||
vim9script
|
||
func Maybe()
|
||
if !has('ebcdic')
|
||
return 'yes'
|
||
endif
|
||
return [] + 'yes'
|
||
endfunc
|
||
echo Maybe() # yes
|
||
<
|
||
The equivalent Vim9 script gives |E1051|:
|
||
>vim9
|
||
vim9script
|
||
def Maybe(): any
|
||
if !has('ebcdic')
|
||
return 'yes'
|
||
endif
|
||
return [] + 'yes' # E1051: Wrong argument type for +
|
||
enddef
|
||
echo Maybe()
|
||
<
|
||
For a workaround, put the unsupported code inside a conditional with a
|
||
constant expression that evaluates to false. The compiler then skips
|
||
compiling the unsupported code. For example:
|
||
>vim9
|
||
vim9script
|
||
def Maybe(): any
|
||
if has('ebcdic')
|
||
return [] + 'yes'
|
||
endif
|
||
return 'yes'
|
||
enddef
|
||
echo Maybe() # yes
|
||
<
|
||
Another option is to split it into two functions:
|
||
>vim9
|
||
vim9script
|
||
if has('ebcdic')
|
||
# This is not compiled at all unless on an EBCDIC system
|
||
def MaybeInner(): any
|
||
return [] + 'yes'
|
||
enddef
|
||
endif
|
||
func Maybe()
|
||
if has('ebcdic')
|
||
return MaybeInner()
|
||
else
|
||
return 'yes'
|
||
endif
|
||
endfunc
|
||
echo Maybe() # yes
|
||
<
|
||
Yet another option, though this time using a different scenario, is using
|
||
`exists_compiled()`. Here the builtin function |luaeval()| will only be used
|
||
when it's available:
|
||
>vim9
|
||
vim9script
|
||
def Version(): string
|
||
if !exists_compiled('*luaeval')
|
||
return $"Version using v:version: {v:version}"
|
||
else
|
||
return $"Version via Lua: {luaeval('900 + 2')}"
|
||
endif
|
||
enddef
|
||
echo Version()
|
||
<
|
||
Unreachable code after `:return` gives an |E1095| error in a `:def` or Vim9
|
||
lambda function, whereas it is ignored in a legacy function:
|
||
>vim9
|
||
vim9script
|
||
function Legacy()
|
||
return v:true
|
||
let X = "Although this is unreachable, it's ignored."
|
||
endfunction
|
||
echo Legacy() # true
|
||
var Vim9: func = (): bool => {
|
||
return true
|
||
const X = false # E1095: Unreachable code after :return
|
||
}
|
||
< *vim9-user-command*
|
||
Another side effect of compiling a function is that the presence of a user
|
||
command is checked at compile time. If the user command is defined later an
|
||
error will result. This works:
|
||
>vim9
|
||
vim9script
|
||
command -nargs=1 MyCommand echomsg <q-args>
|
||
def Works()
|
||
MyCommand 'this works'
|
||
enddef
|
||
Works()
|
||
<
|
||
This gives |E476| for "MyCommandFails" not being defined at compile time:
|
||
>vim9
|
||
vim9script
|
||
def Fails()
|
||
command -nargs=1 MyCommandFails echomsg <q-args>
|
||
MyCommandFails 'this fails'
|
||
enddef
|
||
Fails()
|
||
<
|
||
A workaround is to invoke the command indirectly with `:execute`, like this:
|
||
>vim9
|
||
vim9script
|
||
def Works_Using_Execute()
|
||
command -nargs=1 MyCommandUsingExecute echomsg <q-args>
|
||
execute "MyCommandUsingExecute 'this works'"
|
||
enddef
|
||
Works_Using_Execute()
|
||
<
|
||
For unrecognized commands in a conditional statement, there is no bailing out.
|
||
So, if the condition evaluates to false, an invalid command will give |E171|
|
||
and, if the condition evaluates to true, an invalid command will give |E476|.
|
||
Examples:
|
||
>vim9
|
||
vim9script
|
||
def F171()
|
||
if has('ebcdic') | Nah | endif
|
||
enddef
|
||
F171() # E171: Missing :endif
|
||
< >vim9
|
||
vim9script
|
||
def F476()
|
||
if !has('ebcdic') | Nah | endif
|
||
enddef
|
||
F476() # E476: Invalid command: Nah | endif
|
||
<
|
||
|
||
Other differences ~
|
||
|
||
- Patterns use 'magic', unless explicitly overruled. That is, if the global
|
||
option 'nomagic' is set, it is ignored.
|
||
|
||
- If the option value 'edcompatible' is set, it is ignored.
|
||
|
||
- If the option value 'gdefault' is set, it is ignored.
|
||
|
||
*:++* *:--*
|
||
- The ++ and -- commands have been added. They add and subtract one
|
||
respectively. They work with number and float types. For example:
|
||
>vim9
|
||
vim9script
|
||
var flo: float = 41.0
|
||
++flo
|
||
echo flo # 42.0
|
||
<
|
||
- Using ++var or --var in an expression is not supported yet. |E15| is given
|
||
if either is used. For example:
|
||
>vim9
|
||
vim9script
|
||
var num: number = 43
|
||
# The following line gives E15: Invalid expression: "--num"
|
||
echo --num
|
||
<
|
||
|
||
==============================================================================
|
||
|
||
3. New style functions *fast-functions*
|
||
|
||
*:def*
|
||
:def[!] {name}([arguments])[: {return-type}]
|
||
Define a new function by the name {name}. The body of
|
||
the function follows in the next lines, until the
|
||
matching `:enddef`.
|
||
*E1073*
|
||
{name} cannot be reused at the script-local level:
|
||
>vim9
|
||
vim9script
|
||
def F_1073()
|
||
enddef
|
||
def F_1073() # E1073: Name already defined: <SNR...
|
||
enddef
|
||
< *E1011*
|
||
{name} must be less than 100 bytes long.
|
||
|
||
*E1077*
|
||
{arguments} is a sequence of zero or more argument
|
||
declarations. There are three forms:
|
||
{name}: {type}
|
||
{name} = {value}
|
||
{name}: {type} = {value}
|
||
The first form is a mandatory argument. So, the
|
||
declaration must provide a type. Example:
|
||
>vim9
|
||
vim9script
|
||
def F_1077(x): void
|
||
# E1077: Missing argument type for x
|
||
enddef
|
||
<
|
||
For the second form, because the declaration does not
|
||
specify it, Vim infers the type. For both second and
|
||
third forms, a default {value} applies when the
|
||
caller omits it. Examples:
|
||
>vim9
|
||
vim9script
|
||
def SecondForm(arg = "Hi"): void
|
||
echo $'2. arg is a "{arg->typename()}" type ' ..
|
||
$'and the default value of arg is "{arg}"'
|
||
enddef
|
||
SecondForm()
|
||
def ThirdForm(arg2: number = 9): void
|
||
echo $'3. default value of arg2 is {arg2}'
|
||
enddef
|
||
ThirdForm()
|
||
< *E1123*
|
||
Arguments in a builtin function called in a `:def`
|
||
function must have commas between arguments:
|
||
>vim9
|
||
vim9script
|
||
def F_1123(a: number, b: number): void
|
||
echo max(a b) # E1123: Missing comma before ar...
|
||
enddef
|
||
F_1123(1, 2)
|
||
< *E1003* *E1027* *E1096*
|
||
The type of value used with `:return` must match
|
||
{return-type}. When {return-type} is omitted or is
|
||
"void" the function is not allowed to return
|
||
anything. Examples:
|
||
>vim9
|
||
vim9script
|
||
def F_1003(): bool
|
||
return # E1003: Missing return value
|
||
enddef
|
||
F_1003()
|
||
< >vim9
|
||
vim9script
|
||
def F_1027(): bool
|
||
echo false # E1027: Missing return statement
|
||
enddef
|
||
F_1027()
|
||
< >vim9
|
||
vim9script
|
||
def F_1096(): void
|
||
return false # E1096: Returning a value in a f...
|
||
enddef
|
||
F_1096()
|
||
< *E1056* *E1059*
|
||
When ": {return-type}" is specified, {return-type}
|
||
cannot be omitted (leaving a hanging colon). The ": "
|
||
also cannot be preceded by white space. Examples:
|
||
>vim
|
||
def F_1056():
|
||
# E1056: Expected a type:
|
||
enddef
|
||
def F_1059() : bool
|
||
# E1059: No white space allowed before colon: ...
|
||
enddef
|
||
<
|
||
The function will be compiled into instructions when
|
||
called or when either `:defcompile` or `:disassemble` is
|
||
used. (For an example, see |:disassemble|.) Syntax
|
||
and type errors will be produced at that time.
|
||
|
||
*E1058*
|
||
It is possible to nest `:def` inside another `:def` or
|
||
`:function` only up to 49 levels deep. At 50 or more
|
||
levels, it is a |E1058| error.
|
||
|
||
*E1117*
|
||
[!] is allowed only in legacy Vim script because it
|
||
permits function redefinition (as with `:function`!).
|
||
In Vim9 script, ! is not allowed because script-local
|
||
functions cannot be deleted or redefined, though they
|
||
can be removed by reloading the script. Also, nested
|
||
functions cannot use ! for redefinition. Examples:
|
||
>vim
|
||
" Legacy Vim script :def! example
|
||
def! LegacyFunc()
|
||
echo "def! is allowed in a legacy Vim script"
|
||
enddef
|
||
call LegacyFunc()
|
||
< >vim9
|
||
vim9script
|
||
def F1117()
|
||
def! InnerFunc()
|
||
# E1117: Cannot use ! with nested :def
|
||
enddef
|
||
enddef
|
||
F1117()
|
||
< >vim9
|
||
vim9script
|
||
def! F_477(): void # E477: No ! allowed
|
||
enddef
|
||
< >vim9
|
||
vim9script
|
||
def F_1084(): void
|
||
enddef
|
||
delfunction! F_1084
|
||
# E1084: Cannot delete Vim9 script function F_1084
|
||
<
|
||
Note: The generic error *E1028* ("Compiling :def
|
||
function failed") indicates an undeterminable error
|
||
during compilation. If reproducible, it may be
|
||
reported at https://github.com/vim/vim/issues as
|
||
it could represent a gap in Vim's error reporting.
|
||
|
||
*:enddef*
|
||
*E1057* *E1152* *E1173*
|
||
:enddef End of a function defined with `:def`. It should be on
|
||
a line by itself. Examples:
|
||
>vim9
|
||
vim9script
|
||
def MyFunc()
|
||
echo 'Do Something' | enddef
|
||
# E1057: Missing :enddef
|
||
< >vim9
|
||
vim9script
|
||
def F_1173()
|
||
enddef echo 'X'
|
||
# E1173: Text found after enddef: echo 'X'
|
||
< >vim9
|
||
vim9script
|
||
def F_1152()
|
||
function X()
|
||
enddef # E1152: Mismatched enddef
|
||
enddef
|
||
<
|
||
You may also find this wiki useful. It was written by an early adopter of
|
||
Vim9 script: https://github.com/lacygoill/wiki/blob/master/vim/vim9.md
|
||
|
||
*vim9-s:var*
|
||
When referencing a script-local variable, the context determines whether using
|
||
the "s:" prefix is either mandatory, optional, or gives |E1268|. The context
|
||
factors are whether the script version is Vim9 script or legacy Vim script and
|
||
whether the reference is at the script-local level, within a `:def` function,
|
||
or within a `:function`. The three rules are:
|
||
|
||
1. In a `:function`, "s:" is always mandatory. This is regardless of the
|
||
script type or the function's parent context (such as nested within
|
||
another function). Similarly, "s:" is also mandatory in the script-local
|
||
scope of a legacy Vim script.
|
||
2. In a Vim9 script, "s:" always gives |E1268| when used in a `:def` function,
|
||
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.
|
||
|
||
The following three scripts demonstrate these rules:
|
||
>vim
|
||
" 1. In a :function, "s:" is always mandatory. It is also mandatory
|
||
" in a legacy Vim script's script-local scope
|
||
let s:MyVar = v:true
|
||
echo s:MyVar | " v:true
|
||
" echo MyVar (Would give E121: Undefined variable: MyVar)
|
||
vim9cmd echo MyVar # true
|
||
function! MyFunc()
|
||
echo s:MyVar | " v:true
|
||
" echo MyVar (Would give E121: Undefined variable: MyVar)
|
||
vim9cmd echo MyVar # true
|
||
endfunction
|
||
call MyFunc()
|
||
< >vim9
|
||
vim9script
|
||
# 2. In a Vim9 script, "s:" gives E1268 when used in any :def function
|
||
# and in the script-local scope
|
||
var MyVar: bool = true
|
||
echo MyVar # true
|
||
# echo s:MyVar (Would give E1268: Cannot use s: in Vim9...)
|
||
legacy echo s:MyVar | # v:true
|
||
def MyFunc()
|
||
echo MyVar # true
|
||
# echo s:MyVar (Would give E1268: Cannot use s: in Vim9...)
|
||
legacy echo s:MyVar | # v:true
|
||
enddef
|
||
MyFunc()
|
||
< >vim
|
||
" 3. In a legacy Vim script, "s:" is optional in a :def function
|
||
let s:MyVar = v:true
|
||
function! Outer()
|
||
def! MyFunc()
|
||
echo MyVar # true
|
||
echo s:MyVar # true
|
||
enddef
|
||
call MyFunc()
|
||
endfunction
|
||
call Outer()
|
||
<
|
||
Using |exists()|, which is evaluated at runtime, cannot be used conditionally
|
||
to skip undeclared variables, though |exists_compiled()|, which is evaluated at
|
||
compile time, may be used. For example:
|
||
>vim9
|
||
vim9script
|
||
def MyDef()
|
||
if exists_compiled('MyVar') # evaluated at compile time
|
||
echo $"MyVar = {MyVar}" # MyVar = 1
|
||
endif
|
||
if exists_compiled('MyVar2') # evaluated at compile time
|
||
echo $"MyVar2 = {MyVar2}" # not reached
|
||
else
|
||
echo "MyVar2 does not exist at compile time"
|
||
endif
|
||
if exists('MyVar') # evaluated at runtime
|
||
echo $"MyVar = {MyVar}" # MyVar = 1
|
||
endif
|
||
if exists('MyVar2') # evaluated at runtime
|
||
# The following would give E1001: Variable not found: MyVar2
|
||
# echo MyVar2
|
||
else
|
||
echo "MyVar2 does not exist at runtime"
|
||
endif
|
||
enddef
|
||
var MyVar: number = 1 # Declared before MyDef() is compiled
|
||
MyDef()
|
||
var MyVar2: number = 2 # Declared after MyDef() is compiled
|
||
<
|
||
*E1269*
|
||
Script-local variables in a Vim9 script must be declared at the script
|
||
level. They cannot be created in a `:def` function and may not be declared
|
||
in a legacy function with the "s:" prefix. For example:
|
||
>vim9
|
||
vim9script
|
||
function F_1269()
|
||
let s:i_wish = v:true
|
||
endfunction
|
||
F_1269()
|
||
# E1269: Cannot create a Vim9 script variable in a function: s:i_wish
|
||
<
|
||
*:defc* *:defcompile*
|
||
:defc[ompile] Compile functions and classes (|class-compile|)
|
||
defined in the current script that were not compiled
|
||
yet. This will report any errors found during
|
||
compilation.
|
||
|
||
Example: When the three lines (up to and including
|
||
`enddef`) are sourced, there is no error because the
|
||
Vim9 `:def` function is not compiled. However, if all
|
||
four lines are sourced, compilation fails:
|
||
>vim9
|
||
vim9script
|
||
def F_1027(): string
|
||
enddef
|
||
defcompile F_1027 # E1027: Missing return statement
|
||
<
|
||
:defc[ompile] MyClass Compile all methods in a class. (See |:disassemble|
|
||
for an example.)
|
||
|
||
:defc[ompile] {func}
|
||
:defc[ompile] debug {func}
|
||
:defc[ompile] profile {func}
|
||
Compile function {func}, if needed. Use "debug" and
|
||
"profile" to specify the compilation mode.
|
||
This will report any errors found during compilation.
|
||
{func} can also be "ClassName.functionName" to
|
||
compile a function or method in a class.
|
||
{func} can also be "ClassName" to compile all
|
||
functions and methods in a class.
|
||
|
||
*:disa* *:disassemble*
|
||
:disa[ssemble] {func} Show the instructions generated for {func}.
|
||
This is for debugging and testing.
|
||
If {func} is not found, error *E1061* occurs.
|
||
{func} can also be "ClassName.functionName" to
|
||
disassemble a function in a class.
|
||
The following example demonstrates using `:defcompile`
|
||
with a |class| and `:disassemble` with a
|
||
"ClassName.functionName" (positioning the cursor on
|
||
the last line of the visually sourced script):
|
||
>vim9
|
||
vim9script
|
||
class Line
|
||
var lnum: number
|
||
def new(this.lnum)
|
||
enddef
|
||
def SetLnum()
|
||
cursor(this.lnum, 52)
|
||
enddef
|
||
endclass
|
||
defcompile Line
|
||
disassemble Line.SetLnum
|
||
var vlast: Line = Line.new(line("'>"))
|
||
vlast.SetLnum() # Cursor is positioned here->_
|
||
<
|
||
:disa[ssemble] profile {func}
|
||
Like `:disassemble` but with the instructions used for
|
||
profiling.
|
||
|
||
:disa[ssemble] debug {func}
|
||
Like `:disassemble` but with the instructions used for
|
||
debugging.
|
||
|
||
Note: For command line completion of {func}, script-local functions
|
||
are shown with their <SNR>. Depending on options, including
|
||
|wildmenumode()|, completion may work with "s:", "<S", or the function
|
||
name directly. (For example, in Vim started with |-u| NONE,
|
||
":disa s:" and |c_CTRL-E| lists script-local function names.)
|
||
|
||
|
||
Limitations ~
|
||
|
||
Variables local to `:def` functions are not visible to string evaluation.
|
||
The following example shows that the script-local constant "SCRIPT_LOCAL" is
|
||
visible whereas the function-local constant "DEF_LOCAL" is not:
|
||
>vim9
|
||
vim9script
|
||
const SCRIPT_LOCAL = ['A', 'script-local', 'list']
|
||
def MapList(scope: string): list<string>
|
||
const DEF_LOCAL: list<string> = ['A', 'def-local', 'list']
|
||
if scope == 'script-local'
|
||
return [1]->map('SCRIPT_LOCAL[v:val]')
|
||
else
|
||
return [1]->map('DEF_LOCAL[v:val]')
|
||
endif
|
||
enddef
|
||
echo 'script-local'->MapList() # ['script-local']
|
||
echo 'def-local'->MapList() # E121: Undefined variable: DEF_LOCAL
|
||
<
|
||
The map argument is a string expression, which is evaluated without the
|
||
function scope. Instead, in Vim9 script, use a lambda:
|
||
>vim9
|
||
vim9script
|
||
def MapList(): list<string>
|
||
const DEF_LOCAL: list<string> = ['A', 'def-local', 'list']
|
||
return [1]->map((_, v): string => DEF_LOCAL[v])
|
||
enddef
|
||
echo MapList() # ['def-local']
|
||
<
|
||
For commands that are not compiled, such as `:edit`, |backtick-expansion| can
|
||
be used and it can use the local scope. Example:
|
||
>vim9
|
||
vim9script
|
||
def EditNewBlah()
|
||
var fname: string = 'blah.txt'
|
||
split
|
||
edit `=fname`
|
||
enddef
|
||
EditNewBlah() # A new split is created as buffer 'blah.txt'
|
||
<
|
||
*vim9-closure*
|
||
Closures defined in a loop can either share a variable or each have their own
|
||
copy, depending on where the variable is declared. With a variable declared
|
||
outside the loop, all closures reference the same shared variable.
|
||
The following example demonstrates the consequences, with the "outloop"
|
||
variable existing only once:
|
||
>vim9
|
||
vim9script
|
||
var flist: list<func>
|
||
def ClosureEg(n: number): void
|
||
var outloop: number = 0 # outloop is declared outside the loop!
|
||
for i in range(n)
|
||
outloop = i
|
||
flist[i] = (): number => outloop # Closures ref the same var
|
||
endfor
|
||
echo range(n)->map((i, _): number => flist[i]())
|
||
enddef
|
||
ClosureEg(4) # [3, 3, 3, 3]
|
||
<
|
||
All closures put in the list refer to the same instance, which, in the end,
|
||
is 3.
|
||
|
||
However, when the variable is declared inside the loop, each closure gets its
|
||
own copy, as shown in this example:
|
||
>vim9
|
||
vim9script
|
||
var flist: list<func>
|
||
def ClosureEg(n: number): void
|
||
for i in range(n)
|
||
var inloop: number = i # inloop is declared inside the loop
|
||
flist[i] = (): number => inloop # Closures ref each inloop
|
||
endfor
|
||
echo range(n)->map((i, _): number => flist[i]())
|
||
enddef
|
||
ClosureEg(4) # [0, 1, 2, 3]
|
||
<
|
||
Another way to have a separate context for each closure is to call a
|
||
function to define it:
|
||
>vim9
|
||
vim9script
|
||
def GetClosure(i: number): func
|
||
var infunc: number = i
|
||
return (): number => infunc
|
||
enddef
|
||
var flist: list<func>
|
||
def ClosureEg(n: number): void
|
||
for i in range(n)
|
||
flist[i] = GetClosure(i)
|
||
endfor
|
||
echo range(n)->map((i, _): number => flist[i]())
|
||
enddef
|
||
ClosureEg(4) # [0, 1, 2, 3]
|
||
< *E1271*
|
||
A closure must be compiled in the context that it is defined in, so that
|
||
variables in that context can be found. This mostly happens correctly,
|
||
except when a function is marked for debugging with `:breakadd` after it was
|
||
compiled. Make sure to define the breakpoint before compiling the outer
|
||
function.
|
||
*E1248*
|
||
In some situations, such as when a Vim9 closure which captures local variables
|
||
is converted to a string and then executed, an error occurs. This happens
|
||
because the string execution context cannot access the local variables from
|
||
the original context where the closure was defined. For example:
|
||
>vim9
|
||
vim9script
|
||
def F_1248(): void
|
||
var n: number
|
||
var F: func = () => {
|
||
n += 1
|
||
}
|
||
try
|
||
execute printf("call %s()", F)
|
||
catch
|
||
echo v:exception
|
||
endtry
|
||
enddef
|
||
F_1248() # Vim(call):E1248: Closure called from invalid context
|
||
<
|
||
In Vim9 script, a loop variable is invalid after the loop is closed.
|
||
For example, this timer will echo 0 to 2 on separate lines. However, if
|
||
the variable "n" is used after the `:endfor`, that is an |E121| error:
|
||
>vim9
|
||
vim9script
|
||
for n in range(3)
|
||
var nr: number = n
|
||
timer_start(1000 * n, (_) => {
|
||
echowindow nr
|
||
})
|
||
endfor
|
||
try
|
||
echowindow n
|
||
catch
|
||
echo v:exception
|
||
endtry
|
||
<
|
||
Note: Using `:echowindow` is useful in a timer because messages go
|
||
into a popup and will not interfere with what the user is doing when
|
||
it triggers.
|
||
|
||
|
||
Converting a :function to a :def~
|
||
*convert_legacy_function_to_vim9*
|
||
*convert_:function_to_:def*
|
||
There are many changes that need to be made to convert a `:function` to
|
||
a `:def` function. The following are some of them:
|
||
|
||
- Change `let` used to declare variables to one of `var`, `const`, or `final`,
|
||
and remove the "s:" from each |script-variable|.
|
||
- Change `func` or `function` to `def`.
|
||
- Change `endfunc` or `endfunction` to `enddef`.
|
||
- Add the applicable type (or "any") to each function argument.
|
||
- Remove "a:" from each |function-argument|.
|
||
- Remove inapplicable options such as |:func-range|, |:func-abort|,
|
||
|:func-dict|, and |:func-closure|.
|
||
- If the function returns something, add the return type. (Ideally, add
|
||
"void" if it does not return anything.)
|
||
- Remove line continuation backslashes from places they are not required.
|
||
- Remove `let` for assigning values to global (|g:|), buffer (|b:|),
|
||
window (|w:|), tab (|t:|), and local (|l:|) variables.
|
||
- Rewrite |lambda| expressions in Vim9 script syntax (see |vim9-lambda|).
|
||
- Change comments to start with # (preceded by white space) instead of ".
|
||
- Insert white space in expressions where required (see |vim9-white-space|).
|
||
- Change "." used for string concatenation to " .. ". (Alternatively, use
|
||
an |interpolated-string|.)
|
||
|
||
The following legacy Vim script and Vim9 script examples demonstrate all
|
||
those differences. First, legacy Vim script:
|
||
>vim
|
||
let s:lnum=0
|
||
function Leg8(arg) abort
|
||
let l:pre=['Result',
|
||
\': ']
|
||
let b:arg=a:arg
|
||
let s:lnum+=2
|
||
let b:arg*=4
|
||
let l:result={pre->join(pre,'')}(l:pre)
|
||
return l:result.(b:arg+s:lnum)"no space before comment
|
||
endfunction
|
||
call Leg8(10)->popup_notification(#{time: 3000})" Pops up 'Result: 42'
|
||
<
|
||
The equivalent in Vim9 script:
|
||
>vim9
|
||
vim9script
|
||
var lnum: number
|
||
def Vim9(arg: number): string
|
||
final pre = ['Result',
|
||
': ']
|
||
b:arg = arg
|
||
lnum += 2
|
||
b:arg *= 4
|
||
const RESULT: string = ((lpre) => join(lpre, ''))(pre)
|
||
return RESULT .. (b:arg + lnum) # space required before # comment
|
||
enddef
|
||
Vim9(10)->popup_notification({time: 3000}) # Pops up 'Result: 42'
|
||
<
|
||
Note: This example also demonstrates (outside the `:def` function):
|
||
- Removing "#" from the legacy |#{}| - see |vim9-literal-dict|, and
|
||
- Omitting `:call` (allowed, though unnecessary in Vim9 script)
|
||
|
||
|
||
Calling a :def function in an expr option ~
|
||
*expr-option-function*
|
||
The value of a few options, such as 'foldexpr', is an expression that is
|
||
evaluated to get a value. The evaluation can have quite a bit of overhead.
|
||
One way to minimize the overhead, and also to keep the option value simple,
|
||
is to define a compiled function and set the option to call it without
|
||
arguments. For example:
|
||
>vim9
|
||
vim9script
|
||
def MyFoldFunc(): string
|
||
# This matches start of line (^), followed by a digit, a full stop
|
||
# a space or tab, an uppercase character, with an empty next line
|
||
return getline(v:lnum) =~ '^[[:digit:]]\.[[:blank:]][[:upper:]]'
|
||
&& getline(v:lnum + 1)->empty() ? '>1' : '1'
|
||
enddef
|
||
set foldexpr=MyFoldFunc()
|
||
set foldmethod=expr
|
||
normal! zM
|
||
<
|
||
Warning: This script creates and applies folds at the "Heading 1" level of
|
||
this vim9.txt help buffer. (You can use |zR|, in Normal mode, to open all the
|
||
folds after sourcing the script.)
|
||
|
||
|
||
==============================================================================
|
||
|
||
4. Types *vim9-types*
|
||
|
||
The following types, each shown with its corresponding internal |v:t_TYPE|
|
||
variable, are supported:
|
||
|
||
number |v:t_number|
|
||
string |v:t_string|
|
||
func |v:t_func|
|
||
func: {type} |v:t_func|
|
||
func({type}, ...) |v:t_func|
|
||
func({type}, ...): {type} |v:t_func|
|
||
list<{type}> |v:t_list|
|
||
dict<{type}> |v:t_dict|
|
||
float |v:t_float|
|
||
bool |v:t_bool|
|
||
none |v:t_none|
|
||
job |v:t_job|
|
||
channel |v:t_channel|
|
||
blob |v:t_blob|
|
||
class |v:t_class|
|
||
object |v:t_object|
|
||
typealias |v:t_typealias|
|
||
enum |v:t_enum|
|
||
enumvalue |v:t_enumvalue|
|
||
tuple<{type}> |v:t_tuple|
|
||
tuple<{type}, {type}, ...> |v:t_tuple|
|
||
tuple<...list<{type}>> |v:t_tuple|
|
||
tuple<{type}, ...list<{type}>> |v:t_tuple|
|
||
void
|
||
|
||
*E1031* *E1186*
|
||
These types can be used in declarations, though no simple value can have the
|
||
"void" type. Trying to use a void as a value results in an error.
|
||
Examples:
|
||
>vim9
|
||
vim9script
|
||
def NoReturnValue(): void
|
||
enddef
|
||
try
|
||
const X: any = NoReturnValue()
|
||
catch
|
||
echo v:exception # E1031: Cannot use void value
|
||
try
|
||
echo NoReturnValue()
|
||
catch
|
||
echo v:exception # E1186: Expression does not result in a valu...
|
||
endtry
|
||
endtry
|
||
< *E1008* *E1009* *E1010* *E1012*
|
||
Ill-formed declarations and mismatching types result in errors. The following
|
||
are examples of errors E1008, E1009, E1010, and E1012:
|
||
>vim9
|
||
vim9cmd var l_1008: list
|
||
vim9cmd var l_1009: list<number
|
||
vim9cmd var l_1010: list<invalidtype>
|
||
vim9cmd var l_1012: list<number> = ['42']
|
||
<
|
||
*E1439*
|
||
A type cannot be nested too deeply, e.g. a "list<list<...>>" type nested more
|
||
than 1000 times results in an error. This includes nesting by using type
|
||
aliases.
|
||
|
||
There is no array type. Instead, use either a list or a tuple. Those types
|
||
may also be literals (constants). In the following example, [5, 6] is a list
|
||
literal and (7, ) a tuple literal. The echoed list is a list literal too:
|
||
>vim9
|
||
vim9script
|
||
var l: list<number> = [1, 2]
|
||
var t: tuple<...list<number>> = (3, 4)
|
||
echo [l, t, [5, 6], (7, )]
|
||
<
|
||
*tuple-type*
|
||
A tuple type may be declared in the following ways:
|
||
tuple<number> a tuple with a single item of type |Number|
|
||
tuple<number, string> a tuple with two items of type |Number| and
|
||
|String|
|
||
tuple<number, float, bool> a tuple with three items of type |Number|,
|
||
|Float| and |Boolean|
|
||
tuple<...list<number>> a variadic tuple with zero or more items of
|
||
type |Number|
|
||
tuple<number, ...list<string>> a tuple with an item of type |Number| followed
|
||
by zero or more items of type |String|
|
||
|
||
Examples:
|
||
>vim9
|
||
vim9script
|
||
var t1: tuple<number> = (20,)
|
||
var t2: tuple<number, string> = (30, 'vim')
|
||
var t3: tuple<number, float, bool> = (40, 1.1, true)
|
||
var t4: tuple<...list<string>> = ('a', 'b', 'c')
|
||
var t5: tuple<number, ...list<string>> = (3, 'a', 'b', 'c', 'd')
|
||
<
|
||
*variadic-tuple* *E1539*
|
||
A variadic tuple has zero or more items of the same type. The type of a
|
||
variadic tuple must end with a list type otherwise E1539 is given. Examples:
|
||
>vim9
|
||
vim9script
|
||
var t6: tuple<...list<number>> = (1, 2, 3)
|
||
var t7: tuple<...list<string>> = ('a', 'b', 'c')
|
||
var t8: tuple<...list<bool>> = ()
|
||
var tE: tuple<...any> = () # E1539: Variadic tuple must end with li...
|
||
<
|
||
*vim9-func-declaration*
|
||
*vim9-partial-declaration*
|
||
*vim9-func-type*
|
||
A function (or partial) may be declared in many ways, including:
|
||
>
|
||
func
|
||
func: void
|
||
func: {type}
|
||
func()[: void]
|
||
func(): {type}
|
||
func({type})[: void]
|
||
func({type}): {type}
|
||
func(?{type})[: void]
|
||
func(?{type}): {type}
|
||
func(...list<{type}>)[: void]
|
||
func(...list<{type}>): {type}
|
||
func({type}, ...list<{type}>)[: void]
|
||
func({type}, ...list<{type}>): {type}
|
||
<
|
||
If the return type is "void", the function does not return a value. All the
|
||
ways listed above are explained and demonstrated in the following examples,
|
||
all of which echo '9'.
|
||
|
||
func
|
||
- any kind of function reference
|
||
- any type of return value or no return value
|
||
>vim9
|
||
vim9script
|
||
const I = (n) => n + 1
|
||
const F: func = I->function()
|
||
echo F(8)
|
||
<
|
||
func: void
|
||
- any number and type of arguments
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
def I(n: number): void
|
||
echo n + 1
|
||
enddef
|
||
const F: func: void = I->function()
|
||
F(8)
|
||
<
|
||
func: {type}
|
||
- any number and type of arguments
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
const I = (n): number => n + 1
|
||
const F: func: number = I->function()
|
||
echo F(8)
|
||
<
|
||
func()[: void]
|
||
- no arguments
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
var n: number = 8
|
||
def I(): void
|
||
echo n + 1
|
||
enddef
|
||
const F: func(): void = I->function()
|
||
F()
|
||
<
|
||
func(): {type}
|
||
- no arguments
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
var n: number = 8
|
||
def I(): number
|
||
return n + 1
|
||
enddef
|
||
const F: func(): number = I->function()
|
||
echo F()
|
||
<
|
||
func({type})[: void]
|
||
- typed argument
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
def I(n: number): void
|
||
echo n + 1
|
||
enddef
|
||
const F: func(number): void = I->function()
|
||
F(8)
|
||
<
|
||
func({type}): {type}
|
||
- typed argument
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
const I = (n: number): number => n + 1
|
||
const F: func(number): number = I->function()
|
||
echo F(8)
|
||
<
|
||
func(?{type})[: void]
|
||
- optional typed argument
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
def I(n: number = 8): void
|
||
echo n + 1
|
||
enddef
|
||
const F: func(?number): void = I->function()
|
||
F()
|
||
<
|
||
func(?{type}): {type}
|
||
- optional typed argument
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
def I(n: number = 8): number
|
||
return n + 1
|
||
enddef
|
||
const F: func(?number): number = I->function()
|
||
echo F()
|
||
<
|
||
func(...list<{type}>)[: void]
|
||
- typed list for variable number of arguments
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
def L(...l: list<number>): void
|
||
echo l->reduce((x, y) => x + y)
|
||
enddef
|
||
const F: func(...list<number>): void = L->function()
|
||
F(8, 0, 0, 1)
|
||
<
|
||
func(...list<{type}>): {type}
|
||
- typed list for variable number of arguments
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
def L(...l: list<number>): number
|
||
return l->reduce((x, y) => x + y)
|
||
enddef
|
||
const F: func(...list<number>): number = L->function()
|
||
echo F(8, 0, 0, 1)
|
||
<
|
||
func({type}, ...list<{type}>)[: void]
|
||
- typed mandatory argument
|
||
- typed list for variable number of arguments
|
||
- does not return a value
|
||
>vim9
|
||
vim9script
|
||
def D(a: float, ...l: list<number>): void
|
||
echo (a + l->reduce((x, y) => x + y))->float2nr()
|
||
enddef
|
||
const F: func(float, ...list<number>): void = D->function()
|
||
F(8.0, 0, 0, 1)
|
||
<
|
||
func({type}, ...list<{type}>): {type}
|
||
- typed mandatory argument
|
||
- typed list for variable number of arguments
|
||
- returns a typed value
|
||
>vim9
|
||
vim9script
|
||
def D(a: float, ...l: list<number>): number
|
||
return (a + l->reduce((x, y) => x + y))->float2nr()
|
||
enddef
|
||
const F: func(float, ...list<number>): number = D->function()
|
||
echo F(8.0, 0, 0, 1)
|
||
<
|
||
The reference can also be a |Partial|, in which case it stores extra arguments
|
||
and/or a dictionary, which are not visible to the caller. Since they are
|
||
called in the same way, the declaration is the same. This interactive example
|
||
prompts for a circle's radius and returns its area using a partial:
|
||
>vim9
|
||
vim9script
|
||
def CircleArea(pi: float, radius: float): float
|
||
return pi * radius->pow(2)
|
||
enddef
|
||
const AREA: func(float): float = CircleArea->function([3.14])
|
||
const RADIUS: float = "Enter a radius value: "->input()->str2float()
|
||
echo $"\nThe area of a circle with a radius of {RADIUS} is " ..
|
||
$"{AREA(RADIUS)} (π to two d.p.)"
|
||
<
|
||
*E1005*
|
||
No more than 19 argument types may be used in a Funcref (one less than what is
|
||
allowed in a function, including a `:def` or lambda - see |E740|). Twenty or
|
||
more argument types gives E1005, as this example shows:
|
||
>vim9
|
||
vim9script
|
||
var X: func
|
||
X = (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) => 0
|
||
var F: func(any, any, any, any, any, any, any, any, any, any, any,
|
||
\ any, any, any, any, any, any, any, any, any): any = X->function()
|
||
# E1005: Too many argument types
|
||
<
|
||
*E1007*
|
||
A mandatory argument may not come after an optional argument in a Funcref:
|
||
>vim9
|
||
vim9script
|
||
def I(n: number, f: float): void
|
||
echo n + f
|
||
enddef
|
||
const F: func(?number, float) = I->function()
|
||
# E1007: Mandatory argument after optional argument
|
||
<
|
||
*vim9-typealias-type*
|
||
Custom types (|typealias|) can be defined with `:type`. They must start with
|
||
a capital letter (which avoids name clashes with either current or future
|
||
builtin types) similar to user functions. This example creates a list of
|
||
perfect squares, reporting on |type()| (14, a typealias) and the |typename()|:
|
||
>vim9
|
||
vim9script
|
||
type Ln = list<number>
|
||
final perfect_squares: Ln = [1, 4, 9, 16, 25]
|
||
echo "Typename (Ln): " ..
|
||
$"type() is {Ln->type()} and typename() is {Ln->typename()}"
|
||
<
|
||
*E1105*
|
||
A typealias itself cannot be converted to a string:
|
||
>vim9
|
||
vim9script
|
||
type Ln = list<number>
|
||
const FAILS: func = (): string => {
|
||
echo $"{Ln}" # E1105: Cannot convert typealias to string
|
||
}
|
||
< *vim9-class-type* *vim9-interface-type*
|
||
*vim9-object-type*
|
||
A |class|, |object|, and |interface| may all be used as types. The following
|
||
interactive example prompts for a float value and returns the area of two
|
||
different shapes. It also reports on the |type()| and |typename()| of the
|
||
classes, objects, and interface:
|
||
>vim9
|
||
vim9script
|
||
interface Shape
|
||
def InfoArea(): tuple<string, float>
|
||
endinterface
|
||
class Circle implements Shape
|
||
var radius: float
|
||
def InfoArea(): tuple<string, float>
|
||
return ('Circle (π × r²)', 3.141593 * this.radius->pow(2))
|
||
enddef
|
||
endclass
|
||
class Square implements Shape
|
||
var side: float
|
||
def InfoArea(): tuple<string, float>
|
||
return ('Square (s²)', this.side->pow(2))
|
||
enddef
|
||
endclass
|
||
const INPUT: float = "Enter a float value: "->input()->str2float()
|
||
echo "\nAreas of shapes:"
|
||
var myCircle: object<Circle> = Circle.new(INPUT)
|
||
var mySquare: object<Square> = Square.new(INPUT)
|
||
final shapes: list<Shape> = [myCircle, mySquare]
|
||
for shape in shapes
|
||
const [N: string, A: float] = shape.InfoArea()
|
||
echo $"\t- {N} has area of {A}"
|
||
endfor
|
||
echo "\n\t\ttype()\ttypename()\n\t\t------\t----------"
|
||
echo $"Circle\t\t{Circle->type()}\t{Circle->typename()}"
|
||
echo $"Square\t\t{Square->type()}\t{Square->typename()}"
|
||
echo $"Shape\t\t{Shape->type()}\t{Shape->typename()}"
|
||
echo $"MyCircle\t{myCircle->type()}\t{myCircle->typename()}"
|
||
echo $"MySquare\t{mySquare->type()}\t{mySquare->typename()}"
|
||
echo $"shapes\t\t{shapes->type()}\t{shapes->typename()}"
|
||
<
|
||
*vim9-enum-type* *vim9-enumvalue-type*
|
||
An |enum| may be used as a type (|v:t_enum|). Variables holding enum values
|
||
have the enumvalue type (|v:t_enumvalue|) at runtime. The following
|
||
interactive example prompts for a character and returns information about
|
||
either a square or a rhombus. It also reports on the |type()| and the
|
||
|typename()| of the enum and enumvalue:
|
||
>vim9
|
||
vim9script
|
||
enum Quad
|
||
Square('four', 'only'),
|
||
Rhombus('opposite', 'no')
|
||
var eq: string
|
||
var ra: string
|
||
def string(): string
|
||
return $"\nA {this.name} has " ..
|
||
$"{this.eq} equal sides and {this.ra} right angles\n\n"
|
||
enddef
|
||
endenum
|
||
echo "Rhombus (r) or Square (s)?"
|
||
var myQuad: Quad = getcharstr() =~ '\c^R' ? Quad.Rhombus : Quad.Square
|
||
echo myQuad.string() .. "\ttype()\ttypename()"
|
||
echo $"Quad \t{Quad->type()} \t{Quad->typename()}"
|
||
echo $"myQuad\t{myQuad->type()}\t{myQuad->typename()}"
|
||
<
|
||
Notes: This uses builtin method "string()" - see |object-string()|.
|
||
The typename() of Quad and myQuad are the same ("enum<Quad>") whereas
|
||
the type() is distinguished (myQuad returns 16, which is an EnumValue,
|
||
whereas Quad returns 15, which is an Enum).
|
||
|
||
Variable types and type casting ~
|
||
*variable-types*
|
||
Variables declared in Vim9 script or in a `:def` function have a type, either
|
||
specified explicitly or inferred from the initialization.
|
||
|
||
Global, buffer, window and tab page variables do not have a specific type.
|
||
Consequently, their values may change at any time, possibly changing the type.
|
||
Therefore, in compiled code, the "any" type is assumed.
|
||
|
||
This can be a problem when stricter typing is desired, for example, when
|
||
declaring a list: >
|
||
var l: list<number> = [1, b:two]
|
||
Since Vim doesn't know the type of "b:two", the expression becomes list<any>.
|
||
A runtime check verifies the list matches the declared type before assignment.
|
||
|
||
*type-casting*
|
||
To get more specific type checking, use type casting. This checks the
|
||
variable's type before building the list, rather than checking whether
|
||
list items match the declared type. For example: >
|
||
var l: list<number> = [1, <number>b:two]
|
||
<
|
||
So, here the type cast checks whether "b:two" is a number and gives an error
|
||
if it isn't.
|
||
|
||
The difference is demonstrated in the following example. With funcref
|
||
variable "NTC", Vim infers the expression type "[1, b:two]" as list<any>, then
|
||
verifies whether it can be assigned to the list<number> return type. With
|
||
funcref variable "TC", the type cast means Vim first checks whether "b:two" is
|
||
a <number> type:
|
||
>vim9
|
||
vim9script
|
||
b:two = '2'
|
||
const NTC: func = (): list<number> => {
|
||
return [1, b:two]
|
||
}
|
||
disassemble NTC # 3 CHECKTYPE list<number> stack [-1]
|
||
try
|
||
NTC()
|
||
catch
|
||
echo v:exception .. "\n\n" # E1012: Type mismatch; Expected list...
|
||
endtry
|
||
const TC: func = (): list<number> => {
|
||
return [1, <number>b:two]
|
||
}
|
||
disassemble TC # 2 CHECKTYPE number stack [-1]
|
||
try
|
||
TC()
|
||
catch
|
||
echo v:exception # E1012: Type mismatch; Expected number but got...
|
||
endtry
|
||
<
|
||
Note: Notice how the error messages differ, showing when type checking
|
||
occurs.
|
||
|
||
*E1104*
|
||
The syntax of a type cast is "<{type}>". An error occurs if either the
|
||
opening "<" (|E121|) or closing ">" (E1104) is omitted. Also, white space
|
||
is not allowed either after the "<" (|E15|) or before the ">" (|E1068|), which
|
||
avoids ambiguity with smaller-than and greater-than operators.
|
||
|
||
Although a type casting forces explicit type checking, it neither changes the
|
||
value of, nor the type of, a variable. If you need to alter the type, use a
|
||
function such as |string()| to convert to a string, or |str2nr()| to convert a
|
||
string to a number.
|
||
|
||
If type casting is applied to a chained expression, it must be compatible with
|
||
the final result. Examples:
|
||
>vim9
|
||
vim9script
|
||
# These type casts work
|
||
echo <list<any>>[3, 2, 1]->extend(['Go!'])
|
||
echo <string>[3, 2, 1]->extend(['Go!'])->string()
|
||
echo <tuple<...list<number>>>[3, 2, 1]->list2tuple()
|
||
# This type cast fails
|
||
echo <number>[3, 2, 1]->extend(['Go!'])->string()
|
||
<
|
||
*E1272*
|
||
If a type is used in a context where types are not expected you can get
|
||
E1272. For example: >
|
||
:vim9cmd echo islocked('x: string')
|
||
< Note: To see the error, this command must be executed from Vim's command
|
||
line, not sourced.
|
||
|
||
*E1363* *E1395*
|
||
If a type is incomplete, such as when an object's class is unknown, E1363
|
||
or E1395 are given. Examples:
|
||
>vim9
|
||
vim9script
|
||
var E1363 = null_class.member # E1363: Incomplete type
|
||
< >vim9
|
||
vim9script
|
||
def F1395(): void
|
||
echo null_class.member
|
||
enddef
|
||
F1395() # E1395: Using a null class
|
||
<
|
||
Another null object-related error is |E1360|:
|
||
>vim9
|
||
vim9script
|
||
var obj = null_object
|
||
var E1360 = obj.MyMethod() # E1360: Using a null object
|
||
<
|
||
|
||
Type inference ~
|
||
*type-inference*
|
||
Declaring types explicitly provides many benefits, including targeted type
|
||
checking and clearer error messages. Nonetheless, Vim often can infer types
|
||
automatically when they are omitted. For example, each of these variables'
|
||
types are inferred, with the |type()| and |typename()| echoed showing those
|
||
inferred types:
|
||
>vim9
|
||
vim9script
|
||
echo "\t type()\t typename()"
|
||
var b = true | echo $"{b} \t {b->type()} \t {b->typename()}"
|
||
var f = 4.2 | echo $"{f} \t {f->type()} \t {f->typename()}"
|
||
var l = [1, 2] | echo $"{l} \t {l->type()} \t {l->typename()}"
|
||
var n = 42 | echo $"{n} \t {n->type()} \t {n->typename()}"
|
||
var s = 'yes' | echo $"{s} \t {s->type()} \t {s->typename()}"
|
||
var t = (42, ) | echo $"{t} \t {t->type()} \t {t->typename()}"
|
||
<
|
||
The type of a list, tuple, or dictionary is inferred from the common type of
|
||
its values. When the values are all the same type, that type is used.
|
||
If there is a mix of types, the "any" type is used. In the following example,
|
||
the echoed |typename()| for each literal demonstrates these points:
|
||
>vim9
|
||
vim9script
|
||
echo [1, 2]->typename() # list<number>
|
||
echo [1, 'x']->typename() # list<any>
|
||
echo {ints: [1, 2], bools: [false]}->typename() # dict<list<any>>
|
||
echo (true, false)->typename() # tuple<bool, bool>
|
||
<
|
||
The common type of function references, when they do not all have the same
|
||
number of arguments, is indicated with "(...)", meaning the number of
|
||
arguments is unequal. This script demonstrates a "list<func(...): void>":
|
||
>vim9
|
||
vim9script
|
||
def Foo(x: bool): void
|
||
enddef
|
||
def Bar(x: bool, y: bool): void
|
||
enddef
|
||
var funclist = [Foo, Bar]
|
||
echo funclist->typename()
|
||
<
|
||
Script-local variables in a Vim9 script are type checked. The type is
|
||
also checked for variables declared in a legacy function. For example:
|
||
>vim9
|
||
vim9script
|
||
var my_local = (1, 2)
|
||
function Legacy()
|
||
let b:legacy = [1, 2]
|
||
endfunction
|
||
Legacy()
|
||
echo $"{my_local} is type {my_local->type()} ({my_local->typename()})"
|
||
echo $"{b:legacy} is type {b:legacy->type()} ({b:legacy->typename()})"
|
||
<
|
||
*E1013*
|
||
When a type is declared for a List, Tuple, or Dictionary, the type is attached
|
||
to it. Similarly, if a type is not declared, the type Vim infers is attached.
|
||
In either case, if an expression attempts to change the type, E1013 results.
|
||
This example has its type inferred and demonstrates E1013:
|
||
>vim9
|
||
vim9script
|
||
var lb = [true, true] # Two bools, so Vim infers list<bool> type
|
||
echo lb->typename() # list<bool>
|
||
lb->extend([0]) # E1013 Argument 2: type mismatch, ...
|
||
<
|
||
If you want a permissive list, either explicitly use <any> or declare an
|
||
empty list initially (or both, i.e., `list<any> = []`). Examples:
|
||
>vim9
|
||
vim9script
|
||
final la: list<any> = []
|
||
echo la->extend(['two', 1])
|
||
final le = []
|
||
echo le->extend(la)
|
||
<
|
||
Similarly for a permissive dictionary:
|
||
>vim9
|
||
vim9script
|
||
final da: dict<any> = {}
|
||
echo da->extend({2: 2, 1: 'One'})
|
||
final de = {}
|
||
echo de->extend(da)->string()
|
||
<
|
||
And, although tuples themselves are immutable, permissive tuple concatenation
|
||
can be achieved with either "any" or an empty tuple:
|
||
>vim9
|
||
vim9script
|
||
var t_any: tuple<...list<any>> = (3, '2')
|
||
t_any = t_any + (true, )
|
||
echo t_any
|
||
var t_dec_empty = ()
|
||
t_dec_empty = t_dec_empty + (3, '2', true)
|
||
echo t_dec_empty
|
||
<
|
||
If a list literal or dictionary literal is not bound to a variable, its type
|
||
may change, as this example shows:
|
||
>vim9
|
||
vim9script
|
||
echo [3, 2, 1]->typename() # list<number>
|
||
echo [3, 2, 1]->extend(['Zero'])->typename() # list<any>
|
||
echo {1: ['One']}->typename() # dict<list<string>>
|
||
echo {1: ['One']}->extend({2: [2]})->typename() # dict<list<any>>
|
||
<
|
||
|
||
Stricter type checking ~
|
||
*type-checking*
|
||
In legacy Vim script, where a number was expected, a string would be
|
||
automatically converted to a number. This was convenient for an actual number
|
||
such as "123", but leads to unexpected problems (and no error message) if the
|
||
string doesn't start with a number. Quite often this leads to hard-to-find
|
||
bugs. For example, in legacy Vim script this echoes "1":
|
||
>vim
|
||
echo 123 == '123'
|
||
<
|
||
However, if an unintended space is included, "0" is echoed:
|
||
>vim
|
||
echo 123 == ' 123'
|
||
<
|
||
*E1206*
|
||
In Vim9 script this has been made stricter. In most places it works just as
|
||
before if the value used matches the expected type. For example, in both
|
||
legacy Vim script and Vim9 script trying to use anything other than a
|
||
dictionary when it is required:
|
||
>vim
|
||
echo [8, 9]->keys()
|
||
vim9cmd echo [8, 9]->keys() # E1206: Dictionary required
|
||
<
|
||
*E1023* *E1024* *E1029*
|
||
*E1030* *E1174* *E1175*
|
||
*E1210* *E1212*
|
||
However, sometimes there will be an error in Vim9 script, which breaks
|
||
backwards compatibility. The following examples illustrate various places
|
||
this happens. The legacy Vim script behavior, which does not fail, is shown
|
||
first. It is followed by the error that occurs if the same command is used
|
||
in Vim9 script.
|
||
|
||
- Using a number (except 0 or 1) where a bool is expected:
|
||
>vim
|
||
echo v:version ? v:true : v:false
|
||
vim9cmd echo v:version ? true : false # E1023: Using a Number as ...
|
||
<
|
||
- Using a number where a string is expected:
|
||
>vim
|
||
echo filter([1, 2], 0)
|
||
vim9cmd echo filter([1, 2], 0) # E1024: Using a Number as a String
|
||
<
|
||
- Assigning a number to an environment variable, a register or a String
|
||
v: variable:
|
||
>vim
|
||
let $FOO = 123
|
||
vim9cmd $FOO = 123 # E1012: Type mismatch; expected string but ...
|
||
<
|
||
- Not using a number where a number is expected:
|
||
>vim
|
||
" In this example, legacy Vim script treats v:false as 0
|
||
function Not1029()
|
||
let b:l = [42] | unlet b:l[v:false]
|
||
endfunction
|
||
call Not1029() | echo b:l
|
||
< >vim9
|
||
vim9script
|
||
def E1029(): void
|
||
b:l = [42] | unlet b:l[false]
|
||
enddef
|
||
E1029() # E1029: Expected number but got bool
|
||
<
|
||
- Using a string as a number:
|
||
>vim
|
||
let b:l = [42] | unlet b:l['#'] | echo b:l
|
||
vim9cmd b:l = [42] | vim9cmd unlet b:l['#'] # E1030: Using a string
|
||
<
|
||
- Not using a string where an argument requires a string:
|
||
>vim9
|
||
echo substitute('Hallo', 'a', 'e', v:true)
|
||
vim9cmd echo substitute('Hallo', 'a', 'e', true) # E1174: String ...
|
||
<
|
||
- Using an empty string in an argument that requires a non-empty string:
|
||
>vim9
|
||
echo exepath('')
|
||
vim9cmd echo exepath('') # E1175: Non-empty string required for ar...
|
||
<
|
||
- Not using a number when it is required:
|
||
>vim
|
||
echo gettabinfo('a')
|
||
vim9cmd echo gettabinfo('a') # E1210: Number required for argument 1
|
||
<
|
||
- Not using a bool when it is required:
|
||
>vim
|
||
echo char2nr('¡', 2)
|
||
vim9cmd echo char2nr('¡', 2) # E1212: Bool required for argument 2
|
||
<
|
||
- Not using a number when a number is required (|E521|):
|
||
>vim
|
||
let &laststatus='2'
|
||
vim9cmd &laststatus = '2'
|
||
<
|
||
- Not using a string when a string is required (|E928|):
|
||
>vim
|
||
let &langmenu = 42
|
||
vim9cmd &langmenu = 42 # E928: String required
|
||
<
|
||
- Comparing a |Special| with "is" often fails (|E1037|, |E1072|):
|
||
>vim
|
||
" 1 is echoed because these are both true in legacy Vim script
|
||
echo v:null is v:null && v:none is v:none
|
||
" Similarly, 0 is echoed for these (false in legacy Vim script)
|
||
echo v:none is v:null || v:none is 8 || v:true is v:none
|
||
" All these are errors in Vim9 script
|
||
vim9cmd echo v:null is v:null # E1037: Cannot use 'is' with special
|
||
vim9cmd echo v:none is v:none # E1037: Cannot use 'is' with special
|
||
vim9cmd echo v:none is v:null # E1037: Cannot use 'is' with special
|
||
vim9cmd echo v:none is 8 # E1072: Cannot compare special wit...
|
||
vim9cmd echo v:true is v:none # E1072: Cannot compare bool with s...
|
||
<
|
||
Note: Although the last two Vim9 script examples above error with
|
||
`v:none`, they return `false` with `v:null` (which is the same as
|
||
`null` - see |v:null|):
|
||
>vim9
|
||
vim9script
|
||
echo v:null is 8 # false
|
||
echo true is v:null # false
|
||
<
|
||
- Using a string where a bool is required (|E1135|):
|
||
>vim
|
||
echo '42' ? v:true : v:false
|
||
vim9cmd echo '42' ? true : false # E1135: Using a String as a Bool
|
||
<
|
||
- Using a bool as a number (|E1138|):
|
||
>vim
|
||
let &laststatus=v:true
|
||
vim9cmd &laststatus = true
|
||
<
|
||
One consequence is that the item type of a list or dict given to |map()| must
|
||
not change when its type is either declared or inferred. For example, this
|
||
list's type is changed successfully in legacy Vim script:
|
||
>vim
|
||
" legacy Vim script changing list [0, 1] to ['item 0', 'item 1']
|
||
let s:mylist = [0, 1]
|
||
call map(s:mylist, {i -> $"item {i}"})
|
||
echo s:mylist
|
||
<
|
||
whereas in Vim9 script it gives |E1012|:
|
||
>vim9
|
||
vim9script
|
||
var mylist = [0, 1] # Vim infers mylist is list<number>
|
||
map(mylist, (i, _) => $"item {i}") # E1012: Type mismatch; expected...
|
||
<
|
||
The error occurs because `map()` tries to modify the list elements to strings,
|
||
which conflicts with the declared type.
|
||
|
||
Use |mapnew()| instead. It creates a new list, and Vim infers its type when it
|
||
is not specified. Inferred and declared types are shown in this example:
|
||
>vim9
|
||
vim9script
|
||
var mylist = [0, 1]
|
||
var infer = mylist->mapnew((i, _) => $"item {i}")
|
||
echo [infer, infer->typename()]
|
||
var declare: list<string> = mylist->mapnew((i, _) => $"item {i}")
|
||
echo [declare, declare->typename()]
|
||
<
|
||
The key concept here is, variables with declared or inferred types cannot
|
||
have the types of the elements within their containers change. However, type
|
||
"changes" are allowed for either:
|
||
- a container literal (not bound to a variable), or
|
||
- a container where |copy()| or |deepcopy()| is used in method chaining.
|
||
Both are demonstrated in this example:
|
||
>vim9
|
||
vim9script
|
||
# list literal
|
||
echo [1, 2]->map((_, v) => $"#{v}")
|
||
echo [1, 2]->map((_, v) => $"#{v}")->typename()
|
||
# deepcopy() in a method chain
|
||
var mylist = [1, 2]
|
||
echo mylist->deepcopy()->map((_, v) => $"#{v}")
|
||
echo mylist->deepcopy()->map((_, v) => $"#{v}")->typename()
|
||
echo mylist
|
||
<
|
||
The reasoning behind this is, when a type is either declared or inferred
|
||
and the list is passed around and changed, the declaration/inference must
|
||
always hold so that you can rely on the type to match the declared/inferred
|
||
type. For either a list literal or a fully copied list, that type safety is
|
||
not needed because the original list is unchanged (as "echo mylist" shows,
|
||
above).
|
||
|
||
If the item type was not declared or determined to be "<any>", it will not
|
||
change, even if all items later become the same type. However, when
|
||
`mapnew()` is used, inference means that the new list will reflect the type(s)
|
||
present. For example:
|
||
>vim9
|
||
vim9script
|
||
# list<any>
|
||
var mylist = [1, '2'] # mixed types, i.e., list<any>
|
||
echo (mylist, mylist->typename()) # ([1, '2'], 'list<any>')
|
||
mylist->map((_, v) => $"item {v}") # all items are now strings
|
||
echo (mylist, mylist->typename()) # both strings, but list<any>
|
||
# mapnew()
|
||
var newlist = mylist->mapnew((_, v) => v)
|
||
echo (newlist, newlist->typename()) # newlist is a list<string>
|
||
<
|
||
Using |extend()| and |extendnew()| is similar, i.e., a list literal may use
|
||
the former, so, this is okay:
|
||
>vim9
|
||
vim9cmd echo [1, 2]->extend(['3']) # [1, 2, '3']
|
||
<
|
||
whereas, this is not:
|
||
>vim9
|
||
vim9script
|
||
var mylist: list<number> = [1, 2]
|
||
echo mylist->extend(['3']) # E1013: Argument 2: type mismatch
|
||
<
|
||
Using |extendnew()| is needed for extending an existing typed list, except
|
||
where the extension matches the list's type (or it is "any"). For example,
|
||
first extending with an element of the same type, then extending with a
|
||
different type:
|
||
>vim9
|
||
vim9script
|
||
var mylist: list<number> = [1, 2]
|
||
mylist->extend([3])
|
||
echo mylist->extendnew(['4']) # [1, 2, 3, '4']
|
||
<
|
||
*E1158*
|
||
Using |flatten()| is not allowed in Vim9 script, because it is intended
|
||
always to change the type. This even applies to a list literal
|
||
(unlike |map()| and |extend()|). Instead, use |flattennew()|:
|
||
>vim9
|
||
vim9cmd [1, [2, 3]]->flatten() # E1158: Cannot use flatten
|
||
vim9cmd echo [1, [2, 3]]->flattennew() # [1, 2, 3]
|
||
<
|
||
Assigning to a funcref with specified arguments (see |vim9-func-declaration|)
|
||
involves strict type checking of the arguments. For example, this works:
|
||
>vim9
|
||
vim9script
|
||
var F_name_age: func(string, number): string
|
||
F_name_age = (n: string, a: number): string => $"Name: {n}, Age: {a}"
|
||
echo F_name_age('Bob', 42)
|
||
<
|
||
whereas this fails with error |E1012| (type mismatch):
|
||
>vim9
|
||
vim9script
|
||
var F_name_age: func(string, number): string
|
||
F_name_age = (n: string, a: string): string => $"Name: {n}, Age: {a}"
|
||
<
|
||
If there is a variable number of arguments they must have the same type, as in
|
||
this example:
|
||
>vim9
|
||
vim9script
|
||
var Fproduct: func(...list<number>): number
|
||
Fproduct = (...v: list<number>): number => reduce(v, (a, b) => a * b)
|
||
echo Fproduct(3, 2, 4) # 24
|
||
<
|
||
And <any> may be used to accommodate mixed types:
|
||
>vim9
|
||
vim9script
|
||
var FlatSort: func(...list<any>): any
|
||
FlatSort = (...v: list<any>) => flattennew(v)->sort('n')
|
||
echo FlatSort(true, [[[5, 3], 2], 4]) # [true, 2, 3, 4, 5]
|
||
<
|
||
Note: Using <any> in a lambda does not avoid type checking of the
|
||
funcref. It remains constrained by the declared funcref's type and,
|
||
as these examples show, a runtime or compiling error occurs when the
|
||
types mismatch:
|
||
>vim9
|
||
vim9script
|
||
var FuncSN: func(string): number
|
||
FuncSN = (v: any): number => v->str2nr()
|
||
echo FuncSN('162')->nr2char() # ¢
|
||
echo FuncSN(162)->nr2char() # E1013 (a runtime error)
|
||
< >vim9
|
||
vim9script
|
||
var FuncSN: func(string): number
|
||
FuncSN = (v: any): number => v->str2nr()
|
||
def FuncSNfail(): void
|
||
echo FuncSN('162')->nr2char() # No echo because ...
|
||
echo FuncSN(162)->nr2char() # E1013 (now, a compiling error)
|
||
enddef
|
||
FuncSNfail()
|
||
<
|
||
When the funcref has no arguments specified, there is no type checking. This
|
||
example shows FlexArgs has a string argument the first time and a list the
|
||
following time:
|
||
>vim9
|
||
vim9script
|
||
var FlexArgs: func: string
|
||
FlexArgs = (s: string): string => $"It's countdown time {s}..."
|
||
echo FlexArgs("everyone")
|
||
FlexArgs = (...values: list<string>): string => join(values, ', ')
|
||
echo FlexArgs('3', '2', '1', 'GO!')
|
||
<
|
||
*E1211* *E1217* *E1218* *E1219* *E1220*
|
||
*E1221* *E1222* *E1223* *E1224* *E1225*
|
||
*E1226* *E1228* *E1235* *E1238* *E1251*
|
||
*E1253* *E1256* *E1297* *E1298* *E1301*
|
||
*E1528* *E1529* *E1530* *E1531* *E1534*
|
||
Types are checked for most builtin functions to make it easier to spot
|
||
mistakes. The following one-line |:vim9| commands, calling builtin functions,
|
||
demonstrate many of those type-checking errors:
|
||
>vim9
|
||
vim9 9->list2blob() # E1211: List required for argume...
|
||
vim9 9->ch_close() # E1217: Channel or Job required ...
|
||
vim9 9->job_info() # E1218: Job required for argumen...
|
||
vim9 [9]->cos() # E1219: Float or Number required...
|
||
vim9 {}->remove([]) # E1220: String or Number require...
|
||
vim9 null_channel->ch_evalraw(9) # E1221: String or Blob required ...
|
||
vim9 9->col() # E1222: String or List required ...
|
||
vim9 9->complete_add() # E1223: String or Dictionary req...
|
||
vim9 setbufline(9, 9, {}) # E1224: String, Number or List r...
|
||
vim9 9->count(9) # E1225: String, List, Tuple or D...
|
||
vim9 9->add(9) # E1226: List or Blob required fo...
|
||
vim9 9->remove(9) # E1228: List, Dictionary, or Blo...
|
||
vim9 getcharstr('9') # E1235: Bool or number required ...
|
||
vim9 9->blob2list() # E1238: Blob required for argume...
|
||
vim9 9->filter(9) # E1251: List, Tuple, Dictionary,...
|
||
vim9 9->reverse() # E1253: String, List, Tuple or B...
|
||
vim9 9->call(9) # E1256: String or Function requi...
|
||
vim9 null_dict->winrestview() # E1297: Non-NULL Dictionary requ...
|
||
vim9 {}->prop_add_list(null_list) # E1298: Non-NULL List required f...
|
||
vim9 {}->repeat(9) # E1301: String, Number, List, Tu...
|
||
vim9 9->index(9) # E1528: List or Tuple or Blob re...
|
||
vim9 9->join() # E1529: List or Tuple required f...
|
||
vim9 9->max() # E1530: List or Tuple or Diction...
|
||
vim9 9->get(9) # E1531: Argument of get() must b...
|
||
vim9 9->tuple2list() # E1534: Tuple required for argum...
|
||
<
|
||
Reserved for future use: *E1227* *E1250* *E1252*
|
||
E1227: List or Dictionary required for argument %d
|
||
E1250: Argument of %s must be a List, String, Dictionary or Blob
|
||
E1252: String, List or Blob required for argument %d
|
||
|
||
|
||
Categories of variables, defaults and null handling ~
|
||
*variable-categories* *null-variables*
|
||
There are three categories of variables:
|
||
primitive number, float, boolean
|
||
container string, blob, list, tuple, dict
|
||
specialized function, job, channel, user-defined-object
|
||
|
||
When declaring a variable without an initializer, an explicit type must be
|
||
provided. Each category has different default initialization semantics.
|
||
|
||
Primitives default to type-specific values. All primitives are empty but do
|
||
not equal `null`:
|
||
>vim9
|
||
vim9script
|
||
var n: number | echo [n, n->empty(), n == null] # [0, 1, false]
|
||
var f: float | echo [f, f->empty(), f == null] # [0.0, 1, false]
|
||
var b: bool | echo [b, b->empty(), b == null] # [false, 1, false]
|
||
<
|
||
Containers default to an empty container. Only an empty string equals `null`:
|
||
>vim9
|
||
vim9script
|
||
var s: string | echo [s, s->empty(), s == null] # ['', 1, true]
|
||
var z: blob | echo [z, z->empty(), z == null] # [0z, 1, false]
|
||
var l: list<string> | echo [l, l->empty(), l == null] # [[], 1, false]
|
||
var t: tuple<any> | echo [t, t->empty(), t == null] # [(), 1, false]
|
||
var d: dict<number> | echo [d, d->empty(), d == null] # [{}, 1, false]
|
||
<
|
||
Specialized types default to equaling `null`:
|
||
>vim9
|
||
vim9script
|
||
var F: func | echo [F, F == null] # [function(''), true]
|
||
var j: job | echo [j, j == null] # ['no process', true]
|
||
var c: channel | echo [c, c == null] # ['channel fail', true]
|
||
class Class
|
||
endclass
|
||
var o: Class | echo [o, o == null] # [object of [unknown], true]
|
||
enum Enum
|
||
endenum
|
||
var e: Enum | echo [e, e == null] # [object of [unknown], true]
|
||
<
|
||
Notes: (1) See |empty()| for explanations of empty job, empty channel,
|
||
and empty object types. (2) The `class`, `enum`, and `typealias`
|
||
types cannot be used as values, so cannot be compared to `null` (see
|
||
|E1405|, |E1421|, and |E1403| respectively).
|
||
|
||
Vim does not have a familiar null value. Instead, it has various null_<type>
|
||
predefined values including |null_string|, |null_list|, and |null_job|.
|
||
Primitives do not have a null_<type>. Typical use cases for null_<type> are:
|
||
- to clear a variable and release its resources,
|
||
- as a default for a parameter in a function definition (for an example,
|
||
see |null_blob|), or
|
||
- assigned to a container or specialized variable to set it to null
|
||
for later comparison (for an example, see |null-compare|).
|
||
|
||
For a specialized variable, like `job`, null_<type> is used to clear the
|
||
resources. For example:
|
||
>vim9
|
||
vim9script
|
||
var mydate: list<string>
|
||
def Date(channel: channel, msg: string): void
|
||
mydate->add(msg)
|
||
enddef
|
||
var myjob = job_start([&shell, &shellcmdflag, 'date'], {out_cb: Date})
|
||
echo [myjob, myjob->job_status()]
|
||
sleep 2
|
||
echo $"The date and time is {mydate->join('')}"
|
||
echo [myjob, myjob->job_status()]
|
||
myjob = null_job # Clear the variable; release the job's resources.
|
||
echo myjob
|
||
<
|
||
For a container variable, resources may also be cleared by assigning an
|
||
empty container to the variable. For example:
|
||
>vim9
|
||
vim9script
|
||
var perfect: list<number> = [1, 4]
|
||
perfect->extend([9, 16, 25])
|
||
perfect = []
|
||
echo perfect
|
||
<
|
||
Using an empty container, rather than null_<type>, to clear a container
|
||
variable may avoid null complications - see |null-anomalies|.
|
||
|
||
The initialization semantics of container variables and specialized variables
|
||
differ. For containers:
|
||
- An uninitialized container defaults to empty but does not equal `null`
|
||
(except for an uninitialized string).
|
||
- A container initialized to [], (), {}, "", or 0z is empty but does not equal
|
||
`null`.
|
||
- A container initialized as null_<type> defaults to empty and it also equals
|
||
`null`.
|
||
|
||
In the following example, the uninitialized list ("lu") and [] initialized
|
||
list ("li") are equivalent and indistinguishable whereas "ln" is a null
|
||
container, which is similar to, but not equivalent to, an empty container
|
||
(see |null-anomalies|).
|
||
>vim9
|
||
vim9script
|
||
# uninitialized: empty container, not null
|
||
var lu: list<any>
|
||
echo ['lu', $"empty={lu->empty()}", $"null={lu == null}"]
|
||
# initialized: empty container, not null
|
||
var li: list<any> = []
|
||
echo ['li', $"empty={li->empty()}", $"null={li == null}"]
|
||
# initialized: empty container, null
|
||
var ln: list<any> = null_list
|
||
echo ['ln', $"empty={ln->empty()}", $"null={ln == null}"]
|
||
<
|
||
Specialized variables default to equaling null. These job initializations
|
||
are equivalent and indistinguishable:
|
||
>vim9
|
||
vim9script
|
||
var j1: job
|
||
var j2: job = null_job
|
||
var j3 = null_job
|
||
echo (j1 == j2) == (j2 == j3) # true (equivalent, indistinguishable)
|
||
<
|
||
When a list, tuple, or dict is declared, if the item type is not specified
|
||
it cannot be inferred. Consequently, the item type defaults to "any":
|
||
>vim9
|
||
vim9script
|
||
var [t1, t2] = [(), null_tuple]
|
||
echo $'t1 is {t1->typename()} and t2 is {t2->typename()} too'
|
||
<
|
||
Tuples and functions (or partials) may be declared in various ways.
|
||
See |tuple-type|, |variadic-tuple|, and |vim9-func-declaration|.
|
||
|
||
*null-compare*
|
||
For familiar null compare semantics, where an empty container is not equal to
|
||
a null container, do not use null_<type> in a comparison. That is because,
|
||
in Vim9 script, although null_<type> == `null`, comparing an:
|
||
- empty container to `null` is `false`, but
|
||
- empty container to null_<type> is `true`.
|
||
|
||
So, compare against `null`, not null_<type>. For example:
|
||
>vim9
|
||
vim9script
|
||
var bonds: dict<list<string>> = {g: ['007', '008'], o: ['007', '009']}
|
||
def Search(query: string): list<string>
|
||
return query == "\r" ? null_list : bonds->get(query, [])
|
||
enddef
|
||
echo "Goldfinger (g) or Octopussy (o)?: "
|
||
const C: string = getcharstr()
|
||
var result: list<string> = C->Search()
|
||
if result == null # <<< DO NOT USE null_list HERE!
|
||
echo "Error: Nothing was entered"
|
||
else
|
||
echo result->empty() ? $"No matches for '{C}'" : $"{result}"
|
||
endif
|
||
<
|
||
Note: Using "result == null_list" instead of "result == null" would
|
||
fail to distinguish the error (nothing entered) and the valid
|
||
(nothing matched) result because [] == null_list whereas [] != null.
|
||
|
||
Conceptually, think of the null_<type> construct as a hybrid/bridge between
|
||
the general `null` and typed `empty` containers, having properties of both.
|
||
In the following section there are details about comparison results.
|
||
|
||
*null-details* *null-anomalies*
|
||
This section describes issues about using null and null_<type>; included below
|
||
are the enumerated results of null comparisons. In some cases, if familiar
|
||
with vim9 null semantics, the programmer may choose to use null_<type> in
|
||
comparisons and/or other situations.
|
||
|
||
Elsewhere in the documentation it says, "often a null value is handled the
|
||
same as an empty value, but not always". For example, you cannot add to a
|
||
null container:
|
||
>vim9
|
||
vim9script
|
||
var le: list<any> = []
|
||
le->add('Okay') # le is now ['Okay']
|
||
var ln = null_list
|
||
ln->add("E1130") # E1130: Cannot add to null list
|
||
<
|
||
As explained in |null-compare|, there is a non-transitive relationship among
|
||
`null`, null_<type> containers, and `empty`. To recap, for example:
|
||
>vim9
|
||
vim9cmd echo (null_dict == {}, null_dict == null, {} != null)
|
||
<
|
||
The exception is an uninitialized string. It is equal to `null` (and is the
|
||
same instance as `null_string`). The "is" operator (|expr-is|) may be used to
|
||
determine whether a string is uninitialized:
|
||
>vim9
|
||
vim9script
|
||
var s: string
|
||
echo s == null_string # true
|
||
echo s is null_string # true (the same instance)
|
||
echo s == null # true (unexpected, perhaps)
|
||
echo s is null # false (not the same instance)
|
||
<
|
||
However, don't do the same for the other containers because, when evaluated
|
||
against their applicable null_<type> with "is", they return `false`:
|
||
>vim9
|
||
vim9script
|
||
var d: dict<any>
|
||
echo d == null_dict # true
|
||
echo d is null_dict # false (not the same instance)
|
||
echo d == null # false (as expected)
|
||
echo d is null # false (not the same instance)
|
||
<
|
||
The key distinction here is an uninitialized string is implemented as
|
||
`null_string`, while an uninitialized list, dict, tuple, or blob is
|
||
implemented as an empty container ([], {}, (), and 0z respectively).
|
||
So, those uninitialized types are equal to, but not the same instance as,
|
||
their null_<type> counterparts, as this example shows:
|
||
>vim9
|
||
vim9script
|
||
var t: tuple<any>
|
||
echo t == null_tuple # true
|
||
echo t is null_tuple # false
|
||
<
|
||
However, a variable initialized to the null_<type> is equal not only to the
|
||
null_<type>, it is also equal to null. For example:
|
||
>vim9
|
||
vim9script
|
||
var t: tuple<any> = null_tuple
|
||
echo t == null_tuple # true
|
||
echo t is null_tuple # true
|
||
echo t == null # true
|
||
<
|
||
An uninitialized container variable is not equal to null, except for an
|
||
uninitialized string, which is explained in an example, above. So, these
|
||
all echo `true`:
|
||
>vim9
|
||
vim9script
|
||
var b: blob | echo b != null
|
||
var d: dict<any> | echo d != null
|
||
var l: list<any> | echo l != null
|
||
var t: tuple<any> | echo t != null
|
||
var s: string | echo s == null
|
||
<
|
||
An uninitialized specialized variable is equal to null. So, these all
|
||
echo `true`:
|
||
>vim9
|
||
vim9script
|
||
var c: channel | echo c == null
|
||
var F: func | echo F == null
|
||
var j: job | echo j == null
|
||
class Class
|
||
endclass
|
||
var nc: Class | echo nc == null
|
||
enum Enum
|
||
endenum
|
||
var ne: Enum | echo ne == null
|
||
<
|
||
Note: the specialized variables, like job, default to null and no
|
||
specialized variable has a corresponding empty value.
|
||
|
||
A container variable initialized to empty equals null_<type>, so these are all
|
||
`true`:
|
||
>vim9
|
||
vim9script
|
||
var s: string = "" | echo s == null_string
|
||
var b: blob = 0z | echo b == null_blob
|
||
var l: list<any> = [] | echo l == null_list
|
||
var t: tuple<any> = () | echo t == null_tuple
|
||
var d: dict<any> = {} | echo d == null_dict
|
||
<
|
||
However, a container variable initialized to empty does not equal null, so
|
||
these are all `true`:
|
||
>vim9
|
||
vim9script
|
||
var s: string = "" | echo s != null
|
||
var b: blob = 0z | echo b != null
|
||
var l: list<any> = [] | echo l != null
|
||
var t: tuple<any> = () | echo t != null
|
||
var d: dict<any> = {} | echo d != null
|
||
<
|
||
|
||
==============================================================================
|
||
|
||
5. Generic functions *generic-functions*
|
||
|
||
A generic function allows using the same function with different type
|
||
arguments, while retaining type checking for arguments and the return value.
|
||
This provides type safety and code reusability.
|
||
|
||
|
||
Declaration~
|
||
*generic-function-declaration*
|
||
*E1553* *E1554*
|
||
The type variables for a generic function are declared as its type parameters
|
||
within angle brackets ("<" and ">"), directly after the function name.
|
||
Multiple type parameters are separated by commas:
|
||
>
|
||
def[!] {funcname}<{type} [, {types}]>([arguments])[: {return-type}]
|
||
{function body}
|
||
enddef
|
||
< *generic-function-example*
|
||
These type parameters may then be used, like any other type, within the
|
||
function signature and its body. The following example combines two lists
|
||
into a list of tuples:
|
||
>vim9
|
||
vim9script
|
||
def Zip<T, U>(first: list<T>, second: list<U>): list<tuple<T, U>>
|
||
const LEN: number = ([first->len(), second->len()])->min()
|
||
final result: list<tuple<T, U>> = []
|
||
for i in range(LEN)
|
||
result->add((first[i], second[i]))
|
||
endfor
|
||
return result
|
||
enddef
|
||
var n: list<number> = [61, 62, 63]
|
||
var s: list<string> = ['a', 'b', 'c']
|
||
echo $"Zip example #1: {Zip<number, string>(n, s)}"
|
||
echo $"Zip example #2: {Zip<string, number>(s, n)}"
|
||
<
|
||
*type-variable-naming* *E1552*
|
||
*type-parameter-naming*
|
||
As in the preceding example, the convention is to use a single capital letter
|
||
for a name (e.g., T, U, A, etc.). Although they may comprise more than one
|
||
letter, names must start with a capital letter. In this example, "Ok" is
|
||
valid whereas "n" is not:
|
||
>vim9
|
||
vim9script
|
||
def MyFail<Ok, n>(): void
|
||
enddef
|
||
# E1552: Type variable name must start with an uppercase letter: n>...
|
||
<
|
||
*E1558* *E1560*
|
||
A function must be declared and used either as a generic function or as a
|
||
regular function, but not both. The following Vim9 scripts demonstrate these
|
||
errors:
|
||
>vim9
|
||
vim9script
|
||
My1558<number>()
|
||
# E1558: Unknown generic function: My1558
|
||
< >vim9
|
||
vim9script
|
||
def My1560(): void
|
||
enddef
|
||
My1560<string>()
|
||
# E1560: Not a generic function: My1560
|
||
<
|
||
*E1561*
|
||
Type parameter names must not clash with other identifiers:
|
||
>vim9
|
||
vim9script
|
||
def My1561<D, E, D>(): D
|
||
enddef
|
||
# E1561: Duplicate type variable name: D
|
||
|
||
vim9script
|
||
enum E
|
||
Yes, No
|
||
endenum
|
||
def My1041<E>(): E
|
||
enddef
|
||
# E1041: Redefining script item: "E"
|
||
<
|
||
|
||
Calling a generic function~
|
||
*generic-function-call*
|
||
To call a generic function, specify the concrete types in "<" and ">"
|
||
between the function name and the argument list:
|
||
>
|
||
MyFunc<number, string, list<number>>()
|
||
<
|
||
Note: There are several working examples in this section, which may be
|
||
sourced, including |generic-function-example|.
|
||
|
||
*E1555* *E1556* *E1557* *E1559*
|
||
The number of passed type arguments to the function must match the number
|
||
of its declared type parameters. An empty type list is not allowed.
|
||
Examples:
|
||
>vim9
|
||
vim9script
|
||
def My1555<>(): void
|
||
enddef # E1555: Empty type list specified for generic function '<S...
|
||
< >vim9
|
||
vim9script
|
||
def My1556<T>(): void
|
||
enddef
|
||
My1556<bool, bool>() # E1556: Too many types specified for generic...
|
||
< >vim9
|
||
vim9script
|
||
def My1557<T, U>(): void
|
||
enddef
|
||
My1557<bool>() # E1557: Not enough types specified for generic fun...
|
||
< >vim9
|
||
vim9script
|
||
def My1559<T>(): T
|
||
enddef
|
||
My1559() # E1559: Type arguments missing for generic function '<SN...
|
||
<
|
||
Any Vim9 type (|vim9-types|) can be used as a concrete type in a generic
|
||
function.
|
||
|
||
Spaces are not allowed:
|
||
- Between the function name and "<" (|E1068|)
|
||
- Between ">" and the opening "(" (|E1068|), or
|
||
- Within the "<" and ">", except where required after the comma separating
|
||
the types (|E1202|).
|
||
|
||
A generic function can be exported and imported like a regular function.
|
||
See |:export| and |:import|.
|
||
|
||
A generic function can be defined inside another regular or generic function.
|
||
An example:
|
||
>vim9
|
||
vim9script
|
||
def Outer(): void
|
||
# Returns either the first item of a list or a default value
|
||
def FirstOrDefault<T, U>(lst: list<T>, default: U): any
|
||
return lst->len() > 0 ? lst[0] : default
|
||
enddef
|
||
echo FirstOrDefault<string, bool>(['B', 'C'], false) # B
|
||
echo FirstOrDefault<number, number>([], 42) # 42
|
||
enddef
|
||
Outer()
|
||
<
|
||
A nested generic function can also be called recursively and used in a
|
||
closure, such as a lambda or another nested function.
|
||
|
||
Using a type variable as a type argument ~
|
||
|
||
A type variable may also be passed as a type argument. For example:
|
||
>vim9
|
||
vim9script
|
||
# T is declared as a type parameter
|
||
# It is used for the 'value' parameter and the return type
|
||
def Id<T>(value: T): T
|
||
return value
|
||
enddef
|
||
# U is declared as a type parameter
|
||
# It is used for the 'value' parameter and the return type
|
||
def CallId<U>(value: U): U
|
||
# U is a type variable passed/used as a type argument
|
||
return Id<U>(value)
|
||
enddef
|
||
echo CallId<string>('I am') .. ' ' .. CallId<number>(42)
|
||
<
|
||
This is useful for complex data structures like dictionaries of lists or,
|
||
as in the following example, lists of dictionaries:
|
||
>vim9
|
||
vim9script
|
||
def Flatten<T>(x: list<list<T>>): list<T>
|
||
final result: list<T> = []
|
||
for inner in x
|
||
result->extend(inner)
|
||
endfor
|
||
return result
|
||
enddef
|
||
const ENGLISH: list<dict<string>> = [{1: 'one'}, {2: 'two'}]
|
||
const MANDARIN: list<dict<string>> = [{1: '壹'}, {2: '贰'}]
|
||
const ARABIC_N: list<dict<number>> = [{1: 1}, {2: 2}]
|
||
echo Flatten<dict<string>>([ENGLISH, MANDARIN])
|
||
echo Flatten<dict<any>>([ENGLISH, ARABIC_N])
|
||
<
|
||
In "Flatten<T>", "T" is a declared type parameter. Everywhere else in
|
||
the function, "T" is a type variable referencing that type parameter.
|
||
|
||
|
||
Generic class method~
|
||
|
||
A Vim9 class method can be a generic function:
|
||
>vim9
|
||
vim9script
|
||
class Config
|
||
var settings: dict<any>
|
||
def Get<T>(key: string): T
|
||
return this.settings[key]
|
||
enddef
|
||
endclass
|
||
var c: Config = Config.new({timeout: 30, debug: true})
|
||
echo c.Get<number>('timeout')
|
||
echo c.Get<bool>('debug')
|
||
<
|
||
*E1432* *E1433* *E1434*
|
||
A generic class method in a base class can be overridden by a generic method
|
||
in a child class. The number of type variables must match between both
|
||
methods. A concrete class method cannot be overridden by a generic method,
|
||
and vice versa.
|
||
*E1438*
|
||
A method that implements an interface method must have the same number of
|
||
type variables as the interface method. A generic method cannot implement a
|
||
regular method, and vice versa.
|
||
|
||
|
||
Generic function reference~
|
||
|
||
A function reference (|Funcref|) can be a generic function. This allows for
|
||
creating factories of functions that operate on specific types:
|
||
>vim9
|
||
vim9script
|
||
# Match a specified character in a string or the decimal value of the
|
||
# character in a list. Note: '*' is decimal 42 (U+002A)
|
||
var c: string = "*"
|
||
var char_dec: tuple<string, string> = (c, c->char2nr()->string())
|
||
def Matcher<T>(pattern: string): func(T): bool
|
||
return (value: T): bool => match(value, pattern) >= 0
|
||
enddef
|
||
var StringMatch = Matcher<string>(char_dec[0])
|
||
echo "*+"->StringMatch() # true (has *)
|
||
echo ",-"->StringMatch() # false
|
||
var ListMatch = Matcher<list<number>>(char_dec[1])
|
||
echo [42, 43]->ListMatch() # true (has 42)
|
||
echo [44, 45]->ListMatch() # false
|
||
<
|
||
|
||
Compiling and Disassembling Generic functions~
|
||
|
||
The |:defcompile| command can be used to compile a generic function with a
|
||
specific list of concrete types:
|
||
>
|
||
defcompile MyFunc<number, list<number>, dict<string>>
|
||
<
|
||
The |:disassemble| command can be used to list the instructions generated for
|
||
a generic function:
|
||
>
|
||
disassemble MyFunc<string, dict<string>>
|
||
disassemble MyFunc<number, list<blob>>
|
||
<
|
||
|
||
Limitations and Future Work~
|
||
|
||
Currently, Vim does not support:
|
||
- Type inference for type variables: All types must be explicitly specified
|
||
when calling a generic function.
|
||
- Type constraints: It's not possible to restrict a type variable to a
|
||
specific class or interface (e.g., `T extends SomeInterface`).
|
||
- Default type arguments: Providing a default type for a type parameter
|
||
when not explicitly specified.
|
||
|
||
==============================================================================
|
||
|
||
6. Namespace, Import and Export *vim9script*
|
||
*vim9-export* *vim9-import*
|
||
A Vim9 script can be written to be imported. This means that some items are
|
||
intentionally exported, made available to other scripts. When the exporting
|
||
script is imported in another script, these exported items can then be used in
|
||
that script. All the other items remain script-local in the exporting script
|
||
and cannot be accessed by the importing script.
|
||
|
||
This mechanism exists for writing a script that can be sourced (imported) by
|
||
other scripts, while making sure these other scripts only have access to what
|
||
you want them to. This also avoids using the global namespace, which has a
|
||
risk of name collisions. For example when you have two plugins with similar
|
||
functionality.
|
||
|
||
You can cheat by using the global namespace explicitly. That should be done
|
||
only for things that really are global.
|
||
|
||
|
||
Namespace ~
|
||
*vim9-namespace*
|
||
To recognize a file that can be imported, the `vim9script` command must appear
|
||
as the first command in the file (though see |vim9-mix| for an exception).
|
||
It tells Vim to interpret the script in its own namespace, instead of the
|
||
global namespace. Consider this script:
|
||
>vim9
|
||
vim9script
|
||
var myvar = 'yes'
|
||
<
|
||
The variable "myvar" will only exist in this script's scope. That is
|
||
different from legacy Vim script where "let myvar" would make "myvar"
|
||
available to other scripts and functions (as `g:myvar`).
|
||
*E1101*
|
||
The variables at the file level are very much like the script-local "s:"
|
||
variables in legacy Vim script, but the "s:" is omitted. And they cannot be
|
||
deleted.
|
||
*E1304*
|
||
In Vim9 script the global (`g:`), buffer (`b:`), window (`w:`), and tab (`t:`)
|
||
namespaces can be used like in legacy Vim script. In Vim9 script, namespace
|
||
prefixed variables:
|
||
- are not declared
|
||
- have no specific type, and
|
||
- can be deleted.
|
||
If a namespace prefixed variable is declared, |E1016| is given. And, if a
|
||
type is specified, E1304 is given:
|
||
>vim9
|
||
vim9cmd var b:fails: bool # E1016: Cannot declare a buffer va...
|
||
vim9cmd b:fails: bool = false # E1304: Cannot use type with this ...
|
||
<
|
||
A side effect of `:vim9script` is that the 'cpoptions' option is set to the
|
||
Vim default value, like with:
|
||
>
|
||
:set cpo&vim
|
||
<
|
||
One of the effects is that |line-continuation| is always enabled.
|
||
The original value of 'cpoptions' is restored at the end of the script, while
|
||
flags added or removed in the script are also added to or removed from the
|
||
original value to get the same effect. The order of flags may change.
|
||
In the |vimrc| file sourced on startup this does not happen.
|
||
|
||
*vim9-mix*
|
||
There is one way to use both legacy and Vim9 syntax in one script file:
|
||
>vim9
|
||
" _legacy Vim script_ comments are placed here
|
||
if !has('vim9script')
|
||
" _legacy Vim script_ comments and commands are placed here
|
||
finish
|
||
endif
|
||
vim9script
|
||
# _Vim9 script_ commands/commands from here onwards
|
||
echowindow $"has('vim9script') == {has('vim9script')}"
|
||
<
|
||
This allows for writing a script that takes advantage of the Vim9 script
|
||
syntax if possible, and prevents the `vim9script` command from giving an
|
||
error if used in a version of Vim without Vim9 script.
|
||
|
||
Note that Vim9 syntax changed before Vim 9 so that scripts using the current
|
||
syntax (such as "import from" instead of "import") might give errors.
|
||
To prevent these, a safer check may be |v:version| >= 900 instead (because
|
||
"has('vim9script')" will return `v:true` back to Vim 8.2 with patch 3965).
|
||
Sometimes it is prudent to cut off even later. Vim9 script's feature set
|
||
continues to grow so, for example, if tuples are used (introduced in Vim 9.1
|
||
patch 1232), a better condition is:
|
||
>vim9
|
||
if !has('patch-9.1.1232')
|
||
echowindow $"Fail: Vim does not have patch 9.1.1232"
|
||
finish
|
||
endif
|
||
vim9script
|
||
echowindow $"Pass: version {v:versionlong}. Continuing ..."
|
||
<
|
||
Whichever vim-mix condition is used, it only works in one of two ways:
|
||
1. The "if" statement evaluates to false, the commands up to `endif` are
|
||
skipped and `vim9script` is then the first command actually executed.
|
||
2. The "if" statement evaluates to true, the commands up to `endif` are
|
||
executed and `finish` bails out before reaching `vim9script`.
|
||
|
||
|
||
Export ~
|
||
*:export*
|
||
Exporting an item can be written as: >
|
||
export var myvar ...
|
||
export const MYCONST ...
|
||
export final myvar ...
|
||
export def MyDef() ...
|
||
export function MyFunc() ...
|
||
export class MyClass ...
|
||
export abstract class MyAbstractClass ...
|
||
export interface MyInterface ...
|
||
export enum MyEnum ...
|
||
export type MyType ...
|
||
< *E1043*
|
||
As this suggests, variables, constants, functions, classes (including abstract
|
||
classes), interfaces, enums, and types can be exported. Trying to export
|
||
something else gives E1043:
|
||
>vim9
|
||
vim9cmd export echo 0 # E1043: Invalid command after :export
|
||
<
|
||
*E1042*
|
||
`:export` can only be used in a Vim9 script scope. So, for example, although
|
||
exporting a `:function` is okay, trying to export an item within such a
|
||
function gives E1042:
|
||
>vim9
|
||
vim9script
|
||
export function F1042()
|
||
export var b: bool
|
||
endfunction
|
||
F1042() # E1042: Export can only be used in vim9script
|
||
<
|
||
*E1044*
|
||
An invalid argument can give E1044:
|
||
>vim9
|
||
vim9script
|
||
export def /a1044(): void
|
||
enddef # E1044: Export with invalid argument: ...
|
||
<
|
||
|
||
Import ~
|
||
*:import*
|
||
The exported items can be imported in another script. The import syntax has
|
||
two forms. The simpler form is: >
|
||
import {filename}
|
||
<
|
||
Where {filename} is an expression that must evaluate to a string. In this
|
||
form the filename should end in ".vim" and the portion before ".vim" will
|
||
become the script-local name of the namespace. For example: >
|
||
import "myscript.vim"
|
||
<
|
||
This makes each exported item in "myscript.vim" available as "myscript.item".
|
||
|
||
*E1094*
|
||
Importing is only allowed in the script-local scope. Trying to do so
|
||
elsewhere gives E1094; for example, from a `:function` or `:def` scope:
|
||
>vim9
|
||
vim9script
|
||
def F1094(): void
|
||
import 'nah.vim' # E1094: Import can only be used in a script
|
||
enddef
|
||
F1094()
|
||
<
|
||
*E1053* *E1071*
|
||
Errors are given when either the {filename} is not found or it is invalid:
|
||
>vim9
|
||
vim9cmd import 'e1053.vim' # E1053: Could not import "e1053.vim"
|
||
vim9cmd import false # E1071: Invalid string for :import: false
|
||
<
|
||
*:import-as*
|
||
When the name of the file is long, ambiguous (such as with filenames that are
|
||
the same but in different directories), or if you just want to use a distinct
|
||
structure for the name, this form can be used: >
|
||
import {longfilename} as {name}
|
||
<
|
||
In this longer form, {name} becomes a specific script-local name for the
|
||
imported namespace. Therefore {name} must consist of letters, digits, or '_',
|
||
and cannot start with a digit, like |internal-variables|. The {longfilename}
|
||
expression must evaluate to an existing filename.
|
||
|
||
The following script demonstrates using `:import-as`. It:
|
||
- Creates a constant, "TMP", for the {longfilename} of a temporary Vim9 script
|
||
file to import
|
||
- Writes a two-line Vim9 script to TMP
|
||
- Imports the Vim9 script file "as" the {name}, "that", and
|
||
- Echoes the result, showing the imported Vim9 script file's {longfilename}
|
||
and, on a separate line, the content of "that.myvar" (where "myvar" is an
|
||
exported variable in the imported script).
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = tempname()
|
||
['vim9script', 'export var myvar: string = "YES!"']->writefile(TMP)
|
||
import TMP as that
|
||
echo $"Exported var 'myvar' in 'that' ({TMP}):\n" .. that.myvar
|
||
<
|
||
This example shows you can use "that.myvar", etc. You are free to choose the
|
||
name "that", or something that will be recognized as referring to the imported
|
||
script. It is best to avoid command names, command modifiers, and builtin
|
||
function names, because {name} will shadow any of those. It also is better to
|
||
begin the name with a lowercase letter because that avoids potential shadowing
|
||
of global user commands and functions. The {name} cannot be used for anything
|
||
else in the script, such as a function or variable name (see |E1213|), and it
|
||
cannot be assigned something else (see |E1258|).
|
||
|
||
In case the "." in the name is undesired, a local reference can be made for a
|
||
function: >
|
||
var LongDef = that.LongDefName
|
||
|
||
This also works for constants: >
|
||
const MAXLEN = that.MAXIMUM_LENGTH_OF_NAME
|
||
|
||
This does not work for variables, since the value would be copied once and
|
||
when changing the variable the copy will change, not the original variable.
|
||
You need to use the full name (i.e., including the '.').
|
||
|
||
Imported items are intended to exist at the script level and only be imported
|
||
once. Also, `:import` can not be used in a `:function` or `:def` - see |E1094|.
|
||
|
||
The script name after `:import` can be:
|
||
- A relative path, starting "." or "..". This finds a file relative to the
|
||
location of the script file itself. This is useful to split up a large
|
||
plugin into several files.
|
||
- An absolute path, starting with "/" on Unix or "D:/" on MS-Windows. This
|
||
will rarely be used.
|
||
- A path not being relative or absolute. This will be found in the
|
||
"import" subdirectories of 'runtimepath' entries. The name will usually be
|
||
longer and unique, to avoid loading the wrong file.
|
||
Note that "after/import" is not used.
|
||
|
||
Once a Vim9 script file has been imported, the result is cached and it will
|
||
not be read again.
|
||
|
||
Many syntax and other errors may be given when importing. Examples:
|
||
|
||
*E1047*
|
||
- Using an invalid "as" {name}:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i-cc
|
||
# E1047: Syntax error in import: i-cc
|
||
<
|
||
*E1048*
|
||
- Trying to use a non-existent item in an imported script:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc
|
||
echo i_cc.absent_item # E1048: Item not found in script: absent_item
|
||
<
|
||
*E1049*
|
||
- Trying to use a declared, but non-exported, item in an imported script:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc
|
||
echo i_cc.prepended # E1049: Item not exported in script: prepended
|
||
<
|
||
*E1060*
|
||
When using the imported name, the '.' and the item name must be contiguous.
|
||
So there can be neither white space nor a line break:
|
||
- After the '.' (see |E1074|), or
|
||
- Before the '.', for example:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc
|
||
echo i_cc .GetPath() # E1060: Expected dot after name: i_cc .Get...
|
||
<
|
||
*E1088*
|
||
- Attempting to have an imported script import itself:
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = $'{tempname()}.vim'->substitute('\\', '/', 'g')
|
||
var lines: list<string> = ['vim9script', $'import "{TMP}" as e1088']
|
||
lines->writefile(TMP)
|
||
import TMP # E1088: Script cannot import itself
|
||
<
|
||
*E1236*
|
||
- Trying to use the script itself:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as cc
|
||
cc() # E1236: Cannot use cc itself, it is imported
|
||
<
|
||
*E1257*
|
||
- Not using "as {name}" when the imported filename does not end in ".vim":
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = tempname()
|
||
['vim9script']->writefile(TMP)
|
||
import TMP # E1257: Imported script must use "as" or end in .vim: ...
|
||
<
|
||
*E1258*
|
||
- Omitting the '.' after an imported name:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc
|
||
def F1258(): void
|
||
i_cc =
|
||
enddef
|
||
defcompile # E1258: No '.' after imported name: i_cc =
|
||
<
|
||
*E1259*
|
||
- Omitting the name after an imported name:
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc
|
||
def F1259(): void
|
||
i_cc.8 = 0
|
||
enddef
|
||
defcompile # E1259: Missing name after imported name: i_cc.8 = 0
|
||
<
|
||
*E1260*
|
||
- Attempting to `:unlet` an imported item:
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = tempname()
|
||
['vim9script', 'export var b: bool']->writefile(TMP)
|
||
import TMP as e_unlet
|
||
unlet e_unlet.b # E1260: Cannot unlet an imported item: b
|
||
<
|
||
*E1261*
|
||
- Attempting to import the filename ".vim" without using "as". This script
|
||
writes the temporary file '.vim' to the operating system's applicable
|
||
temporary directory, then attempts to import it:
|
||
>vim9
|
||
vim9script
|
||
const TMPDIR: string = tempname()
|
||
->substitute('\\', '/', 'g') # Ensure Windows \ chars are /
|
||
->substitute('/[^/]\+$', '/', '') # Remove the filename
|
||
['vim9script']->writefile($'{TMPDIR}.vim')
|
||
echo $"Temporary import file name is: {TMPDIR}.vim"
|
||
import $'{TMPDIR}.vim' # E1261: Cannot import .vim without using "as"
|
||
<
|
||
*E1262*
|
||
- Trying to import the same script multiple times (including using different
|
||
"as" names):
|
||
>vim9
|
||
vim9script
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc1
|
||
import $'{$VIMRUNTIME}/autoload/ccomplete.vim' as i_cc2
|
||
# E1262: Cannot import the same script twice: .../ccomplete.vim
|
||
<
|
||
*import-map*
|
||
When you've imported a function from one script into a Vim9 script you can
|
||
refer to the imported function in a mapping by prefixing it with |<SID>|: >
|
||
noremap <silent> ,a :call <SID>name.Function()<CR>
|
||
|
||
When the mapping is defined "<SID>name." will be replaced with <SNR> and the
|
||
script ID of the imported script.
|
||
|
||
An even simpler solution is using |<ScriptCmd>|: >
|
||
noremap ,a <ScriptCmd>name.Function()<CR>
|
||
|
||
Note that this does not work for variables, only for functions.
|
||
This self-contained example demonstrates using <ScriptCmd>, creating and
|
||
writing a temporary script, which is then imported and the function
|
||
"exported.MyDef()" mapped to <leader>a:
|
||
>vim9
|
||
vim9script
|
||
const TMP: string = tempname()
|
||
['vim9script',
|
||
'export def MyDef(): void',
|
||
' popup_notification("exported.MyDef() was called", {time: 4000})',
|
||
'enddef']->writefile(TMP)
|
||
import TMP as exported
|
||
nnoremap <leader>a <ScriptCmd>exported.MyDef()<CR>
|
||
# Now, pressing <leader>a calls exported.MyDef(), generating a popup
|
||
<
|
||
*import-legacy* *legacy-import* *:imp*
|
||
`:import`, including the shortened form `:imp`, can also be used in legacy Vim
|
||
script. The imported namespace still becomes script-local, even when the
|
||
"s:" prefix is not given. For example: >
|
||
import "myfile.vim"
|
||
call s:myfile.MyFunc()
|
||
|
||
When using the "as name" form, the namespace cannot be resolved on its own
|
||
(see also |E1060|). This example demonstrates using `:imp` with `as` successfully,
|
||
then the error:
|
||
>vim
|
||
let s:tmp = $'{tempname()}.vim'->substitute('\\', '/', 'g')
|
||
call writefile(['vim9script',
|
||
\ 'export function L()',
|
||
\ ' return "Vim9 script exporting a :function"',
|
||
\ 'endfunction'], s:tmp)
|
||
imp s:tmp as iLegacy
|
||
echo s:iLegacy.L() |" echoes 'Vim9 script exporting a :function'
|
||
echo s:iLegacy |" E1060: Expected dot after name: s:iLegacy
|
||
<
|
||
This also affects the use of |<SID>| in the legacy mapping context. Since
|
||
|<SID>| is only a valid prefix for a function and NOT for a namespace, you
|
||
cannot use it to scope a function in a script-local namespace. Instead of
|
||
prefixing the function with |<SID>| you should use |<ScriptCmd>|.
|
||
For example:
|
||
>
|
||
noremap ,a <ScriptCmd>:call s:that.OtherFunc()<CR>
|
||
<
|
||
*:import-cycle*
|
||
The `import` commands are executed when encountered. If script A imports
|
||
script B, and B (directly or indirectly) imports A, this will be skipped over.
|
||
At this point items in A after "import B" will not have been processed and
|
||
defined yet. Therefore cyclic imports can exist and not result in an error
|
||
directly, but may result in an error for items in A after "import B" not being
|
||
defined. This does not apply to autoload imports, see the next section.
|
||
|
||
|
||
Importing an autoload script ~
|
||
*vim9-autoload* *import-autoload*
|
||
For optimal startup speed, loading scripts should be postponed until they are
|
||
actually needed. Using the autoload mechanism is recommended:
|
||
*E1264*
|
||
1. In the plugin, define user commands, functions and/or mappings
|
||
referring to items imported from an autoload script.
|
||
>
|
||
import autoload 'for/search.vim'
|
||
command -nargs=1 SearchForStuff search.Stuff(<f-args>)
|
||
<
|
||
This goes in .../plugin/anyname.vim. "anyname.vim" can be freely
|
||
chosen. The "SearchForStuff" command is now available to the user.
|
||
|
||
The "autoload" argument to `:import` means that the script is not
|
||
loaded until one of the items is actually used. The script will be
|
||
found under the "autoload" directory in 'runtimepath' instead of the
|
||
"import" directory. Alternatively, either a relative or absolute
|
||
name can be used - see below.
|
||
|
||
2. In the autoload script put the bulk of the code.
|
||
>
|
||
vim9script
|
||
export def Stuff(arg: string): void
|
||
...
|
||
<
|
||
This goes in .../autoload/for/search.vim.
|
||
|
||
Putting the "search.vim" script under the "/autoload/for/" directory
|
||
has the effect that "for#search#" will be prefixed to every exported
|
||
item. The prefix is obtained from the file name, just as you would
|
||
add it manually in a legacy autoload script. Thus the exported
|
||
function can be found with "for#search#Stuff", but you would normally
|
||
use `import autoload` and not use the prefix (which has the side effect
|
||
of loading the autoload script when compiling a function that
|
||
encounters this name).
|
||
|
||
You can split up the functionality and import other scripts from the
|
||
autoload script as you like. This way you can share code between
|
||
plugins.
|
||
|
||
Searching for the autoload script in all entries in 'runtimepath' can be a bit
|
||
slow. If the plugin knows where the script is located, quite often a relative
|
||
path can be used. This avoids the search and should be quite a bit faster.
|
||
Another advantage is that the script name does not need to be unique. Also,
|
||
an absolute path is possible. Examples: >
|
||
import autoload '../lib/implement.vim'
|
||
import autoload MyScriptsDir .. '/lib/implement.vim'
|
||
|
||
For defining a mapping that uses the imported autoload script the special key
|
||
|<ScriptCmd>| is useful. It allows for a command in a mapping to use the
|
||
script context of where the mapping was defined.
|
||
|
||
When compiling a `:def` function and a function in an autoload script is
|
||
encountered, the script is not loaded until the `:def` function is called.
|
||
This also means you get any errors only at runtime, since the argument and
|
||
return types are not known yet. If you would use the name with '#' characters
|
||
then the autoload script IS loaded.
|
||
|
||
Be careful to not refer to an item in an autoload script that does trigger
|
||
loading it unintentionally. For example, when setting an option that takes a
|
||
function name, make sure to use a string, not a function reference: >
|
||
import autoload 'qftf.vim'
|
||
&quickfixtextfunc = 'qftf.Func' # autoload script NOT loaded
|
||
&quickfixtextfunc = qftf.Func # autoload script IS loaded
|
||
On the other hand, it can be useful to load the script early, at a time when
|
||
any errors should be given.
|
||
|
||
For testing the |test_override()| function can be used to have the
|
||
`import autoload` load the script right away, so that the items and types can
|
||
be checked without waiting for them to be actually used: >
|
||
test_override('autoload', 1)
|
||
Reset it later with: >
|
||
test_override('autoload', 0)
|
||
Or: >
|
||
test_override('ALL', 0)
|
||
|
||
|
||
==============================================================================
|
||
|
||
7. Classes and interfaces *vim9-classes*
|
||
|
||
In legacy Vim script, a Dictionary could be used as a kind-of object by adding
|
||
members that are functions. However, this is quite inefficient and requires
|
||
the writer to do the work of making sure all the objects have the right
|
||
members. See |Dictionary-function|.
|
||
|
||
In |Vim9| script you can have classes, objects, interfaces, and enums like
|
||
in most popular object-oriented programming languages. Since this is a lot
|
||
of functionality, it is located in a separate help file, |vim9class.txt|,
|
||
though there are several sourceable examples within this help file too (at
|
||
|vim9-class-type|, |vim9-enum-type|, and elsewhere).
|
||
|
||
|
||
==============================================================================
|
||
|
||
8. Rationale *vim9-rationale*
|
||
|
||
The :def command ~
|
||
|
||
Plugin writers have asked for much faster Vim script. Investigations have
|
||
shown that keeping the existing semantics of function calls make this close to
|
||
impossible, because of the overhead involved with calling a function, setting
|
||
up the local function scope and executing lines. There are many details that
|
||
need to be handled, such as error messages and exceptions. The need to create
|
||
a dictionary for a: and l: scopes, the a:000 list and several others add too
|
||
much overhead that cannot be avoided.
|
||
|
||
Therefore the `:def` method to define a new-style function had to be added,
|
||
which allows for a function with different semantics. Most things still work
|
||
as before, but some parts do not. A new way to define a function was
|
||
considered the best way to separate the legacy style code from Vim9 style
|
||
code.
|
||
|
||
Using "def" to define a function comes from Python. Other languages use
|
||
"function" which clashes with legacy Vim script.
|
||
|
||
|
||
Type checking ~
|
||
|
||
When compiling lines of Vim commands into instructions as much as possible
|
||
should be done at compile time. Postponing it to runtime makes the execution
|
||
slower and means mistakes are found only later. For example, when
|
||
encountering the "+" character and compiling this into a generic add
|
||
instruction, at runtime the instruction would have to inspect the type of the
|
||
arguments and decide what kind of addition to do. And when the type is
|
||
dictionary, give an error. If the types are known to be numbers then an "add
|
||
number" instruction can be used, which is faster. The error can be given at
|
||
compile time, no error handling is needed at runtime, since adding two numbers
|
||
almost never fails.
|
||
|
||
Note: As a tangential point, the exception is integer overflow, where the
|
||
result exceeds the maximum integer value. For example, adding to a 64-bit
|
||
signed integer where the result is greater than 2^63:
|
||
>vim9
|
||
vim9script
|
||
echo 9223372036854775807 + 1 # -9223372036854775808
|
||
echo 2->pow(63)->float2nr() + 1 # -9223372036854775808
|
||
<
|
||
The syntax for types, using <type> for compound types, is similar to Java.
|
||
It is easy to understand and widely used. The type names are what were used
|
||
in Vim before, with some additions such as "void" and "bool".
|
||
|
||
|
||
Removing clutter and weirdness ~
|
||
|
||
Once decided that `:def` functions have different syntax than legacy
|
||
functions, we are free to add improvements to make the code more familiar for
|
||
users who know popular programming languages. In other words: remove weird
|
||
things that only Vim does.
|
||
|
||
We can also remove clutter, mainly things that were done to make Vim script
|
||
backwards compatible with the good old Vi commands.
|
||
|
||
Examples:
|
||
- Drop `:call` for calling a function and `:eval` for evaluating an
|
||
expression.
|
||
- Drop using a leading backslash for line continuation, automatically figure
|
||
out where an expression ends.
|
||
|
||
However, this does require that some things need to change:
|
||
- Comments start with # instead of ", to avoid confusing them with strings.
|
||
This is good anyway, it is also used by several popular languages.
|
||
- Ex command ranges need to be prefixed with a colon, to avoid confusion with
|
||
expressions (single quote can be a string or a mark, "/" can be divide or a
|
||
search command, etc.).
|
||
|
||
Goal is to limit the differences. A good criteria is that when the old syntax
|
||
is accidentally used you are very likely to get an error message.
|
||
|
||
|
||
Syntax and semantics from popular languages ~
|
||
|
||
Script writers have complained that the Vim script syntax is unexpectedly
|
||
different from what they are used to. To reduce this complaint popular
|
||
languages are used as an example. At the same time, we do not want to abandon
|
||
the well-known parts of legacy Vim script.
|
||
|
||
For many things TypeScript is followed. It's a recent language that is
|
||
gaining popularity and has similarities with Vim script. It also has a
|
||
mix of static typing (a variable always has a known value type) and dynamic
|
||
typing (a variable can have different types, this changes at runtime). Since
|
||
legacy Vim script is dynamically typed and a lot of existing functionality
|
||
(esp. builtin functions) depends on that, while static typing allows for much
|
||
faster execution, we need to have this mix in Vim9 script.
|
||
|
||
There is no intention to completely match TypeScript syntax and semantics. We
|
||
just want to take those parts that we can use for Vim and we expect Vim users
|
||
will be happy with. TypeScript is a complex language with its own history,
|
||
advantages and disadvantages. To get an idea of the disadvantages read the
|
||
book: "JavaScript: The Good Parts". Or find the article "TypeScript: the good
|
||
parts" and read the "Things to avoid" section.
|
||
|
||
People familiar with other languages (Java, Python, etc.) will also find
|
||
things in TypeScript that they do not like or do not understand. We'll try to
|
||
avoid those things.
|
||
|
||
Specific items from TypeScript we avoid:
|
||
- Overloading "+", using it both for addition and string concatenation. This
|
||
goes against legacy Vim script and often leads to mistakes. For that reason
|
||
we will keep using ".." for string concatenation. Lua also uses ".." this
|
||
way. And it allows for conversion to string for more values.
|
||
- TypeScript can use an expression like '99 || "yes"' in a condition, but
|
||
cannot assign the value to a boolean. That is inconsistent and can be
|
||
annoying. Vim recognizes an expression with && or || and allows using the
|
||
result as a bool. The |falsy-operator| was added for the mechanism to use a
|
||
default value - see |vim9-falsy|.
|
||
- TypeScript considers an empty string as Falsy, but an empty list or dict as
|
||
Truthy. That is inconsistent. In Vim an empty list and dict are also
|
||
Falsy.
|
||
- TypeScript has various "Readonly" types, which have limited usefulness,
|
||
since a type cast can remove the immutable nature. Vim locks the value,
|
||
which is more flexible, but is only checked at runtime.
|
||
- TypeScript has a complicated "import" statement that does not match how the
|
||
Vim import mechanism works. A much simpler mechanism is used instead, which
|
||
matches that the imported script is only sourced once.
|
||
|
||
|
||
Declarations ~
|
||
|
||
Legacy Vim script uses `:let` for every assignment, while in Vim9 declarations
|
||
are used. That is different, thus it's good to use a different command:
|
||
`:var`. This is used in many languages. The semantics might be slightly
|
||
different, but it's easily recognized as a declaration.
|
||
|
||
Using `:const` for constants is common, but the semantics varies. Some
|
||
languages only make the variable immutable, others also make the value
|
||
immutable. Since "final" is well known from Java for only making the variable
|
||
immutable we decided to use that. And then `:const` can be used for making
|
||
both immutable. This was also used in legacy Vim script and the meaning is
|
||
almost the same.
|
||
|
||
What we end up with is very similar to Dart: >
|
||
:var name # mutable variable and value
|
||
:final name # immutable variable, mutable value
|
||
:const name # immutable variable and value
|
||
|
||
Since legacy and Vim9 script will be mixed and global variables will be
|
||
shared, optional type checking is desirable. Also, type inference will avoid
|
||
the need for specifying the type in many cases. The TypeScript syntax fits
|
||
best for adding types to declarations:
|
||
>vim9
|
||
vim9script
|
||
var name: string # string type is specified
|
||
name = 'John'
|
||
const GREETING = 'Hello' # string type is inferred
|
||
echo $'{GREETING} {name}'
|
||
<
|
||
This is how we put types in a declaration:
|
||
>vim9
|
||
vim9script
|
||
var vlist: list<number>
|
||
final flist: list<string> = ['Vim']
|
||
def Func(arg1: string, arg2: number): string
|
||
return $'{arg1}{arg2}'
|
||
enddef
|
||
vlist[0] = 9
|
||
echo Func(flist[0], vlist[0]) # Vim9
|
||
<
|
||
These alternatives were considered:
|
||
|
||
1. Put the type before the name, like Dart:
|
||
>
|
||
var list<string> mylist
|
||
final list<string> mylist = ['foo']
|
||
def Func(number arg1, string arg2) bool
|
||
<
|
||
2. Put the type after the variable name, but do not use a colon, like Go:
|
||
>
|
||
var mylist list<string>
|
||
final mylist list<string> = ['foo']
|
||
def Func(arg1 number, arg2 string) bool
|
||
<
|
||
The first is more familiar for anyone used to C or Java. The second one
|
||
doesn't really have an advantage over the first, so let's discard the second.
|
||
|
||
Since we use type inference the type can be left out when it can be inferred
|
||
from the value. This means that after `var` we don't know if a type or a name
|
||
follows. That makes parsing harder, not only for Vim but also for humans.
|
||
Also, it will not be allowed to use a variable name that could be a type name,
|
||
using `var string string` is too confusing.
|
||
|
||
The chosen syntax, using a colon to separate the name from the type, adds
|
||
punctuation, but it actually makes it easier to recognize the parts of a
|
||
declaration.
|
||
|
||
|
||
Expressions ~
|
||
|
||
Expression evaluation was already close to what other languages are doing.
|
||
Some details are unexpected and can be improved. For example a boolean
|
||
condition would accept a string, convert it to a number and check if the
|
||
number is non-zero. This is unexpected and often leads to mistakes, since
|
||
text not starting with a number would be converted to zero, which is
|
||
considered false. Thus using a string for a condition would often not give an
|
||
error and be considered false. That is confusing.
|
||
|
||
In Vim9 type checking is stricter to avoid mistakes. Where a condition is
|
||
used, e.g. with the `:if` command and the `||` operator, only boolean-like
|
||
values are accepted:
|
||
true: `true`, `v:true`, `1`, `0 < 9`
|
||
false: `false`, `v:false`, `0`, `0 > 9`
|
||
Note that the number zero is false and the number one is true. This is more
|
||
permissive than most other languages. It was done because many builtin
|
||
functions return these values, and changing that causes more problems than it
|
||
solves. After using this for a while it turned out to work well.
|
||
|
||
If you have any type of value and want to use it as a boolean, use the `!!`
|
||
operator (see also |vim9-!!|):
|
||
>vim9
|
||
vim9script
|
||
# The following are all true:
|
||
echo [!!'text', !![1], !!{'x': 1}, !!1, !!1.1]
|
||
# And these are all false:
|
||
echo [!!'', !![], !!{}, !!0, !!0.0]
|
||
<
|
||
From a language like JavaScript we have this handy construct: >
|
||
GetName() || 'unknown'
|
||
However, this conflicts with only allowing a boolean for a condition.
|
||
Therefore the "??" operator was added: >
|
||
GetName() ?? 'unknown'
|
||
Here you can explicitly express your intention to use the value as-is and not
|
||
result in a boolean. This is called the |falsy-operator| - see |vim9-falsy|.
|
||
|
||
|
||
Import and Export ~
|
||
|
||
A problem of legacy Vim script is that by default all functions and variables
|
||
are global. It is possible to make them script-local, but then they are not
|
||
available in other scripts. This defies the concept of a package that only
|
||
exports selected items and keeps the rest local.
|
||
|
||
In Vim9 script a mechanism very similar to the JavaScript import and export
|
||
mechanism is supported. It is a variant to the existing `:source` command
|
||
that works like one would expect:
|
||
- Instead of making everything global by default, everything is script-local,
|
||
some of these are exported.
|
||
- When importing a script the symbols that are imported are explicitly listed,
|
||
avoiding name conflicts and failures if functionality is added later.
|
||
- The mechanism allows for writing a big, long script with a very clear API:
|
||
the exported functions, variables and classes.
|
||
- By using relative paths loading can be much faster for an import inside of a
|
||
package, no need to search many directories.
|
||
- Once an import has been used, its items are cached and loading it again is
|
||
not needed.
|
||
- The Vim-specific use of "s:" to make things script-local can be dropped.
|
||
|
||
When sourcing a Vim9 script (from either a Vim9 script or legacy Vim script),
|
||
only the items defined globally can be used, not the exported items.
|
||
Alternatives considered:
|
||
- All the exported items become available as script-local items. This makes
|
||
it uncontrollable what items get defined and likely soon leads to trouble.
|
||
- Use the exported items and make them global. Disadvantage is that it's then
|
||
not possible to avoid name clashes in the global namespace.
|
||
- Completely disallow sourcing a Vim9 script, require using `:import`. That
|
||
makes it difficult to use scripts for testing, or sourcing them from the
|
||
command line to try them out.
|
||
Note that you CAN also use `:import` in legacy Vim script, see above.
|
||
|
||
|
||
Compiling functions early ~
|
||
|
||
Functions are compiled when called or when `:defcompile` is used. Why not
|
||
compile them early, so that syntax and type errors are reported early?
|
||
|
||
The functions can't be compiled right away when encountered, because there may
|
||
be forward references to functions defined later. Consider defining functions
|
||
A, B and C, where A calls B, B calls C, and C calls A again. It's impossible
|
||
to reorder the functions to avoid forward references.
|
||
|
||
An alternative would be to first scan through the file to locate items and
|
||
figure out their type, so that forward references are found, and only then
|
||
execute the script and compile the functions. This means the script has to be
|
||
parsed twice, which is slower, and some conditions at the script level, such
|
||
as checking if a feature is supported, are hard to use. An attempt was made
|
||
to see if it works, but it turned out to be impossible to make work well.
|
||
|
||
It would be possible to compile all the functions at the end of the script.
|
||
The drawback is that if a function never gets called, the overhead of
|
||
compiling it counts anyway. Since startup speed is very important, in most
|
||
cases it's better to do it later and accept that syntax and type errors are
|
||
only reported then. In case these errors should be found early, e.g. when
|
||
testing, a `:defcompile` command at the end of the script will help out.
|
||
|
||
|
||
Why not use an existing embedded language? ~
|
||
|
||
Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But
|
||
these interfaces have never become widely used, for various reasons. When
|
||
Vim9 was designed a decision was made to make these interfaces lower priority
|
||
and concentrate on Vim script.
|
||
|
||
Still, plugin writers may find other languages more familiar, want to use
|
||
existing libraries or see a performance benefit. We encourage plugin authors
|
||
to write code in any language and run it as an external process, using jobs
|
||
and channels. We can try to make this easier somehow.
|
||
|
||
Using an external tool also has disadvantages. An alternative is to convert
|
||
the tool into Vim script. For that to be possible without too much
|
||
translation, and keeping the code fast at the same time, the constructs of the
|
||
tool need to be supported. Since Vim9 script now includes support for
|
||
classes, objects, interfaces, and enums, that is increasingly feasible.
|
||
|
||
|
||
|
||
vim:tw=78:ts=8:noet:ft=help:norl:
|