mirror of
https://github.com/vim/vim.git
synced 2025-11-13 22:54:27 -05:00
closes: #18724 Signed-off-by: Doug Kearns <dougkearns@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
3348 lines
122 KiB
Plaintext
3348 lines
122 KiB
Plaintext
*vim9.txt* For Vim version 9.1. Last change: 2025 Nov 11
|
||
|
||
|
||
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.
|
||
|
||
|
||
|
||
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.
|
||
|
||
The Vim9 script syntax and semantics are used 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* *E1164*
|
||
Evaluate and execute {cmd} using Vim9 script syntax and
|
||
semantics. Useful when typing a command and in a legacy
|
||
script or function.
|
||
|
||
:leg[acy] {cmd} *:leg* *:legacy* *E1189* *E1234*
|
||
Evaluate and execute {cmd} using legacy script syntax and
|
||
semantics. Only useful in a Vim9 script or a :def function.
|
||
Note that {cmd} cannot use local variables, since it is parsed
|
||
with legacy expression syntax.
|
||
|
||
See some examples of Vim9 script at |52.6|.
|
||
==============================================================================
|
||
|
||
2. Differences from legacy Vim script *vim9-differences*
|
||
|
||
Overview ~
|
||
*E1146*
|
||
Brief summary of the differences you will most often encounter when using Vim9
|
||
script and `:def` functions; details are below:
|
||
- Comments start with #, not ": >
|
||
echo "hello" # comment
|
||
- Using a backslash for line continuation is hardly ever needed: >
|
||
echo "hello "
|
||
.. yourName
|
||
.. ", how are you?"
|
||
- White space is required in many places to improve readability.
|
||
- Assign values without `:let` *E1126* , declare variables with `:var`: >
|
||
var count = 0
|
||
count += 3
|
||
- Constants can be declared with `:final` and `:const`: >
|
||
final matches = [] # add to the list later
|
||
const names = ['Betty', 'Peter'] # cannot be changed
|
||
- `:final` cannot be used as an abbreviation of `:finally`.
|
||
- Variables and functions are script-local by default.
|
||
- Functions are declared with argument types and return type: >
|
||
def CallMe(count: number, message: string): bool
|
||
- Call functions without `:call`: >
|
||
writefile(['done'], 'file.txt')
|
||
- You cannot use old Ex commands:
|
||
`:Print`
|
||
`:append`
|
||
`:change`
|
||
`:d` directly followed by 'd' or 'p'.
|
||
`:insert`
|
||
`:k`
|
||
`:mode`
|
||
`:open`
|
||
`:s` with only flags
|
||
`:t`
|
||
`:xit`
|
||
- Some commands, especially those used for flow control, cannot be shortened.
|
||
E.g., `:throw` cannot be written as `:th`. *vim9-no-shorten*
|
||
- You cannot use curly-braces names.
|
||
- A range before a command must be prefixed with a colon: >
|
||
:%s/this/that
|
||
- Executing a register with "@r" does not work, you can prepend a colon or use
|
||
`:exe`: >
|
||
:exe @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:
|
||
|vim9-string-index|
|
||
- Some possibly unexpected differences: |vim9-gotchas|.
|
||
|
||
|
||
Comments starting with # ~
|
||
|
||
In legacy Vim script comments start with double quote. In Vim9 script
|
||
comments start with #. >
|
||
# declarations
|
||
var count = 0 # number of occurrences
|
||
|
||
The reason is that a double quote can also be the start of a string. In many
|
||
places, especially halfway through an expression with a line break, it's hard
|
||
to tell what the meaning is, since 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 programs.
|
||
|
||
In Vi # is a command to list text with numbers. In Vim9 script you can use
|
||
`:number` for that. >
|
||
:101 number
|
||
|
||
To improve readability there must be a space between a command and the #
|
||
that starts a comment: >
|
||
var name = value # comment
|
||
var name = value# error!
|
||
< *E1170*
|
||
Do not start a comment with #{, it looks like the legacy dictionary literal
|
||
and produces an error where this might be confusing. #{{ or #{{{ are OK,
|
||
these can be used to start a fold.
|
||
|
||
When starting to read a script file Vim doesn't know it is |Vim9| script until
|
||
the `vim9script` command is found. Until that point you would need to use
|
||
legacy comments: >
|
||
" legacy comment
|
||
vim9script
|
||
# Vim9 comment
|
||
|
||
That looks ugly, better put `vim9script` in the very first line: >
|
||
vim9script
|
||
# Vim9 comment
|
||
|
||
In legacy Vim script # is also used for the alternate file name. In Vim9
|
||
script you need to use %% instead. Instead of ## use %%% (stands for all
|
||
arguments).
|
||
|
||
|
||
Vim9 functions ~
|
||
*E1099*
|
||
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.
|
||
- 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)
|
||
*E1091* *E1191*
|
||
If compilation fails it is not tried again on the next call, instead this
|
||
error is given: "E1091: Function is not compiled: {name}".
|
||
Compilation will fail when encountering a user command that has not been
|
||
created yet. In this case you can call `execute()` to invoke it at runtime. >
|
||
def MyFunc()
|
||
execute('DefinedLater')
|
||
enddef
|
||
|
||
`:def` has no options like `:function` does: "range", "abort", "dict" or
|
||
"closure". A `:def` function always aborts on an error (unless `:silent!` was
|
||
used for the command or the error was caught a `:try` block), does not get a
|
||
range passed, cannot be a "dict" function, and can always be a closure.
|
||
*vim9-no-dict-function* *E1182*
|
||
You can use a Vim9 Class (|Vim9-class|) instead of a "dict function".
|
||
You can also pass the dictionary explicitly: >
|
||
def DictFunc(self: dict<any>, arg: string)
|
||
echo self[arg]
|
||
enddef
|
||
var ad = {item: 'value', func: DictFunc}
|
||
ad.func(ad, 'item')
|
||
|
||
You can call a legacy dict function though: >
|
||
func Legacy() dict
|
||
echo self.value
|
||
endfunc
|
||
def CallLegacy()
|
||
var d = {func: Legacy, value: 'text'}
|
||
d.func()
|
||
enddef
|
||
< *E1096* *E1174* *E1175*
|
||
The argument types and return type need to be specified. The "any" type can
|
||
be used, type checking will then be done at runtime, like with legacy
|
||
functions.
|
||
*E1106*
|
||
Arguments are accessed by name, without "a:", just like any other language.
|
||
There is no "a:" dictionary or "a:000" list.
|
||
*vim9-variable-arguments* *E1055* *E1160* *E1180*
|
||
Variable arguments are defined as the last argument, with a name and have a
|
||
list type, similar to TypeScript. For example, a list of numbers: >
|
||
def MyFunc(...itemlist: list<number>)
|
||
for item in itemlist
|
||
...
|
||
|
||
When a function argument is optional (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. Example: >
|
||
def MyFunc(one = 'one', last = 'last')
|
||
...
|
||
enddef
|
||
MyFunc(v:none, 'LAST') # first argument uses default value 'one'
|
||
<
|
||
*vim9-ignored-argument* *E1181*
|
||
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. E.g. when using map() two arguments are passed,
|
||
the key and the value, to ignore the key: >
|
||
map(numberList, (_, v) => v * 2)
|
||
There is no error for using the "_" argument multiple times. No type needs to
|
||
be given.
|
||
|
||
|
||
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 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
|
||
< *E1058* *E1075*
|
||
When using `:function` or `:def` to specify a nested function inside a `:def`
|
||
function and no namespace was given, this nested function is local to the code
|
||
block it is defined in. It cannot be used in `function()` with a string
|
||
argument, pass the function reference itself: >
|
||
def Outer()
|
||
def Inner()
|
||
echo 'inner'
|
||
enddef
|
||
var Fok = function(Inner) # OK
|
||
var Fbad = function('Inner') # does not work
|
||
|
||
Detail: this is because "Inner" will actually become a function reference to a
|
||
function with a generated name.
|
||
|
||
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 (it must have
|
||
been declared at the script level). It is possible to define a global
|
||
function by using the "g:" prefix.
|
||
|
||
When referring to a function and no "s:" or "g:" prefix is used, Vim will
|
||
search for the function:
|
||
- in the function scope, in block scopes
|
||
- in the script scope
|
||
|
||
Imported functions are found with the prefix from the `:import` command.
|
||
|
||
Since a script-local function reference can be used without "s:" the name must
|
||
start with an upper case letter even when using the "s:" prefix. In legacy
|
||
script "s:funcref" could be used, because it could not be referred to with
|
||
"funcref". In Vim9 script it can, therefore "s:Funcref" must be used to avoid
|
||
that the name interferes with builtin functions.
|
||
*vim9-s-namespace* *E1268*
|
||
The use of the "s:" prefix is not supported at the Vim9 script level. All
|
||
functions and variables without a prefix are script-local.
|
||
|
||
In :def functions the use of "s:" depends on the script: Script-local
|
||
variables and functions in a legacy script do use "s:", while in a Vim9 script
|
||
they do not use "s:". This matches what you see in the rest of the file.
|
||
|
||
In legacy functions the use of "s:" for script items is required, as before.
|
||
No matter if the script is Vim9 or legacy.
|
||
|
||
In all cases the function must be defined before used. That is when it is
|
||
called, when `:defcompile` causes it to be compiled, or when code that calls
|
||
it is being compiled (to figure out 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|).
|
||
*E1102*
|
||
Global functions can still be defined and deleted at nearly any time. In
|
||
Vim9 script script-local functions are defined once when the script is sourced
|
||
and cannot be deleted or replaced by itself (it can be by reloading the
|
||
script).
|
||
|
||
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* *E1149* *E1150*
|
||
When loading a legacy Vim script a second time nothing is removed, the
|
||
commands will replace existing variables and functions, create new ones, and
|
||
leave removed things hanging around.
|
||
|
||
When loading a Vim9 script a second time all existing script-local functions
|
||
and variables are deleted, thus 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 still hanging around.
|
||
|
||
If you do want to keep items, use: >
|
||
vim9script noclear
|
||
|
||
You want to use this in scripts that use a `finish` command to bail out at
|
||
some point when loaded again. E.g. when a buffer local option is set to a
|
||
function, the function does not need to be defined more than once: >
|
||
vim9script noclear
|
||
setlocal completefunc=SomeFunc
|
||
if exists('*SomeFunc')
|
||
finish
|
||
endif
|
||
def SomeFunc()
|
||
....
|
||
|
||
|
||
Variable declarations with :var, :final and :const ~
|
||
*vim9-declaration* *:var* *E1079*
|
||
*E1017* *E1020* *E1054* *E1087* *E1124*
|
||
Local variables need to be declared with `:var`. Local constants need to be
|
||
declared with `:final` or `:const`. We refer to both as "variables" in this
|
||
section.
|
||
|
||
Variables can be local to a script, function or code block: >
|
||
vim9script
|
||
var script_var = 123
|
||
def SomeFunc()
|
||
var func_var = script_var
|
||
if cond
|
||
var block_var = func_var
|
||
...
|
||
|
||
The variables are only visible in the block where they are defined and nested
|
||
blocks. Once the block ends the variable is no longer accessible: >
|
||
if cond
|
||
var inner = 5
|
||
else
|
||
var inner = 0
|
||
endif
|
||
echo inner # Error!
|
||
|
||
The declaration must be done earlier: >
|
||
var inner: number
|
||
if cond
|
||
inner = 5
|
||
else
|
||
inner = 0
|
||
endif
|
||
echo inner
|
||
|
||
Although this is shorter and faster for simple values: >
|
||
var inner = 0
|
||
if cond
|
||
inner = 5
|
||
endif
|
||
echo inner
|
||
< *E1025* *E1128*
|
||
To intentionally hide a variable from code that follows, a block can be
|
||
used: >
|
||
{
|
||
var temp = 'temp'
|
||
...
|
||
}
|
||
echo temp # Error!
|
||
|
||
This is especially useful in a user command: >
|
||
command -range Rename {
|
||
var save = @a
|
||
@a = 'some expression'
|
||
echo 'do something with ' .. @a
|
||
@a = save
|
||
}
|
||
|
||
And with autocommands: >
|
||
au BufWritePre *.go {
|
||
var save = winsaveview()
|
||
silent! exe ':%! some formatting command'
|
||
winrestview(save)
|
||
}
|
||
|
||
Although using a :def function probably works better.
|
||
|
||
*E1022* *E1103* *E1130* *E1131* *E1133*
|
||
*E1134*
|
||
Declaring a variable with a type but without an initializer will initialize to
|
||
false (for bool), empty (for string, list, dict, etc.) or zero (for number,
|
||
any, etc.). This matters especially when using the "any" type, the value will
|
||
default to the number zero. For example, when declaring a list, items can be
|
||
added: >
|
||
var myList: list<number>
|
||
myList->add(7)
|
||
|
||
Initializing a variable to a null value, e.g. `null_list`, differs from not
|
||
initializing the variable. This throws an error: >
|
||
var myList = null_list
|
||
myList->add(7) # E1130: Cannot add to null list
|
||
|
||
< *E1016* *E1052* *E1066*
|
||
In Vim9 script `:let` cannot be used. An existing variable is assigned to
|
||
without any command. The same for global, window, tab, buffer and Vim
|
||
variables, because they are not really declared. Those can also be deleted
|
||
with `:unlet`.
|
||
*E1065*
|
||
You cannot use `:va` to declare a variable, it must be written with the full
|
||
name `:var`. Just to make sure it is easy to read.
|
||
*E1178*
|
||
`:lockvar` does not work on local variables. Use `:const` and `:final`
|
||
instead.
|
||
|
||
The `exists()` and `exists_compiled()` functions do not work on local variables
|
||
or arguments.
|
||
*E1006* *E1041* *E1167* *E1168* *E1213*
|
||
Variables, functions and function arguments cannot shadow previously defined
|
||
or imported variables and functions in the same script file.
|
||
Variables may shadow Ex commands, rename the variable if needed.
|
||
|
||
Global variables must be prefixed with "g:", also at the script level. >
|
||
vim9script
|
||
var script_local = 'text'
|
||
g:global = 'value'
|
||
var Funcref = g:ThatFunction
|
||
|
||
Global functions must be prefixed with "g:": >
|
||
vim9script
|
||
def g:GlobalFunc(): string
|
||
return 'text'
|
||
enddef
|
||
echo g:GlobalFunc()
|
||
The "g:" prefix is not needed for auto-load functions.
|
||
|
||
*vim9-function-defined-later*
|
||
Although global functions can be called without the "g:" prefix, they must
|
||
exist when compiled. By adding the "g:" prefix the function can be defined
|
||
later. Example: >
|
||
def CallPluginFunc()
|
||
if exists('g:loaded_plugin')
|
||
g:PluginFunc()
|
||
endif
|
||
enddef
|
||
|
||
If you do it like this, you get an error at compile time that "PluginFunc"
|
||
does not exist, even when "g:loaded_plugin" does not exist: >
|
||
def CallPluginFunc()
|
||
if exists('g:loaded_plugin')
|
||
PluginFunc() # Error - function not found
|
||
endif
|
||
enddef
|
||
|
||
You can use exists_compiled() to avoid the error, but then the function would
|
||
not be called, even when "g:loaded_plugin" is defined later: >
|
||
def CallPluginFunc()
|
||
if exists_compiled('g:loaded_plugin')
|
||
PluginFunc() # Function may never be called
|
||
endif
|
||
enddef
|
||
|
||
Since `&opt = value` is now assigning a value to option "opt", ":&" cannot be
|
||
used to repeat a `:substitute` command.
|
||
*vim9-unpack-ignore*
|
||
For an unpack assignment the underscore can be used to ignore a list item,
|
||
similar to how a function argument can be ignored: >
|
||
[a, _, c] = theList
|
||
To ignore any remaining items: >
|
||
[a, b; _] = longList
|
||
< *E1163* *E1080*
|
||
Declaring more than one variable at a time, using the unpack notation, is
|
||
possible. Each variable can have a type or infer it from the value: >
|
||
var [v1: number, v2] = GetValues()
|
||
Use this only when there is a list with values, declaring one variable per
|
||
line is much easier to read and change later.
|
||
|
||
|
||
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. In Vim9 we can use both.
|
||
*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.
|
||
Example: >
|
||
const myList = [1, 2]
|
||
myList = [3, 4] # Error!
|
||
myList[0] = 9 # Error!
|
||
myList->add(3) # Error!
|
||
< *:final* *E1125*
|
||
`:final` is used for making only the variable a constant, the value can be
|
||
changed. This is well known from Java. Example: >
|
||
final myList = [1, 2]
|
||
myList = [3, 4] # Error!
|
||
myList[0] = 9 # OK
|
||
myList->add(3) # OK
|
||
|
||
It is common to write constants as ALL_CAPS, but you don't have to.
|
||
|
||
The constant only applies to the value itself, not what it refers to. >
|
||
final females = ["Mary"]
|
||
const NAMES = [["John", "Peter"], females]
|
||
NAMES[0] = ["Jack"] # Error!
|
||
NAMES[0][0] = "Jack" # Error!
|
||
NAMES[1] = ["Emma"] # Error!
|
||
NAMES[1][0] = "Emma" # OK, now females[0] == "Emma"
|
||
|
||
|
||
Omitting :call and :eval ~
|
||
*E1190*
|
||
Functions can be called without `:call`: >
|
||
writefile(lines, 'file')
|
||
Using `:call` is still possible, but this is discouraged.
|
||
|
||
A method call without `eval` is possible, so long as the start is an
|
||
identifier or can't be an Ex command. For a function either "(" or "->" must
|
||
be following, without a line break. Examples: >
|
||
myList->add(123)
|
||
g:myList->add(123)
|
||
[1, 2, 3]->Process()
|
||
{a: 1, b: 2}->Process()
|
||
"foobar"->Process()
|
||
("foobar")->Process()
|
||
'foobar'->Process()
|
||
('foobar')->Process()
|
||
|
||
In the rare case there is ambiguity between a function name and an Ex command,
|
||
prepend ":" to make clear you want to use the Ex command. For example, there
|
||
is both the `:substitute` command and the `substitute()` function. When the
|
||
line starts with `substitute(` this will use the function. Prepend a colon to
|
||
use the command instead: >
|
||
:substitute(pattern (replacement (
|
||
|
||
If the expression starts with "!" this is interpreted as a shell command, not
|
||
negation of a condition. Thus this is a shell command: >
|
||
!shellCommand->something
|
||
Put the expression in parentheses to use the "!" for negation: >
|
||
(!expression)->Method()
|
||
|
||
Note that while variables need to be defined before they can be used,
|
||
functions can be called before being defined. 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. And a typo 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()`. The argument types and return type will then be checked.
|
||
The function must already have been defined. >
|
||
|
||
var Funcref = MyFunction
|
||
|
||
When using `function()` the resulting type is "func", a function with any
|
||
number of arguments and any return type (including void). The function can be
|
||
defined later if the argument is in quotes.
|
||
|
||
|
||
Lambda using => instead of -> ~
|
||
*vim9-lambda*
|
||
In legacy script there can be confusion between using "->" for a method call
|
||
and for a lambda. Also, when a "{" is found the parser needs to figure out if
|
||
it is the start of a lambda or a dictionary, which is now more complicated
|
||
because of the use of argument types.
|
||
|
||
To avoid these problems Vim9 script uses a different syntax for a lambda,
|
||
which is similar to JavaScript: >
|
||
var Lambda = (arg) => expression
|
||
var Lambda = (arg): type => expression
|
||
< *E1157*
|
||
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 OK: >
|
||
filter(list, (k, v) =>
|
||
v > 0)
|
||
This does not work: >
|
||
filter(list, (k, v)
|
||
=> v > 0)
|
||
This also does not work: >
|
||
filter(list, (k,
|
||
v) => v > 0)
|
||
But you can use a backslash to concatenate the lines before parsing: >
|
||
filter(list, (k,
|
||
\ v)
|
||
\ => v > 0)
|
||
< *vim9-lambda-arguments* *E1172*
|
||
In legacy script a lambda could be called with any number of extra arguments,
|
||
there was no way to warn for not using them. In Vim9 script the number of
|
||
arguments must match. If you do want to accept any arguments, or any further
|
||
arguments, use "..._", which makes the function accept
|
||
|vim9-variable-arguments|. Example: >
|
||
var Callback = (..._) => 'anything'
|
||
echo Callback(1, 2, 3) # displays "anything"
|
||
|
||
< *inline-function* *E1171*
|
||
Additionally, a lambda can contain statements in {}: >
|
||
var Lambda = (arg) => {
|
||
g:was_called = 'yes'
|
||
return expression
|
||
}
|
||
This can be useful for a timer, for example: >
|
||
var count = 0
|
||
var timer = timer_start(500, (_) => {
|
||
count += 1
|
||
echom 'Handler called ' .. count
|
||
}, {repeat: 3})
|
||
|
||
The ending "}" must be at the start of a line. It can be followed by other
|
||
characters, e.g.: >
|
||
var d = mapnew(dict, (k, v): string => {
|
||
return 'value'
|
||
})
|
||
No command can follow the "{", only a comment can be used there.
|
||
|
||
*command-block* *E1026*
|
||
The block can also be used for defining a user command. Inside the block Vim9
|
||
syntax will be used.
|
||
|
||
This is an example of using here-docs: >
|
||
com SomeCommand {
|
||
g:someVar =<< trim eval END
|
||
ccc
|
||
ddd
|
||
END
|
||
}
|
||
|
||
If the statements include a dictionary, its closing bracket must not be
|
||
written at the start of a line. Otherwise, it would be parsed as the end of
|
||
the block. This does not work: >
|
||
command NewCommand {
|
||
g:mydict = {
|
||
'key': 'value',
|
||
} # ERROR: will be recognized as the end of the block
|
||
}
|
||
Put the '}' after the last item to avoid this: >
|
||
command NewCommand {
|
||
g:mydict = {
|
||
'key': 'value' }
|
||
}
|
||
|
||
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
|
||
"{". Unfortunately this means using "() => { command }" does not work, line
|
||
breaks are always required.
|
||
|
||
*vim9-curly*
|
||
To avoid the "{" of a dictionary literal to be recognized as a statement block
|
||
wrap it in parentheses: >
|
||
var Lambda = (arg) => ({key: 42})
|
||
|
||
Also when confused with the start of a command block: >
|
||
({
|
||
key: value
|
||
})->method()
|
||
|
||
|
||
Automatic line continuation ~
|
||
*vim9-line-continuation* *E1097*
|
||
In many cases it is obvious that an expression continues on the next line. In
|
||
those cases there is no need to prefix the line with a backslash (see
|
||
|line-continuation|). For example, when a list spans multiple lines: >
|
||
var mylist = [
|
||
'one',
|
||
'two',
|
||
]
|
||
And when a dict spans multiple lines: >
|
||
var mydict = {
|
||
one: 1,
|
||
two: 2,
|
||
}
|
||
With a function call: >
|
||
var result = Func(
|
||
arg1,
|
||
arg2
|
||
)
|
||
|
||
For binary operators in expressions not in [], {} or () a line break is
|
||
possible just before or after the operator. For example: >
|
||
var text = lead
|
||
.. middle
|
||
.. end
|
||
var total = start +
|
||
end -
|
||
correction
|
||
var result = positive
|
||
? PosFunc(arg)
|
||
: NegFunc(arg)
|
||
|
||
For a method call using "->" and a member using a dot, a line break is allowed
|
||
before it: >
|
||
var result = GetBuilder()
|
||
->BuilderSetWidth(333)
|
||
->BuilderSetHeight(777)
|
||
->BuilderBuild()
|
||
var result = MyDict
|
||
.member
|
||
|
||
For commands that have an argument that is a list of commands, the | character
|
||
at the start of the line indicates line continuation: >
|
||
autocmd BufNewFile *.match if condition
|
||
| echo 'match'
|
||
| endif
|
||
|
||
Note that this means that in heredoc the first line cannot start with a bar: >
|
||
var lines =<< trim END
|
||
| this doesn't work
|
||
END
|
||
Either use an empty line at the start or do not use heredoc. Or temporarily
|
||
add the "C" flag to 'cpoptions': >
|
||
set cpo+=C
|
||
var lines =<< trim END
|
||
| this works
|
||
END
|
||
set cpo-=C
|
||
If the heredoc is inside a function 'cpoptions' must be set before :def and
|
||
restored after the :enddef.
|
||
|
||
In places where line continuation with a backslash is still needed, such as
|
||
splitting up a long Ex command, comments can start with '#\ ': >
|
||
syn region Text
|
||
\ start='foo'
|
||
#\ comment
|
||
\ end='bar'
|
||
Like with legacy script '"\ ' is used. This is also needed when line
|
||
continuation is used without a backslash and a line starts with a bar: >
|
||
au CursorHold * echom 'BEFORE bar'
|
||
#\ some comment
|
||
| echom 'AFTER bar'
|
||
<
|
||
*E1050*
|
||
To make it possible for the operator at the start of the line to be
|
||
recognized, it is required to put a colon before a range. This example will
|
||
add "start" and "print": >
|
||
var result = start
|
||
+ print
|
||
Like this: >
|
||
var result = start + print
|
||
|
||
This will assign "start" and print a line: >
|
||
var result = start
|
||
:+ 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: >
|
||
MyFunc()
|
||
:% call MyFunc()
|
||
|
||
Note that the colon is not required for the |+cmd| argument: >
|
||
edit +6 fname
|
||
|
||
It is also possible to split a function header over multiple lines, in between
|
||
arguments: >
|
||
def MyFunc(
|
||
text: string,
|
||
separator = '-'
|
||
): string
|
||
|
||
Since a continuation line cannot be easily recognized the parsing of commands
|
||
has been made stricter. E.g., because of the error in the first line, the
|
||
second line is seen as a separate command: >
|
||
popup_create(some invalid expression, {
|
||
exit_cb: Func})
|
||
Now "exit_cb: Func})" is actually a valid command: save any changes to the
|
||
file "_cb: Func})" and exit. To avoid this kind of mistake in Vim9 script
|
||
there must be white space between most command names and the argument.
|
||
*E1144*
|
||
|
||
However, the argument of a command that is a command won't be recognized. For
|
||
example, after "windo echo expr" a line break inside "expr" will not be seen.
|
||
|
||
|
||
Notes:
|
||
- "enddef" cannot be used at the start of a continuation line, it ends the
|
||
current function.
|
||
- No line break is allowed in the LHS of an assignment. Specifically when
|
||
unpacking a list |:let-unpack|. This is OK: >
|
||
[var1, var2] =
|
||
Func()
|
||
< This does not work: >
|
||
[var1,
|
||
var2] =
|
||
Func()
|
||
- No line break is allowed in between arguments of an `:echo`, `:execute` and
|
||
similar commands. This is OK: >
|
||
echo [1,
|
||
2] [3,
|
||
4]
|
||
< This does not work: >
|
||
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`. In
|
||
those cases the line continuation with a backslash has to be used.
|
||
|
||
|
||
White space ~
|
||
*E1004* *E1068* *E1069* *E1074* *E1127* *E1202*
|
||
Vim9 script enforces proper use of white space. This is no longer allowed: >
|
||
var name=234 # Error!
|
||
var name= 234 # Error!
|
||
var name =234 # Error!
|
||
There must be white space before and after the "=": >
|
||
var name = 234 # OK
|
||
White space must also be put before the # that starts a comment after a
|
||
command: >
|
||
var name = 234# Error!
|
||
var name = 234 # OK
|
||
|
||
White space is required around most operators.
|
||
|
||
White space is required in a sublist (list slice) around the ":", except at
|
||
the start and end: >
|
||
otherlist = mylist[v : count] # v:count has a different meaning
|
||
otherlist = mylist[:] # make a copy of the List
|
||
otherlist = mylist[v :]
|
||
otherlist = mylist[: v]
|
||
|
||
White space is not allowed:
|
||
- Between a function name and the "(": >
|
||
Func (arg) # Error!
|
||
Func
|
||
\ (arg) # Error!
|
||
Func
|
||
(arg) # Error!
|
||
Func(arg) # OK
|
||
Func(
|
||
arg) # OK
|
||
Func(
|
||
arg # OK
|
||
)
|
||
< *E1205*
|
||
White space is not allowed in a `:set` command between the option name and a
|
||
following "&", "!", "<", "=", "+=", "-=" or "^=".
|
||
|
||
|
||
No curly braces expansion ~
|
||
|
||
|curly-braces-names| cannot be used.
|
||
|
||
|
||
Command modifiers are not ignored ~
|
||
*E1176*
|
||
Using a command modifier for a command that does not use it gives an error.
|
||
*E1082*
|
||
Also, using a command modifier without a following command is now an error.
|
||
|
||
|
||
Dictionary literals ~
|
||
*vim9-literal-dict* *E1014*
|
||
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
|
||
that 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}
|
||
|
||
This works for alphanumeric characters, underscore and dash. If you want to
|
||
use another character, use a single or double quoted string: >
|
||
var dict = {'key with space': value}
|
||
var dict = {"key\twith\ttabs": value}
|
||
var dict = {'': value} # empty key
|
||
< *E1139*
|
||
In case the key needs to be an expression, square brackets can be used, just
|
||
like in JavaScript: >
|
||
var dict = {["key" .. nr]: value}
|
||
|
||
The key type can be string, number, bool or float. Other types result in an
|
||
error. Without using [] the value is used as a string, keeping leading zeros.
|
||
An expression given with [] is evaluated and then converted to a string.
|
||
Leading zeros will then be dropped: >
|
||
var dict = {000123: 'without', [000456]: 'with'}
|
||
echo dict
|
||
{'456': 'with', '000123': 'without'}
|
||
A float only works inside [] because the dot is not accepted otherwise: >
|
||
var dict = {[00.013]: 'float'}
|
||
echo dict
|
||
{'0.013': 'float'}
|
||
|
||
|
||
No :xit, :t, :k, :append, :change or :insert ~
|
||
*E1100*
|
||
These commands are too easily confused with local variable names.
|
||
Instead of `:x` or `:xit` you can use `:exit`.
|
||
Instead of `:t` you can use `:copy`.
|
||
Instead of `:k` you can use `:mark`.
|
||
|
||
|
||
Comparators ~
|
||
|
||
The 'ignorecase' option is not used for comparators that use strings.
|
||
Thus "=~" works like "=~#".
|
||
|
||
"is" and "isnot" (|expr-is| and |expr-isnot|) when used on strings now return
|
||
false. In legacy script they just compare the strings, in |Vim9| script they
|
||
check identity, and strings are copied when used, thus two strings are never
|
||
the same (this might change someday if strings are not copied but reference
|
||
counted).
|
||
|
||
|
||
Abort after error ~
|
||
|
||
In legacy script, when an error is encountered, Vim continues to execute
|
||
following lines. This can lead to a long sequence of errors and need to type
|
||
CTRL-C to stop it. In Vim9 script execution of commands stops at the first
|
||
error. Example: >
|
||
vim9script
|
||
var x = does-not-exist
|
||
echo 'not executed'
|
||
|
||
|
||
For loop ~
|
||
*E1254*
|
||
The loop variable must not be declared yet: >
|
||
var i = 1
|
||
for i in [1, 2, 3] # Error!
|
||
|
||
It is possible to use a global variable though: >
|
||
g:i = 1
|
||
for g:i in [1, 2, 3]
|
||
echo g:i
|
||
endfor
|
||
|
||
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 it just uses
|
||
the index, if items are deleted then items in the list will be skipped.
|
||
Example legacy script: >
|
||
let l = [1, 2, 3, 4]
|
||
for i in l
|
||
echo i
|
||
call remove(l, index(l, i))
|
||
endfor
|
||
Would echo:
|
||
1
|
||
2
|
||
3
|
||
4
|
||
In compiled Vim9 script you get:
|
||
1
|
||
3
|
||
Generally, you should not change the list that is iterated over. Make a copy
|
||
first if needed.
|
||
When looping over a list of lists, the nested lists can be changed. The loop
|
||
variable is "final", it cannot be changed but what its value can be changed.
|
||
*E1306*
|
||
The depth of loops, :for and :while loops added together, cannot exceed 10.
|
||
|
||
|
||
Conditions and expressions ~
|
||
*vim9-boolean*
|
||
Conditions and expressions are mostly working like they do in other languages.
|
||
Some values are different from legacy Vim script:
|
||
value legacy Vim script Vim9 script ~
|
||
0 falsy falsy
|
||
1 truthy truthy
|
||
99 truthy Error!
|
||
"0" falsy Error!
|
||
"99" truthy Error!
|
||
"text" falsy Error!
|
||
|
||
For the "??" operator and when using "!" then there is no error, every value
|
||
is either falsy or truthy. This is mostly like JavaScript, except that an
|
||
empty list and dict is falsy:
|
||
|
||
type truthy when ~
|
||
bool true, v:true or 1
|
||
number non-zero
|
||
float non-zero
|
||
string non-empty
|
||
blob non-empty
|
||
list non-empty (different from JavaScript)
|
||
tuple non-empty (different from JavaScript)
|
||
dictionary non-empty (different from JavaScript)
|
||
func when there is a function name
|
||
special true or v:true
|
||
job when not NULL
|
||
channel when not NULL
|
||
class when not NULL
|
||
object when not NULL (TODO: when isTrue() returns true)
|
||
|
||
The boolean operators "||" and "&&" expect the values to be boolean, zero or
|
||
one: >
|
||
1 || false == true
|
||
0 || 1 == true
|
||
0 || false == false
|
||
1 && true == true
|
||
0 && 1 == false
|
||
8 || 0 Error!
|
||
'yes' && 0 Error!
|
||
[] || 99 Error!
|
||
|
||
When using "!" for inverting, there is no error for using any type and the
|
||
result is a boolean. "!!" can be used to turn any value into boolean: >
|
||
!'yes' == false
|
||
!![] == false
|
||
!![1, 2, 3] == true
|
||
|
||
When using "`.."` for string concatenation arguments of simple types are
|
||
always converted to string: >
|
||
'hello ' .. 123 == 'hello 123'
|
||
'hello ' .. v:true == 'hello true'
|
||
|
||
Simple types are Number, Float, Special and Bool. For other types |string()|
|
||
should be used.
|
||
*false* *true* *null* *null_blob* *null_channel*
|
||
*null_class* *null_dict* *null_function* *null_job*
|
||
*null_list* *null_object* *null_partial* *null_string*
|
||
*E1034*
|
||
In Vim9 script one can use the following predefined values: >
|
||
true
|
||
false
|
||
null
|
||
null_blob
|
||
null_channel
|
||
null_class
|
||
null_dict
|
||
null_function
|
||
null_job
|
||
null_list
|
||
null_tuple
|
||
null_object
|
||
null_partial
|
||
null_string
|
||
`true` is the same as `v:true`, `false` the same as `v:false`, `null` the same
|
||
as `v:null`.
|
||
|
||
While `null` has the type "special", the other "null_" values have the type
|
||
indicated by their name. Quite often a null value is handled the same as an
|
||
empty value, but not always. The values can be useful to clear a script-local
|
||
variable, since they cannot be deleted with `:unlet`. E.g.: >
|
||
var theJob = job_start(...)
|
||
# let the job do its work
|
||
theJob = null_job
|
||
|
||
The values can also be useful as the default value for an argument: >
|
||
def MyFunc(b: blob = null_blob)
|
||
# Note: compare against null, not null_blob,
|
||
# to distinguish the default value from an empty blob.
|
||
if b == null
|
||
# b argument was not given
|
||
See |null-compare| for more information about testing against null.
|
||
|
||
It is possible to compare `null` with any value, this will not give a type
|
||
error. However, comparing `null` with a number, float or bool will always
|
||
result in `false`. This is different from legacy script, where comparing
|
||
`null` with zero or `false` would return `true`.
|
||
*vim9-false-true*
|
||
When converting a boolean to a string `false` and `true` are used, not
|
||
`v:false` and `v:true` like in legacy script. `v:none` has no `none`
|
||
replacement, 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. Composing characters are included.
|
||
Example: >
|
||
echo 'bár'[1]
|
||
In legacy script this results in the character 0xc3 (an illegal byte), in Vim9
|
||
script this results in the string 'á'.
|
||
A negative index is counting from the end, "[-1]" is the last character.
|
||
To exclude the last character use |slice()|.
|
||
To count composing characters separately use |strcharpart()|.
|
||
If the index is out of range then an empty string results.
|
||
|
||
In legacy script "++var" and "--var" would be silently accepted and have no
|
||
effect. This is an error in Vim9 script.
|
||
|
||
Numbers starting with zero are not considered to be octal, only numbers
|
||
starting with "0o" are octal: "0o744". |scriptversion-4|
|
||
|
||
|
||
What to watch out for ~
|
||
*vim9-gotchas*
|
||
Vim9 was designed to be closer to often used programming languages, but at the
|
||
same time tries to support the legacy Vim commands. Some compromises had to
|
||
be made. Here is a summary of what might be unexpected.
|
||
|
||
Ex command ranges need to be prefixed with a colon. >
|
||
-> legacy Vim: shifts the previous line to the right
|
||
->func() Vim9: method call in a continuation line
|
||
:-> Vim9: shifts the previous line to the right
|
||
|
||
%s/a/b legacy Vim: substitute on all lines
|
||
x = alongname
|
||
% another Vim9: modulo operator in a continuation line
|
||
:%s/a/b Vim9: substitute on all lines
|
||
't legacy Vim: jump to mark t
|
||
'text'->func() Vim9: method call
|
||
:'t Vim9: jump to mark t
|
||
|
||
Some Ex commands can be confused with assignments in Vim9 script: >
|
||
g:name = value # assignment
|
||
:g:pattern:cmd # :global command
|
||
|
||
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 '.'. >
|
||
g:pattern:cmd # invalid command - ERROR
|
||
s:pattern:repl # invalid command - ERROR
|
||
g-pattern-cmd # invalid command - ERROR
|
||
s-pattern-repl # invalid command - ERROR
|
||
g.pattern.cmd # invalid command - ERROR
|
||
s.pattern.repl # invalid command - ERROR
|
||
|
||
Also, there cannot be a space between the command and the separator: >
|
||
g /pattern/cmd # invalid command - ERROR
|
||
s /pattern/repl # invalid command - ERROR
|
||
|
||
Functions defined with `:def` compile the whole function. Legacy functions
|
||
can bail out, and the following lines are not parsed: >
|
||
func Maybe()
|
||
if !has('feature')
|
||
return
|
||
endif
|
||
use-feature
|
||
endfunc
|
||
Vim9 functions are compiled as a whole: >
|
||
def Maybe()
|
||
if !has('feature')
|
||
return
|
||
endif
|
||
use-feature # May give a compilation error
|
||
enddef
|
||
For a workaround, split it in two functions: >
|
||
func Maybe()
|
||
if has('feature')
|
||
call MaybeInner()
|
||
endif
|
||
endfunc
|
||
if has('feature')
|
||
def MaybeInner()
|
||
use-feature
|
||
enddef
|
||
endif
|
||
Or put the unsupported code inside an `if` with a constant expression that
|
||
evaluates to false: >
|
||
def Maybe()
|
||
if has('feature')
|
||
use-feature
|
||
endif
|
||
enddef
|
||
The `exists_compiled()` function can also be used for this.
|
||
*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: >
|
||
command -nargs=1 MyCommand echom <q-args>
|
||
def Works()
|
||
MyCommand 123
|
||
enddef
|
||
This will give an error for "MyCommand" not being defined: >
|
||
def Works()
|
||
command -nargs=1 MyCommand echom <q-args>
|
||
MyCommand 123
|
||
enddef
|
||
A workaround is to invoke the command indirectly with `:execute`: >
|
||
def Works()
|
||
command -nargs=1 MyCommand echom <q-args>
|
||
execute 'MyCommand 123'
|
||
enddef
|
||
|
||
Note that for unrecognized commands there is no check for "|" and a following
|
||
command. This will give an error for missing `endif`: >
|
||
def Maybe()
|
||
if has('feature') | use-feature | endif
|
||
enddef
|
||
|
||
Other differences ~
|
||
|
||
Patterns are used like 'magic' is set, unless explicitly overruled.
|
||
The 'edcompatible' option value is not used.
|
||
The 'gdefault' option value is not used.
|
||
|
||
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
|
||
|
||
*:++* *:--*
|
||
The ++ and -- commands have been added. They are very similar to adding or
|
||
subtracting one: >
|
||
++var
|
||
var += 1
|
||
--var
|
||
var -= 1
|
||
|
||
Using ++var or --var in an expression is not supported yet.
|
||
|
||
==============================================================================
|
||
|
||
3. New style functions *fast-functions*
|
||
|
||
*:def* *E1028*
|
||
: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*
|
||
*E1011*
|
||
The {name} must be less than 100 bytes long.
|
||
*E1003* *E1027* *E1056* *E1059*
|
||
The type of value used with `:return` must match
|
||
{return-type}. When {return-type} is omitted or is
|
||
"void" the function is not expected to return
|
||
anything.
|
||
*E1077* *E1123*
|
||
{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, the caller
|
||
must always provide them.
|
||
The second and third form are optional arguments.
|
||
When the caller omits an argument the {value} is used.
|
||
|
||
The function will be compiled into instructions when
|
||
called, or when `:disassemble` or `:defcompile` is
|
||
used. Syntax and type errors will be produced at that
|
||
time.
|
||
|
||
It is possible to nest `:def` inside another `:def` or
|
||
`:function` up to about 50 levels deep.
|
||
*E1117*
|
||
[!] is used as with `:function`. Note that
|
||
script-local functions cannot be deleted or redefined
|
||
later in Vim9 script. They can only be removed by
|
||
reloading the same script.
|
||
|
||
*:enddef* *E1057* *E1152* *E1173*
|
||
:enddef End of a function defined with `:def`. It should be on
|
||
a line by its own.
|
||
|
||
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
|
||
|
||
If the script the function is defined in is Vim9 script, then script-local
|
||
variables can be accessed without the "s:" prefix. They must be defined
|
||
before the function is compiled. If the script the function is defined in is
|
||
legacy script, then script-local variables must be accessed with the "s:"
|
||
prefix if they do not exist at the time of compiling.
|
||
*E1269*
|
||
Script-local variables in a |Vim9| script must be declared at the script
|
||
level. They cannot be created in a function, also not in a legacy function.
|
||
|
||
*: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.
|
||
|
||
:defc[ompile] MyClass Compile all methods in a class. |class-compile|
|
||
|
||
: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} call also be "ClassName.functionName" to
|
||
compile a function or method in a class.
|
||
{func} call 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. *E1061*
|
||
Note that for command line completion of {func} you
|
||
can prepend "s:" to find script-local functions.
|
||
|
||
: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.
|
||
|
||
Limitations ~
|
||
|
||
Local variables will not be visible to string evaluation. For example: >
|
||
def MapList(): list<string>
|
||
var list = ['aa', 'bb', 'cc', 'dd']
|
||
return range(1, 2)->map('list[v:val]')
|
||
enddef
|
||
|
||
The map argument is a string expression, which is evaluated without the
|
||
function scope. Instead, use a lambda: >
|
||
def MapList(): list<string>
|
||
var list = ['aa', 'bb', 'cc', 'dd']
|
||
return range(1, 2)->map((_, v) => list[v])
|
||
enddef
|
||
|
||
For commands that are not compiled, such as `:edit`, backtick expansion can be
|
||
used and it can use the local scope. Example: >
|
||
def Replace()
|
||
var fname = 'blah.txt'
|
||
edit `=fname`
|
||
enddef
|
||
|
||
Closures defined in a loop will share the same context. For example: >
|
||
var flist: list<func>
|
||
for i in range(5)
|
||
var inloop = i
|
||
flist[i] = () => inloop
|
||
endfor
|
||
echo range(5)->map((i, _) => flist[i]())
|
||
# Result: [4, 4, 4, 4, 4]
|
||
< *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.
|
||
|
||
The "inloop" variable will exist only once, all closures put in the list refer
|
||
to the same instance, which in the end will have the value 4. This is
|
||
efficient, also when looping many times. If you do want a separate context
|
||
for each closure, call a function to define it: >
|
||
def GetClosure(i: number): func
|
||
var infunc = i
|
||
return () => infunc
|
||
enddef
|
||
|
||
var flist: list<func>
|
||
for i in range(5)
|
||
flist[i] = GetClosure(i)
|
||
endfor
|
||
echo range(5)->map((i, _) => flist[i]())
|
||
# Result: [0, 1, 2, 3, 4]
|
||
|
||
In some situations, especially when calling a Vim9 closure from legacy
|
||
context, the evaluation will fail. *E1248*
|
||
|
||
Note that at the script level the loop variable will be invalid after the
|
||
loop, also when used in a closure that is called later, e.g. with a timer.
|
||
This will generate error |E1302|: >
|
||
for n in range(4)
|
||
timer_start(500 * n, (_) => {
|
||
echowin n
|
||
})
|
||
endfor
|
||
|
||
You need to use a block and define a variable there, and use that one in the
|
||
closure: >
|
||
for n in range(4)
|
||
{
|
||
var nr = n
|
||
timer_start(500 * n, (_) => {
|
||
echowin nr
|
||
})
|
||
}
|
||
endfor
|
||
|
||
Using `:echowindow` is useful in a timer, the messages go into a popup and will
|
||
not interfere with what the user is doing when it triggers.
|
||
|
||
|
||
Converting a function from legacy to Vim9 ~
|
||
*convert_legacy_function_to_vim9*
|
||
These are the most changes that need to be made to convert a legacy function
|
||
to a Vim9 function:
|
||
|
||
- Change `func` or `function` to `def`.
|
||
- Change `endfunc` or `endfunction` to `enddef`.
|
||
- Add types to the function arguments.
|
||
- If the function returns something, add the return type.
|
||
- Change comments to start with # instead of ".
|
||
|
||
For example, a legacy function: >
|
||
func MyFunc(text)
|
||
" function body
|
||
endfunc
|
||
< Becomes: >
|
||
def MyFunc(text: string): number
|
||
# function body
|
||
enddef
|
||
|
||
- Remove "a:" used for arguments. E.g.: >
|
||
return len(a:text)
|
||
< Becomes: >
|
||
return len(text)
|
||
|
||
- Change `let` used to declare a variable to `var`.
|
||
- Remove `let` used to assign a value to a variable. This is for local
|
||
variables already declared and b: w: g: and t: variables.
|
||
|
||
For example, legacy function: >
|
||
let lnum = 1
|
||
let lnum += 3
|
||
let b:result = 42
|
||
< Becomes: >
|
||
var lnum = 1
|
||
lnum += 3
|
||
b:result = 42
|
||
|
||
- Insert white space in expressions where needed.
|
||
- Change "." used for concatenation to "..".
|
||
|
||
For example, legacy function: >
|
||
echo line(1).line(2)
|
||
< Becomes: >
|
||
echo line(1) .. line(2)
|
||
|
||
- line continuation does not always require a backslash: >
|
||
echo ['one',
|
||
\ 'two',
|
||
\ 'three'
|
||
\ ]
|
||
< Becomes: >
|
||
echo ['one',
|
||
'two',
|
||
'three'
|
||
]
|
||
|
||
|
||
Calling a 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 very
|
||
simple, is to define a compiled function and set the option to call it
|
||
without arguments. Example: >
|
||
vim9script
|
||
def MyFoldFunc(): any
|
||
... compute fold level for line v:lnum
|
||
return level
|
||
enddef
|
||
set foldexpr=s:MyFoldFunc()
|
||
|
||
==============================================================================
|
||
|
||
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 ...
|
||
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: list
|
||
vim9cmd var l: list<number
|
||
vim9cmd var l: list<invalidtype>
|
||
vim9cmd var l: list<number> = ['42']
|
||
<
|
||
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: >
|
||
var myTuple: tuple<number> = (20,)
|
||
var myTuple: tuple<number, string> = (30, 'vim')
|
||
var myTuple: tuple<number, float, bool> = (40, 1.1, true)
|
||
var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
|
||
var myTuple: tuple<number, ...list<string>> = (3, 'a', 'b', 'c')
|
||
<
|
||
*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. Examples: >
|
||
var myTuple: tuple<...list<number>> = (1, 2, 3)
|
||
var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
|
||
var myTuple: tuple<...list<bool>> = ()
|
||
<
|
||
*vim9-func-declaration* *E1005* *E1007*
|
||
*vim9-partial-declaration*
|
||
*vim9-func-type*
|
||
A function (or partial) may be declared in the following ways:
|
||
func any kind of function reference, no type
|
||
checking for arguments or return value
|
||
func: void any number and type of arguments, no return
|
||
value
|
||
func: {type} any number and type of arguments with specific
|
||
return type
|
||
|
||
func() function with no argument, does not return a
|
||
value
|
||
func(): void same
|
||
func(): {type} function with no argument and return type
|
||
|
||
func({type}) function with argument type, does not return
|
||
a value
|
||
func({type}): {type} function with argument type and return type
|
||
func(?{type}) function with type of optional argument, does
|
||
not return a value
|
||
func(...list<{type}>) function with type of list for variable number
|
||
of arguments, does not return a value
|
||
func({type}, ?{type}, ...list<{type}>): {type}
|
||
function with:
|
||
- type of mandatory argument
|
||
- type of optional argument
|
||
- type of list for variable number of
|
||
arguments
|
||
- return type
|
||
|
||
If the return type is "void" the function does not return a value.
|
||
|
||
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 to two decimal places,
|
||
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.)"
|
||
<
|
||
*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 and reports 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 |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 script uses builtin method "string()" (|object-string()|).
|
||
The typename() of Quad and myQuad are the same ("enum<Quad>")
|
||
whereas the type() is distinguished (myQuad returns 16,
|
||
enumvalue, whereas Quad returns 15, 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" # expected list<number> but...
|
||
endtry
|
||
const TC: func = (): list<number> => {
|
||
return [1, <number>b:two]
|
||
}
|
||
disassemble TC # 2 CHECKTYPE number stack [-1]
|
||
try
|
||
TC()
|
||
catch
|
||
echo v:exception # expected number but got string
|
||
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: This must be executed from Vim's command line, not sourced.
|
||
|
||
*E1363*
|
||
If a type is incomplete, such as when an object's class is unknown, E1363
|
||
results. For example: >vim9
|
||
|
||
vim9script
|
||
var E1363 = null_class.member # E1363: Incomplete type
|
||
<
|
||
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() # Echoes 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* *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 a...
|
||
<
|
||
- 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
|
||
<
|
||
- Not using a number where a number is expected: >vim
|
||
|
||
" In this example, 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 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' fails in some instances (|E1037|, |E1072|): >vim
|
||
|
||
" 1 is echoed because these are both true
|
||
echo v:null is v:null && v:none is v:none
|
||
" 0 is echoed because all these expressions are false
|
||
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 with numb
|
||
vim9cmd echo v:true is v:none # E1072: Cannot compare bool with special
|
||
<
|
||
Note: Although the last two Vim9 script examples above error using
|
||
`v:none`, they return `false` using `null` (which is the same
|
||
as `v:null` - see |v:null|): >vim9
|
||
|
||
vim9script
|
||
echo null is 8 # false
|
||
echo true is 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
|
||
<
|
||
- Not using a string where an argument requires a string (|E1174|) >vim
|
||
|
||
echo substitute('Hallo', 'a', 'e', v:true)
|
||
vim9cmd echo substitute('Hallo', 'a', 'e', true) # E1174: String...
|
||
<
|
||
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
|
||
gives an error in Vim9 script, whereas in legacy Vim script it is allowed: >vim
|
||
|
||
" legacy Vim script changes s:mylist to ['item 0', 'item 1']
|
||
let s:mylist = [0, 1]
|
||
call map(s:mylist, {i -> $"item {i}"})
|
||
echo s:mylist
|
||
< >vim9
|
||
vim9script
|
||
var mylist = [0, 1] # Vim infers mylist is list<number>
|
||
map(mylist, (i, _) => $"item {i}") # E1012: type mismatch...
|
||
<
|
||
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 if 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) # Echoes 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]) # Echoes [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() # Echoes ¢
|
||
echo FuncSN(162)->nr2char()) # E1013 (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() # Error while compiling
|
||
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 argument 1
|
||
vim9 9->ch_close() # E1217: Channel or Job required for
|
||
vim9 9->job_info() # E1218: Job required for argument 1
|
||
vim9 [9]->cos() # E1219: Float or Number required for
|
||
vim9 {}->remove([]) # E1220: String or Number required
|
||
vim9 null_channel->ch_evalraw(9) # E1221: String or Blob required for
|
||
vim9 9->col() # E1222: String or List required for
|
||
vim9 9->complete_add() # E1223: String or Dictionary require
|
||
vim9 setbufline(9, 9, {}) # E1224: String, Number or List
|
||
vim9 9->count(9) # E1225: String, List, Tuple or Dict
|
||
vim9 9->add(9) # E1226: List or Blob required for
|
||
vim9 9->remove(9) # E1228: List, Dictionary, or Blob
|
||
vim9 getcharstr('9') # E1235: Bool or number required for
|
||
vim9 9->blob2list() # E1238: Blob required for argument 1
|
||
vim9 9->filter(9) # E1251: List, Tuple, Dictionary, Blo
|
||
vim9 9->reverse() # E1253: String, List, Tuple or Blob
|
||
vim9 9->call(9) # E1256: String or Function required
|
||
vim9 null_dict->winrestview() # E1297: Non-NULL Dictionary required
|
||
vim9 {}->prop_add_list(null_list) # E1298: Non-NULL List required for
|
||
vim9 {}->repeat(9) # E1301: String, Number, List, Tuple
|
||
vim9 9->index(9) # E1528: List or Tuple or Blob
|
||
vim9 9->join() # E1529: List or Tuple required for
|
||
vim9 9->max() # E1530: List or Tuple or Dictionary
|
||
vim9 9->get(9) # E1531: Argument of get() must be a
|
||
vim9 9->tuple2list() # E1534: Tuple required for argument
|
||
<
|
||
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]
|
||
<
|
||
Note: See |empty()| for explanations of empty job, empty channel, and
|
||
empty object types.
|
||
|
||
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 a 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 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
|
||
<
|
||
|
||
==============================================================================
|
||
|
||
*generic-functions*
|
||
5. 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
|
||
# E0141: 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 ...
|
||
< >vim9
|
||
vim9script
|
||
def My1556<T>(): void
|
||
enddef
|
||
My1556<bool, bool>()
|
||
# E1556: Too many types specified for generic function ...
|
||
< >vim9
|
||
vim9script
|
||
def My1557<T, U>(): void
|
||
enddef
|
||
My1557<bool>()
|
||
# E1557: Not enough types specified for generic function ...
|
||
< >vim9
|
||
vim9script
|
||
def My1559<T>(): T
|
||
enddef
|
||
My1559()
|
||
# Vim(eval):E1559: Type arguments missing for generic function ...
|
||
<
|
||
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.
|
||
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) # echos B
|
||
echo FirstOrDefault<number, number>([], 42) # echos 42
|
||
enddef
|
||
Outer()
|
||
<
|
||
|
||
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.
|
||
|
||
|
||
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` statement must
|
||
appear as the first statement in the file (see |vim9-mix| for an exception).
|
||
It tells Vim to interpret the script in its own namespace, instead of the
|
||
global namespace. If a file starts with: >
|
||
vim9script
|
||
var myvar = 'yes'
|
||
Then "myvar" will only exist in this file. While without `vim9script` it would
|
||
be available as `g:myvar` from any other script and function.
|
||
*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.
|
||
|
||
In Vim9 script the global "g:" namespace can still be used as before. And the
|
||
"w:", "b:" and "t:" namespaces. These have in common that variables are not
|
||
declared, have no specific type and they can be deleted. *E1304*
|
||
|
||
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 here
|
||
if !has('vim9script')
|
||
" _legacy Vim script_ comments/commands 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 throwing an
|
||
error if used in a version of Vim without 'vim9script'.
|
||
|
||
Note that Vim9 syntax changed before Vim 9 so that scripts using the current
|
||
syntax (such as "import from" instead of "import") might throw 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* *:exp*
|
||
Exporting an item can be written as: >
|
||
export const EXPORTED_CONST = 1234
|
||
export var someValue = ...
|
||
export final someValue = ...
|
||
export const someValue = ...
|
||
export def MyFunc() ...
|
||
export class MyClass ...
|
||
export interface MyClass ...
|
||
export enum MyEnum ...
|
||
< *E1043* *E1044*
|
||
As this suggests, only constants, variables, `:def` functions, classes,
|
||
interfaces and enums can be exported.
|
||
|
||
*E1042*
|
||
`:export` can only be used in Vim9 script, at the script level.
|
||
|
||
|
||
Import ~
|
||
*:import* *:imp* *E1094* *E1047* *E1262*
|
||
*E1048* *E1049* *E1053* *E1071* *E1088* *E1236*
|
||
The exported items can be imported in another script. The import syntax has
|
||
two forms. The simple form: >
|
||
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".
|
||
*:import-as* *E1257* *E1261*
|
||
In case the name is long or ambiguous, this form can be used to specify
|
||
another name: >
|
||
import {longfilename} as {name}
|
||
<
|
||
In this form {name} becomes a specific script local name for the imported
|
||
namespace. Therefore {name} must consist of letters, digits and '_', like
|
||
|internal-variables|. The {longfilename} expression must evaluate to any
|
||
filename. For example: >
|
||
import "thatscript.vim.v2" as that
|
||
< *E1060* *E1258* *E1259* *E1260*
|
||
Then you can use "that.item", etc. You are free to choose the name "that".
|
||
Use something that will be recognized as referring to the imported script.
|
||
Avoid command names, command modifiers and builtin function names, because the
|
||
name will shadow them. It's better not to start the name with a capital
|
||
letter, since it can then also shadow global user commands and functions.
|
||
Also, you cannot use the name for something else in the script, such as a
|
||
function or variable name.
|
||
|
||
In case the dot in the name is undesired, a local reference can be made for a
|
||
function: >
|
||
var LongFunc = that.LongFuncName
|
||
|
||
This also works for constants: >
|
||
const MAXLEN = that.MAX_LEN_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 will need to use the full name, with the dot.
|
||
|
||
`:import` can not be used in a function. Imported items are intended to exist
|
||
at the script level and only imported once.
|
||
|
||
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.
|
||
|
||
If the name does not end in ".vim" then the use of "as name" is required.
|
||
|
||
Once a Vim9 script file has been imported, the result is cached and used the
|
||
next time the same script is imported. It will not be read again.
|
||
|
||
It is not allowed to import the same script twice, also when using two
|
||
different "as" names.
|
||
|
||
When using the imported name the dot and the item name must be in the same
|
||
line, there can be no line break: >
|
||
echo that.
|
||
name # Error!
|
||
echo that
|
||
.name # Error!
|
||
< *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.
|
||
|
||
*import-legacy* *legacy-import*
|
||
`:import` 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()
|
||
|
||
And using the "as name" form: >
|
||
import "otherfile.vim9script" as that
|
||
call s:that.OtherFunc()
|
||
|
||
However, the namespace cannot be resolved on its own: >
|
||
import "that.vim"
|
||
echo s:that
|
||
" ERROR: E1060: Expected dot after name: s:that
|
||
<
|
||
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|.
|
||
|
||
|
||
==============================================================================
|
||
|
||
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 throw 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.
|
||
- 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: >
|
||
var name: string # string type is specified
|
||
...
|
||
name = 'John'
|
||
const greeting = 'hello' # string type is inferred
|
||
|
||
This is how we put types in a declaration: >
|
||
var mylist: list<string>
|
||
final mylist: list<string> = ['foo']
|
||
def Func(arg1: number, arg2: string): bool
|
||
|
||
Two 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 |expr-!|): >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|.
|
||
|
||
|
||
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:
|