0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.2.1744: Vim9: using ":const!" is weird

Problem:    Vim9: using ":const!" is weird.
Solution:   Use "var" - "final" - "const" like Dart.  "let" still works for
            now.
This commit is contained in:
Bram Moolenaar 2020-09-26 15:09:30 +02:00
parent 273af497ca
commit 30fd8204ce
16 changed files with 512 additions and 382 deletions

View File

@ -1,4 +1,4 @@
*vim9.txt* For Vim version 8.2. Last change: 2020 Sep 17 *vim9.txt* For Vim version 8.2. Last change: 2020 Sep 26
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -70,7 +70,7 @@ Comments starting with # ~
In legacy Vim script comments start with double quote. In Vim9 script In legacy Vim script comments start with double quote. In Vim9 script
comments start with #. > comments start with #. >
# declarations # declarations
let count = 0 # number of occurrences var count = 0 # number of occurrences
The reason is that a double quote can also be the start of a string. In many 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 places, especially halfway through an expression with a line break, it's hard
@ -154,31 +154,32 @@ Vim9 script script-local functions are defined once when the script is sourced
and cannot be deleted or replaced. and cannot be deleted or replaced.
Variable declarations with :let and :const ~ Variable declarations with :var, :final and :const ~
*vim9-declaration* *vim9-declaration*
Local variables need to be declared with `:let`. Local constants need to be Local variables need to be declared with `:var`. Local constants need to be
declared with `:const`. We refer to both as "variables". 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: > Variables can be local to a script, function or code block: >
vim9script vim9script
let script_var = 123 var script_var = 123
def SomeFunc() def SomeFunc()
let func_var = script_var var func_var = script_var
if cond if cond
let block_var = func_var var block_var = func_var
... ...
The variables are only visible in the block where they are defined and nested 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: > blocks. Once the block ends the variable is no longer accessible: >
if cond if cond
let inner = 5 var inner = 5
else else
let inner = 0 var inner = 0
endif endif
echo inner # Error! echo inner # Error!
The declaration must be done earlier: > The declaration must be done earlier: >
let inner: number var inner: number
if cond if cond
inner = 5 inner = 5
else else
@ -186,10 +187,10 @@ The declaration must be done earlier: >
endif endif
echo inner echo inner
To intentionally avoid a variable being available later, a block can be used: To intentionally hide a variable from code that follows, a block can be
> used: >
{ {
let temp = 'temp' var temp = 'temp'
... ...
} }
echo temp # Error! echo temp # Error!
@ -197,9 +198,9 @@ To intentionally avoid a variable being available later, a block can be used:
Declaring a variable with a type but without an initializer will initialize to Declaring a variable with a type but without an initializer will initialize to
zero, false or empty. zero, false or empty.
An existing variable cannot be assigned to with `:let`, since that implies a In Vim9 script `:let` cannot be used. An existing variable is assigned to
declaration. Global, window, tab, buffer and Vim variables can only be used without any command. The same for global, window, tab, buffer and Vim
without `:let`, because they are not really declared, they can also be deleted variables, because they are not really declared. They can also be deleted
with `:unlet`. with `:unlet`.
Variables and functions cannot shadow previously defined or imported variables Variables and functions cannot shadow previously defined or imported variables
@ -209,51 +210,50 @@ Variables may shadow Ex commands, rename the variable if needed.
Global variables and user defined functions must be prefixed with "g:", also Global variables and user defined functions must be prefixed with "g:", also
at the script level. > at the script level. >
vim9script vim9script
let script_local = 'text' var script_local = 'text'
g:global = 'value' g:global = 'value'
let Funcref = g:ThatFunction var Funcref = g:ThatFunction
Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be Since `&opt = value` is now assigning a value to option "opt", ":&" cannot be
used to repeat a `:substitute` command. used to repeat a `:substitute` command.
*vim9-const*
In legacy Vim script "const list = []" would make the variable "list"
immutable and also the value. Thus you cannot add items to the list. This
differs from what many languages do. Vim9 script does it like TypeScript: only
"list" is immutable, the value can be changed.
One can use `:const!` to make both the variable and the value immutable. Use
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.
`: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. 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!
muList->add(3) # Error!
How this works: > `:final` is used for making only the variable a constant, the value can be
vim9script changed. This is well known from Java. Example: >
const list = [1, 2] final myList = [1, 2]
list = [3, 4] # Error! myList = [3, 4] # Error!
list[0] = 2 # OK myList[0] = 9 # OK
muList->add(3) # OK
const! LIST = [1, 2]
LIST = [3, 4] # Error!
LIST[0] = 2 # Error!
It is common to write constants as ALL_CAPS, but you don't have to. 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. > The constant only applies to the value itself, not what it refers to. >
cont females = ["Mary"] final females = ["Mary"]
const! NAMES = [["John", "Peter"], females] const NAMES = [["John", "Peter"], females]
NAMES[0] = ["Jack"] # Error! NAMES[0] = ["Jack"] # Error!
NAMES[0][0] = ["Jack"] # Error! NAMES[0][0] = "Jack" # Error!
NAMES[1] = ["Emma"] # Error! NAMES[1] = ["Emma"] # Error!
Names[1][0] = "Emma" # OK, now females[0] == "Emma" Names[1][0] = "Emma" # OK, now females[0] == "Emma"
Rationale: TypeScript has no way to make the value immutable. One can use < *E1092*
immutable types, but that quickly gets complicated for nested values. And
with a type cast the value can be made mutable again, which means there is no
guarantee the value won't change. Vim supports immutable values, in legacy
script this was done with `:lockvar`. But that is an extra statement and also
applies to nested values. Therefore the solution to use `:const!`.
*E1092*
Declaring more than one variable at a time, using the unpack notation, is Declaring more than one variable at a time, using the unpack notation, is
currently not supported: > currently not supported: >
let [v1, v2] = GetValues() # Error! var [v1, v2] = GetValues() # Error!
That is because the type needs to be inferred from the list item type, which That is because the type needs to be inferred from the list item type, which
isn't that easy. isn't that easy.
@ -296,7 +296,7 @@ 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. without `function()`. The argument types and return type will then be checked.
The function must already have been defined. > The function must already have been defined. >
let Funcref = MyFunction var Funcref = MyFunction
When using `function()` the resulting type is "func", a function with any When using `function()` the resulting type is "func", a function with any
number of arguments and any return type. The function can be defined later. number of arguments and any return type. The function can be defined later.
@ -307,53 +307,53 @@ Automatic line continuation ~
In many cases it is obvious that an expression continues on the next line. In 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 those cases there is no need to prefix the line with a backslash
|line-continuation|. For example, when a list spans multiple lines: > |line-continuation|. For example, when a list spans multiple lines: >
let mylist = [ var mylist = [
'one', 'one',
'two', 'two',
] ]
And when a dict spans multiple lines: > And when a dict spans multiple lines: >
let mydict = #{ var mydict = #{
one: 1, one: 1,
two: 2, two: 2,
} }
Function call: > Function call: >
let result = Func( var result = Func(
arg1, arg1,
arg2 arg2
) )
For binary operators in expressions not in [], {} or () a line break is For binary operators in expressions not in [], {} or () a line break is
possible just before or after the operator. For example: > possible just before or after the operator. For example: >
let text = lead var text = lead
.. middle .. middle
.. end .. end
let total = start + var total = start +
end - end -
correction correction
let result = positive var result = positive
? PosFunc(arg) ? PosFunc(arg)
: NegFunc(arg) : NegFunc(arg)
For a method call using "->" and a member using a dot, a line break is allowed For a method call using "->" and a member using a dot, a line break is allowed
before it: > before it: >
let result = GetBuilder() var result = GetBuilder()
->BuilderSetWidth(333) ->BuilderSetWidth(333)
->BuilderSetHeight(777) ->BuilderSetHeight(777)
->BuilderBuild() ->BuilderBuild()
let result = MyDict var result = MyDict
.member .member
< *E1050* < *E1050*
To make it possible for the operator at the start of the line to be 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 will add recognized, it is required to put a colon before a range. This will add
"start" and print: > "start" and print: >
let result = start var result = start
+ print + print
Like this: > Like this: >
let result = start + print var result = start + print
This will assign "start" and print a line: > This will assign "start" and print a line: >
let result = start var result = start
:+ print :+ print
It is also possible to split a function header over multiple lines, in between It is also possible to split a function header over multiple lines, in between
@ -411,15 +411,15 @@ The 'ignorecase' option is not used for comparators that use strings.
White space ~ White space ~
Vim9 script enforces proper use of white space. This is no longer allowed: > Vim9 script enforces proper use of white space. This is no longer allowed: >
let var=234 # Error! var name=234 # Error!
let var= 234 # Error! var name= 234 # Error!
let var =234 # Error! var name =234 # Error!
There must be white space before and after the "=": > There must be white space before and after the "=": >
let var = 234 # OK var name = 234 # OK
White space must also be put before the # that starts a comment after a White space must also be put before the # that starts a comment after a
command: > command: >
let var = 234# Error! var name = 234# Error!
let var = 234 # OK var name = 234 # OK
White space is required around most operators. White space is required around most operators.
@ -440,7 +440,7 @@ Conditions and expressions ~
Conditions and expressions are mostly working like they do in JavaScript. A Conditions and expressions are mostly working like they do in JavaScript. A
difference is made where JavaScript does not work like most people expect. difference is made where JavaScript does not work like most people expect.
Specifically, an empty list is falsey. Specifically, an empty list is falsy.
Any type of variable can be used as a condition, there is no error, not even Any type of variable can be used as a condition, there is no error, not even
for using a list or job. This is very much like JavaScript, but there are a for using a list or job. This is very much like JavaScript, but there are a
@ -582,9 +582,10 @@ THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
It is possible to nest `:def` inside another `:def` or It is possible to nest `:def` inside another `:def` or
`:function` up to about 50 levels deep. `:function` up to about 50 levels deep.
[!] is used as with `:function`. Note that in Vim9 [!] is used as with `:function`. Note that
script script-local functions cannot be deleted or script-local functions cannot be deleted or redefined
redefined later in the same script. later in Vim9 script. They can only be removed by
reloading the same script.
*:enddef* *:enddef*
:enddef End of a function defined with `:def`. It should be on :enddef End of a function defined with `:def`. It should be on
@ -612,14 +613,14 @@ Limitations ~
Local variables will not be visible to string evaluation. For example: > Local variables will not be visible to string evaluation. For example: >
def EvalString(): list<string> def EvalString(): list<string>
let list = ['aa', 'bb', 'cc', 'dd'] var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map('list[v:val]') return range(1, 2)->map('list[v:val]')
enddef enddef
The map argument is a string expression, which is evaluated without the The map argument is a string expression, which is evaluated without the
function scope. Instead, use a lambda: > function scope. Instead, use a lambda: >
def EvalString(): list<string> def EvalString(): list<string>
let list = ['aa', 'bb', 'cc', 'dd'] var list = ['aa', 'bb', 'cc', 'dd']
return range(1, 2)->map({ _, v -> list[v] }) return range(1, 2)->map({ _, v -> list[v] })
enddef enddef
@ -690,23 +691,23 @@ builtin types added later, similarly to user functions.
And classes and interfaces can be used as types: > And classes and interfaces can be used as types: >
:class MyClass :class MyClass
:let mine: MyClass :var mine: MyClass
:interface MyInterface :interface MyInterface
:let mine: MyInterface :var mine: MyInterface
:class MyTemplate<Targ> :class MyTemplate<Targ>
:let mine: MyTemplate<number> :var mine: MyTemplate<number>
:let mine: MyTemplate<string> :var mine: MyTemplate<string>
:class MyInterface<Targ> :class MyInterface<Targ>
:let mine: MyInterface<number> :var mine: MyInterface<number>
:let mine: MyInterface<string> :var mine: MyInterface<string>
{not implemented yet} {not implemented yet}
Variable types and type casting *variable-types* Variable types and type casting ~
*variable-types*
Variables declared in Vim9 script or in a `:def` function have a type, either Variables declared in Vim9 script or in a `:def` function have a type, either
specified explicitly or inferred from the initialization. specified explicitly or inferred from the initialization.
@ -716,10 +717,10 @@ compiled code the "any" type is assumed.
This can be a problem when the "any" type is undesired and the actual type is This can be a problem when the "any" type is undesired and the actual type is
expected to always be the same. For example, when declaring a list: > expected to always be the same. For example, when declaring a list: >
let l: list<number> = [1, g:two] var l: list<number> = [1, g:two]
This will give an error, because "g:two" has type "any". To avoid this, use a This will give an error, because "g:two" has type "any". To avoid this, use a
type cast: > type cast: >
let l: list<number> = [1, <number>g:two] var l: list<number> = [1, <number>g:two]
< *type-casting* < *type-casting*
The compiled code will then check that "g:two" is a number at runtime and give The compiled code will then check that "g:two" is a number at runtime and give
an error if it isn't. This is called type casting. an error if it isn't. This is called type casting.
@ -734,12 +735,12 @@ it to a string, use the |string()| function. Or use |str2nr()| to convert a
string to a number. string to a number.
Type inference *type-inference* Type inference ~
*type-inference*
In general: Whenever the type is clear it can be omitted. For example, when In general: Whenever the type is clear it can be omitted. For example, when
declaring a variable and giving it a value: > declaring a variable and giving it a value: >
let var = 0 # infers number type var name = 0 # infers number type
let var = 'hello' # infers string type var name = 'hello' # infers string type
The type of a list and dictionary comes from the common type of the values. The type of a list and dictionary comes from the common type of the values.
If the values all have the same type, that type is used for the list or If the values all have the same type, that type is used for the list or
@ -749,8 +750,8 @@ dictionary. If there is a mix of types, the "any" type is used. >
[1, 'x', 3] list<any> [1, 'x', 3] list<any>
Stricter type checking *type-checking* Stricter type checking ~
*type-checking*
In legacy Vim script, where a number was expected, a string would be 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 automatically converted to a number. This was convenient for an actual number
such as "123", but leads to unexpected problems (but no error message) if the such as "123", but leads to unexpected problems (but no error message) if the
@ -766,7 +767,7 @@ an error, thus breaking backwards compatibility. For example:
============================================================================== ==============================================================================
5. Namespace, Import and Export 5. Namespace, Import and Export
*vim9script* *vim9-export* *vim9-import* *vim9script* *vim9-export* *vim9-import*
THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
@ -786,7 +787,7 @@ appear as the first statement in the file. It tells Vim to interpret the
script in its own namespace, instead of the global namespace. If a file script in its own namespace, instead of the global namespace. If a file
starts with: > starts with: >
vim9script vim9script
let myvar = 'yes' var myvar = 'yes'
Then "myvar" will only exist in this file. While without `vim9script` it would Then "myvar" will only exist in this file. While without `vim9script` it would
be available as `g:myvar` from any other script and function. be available as `g:myvar` from any other script and function.
@ -809,7 +810,9 @@ Export ~
*:export* *:exp* *:export* *:exp*
Exporting an item can be written as: > Exporting an item can be written as: >
export const EXPORTED_CONST = 1234 export const EXPORTED_CONST = 1234
export let someValue = ... export var someValue = ...
export final someValue = ...
export const someValue = ...
export def MyFunc() ... export def MyFunc() ...
export class MyClass ... export class MyClass ...
@ -880,7 +883,7 @@ actually needed. A recommended mechanism:
vim9script vim9script
import FilterFunc from "../import/someother.vim" import FilterFunc from "../import/someother.vim"
def searchfor#Stuff(arg: string) def searchfor#Stuff(arg: string)
let filtered = FilterFunc(arg) var filtered = FilterFunc(arg)
... ...
< This goes in .../autoload/searchfor.vim. "searchfor" in the file name < This goes in .../autoload/searchfor.vim. "searchfor" in the file name
must be exactly the same as the prefix for the function name, that is how must be exactly the same as the prefix for the function name, that is how
@ -889,7 +892,7 @@ actually needed. A recommended mechanism:
3. Other functionality, possibly shared between plugins, contains the exported 3. Other functionality, possibly shared between plugins, contains the exported
items and any private items. > items and any private items. >
vim9script vim9script
let localVar = 'local' var localVar = 'local'
export def FilterFunc(arg: string): string export def FilterFunc(arg: string): string
... ...
< This goes in .../import/someother.vim. < This goes in .../import/someother.vim.
@ -909,7 +912,7 @@ namespace will be used for the imported item, even when "s:" is not specified.
6. Future work: classes *vim9-classes* 6. Future work: classes *vim9-classes*
Above "class" was mentioned a few times, but it has not been implemented yet. Above "class" was mentioned a few times, but it has not been implemented yet.
Most of Vim9 script can be created without this funcionality, and since Most of Vim9 script can be created without this functionality, and since
implementing classes is going to be a lot of work, it is left for the future. implementing classes is going to be a lot of work, it is left for the future.
For now we'll just make sure classes can be added later. For now we'll just make sure classes can be added later.
@ -941,7 +944,7 @@ invoke callbacks and handle timeouts and errors.
The :def command ~ The :def command ~
Plugin writers have asked for a much faster Vim script. Investigations have Plugin writers have asked for much faster Vim script. Investigations have
shown that keeping the existing semantics of function calls make this close to shown that keeping the existing semantics of function calls make this close to
impossible, because of the overhead involved with calling a function, setting impossible, because of the overhead involved with calling a function, setting
up the local function scope and executing lines. There are many details that up the local function scope and executing lines. There are many details that
@ -952,7 +955,7 @@ much overhead that cannot be avoided.
Therefore the `:def` method to define a new-style function had to be added, 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 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 as before, but some parts do not. A new way to define a function was
considered the best way to separate the old-style code from Vim9 script code. 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 Using "def" to define a function comes from Python. Other languages use
"function" which clashes with legacy Vim script. "function" which clashes with legacy Vim script.
@ -968,12 +971,12 @@ instruction, at execution time the instruction would have to inspect the type
of the arguments and decide what kind of addition to do. And when the 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 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 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, adding two given at compile time, no error handling is needed at runtime, since adding
numbers cannot fail. two numbers cannot fail.
The syntax for types is similar to Java, since it is easy to understand and The syntax for types, using <type> for compound types, is similar to Java. It
widely used. The type names are what were used in Vim before, with some is easy to understand and widely used. The type names are what were used in
additions such as "void" and "bool". Vim before, with some additions such as "void" and "bool".
Removing clutter and weirdness ~ Removing clutter and weirdness ~
@ -981,10 +984,10 @@ Removing clutter and weirdness ~
Once decided that `:def` functions have different syntax than legacy functions, 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 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 know popular programming languages. In other words: remove weird things that
only Vim uses. only Vim does.
We can also remove clutter, mainly things that were done to make Vim script We can also remove clutter, mainly things that were done to make Vim script
backwards compatible with good old Vi commands. backwards compatible with the good old Vi commands.
Examples: Examples:
- Drop `:call` for calling a function and `:eval` for manipulating data. - Drop `:call` for calling a function and `:eval` for manipulating data.
@ -993,44 +996,26 @@ Examples:
However, this does require that some things need to change: However, this does require that some things need to change:
- Comments start with # instead of ", to avoid confusing them with strings. - Comments start with # instead of ", to avoid confusing them with strings.
This is good anyway, it is known from several popular languages.
- Ex command ranges need to be prefixed with a colon, to avoid confusion with - 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 expressions (single quote can be a string or a mark, "/" can be divide or a
search command, etc.). search command, etc.).
Goal is to limit the differences. A good criteria is that when the old syntax Goal is to limit the differences. A good criteria is that when the old syntax
is used you are very likely to get an error message. is accidentally used you are very likely to get an error message.
TypeScript syntax and semantics ~ Syntax and semantics from popular languages ~
Script writers have complained that the Vim script syntax is unexpectedly Script writers have complained that the Vim script syntax is unexpectedly
different from what they are used to. To reduce this complaint popular 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 languages are used as an example. At the same time, we do not want to abandon
the well-known parts of legacy Vim script. the well-known parts of legacy Vim script.
Since Vim already uses `:let` and `:const` and optional type checking is For many things TypeScript is followed. It's a recent language that is
desirable, the JavaScript/TypeScript syntax fits best for variable gaining popularity and has similarities with Vim script. It also has a
declarations: >
const greeting = 'hello' # string type is inferred
let name: string
...
name = 'John'
Expression evaluation was already close to what JavaScript and other languages
are doing. Some details are unexpected and can be fixed. For example how the
|| and && operators work. Legacy Vim script: >
let value = 44
...
let result = value || 0 # result == 1
Vim9 script works like JavaScript/TypeScript, keep the value: >
let value = 44
...
let result = value || 0 # result == 44
Another reason why TypeScript can be used as an example for Vim9 script is the
mix of static typing (a variable always has a known value type) and dynamic mix of static typing (a variable always has a known value type) and dynamic
typing (a variable can have different types, this hanges at runtime). Since typing (a variable can have different types, this changes at runtime). Since
legacy Vim script is dynamically typed and a lot of existing functionality legacy Vim script is dynamically typed and a lot of existing functionality
(esp. builtin functions) depends on that, while static typing allows for much (esp. builtin functions) depends on that, while static typing allows for much
faster execution, we need to have this mix in Vim9 script. faster execution, we need to have this mix in Vim9 script.
@ -1054,7 +1039,7 @@ Specific items from TypeScript we avoid:
- TypeScript can use an expression like "99 || 'yes'" in a condition, but - 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 cannot assign the value to a boolean. That is inconsistent and can be
annoying. Vim recognizes an expression with && or || and allows using the annoying. Vim recognizes an expression with && or || and allows using the
result as a bool. result as a bool. TODO: to be reconsidered
- TypeScript considers an empty string as Falsy, but an empty list or dict as - 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 Truthy. That is inconsistent. In Vim an empty list and dict are also
Falsy. Falsy.
@ -1063,6 +1048,80 @@ Specific items from TypeScript we avoid:
which is more flexible, but is only checked at runtime. which is more flexible, but is only checked at runtime.
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 vary. 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 has 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 JavaScript and other languages
are doing. Some details are unexpected and can be fixed. For example how the
|| and && operators work. Legacy Vim script: >
var value = 44
...
var result = value || 0 # result == 1
Vim9 script works like JavaScript/TypeScript, keep the value: >
var value = 44
...
var result = value || 0 # result == 44
TODO: the semantics of || and && need to be reconsidered.
Import and Export ~ Import and Export ~
A problem of legacy Vim script is that by default all functions and variables A problem of legacy Vim script is that by default all functions and variables
@ -1122,7 +1181,7 @@ only reported then. In case these errors should be found early, e.g. when
testing, the `:defcompile` command will help out. testing, the `:defcompile` command will help out.
Why not use an embeded language? ~ Why not use an embedded language? ~
Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But
these interfaces have never become widely used, for various reasons. When these interfaces have never become widely used, for various reasons. When

View File

@ -1513,8 +1513,10 @@ set_one_cmd_context(
break; break;
#endif #endif
#ifdef FEAT_EVAL #ifdef FEAT_EVAL
case CMD_final:
case CMD_const: case CMD_const:
case CMD_let: case CMD_let:
case CMD_var:
case CMD_if: case CMD_if:
case CMD_elseif: case CMD_elseif:
case CMD_while: case CMD_while:

View File

@ -270,4 +270,8 @@ EXTERN char e_variable_is_locked_str[]
INIT(= N_("E1122: Variable is locked: %s")); INIT(= N_("E1122: Variable is locked: %s"));
EXTERN char e_missing_comma_before_argument_str[] EXTERN char e_missing_comma_before_argument_str[]
INIT(= N_("E1123: Missing comma before argument: %s")); INIT(= N_("E1123: Missing comma before argument: %s"));
EXTERN char e_str_cannot_be_used_in_legacy_vim_script[]
INIT(= N_("E1124: \"%s\" cannot be used in legacy Vim script"));
EXTERN char e_final_requires_a_value[]
INIT(= N_("E1125: Final requires a value"));
#endif #endif

View File

@ -1213,7 +1213,7 @@ set_var_lval(
char_u *endp, char_u *endp,
typval_T *rettv, typval_T *rettv,
int copy, int copy,
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND int flags, // ASSIGN_CONST, ASSIGN_NO_DECL
char_u *op) char_u *op)
{ {
int cc; int cc;
@ -1281,7 +1281,7 @@ set_var_lval(
{ {
typval_T tv; typval_T tv;
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_(e_cannot_mod)); emsg(_(e_cannot_mod));
*endp = cc; *endp = cc;
@ -1319,7 +1319,7 @@ set_var_lval(
listitem_T *ll_li = lp->ll_li; listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1; int ll_n1 = lp->ll_n1;
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_("E996: Cannot lock a range")); emsg(_("E996: Cannot lock a range"));
return; return;
@ -1378,7 +1378,7 @@ set_var_lval(
/* /*
* Assign to a List or Dictionary item. * Assign to a List or Dictionary item.
*/ */
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_("E996: Cannot lock a list or dict")); emsg(_("E996: Cannot lock a list or dict"));
return; return;
@ -1688,7 +1688,7 @@ next_for_item(void *fi_void, char_u *arg)
{ {
forinfo_T *fi = (forinfo_T *)fi_void; forinfo_T *fi = (forinfo_T *)fi_void;
int result; int result;
int flag = in_vim9script() ? LET_NO_COMMAND : 0; int flag = in_vim9script() ? ASSIGN_NO_DECL : 0;
listitem_T *item; listitem_T *item;
if (fi->fi_blob != NULL) if (fi->fi_blob != NULL)
@ -1741,11 +1741,12 @@ set_context_for_expression(
char_u *arg, char_u *arg,
cmdidx_T cmdidx) cmdidx_T cmdidx)
{ {
int got_eq = FALSE; int has_expr = cmdidx != CMD_let && cmdidx != CMD_var;
int c; int c;
char_u *p; char_u *p;
if (cmdidx == CMD_let || cmdidx == CMD_const) if (cmdidx == CMD_let || cmdidx == CMD_var
|| cmdidx == CMD_const || cmdidx == CMD_final)
{ {
xp->xp_context = EXPAND_USER_VARS; xp->xp_context = EXPAND_USER_VARS;
if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL)
@ -1774,8 +1775,7 @@ set_context_for_expression(
if (c == '&') if (c == '&')
{ {
++xp->xp_pattern; ++xp->xp_pattern;
xp->xp_context = cmdidx != CMD_let || got_eq xp->xp_context = has_expr ? EXPAND_EXPRESSION : EXPAND_NOTHING;
? EXPAND_EXPRESSION : EXPAND_NOTHING;
} }
else if (c != ' ') else if (c != ' ')
{ {
@ -1792,7 +1792,7 @@ set_context_for_expression(
} }
else if (c == '=') else if (c == '=')
{ {
got_eq = TRUE; has_expr = TRUE;
xp->xp_context = EXPAND_EXPRESSION; xp->xp_context = EXPAND_EXPRESSION;
} }
else if (c == '#' else if (c == '#'
@ -1808,7 +1808,7 @@ set_context_for_expression(
// Function name can start with "<SNR>" and contain '#'. // Function name can start with "<SNR>" and contain '#'.
break; break;
} }
else if (cmdidx != CMD_let || got_eq) else if (has_expr)
{ {
if (c == '"') // string if (c == '"') // string
{ {

View File

@ -668,6 +668,25 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get)
return l; return l;
} }
/*
* Vim9 variable declaration:
* ":var name"
* ":var name: type"
* ":var name = expr"
* ":var name: type = expr"
* etc.
*/
void
ex_var(exarg_T *eap)
{
if (!in_vim9script())
{
semsg(_(e_str_cannot_be_used_in_legacy_vim_script), ":var");
return;
}
ex_let(eap);
}
/* /*
* ":let" list all variable values * ":let" list all variable values
* ":let var1 var2" list variable values * ":let var1 var2" list variable values
@ -683,6 +702,9 @@ heredoc_get(exarg_T *eap, char_u *cmd, int script_get)
* ":let var =<< ..." heredoc * ":let var =<< ..." heredoc
* ":let var: string" Vim9 declaration * ":let var: string" Vim9 declaration
* *
* ":final var = expr" assignment command.
* ":final [var1, var2] = expr" unpack list.
*
* ":const" list all variable values * ":const" list all variable values
* ":const var1 var2" list variable values * ":const var1 var2" list variable values
* ":const var = expr" assignment command. * ":const var = expr" assignment command.
@ -702,14 +724,22 @@ ex_let(exarg_T *eap)
int first = TRUE; int first = TRUE;
int concat; int concat;
int has_assign; int has_assign;
int flags = eap->cmdidx == CMD_const ? LET_IS_CONST : 0; int flags = eap->cmdidx == CMD_const ? ASSIGN_CONST : 0;
int vim9script = in_vim9script(); int vim9script = in_vim9script();
if (eap->cmdidx == CMD_final && !vim9script)
{
// In legacy Vim script ":final" is short for ":finally".
ex_finally(eap);
return;
}
if (eap->cmdidx == CMD_const && !vim9script && !eap->forceit)
// In legacy Vim script ":const" works like ":final".
eap->cmdidx = CMD_final;
// detect Vim9 assignment without ":let" or ":const" // detect Vim9 assignment without ":let" or ":const"
if (eap->arg == eap->cmd) if (eap->arg == eap->cmd)
flags |= LET_NO_COMMAND; flags |= ASSIGN_NO_DECL;
if (eap->forceit)
flags |= LET_FORCEIT;
argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE); argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
if (argend == NULL) if (argend == NULL)
@ -787,7 +817,7 @@ ex_let(exarg_T *eap)
op[1] = NUL; op[1] = NUL;
if (*expr != '=') if (*expr != '=')
{ {
if (vim9script && (flags & LET_NO_COMMAND) == 0) if (vim9script && (flags & ASSIGN_NO_DECL) == 0)
{ {
// +=, /=, etc. require an existing variable // +=, /=, etc. require an existing variable
semsg(_(e_cannot_use_operator_on_new_variable), eap->arg); semsg(_(e_cannot_use_operator_on_new_variable), eap->arg);
@ -860,7 +890,7 @@ ex_let_vars(
int copy, // copy values from "tv", don't move int copy, // copy values from "tv", don't move
int semicolon, // from skip_var_list() int semicolon, // from skip_var_list()
int var_count, // from skip_var_list() int var_count, // from skip_var_list()
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND int flags, // ASSIGN_CONST, ASSIGN_NO_DECL
char_u *op) char_u *op)
{ {
char_u *arg = arg_start; char_u *arg = arg_start;
@ -1215,7 +1245,7 @@ ex_let_one(
char_u *arg, // points to variable name char_u *arg, // points to variable name
typval_T *tv, // value to assign to variable typval_T *tv, // value to assign to variable
int copy, // copy value from "tv" int copy, // copy value from "tv"
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND int flags, // ASSIGN_CONST, ASSIGN_NO_DECL
char_u *endchars, // valid chars after variable name or NULL char_u *endchars, // valid chars after variable name or NULL
char_u *op) // "+", "-", "." or NULL char_u *op) // "+", "-", "." or NULL
{ {
@ -1227,7 +1257,7 @@ ex_let_one(
int opt_flags; int opt_flags;
char_u *tofree = NULL; char_u *tofree = NULL;
if (in_vim9script() && (flags & LET_NO_COMMAND) == 0 if (in_vim9script() && (flags & ASSIGN_NO_DECL) == 0
&& vim_strchr((char_u *)"$@&", *arg) != NULL) && vim_strchr((char_u *)"$@&", *arg) != NULL)
{ {
vim9_declare_error(arg); vim9_declare_error(arg);
@ -1237,7 +1267,7 @@ ex_let_one(
// ":let $VAR = expr": Set environment variable. // ":let $VAR = expr": Set environment variable.
if (*arg == '$') if (*arg == '$')
{ {
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_("E996: Cannot lock an environment variable")); emsg(_("E996: Cannot lock an environment variable"));
return NULL; return NULL;
@ -1289,7 +1319,7 @@ ex_let_one(
// ":let &g:option = expr": Set global option value. // ":let &g:option = expr": Set global option value.
else if (*arg == '&') else if (*arg == '&')
{ {
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_(e_const_option)); emsg(_(e_const_option));
return NULL; return NULL;
@ -1373,7 +1403,7 @@ ex_let_one(
// ":let @r = expr": Set register contents. // ":let @r = expr": Set register contents.
else if (*arg == '@') else if (*arg == '@')
{ {
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_("E996: Cannot lock a register")); emsg(_("E996: Cannot lock a register"));
return NULL; return NULL;
@ -2926,7 +2956,7 @@ set_var(
typval_T *tv, typval_T *tv,
int copy) // make copy of value in "tv" int copy) // make copy of value in "tv"
{ {
set_var_const(name, NULL, tv, copy, LET_NO_COMMAND); set_var_const(name, NULL, tv, copy, ASSIGN_NO_DECL);
} }
/* /*
@ -2940,7 +2970,7 @@ set_var_const(
type_T *type, type_T *type,
typval_T *tv_arg, typval_T *tv_arg,
int copy, // make copy of value in "tv" int copy, // make copy of value in "tv"
int flags) // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND int flags) // ASSIGN_CONST, ASSIGN_NO_DECL
{ {
typval_T *tv = tv_arg; typval_T *tv = tv_arg;
typval_T bool_tv; typval_T bool_tv;
@ -2960,7 +2990,7 @@ set_var_const(
if (vim9script if (vim9script
&& !is_script_local && !is_script_local
&& (flags & LET_NO_COMMAND) == 0 && (flags & ASSIGN_NO_DECL) == 0
&& name[1] == ':') && name[1] == ':')
{ {
vim9_declare_error(name); vim9_declare_error(name);
@ -2990,7 +3020,7 @@ set_var_const(
{ {
if ((di->di_flags & DI_FLAGS_RELOAD) == 0) if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
{ {
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
{ {
emsg(_(e_cannot_mod)); emsg(_(e_cannot_mod));
goto failed; goto failed;
@ -2998,7 +3028,7 @@ set_var_const(
if (is_script_local && vim9script) if (is_script_local && vim9script)
{ {
if ((flags & LET_NO_COMMAND) == 0) if ((flags & ASSIGN_NO_DECL) == 0)
{ {
semsg(_(e_redefining_script_item_str), name); semsg(_(e_redefining_script_item_str), name);
goto failed; goto failed;
@ -3094,7 +3124,7 @@ set_var_const(
goto failed; goto failed;
} }
di->di_flags = DI_FLAGS_ALLOC; di->di_flags = DI_FLAGS_ALLOC;
if (flags & LET_IS_CONST) if (flags & ASSIGN_CONST)
di->di_flags |= DI_FLAGS_LOCK; di->di_flags |= DI_FLAGS_LOCK;
if (is_script_local && vim9script) if (is_script_local && vim9script)
@ -3113,7 +3143,7 @@ set_var_const(
sv->sv_type = typval2type(tv, &si->sn_type_list); sv->sv_type = typval2type(tv, &si->sn_type_list);
else else
sv->sv_type = type; sv->sv_type = type;
sv->sv_const = (flags & LET_IS_CONST); sv->sv_const = (flags & ASSIGN_CONST);
sv->sv_export = is_export; sv->sv_export = is_export;
++si->sn_var_vals.ga_len; ++si->sn_var_vals.ga_len;
@ -3132,8 +3162,8 @@ set_var_const(
init_tv(tv); init_tv(tv);
} }
// ":const var = val" locks the value; in Vim9 script only with ":const!" // ":const var = val" locks the value
if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT))) if (flags & ASSIGN_CONST)
// Like :lockvar! name: lock the value and what it contains, but only // Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal // if the reference count is up to one. That locks only literal
// values. // values.

View File

@ -11,26 +11,26 @@ static const unsigned short cmdidxs1[26] =
/* d */ 108, /* d */ 108,
/* e */ 133, /* e */ 133,
/* f */ 156, /* f */ 156,
/* g */ 172, /* g */ 173,
/* h */ 178, /* h */ 179,
/* i */ 187, /* i */ 188,
/* j */ 206, /* j */ 207,
/* k */ 208, /* k */ 209,
/* l */ 213, /* l */ 214,
/* m */ 275, /* m */ 276,
/* n */ 293, /* n */ 294,
/* o */ 313, /* o */ 314,
/* p */ 325, /* p */ 326,
/* q */ 364, /* q */ 365,
/* r */ 367, /* r */ 368,
/* s */ 387, /* s */ 388,
/* t */ 456, /* t */ 457,
/* u */ 501, /* u */ 502,
/* v */ 512, /* v */ 513,
/* w */ 531, /* w */ 533,
/* x */ 545, /* x */ 547,
/* y */ 555, /* y */ 557,
/* z */ 556 /* z */ 558
}; };
/* /*
@ -46,7 +46,7 @@ static const unsigned char cmdidxs2[26][26] =
/* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 37, 40, 46, 56, 58, 59, 60, 0, 62, 0, 65, 0, 0, 0 }, /* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 37, 40, 46, 56, 58, 59, 60, 0, 62, 0, 65, 0, 0, 0 },
/* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 19, 0, 0, 20, 0, 0, 22, 23, 0, 0, 0, 0, 0, 0, 0 }, /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 19, 0, 0, 20, 0, 0, 22, 23, 0, 0, 0, 0, 0, 0, 0 },
/* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 10, 0, 0, 0, 0, 0, 0, 0, 17, 0, 18, 0, 0 }, /* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 10, 0, 0, 0, 0, 0, 0, 0, 17, 0, 18, 0, 0 },
/* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0 }, /* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 },
/* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 }, /* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 },
/* h */ { 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* h */ { 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* i */ { 1, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 5, 6, 0, 0, 0, 0, 0, 14, 0, 16, 0, 0, 0, 0, 0 }, /* i */ { 1, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 5, 6, 0, 0, 0, 0, 0, 14, 0, 16, 0, 0, 0, 0, 0 },
@ -62,11 +62,11 @@ static const unsigned char cmdidxs2[26][26] =
/* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 50, 0, 51, 0, 63, 64, 0, 65, 0 }, /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 50, 0, 51, 0, 63, 64, 0, 65, 0 },
/* t */ { 2, 0, 19, 0, 24, 26, 0, 27, 0, 28, 0, 29, 33, 36, 38, 39, 0, 40, 42, 0, 43, 0, 0, 0, 0, 0 }, /* t */ { 2, 0, 19, 0, 24, 26, 0, 27, 0, 28, 0, 29, 33, 36, 38, 39, 0, 40, 42, 0, 43, 0, 0, 0, 0, 0 },
/* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 10, 13, 0, 0, 0, 0, 16, 0, 17, 0, 0, 0, 0, 0 }, /* v */ { 1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 11, 14, 0, 0, 0, 0, 17, 0, 18, 0, 0, 0, 0, 0 },
/* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 0, 0, 12, 13, 0, 0, 0, 0 }, /* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 0, 0, 12, 13, 0, 0, 0, 0 },
/* x */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 0, 0, 0, 7, 0, 0, 8, 0, 0, 0, 0, 0 }, /* x */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 0, 0, 0, 7, 0, 0, 8, 0, 0, 0, 0, 0 },
/* y */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* y */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
}; };
static const int command_count = 569; static const int command_count = 571;

View File

@ -592,6 +592,9 @@ EXCMD(CMD_filter, "filter", ex_wrongmodifier,
EXCMD(CMD_find, "find", ex_find, EXCMD(CMD_find, "find", ex_find,
EX_RANGE|EX_BANG|EX_FILE1|EX_CMDARG|EX_ARGOPT|EX_TRLBAR|EX_NEEDARG, EX_RANGE|EX_BANG|EX_FILE1|EX_CMDARG|EX_ARGOPT|EX_TRLBAR|EX_NEEDARG,
ADDR_OTHER), ADDR_OTHER),
EXCMD(CMD_final, "final", ex_let,
EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE),
EXCMD(CMD_finally, "finally", ex_finally, EXCMD(CMD_finally, "finally", ex_finally,
EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),
@ -1648,6 +1651,9 @@ EXCMD(CMD_update, "update", ex_update,
EXCMD(CMD_vglobal, "vglobal", ex_global, EXCMD(CMD_vglobal, "vglobal", ex_global,
EX_RANGE|EX_WHOLEFOLD|EX_EXTRA|EX_DFLALL|EX_CMDWIN|EX_LOCK_OK, EX_RANGE|EX_WHOLEFOLD|EX_EXTRA|EX_DFLALL|EX_CMDWIN|EX_LOCK_OK,
ADDR_LINES), ADDR_LINES),
EXCMD(CMD_var, "var", ex_var,
EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE),
EXCMD(CMD_version, "version", ex_version, EXCMD(CMD_version, "version", ex_version,
EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),

View File

@ -2421,6 +2421,7 @@ do_one_cmd(
case CMD_eval: case CMD_eval:
case CMD_execute: case CMD_execute:
case CMD_filter: case CMD_filter:
case CMD_final:
case CMD_help: case CMD_help:
case CMD_hide: case CMD_hide:
case CMD_ijump: case CMD_ijump:
@ -2442,9 +2443,9 @@ do_one_cmd(
case CMD_noswapfile: case CMD_noswapfile:
case CMD_perl: case CMD_perl:
case CMD_psearch: case CMD_psearch:
case CMD_python:
case CMD_py3: case CMD_py3:
case CMD_python3: case CMD_python3:
case CMD_python:
case CMD_return: case CMD_return:
case CMD_rightbelow: case CMD_rightbelow:
case CMD_ruby: case CMD_ruby:
@ -2460,6 +2461,7 @@ do_one_cmd(
case CMD_topleft: case CMD_topleft:
case CMD_unlet: case CMD_unlet:
case CMD_unlockvar: case CMD_unlockvar:
case CMD_var:
case CMD_verbose: case CMD_verbose:
case CMD_vertical: case CMD_vertical:
case CMD_wincmd: case CMD_wincmd:
@ -3244,7 +3246,7 @@ find_ex_command(
if (skip_expr(&after) == OK if (skip_expr(&after) == OK
&& (*after == '=' && (*after == '='
|| (*after != NUL && after[1] == '='))) || (*after != NUL && after[1] == '=')))
eap->cmdidx = CMD_let; eap->cmdidx = CMD_var;
else else
eap->cmdidx = CMD_eval; eap->cmdidx = CMD_eval;
--emsg_silent; --emsg_silent;
@ -3268,7 +3270,7 @@ find_ex_command(
} }
if (p > eap->cmd && *skipwhite(p) == '=') if (p > eap->cmd && *skipwhite(p) == '=')
{ {
eap->cmdidx = CMD_let; eap->cmdidx = CMD_var;
return eap->cmd; return eap->cmd;
} }
} }
@ -3287,7 +3289,7 @@ find_ex_command(
|| *eap->cmd == '@' || *eap->cmd == '@'
|| lookup(eap->cmd, p - eap->cmd, cctx) != NULL) || lookup(eap->cmd, p - eap->cmd, cctx) != NULL)
{ {
eap->cmdidx = CMD_let; eap->cmdidx = CMD_var;
return eap->cmd; return eap->cmd;
} }
} }

View File

@ -14,6 +14,7 @@ int get_spellword(list_T *list, char_u **pp);
void prepare_vimvar(int idx, typval_T *save_tv); void prepare_vimvar(int idx, typval_T *save_tv);
void restore_vimvar(int idx, typval_T *save_tv); void restore_vimvar(int idx, typval_T *save_tv);
list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get); list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get);
void ex_var(exarg_T *eap);
void ex_let(exarg_T *eap); void ex_let(exarg_T *eap);
int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op);
char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon, int silent); char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon, int silent);

View File

@ -12,30 +12,30 @@ let g:alist = [7]
let g:astring = 'text' let g:astring = 'text'
def Test_assignment_bool() def Test_assignment_bool()
let bool1: bool = true var bool1: bool = true
assert_equal(v:true, bool1) assert_equal(v:true, bool1)
let bool2: bool = false var bool2: bool = false
assert_equal(v:false, bool2) assert_equal(v:false, bool2)
let bool3: bool = 0 var bool3: bool = 0
assert_equal(false, bool3) assert_equal(false, bool3)
let bool4: bool = 1 var bool4: bool = 1
assert_equal(true, bool4) assert_equal(true, bool4)
let bool5: bool = 'yes' && 'no' var bool5: bool = 'yes' && 'no'
assert_equal(true, bool5) assert_equal(true, bool5)
let bool6: bool = [] && 99 var bool6: bool = [] && 99
assert_equal(false, bool6) assert_equal(false, bool6)
let bool7: bool = [] || #{a: 1} && 99 var bool7: bool = [] || #{a: 1} && 99
assert_equal(true, bool7) assert_equal(true, bool7)
let lines =<< trim END var lines =<< trim END
vim9script vim9script
def GetFlag(): bool def GetFlag(): bool
let flag: bool = 1 var flag: bool = 1
return flag return flag
enddef enddef
let flag: bool = GetFlag() var flag: bool = GetFlag()
assert_equal(true, flag) assert_equal(true, flag)
flag = 0 flag = 0
assert_equal(false, flag) assert_equal(false, flag)
@ -47,41 +47,42 @@ def Test_assignment_bool()
assert_equal(false, flag) assert_equal(false, flag)
END END
CheckScriptSuccess(lines) CheckScriptSuccess(lines)
CheckDefAndScriptFailure(['let x: bool = 2'], 'E1012:') CheckDefAndScriptFailure(['var x: bool = 2'], 'E1012:')
CheckDefAndScriptFailure(['let x: bool = -1'], 'E1012:') CheckDefAndScriptFailure(['var x: bool = -1'], 'E1012:')
CheckDefAndScriptFailure(['let x: bool = [1]'], 'E1012:') CheckDefAndScriptFailure(['var x: bool = [1]'], 'E1012:')
CheckDefAndScriptFailure(['let x: bool = {}'], 'E1012:') CheckDefAndScriptFailure(['var x: bool = {}'], 'E1012:')
CheckDefAndScriptFailure(['let x: bool = "x"'], 'E1012:') CheckDefAndScriptFailure(['var x: bool = "x"'], 'E1012:')
enddef enddef
def Test_syntax() def Test_syntax()
let var = 234 var var = 234
let other: list<string> = ['asdf'] var other: list<string> = ['asdf']
enddef enddef
def Test_assignment() def Test_assignment()
CheckDefFailure(['let x:string'], 'E1069:') CheckDefFailure(['var x:string'], 'E1069:')
CheckDefFailure(['let x:string = "x"'], 'E1069:') CheckDefFailure(['var x:string = "x"'], 'E1069:')
CheckDefFailure(['let a:string = "x"'], 'E1069:') CheckDefFailure(['var a:string = "x"'], 'E1069:')
CheckDefFailure(['let lambda = {-> "lambda"}'], 'E704:') CheckDefFailure(['var lambda = {-> "lambda"}'], 'E704:')
CheckScriptFailure(['var x = "x"'], 'E1124:')
let nr: number = 1234 var nr: number = 1234
CheckDefFailure(['let nr: number = "asdf"'], 'E1012:') CheckDefFailure(['var nr: number = "asdf"'], 'E1012:')
let a: number = 6 #comment var a: number = 6 #comment
assert_equal(6, a) assert_equal(6, a)
if has('channel') if has('channel')
let chan1: channel var chan1: channel
let job1: job var job1: job
let job2: job = job_start('willfail') var job2: job = job_start('willfail')
endif endif
if has('float') if has('float')
let float1: float = 3.4 var float1: float = 3.4
endif endif
let Funky1: func var Funky1: func
let Funky2: func = function('len') var Funky2: func = function('len')
let Party2: func = funcref('g:Test_syntax') var Party2: func = funcref('g:Test_syntax')
g:newvar = 'new' #comment g:newvar = 'new' #comment
assert_equal('new', g:newvar) assert_equal('new', g:newvar)
@ -97,7 +98,7 @@ def Test_assignment()
assert_equal('foobar', $ENVVAR) assert_equal('foobar', $ENVVAR)
$ENVVAR = '' $ENVVAR = ''
let lines =<< trim END var lines =<< trim END
vim9script vim9script
$ENVVAR = 'barfoo' $ENVVAR = 'barfoo'
assert_equal('barfoo', $ENVVAR) assert_equal('barfoo', $ENVVAR)
@ -126,15 +127,15 @@ def Test_assignment()
assert_equal(2, &ts) assert_equal(2, &ts)
if has('float') if has('float')
let f100: float = 100.0 var f100: float = 100.0
f100 /= 5 f100 /= 5
assert_equal(20.0, f100) assert_equal(20.0, f100)
let f200: float = 200.0 var f200: float = 200.0
f200 /= 5.0 f200 /= 5.0
assert_equal(40.0, f200) assert_equal(40.0, f200)
CheckDefFailure(['let nr: number = 200', 'nr /= 5.0'], 'E1012:') CheckDefFailure(['var nr: number = 200', 'nr /= 5.0'], 'E1012:')
endif endif
lines =<< trim END lines =<< trim END
@ -163,11 +164,11 @@ def Test_assignment()
CheckDefFailure(['&path += 3'], 'E1012:') CheckDefFailure(['&path += 3'], 'E1012:')
CheckDefExecFailure(['&bs = "asdf"'], 'E474:') CheckDefExecFailure(['&bs = "asdf"'], 'E474:')
# test freeing ISN_STOREOPT # test freeing ISN_STOREOPT
CheckDefFailure(['&ts = 3', 'let asdf'], 'E1022:') CheckDefFailure(['&ts = 3', 'var asdf'], 'E1022:')
&ts = 8 &ts = 8
lines =<< trim END lines =<< trim END
let save_TI = &t_TI var save_TI = &t_TI
&t_TI = '' &t_TI = ''
assert_equal('', &t_TI) assert_equal('', &t_TI)
&t_TI = 'xxx' &t_TI = 'xxx'
@ -179,8 +180,8 @@ def Test_assignment()
CheckDefFailure(['&t_TI = 123'], 'E1012:') CheckDefFailure(['&t_TI = 123'], 'E1012:')
CheckScriptFailure(['vim9script', '&t_TI = 123'], 'E928:') CheckScriptFailure(['vim9script', '&t_TI = 123'], 'E928:')
CheckDefFailure(['let s:var = 123'], 'E1101:') CheckDefFailure(['var s:var = 123'], 'E1101:')
CheckDefFailure(['let s:var: number'], 'E1101:') CheckDefFailure(['var s:var: number'], 'E1101:')
lines =<< trim END lines =<< trim END
vim9script vim9script
@ -217,20 +218,20 @@ def Test_assignment()
# this should not leak # this should not leak
if 0 if 0
let text =<< trim END var text =<< trim END
some text some text
END END
endif endif
enddef enddef
def Test_extend_list() def Test_extend_list()
let lines =<< trim END var lines =<< trim END
vim9script vim9script
let l: list<number> var l: list<number>
l += [123] l += [123]
assert_equal([123], l) assert_equal([123], l)
let d: dict<number> var d: dict<number>
d['one'] = 1 d['one'] = 1
assert_equal(#{one: 1}, d) assert_equal(#{one: 1}, d)
END END
@ -239,41 +240,41 @@ enddef
def Test_single_letter_vars() def Test_single_letter_vars()
# single letter variables # single letter variables
let a: number = 123 var a: number = 123
a = 123 a = 123
assert_equal(123, a) assert_equal(123, a)
let b: number var b: number
b = 123 b = 123
assert_equal(123, b) assert_equal(123, b)
let g: number var g: number
g = 123 g = 123
assert_equal(123, g) assert_equal(123, g)
let s: number var s: number
s = 123 s = 123
assert_equal(123, s) assert_equal(123, s)
let t: number var t: number
t = 123 t = 123
assert_equal(123, t) assert_equal(123, t)
let v: number var v: number
v = 123 v = 123
assert_equal(123, v) assert_equal(123, v)
let w: number var w: number
w = 123 w = 123
assert_equal(123, w) assert_equal(123, w)
enddef enddef
def Test_vim9_single_char_vars() def Test_vim9_single_char_vars()
let lines =<< trim END var lines =<< trim END
vim9script vim9script
# single character variable declarations work # single character variable declarations work
let a: string var a: string
let b: number var b: number
let l: list<any> var l: list<any>
let s: string var s: string
let t: number var t: number
let v: number var v: number
let w: number var w: number
# script-local variables can be used without s: prefix # script-local variables can be used without s: prefix
a = 'script-a' a = 'script-a'
@ -298,14 +299,14 @@ def Test_vim9_single_char_vars()
enddef enddef
def Test_assignment_list() def Test_assignment_list()
let list1: list<bool> = [false, true, false] var list1: list<bool> = [false, true, false]
let list2: list<number> = [1, 2, 3] var list2: list<number> = [1, 2, 3]
let list3: list<string> = ['sdf', 'asdf'] var list3: list<string> = ['sdf', 'asdf']
let list4: list<any> = ['yes', true, 1234] var list4: list<any> = ['yes', true, 1234]
let list5: list<blob> = [0z01, 0z02] var list5: list<blob> = [0z01, 0z02]
let listS: list<string> = [] var listS: list<string> = []
let listN: list<number> = [] var listN: list<number> = []
assert_equal([1, 2, 3], list2) assert_equal([1, 2, 3], list2)
list2[-1] = 99 list2[-1] = 99
@ -320,19 +321,19 @@ def Test_assignment_list()
list3 += ['end'] list3 += ['end']
assert_equal(['sdf', 'asdf', 'end'], list3) assert_equal(['sdf', 'asdf', 'end'], list3)
CheckDefExecFailure(['let ll = [1, 2, 3]', 'll[-4] = 6'], 'E684:') CheckDefExecFailure(['var ll = [1, 2, 3]', 'll[-4] = 6'], 'E684:')
CheckDefExecFailure(['let [v1, v2] = [1, 2]'], 'E1092:') CheckDefExecFailure(['var [v1, v2] = [1, 2]'], 'E1092:')
# type becomes list<any> # type becomes list<any>
let somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c'] var somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c']
enddef enddef
def Test_assignment_list_vim9script() def Test_assignment_list_vim9script()
let lines =<< trim END var lines =<< trim END
vim9script vim9script
let v1: number var v1: number
let v2: number var v2: number
let v3: number var v3: number
[v1, v2, v3] = [1, 2, 3] [v1, v2, v3] = [1, 2, 3]
assert_equal([1, 2, 3], [v1, v2, v3]) assert_equal([1, 2, 3], [v1, v2, v3])
END END
@ -340,27 +341,27 @@ def Test_assignment_list_vim9script()
enddef enddef
def Test_assignment_dict() def Test_assignment_dict()
let dict1: dict<bool> = #{one: false, two: true} var dict1: dict<bool> = #{one: false, two: true}
let dict2: dict<number> = #{one: 1, two: 2} var dict2: dict<number> = #{one: 1, two: 2}
let dict3: dict<string> = #{key: 'value'} var dict3: dict<string> = #{key: 'value'}
let dict4: dict<any> = #{one: 1, two: '2'} var dict4: dict<any> = #{one: 1, two: '2'}
let dict5: dict<blob> = #{one: 0z01, two: 0z02} var dict5: dict<blob> = #{one: 0z01, two: 0z02}
# overwrite # overwrite
dict3['key'] = 'another' dict3['key'] = 'another'
# empty key can be used # empty key can be used
let dd = {} var dd = {}
dd[""] = 6 dd[""] = 6
assert_equal({'': 6}, dd) assert_equal({'': 6}, dd)
# type becomes dict<any> # type becomes dict<any>
let somedict = rand() > 0 ? #{a: 1, b: 2} : #{a: 'a', b: 'b'} var somedict = rand() > 0 ? #{a: 1, b: 2} : #{a: 'a', b: 'b'}
# assignment to script-local dict # assignment to script-local dict
let lines =<< trim END var lines =<< trim END
vim9script vim9script
let test: dict<any> = {} var test: dict<any> = {}
def FillDict(): dict<any> def FillDict(): dict<any>
test['a'] = 43 test['a'] = 43
return test return test
@ -371,7 +372,7 @@ def Test_assignment_dict()
lines =<< trim END lines =<< trim END
vim9script vim9script
let test: dict<any> var test: dict<any>
def FillDict(): dict<any> def FillDict(): dict<any>
test['a'] = 43 test['a'] = 43
return test return test
@ -408,7 +409,7 @@ enddef
def Test_assignment_local() def Test_assignment_local()
# Test in a separated file in order not to the current buffer/window/tab is # Test in a separated file in order not to the current buffer/window/tab is
# changed. # changed.
let script_lines: list<string> =<< trim END var script_lines: list<string> =<< trim END
let b:existing = 'yes' let b:existing = 'yes'
let w:existing = 'yes' let w:existing = 'yes'
let t:existing = 'yes' let t:existing = 'yes'
@ -446,37 +447,37 @@ enddef
def Test_assignment_default() def Test_assignment_default()
# Test default values. # Test default values.
let thebool: bool var thebool: bool
assert_equal(v:false, thebool) assert_equal(v:false, thebool)
let thenumber: number var thenumber: number
assert_equal(0, thenumber) assert_equal(0, thenumber)
if has('float') if has('float')
let thefloat: float var thefloat: float
assert_equal(0.0, thefloat) assert_equal(0.0, thefloat)
endif endif
let thestring: string var thestring: string
assert_equal('', thestring) assert_equal('', thestring)
let theblob: blob var theblob: blob
assert_equal(0z, theblob) assert_equal(0z, theblob)
let Thefunc: func var Thefunc: func
assert_equal(test_null_function(), Thefunc) assert_equal(test_null_function(), Thefunc)
let thelist: list<any> var thelist: list<any>
assert_equal([], thelist) assert_equal([], thelist)
let thedict: dict<any> var thedict: dict<any>
assert_equal({}, thedict) assert_equal({}, thedict)
if has('channel') if has('channel')
let thejob: job var thejob: job
assert_equal(test_null_job(), thejob) assert_equal(test_null_job(), thejob)
let thechannel: channel var thechannel: channel
assert_equal(test_null_channel(), thechannel) assert_equal(test_null_channel(), thechannel)
if has('unix') && executable('cat') if has('unix') && executable('cat')
@ -487,14 +488,14 @@ def Test_assignment_default()
endif endif
endif endif
let nr = 1234 | nr = 5678 var nr = 1234 | nr = 5678
assert_equal(5678, nr) assert_equal(5678, nr)
enddef enddef
def Test_assignment_var_list() def Test_assignment_var_list()
let v1: string var v1: string
let v2: string var v2: string
let vrem: list<string> var vrem: list<string>
[v1] = ['aaa'] [v1] = ['aaa']
assert_equal('aaa', v1) assert_equal('aaa', v1)
@ -519,18 +520,18 @@ def Test_assignment_var_list()
enddef enddef
def Test_assignment_vim9script() def Test_assignment_vim9script()
let lines =<< trim END var lines =<< trim END
vim9script vim9script
def Func(): list<number> def Func(): list<number>
return [1, 2] return [1, 2]
enddef enddef
let var1: number var var1: number
let var2: number var var2: number
[var1, var2] = [var1, var2] =
Func() Func()
assert_equal(1, var1) assert_equal(1, var1)
assert_equal(2, var2) assert_equal(2, var2)
let ll = var ll =
Func() Func()
assert_equal([1, 2], ll) assert_equal([1, 2], ll)
@ -551,15 +552,15 @@ def Test_assignment_vim9script()
assert_equal('plus', @+) assert_equal('plus', @+)
endif endif
let a: number = 123 var a: number = 123
assert_equal(123, a) assert_equal(123, a)
let s: string = 'yes' var s: string = 'yes'
assert_equal('yes', s) assert_equal('yes', s)
let b: number = 42 var b: number = 42
assert_equal(42, b) assert_equal(42, b)
let w: number = 43 var w: number = 43
assert_equal(43, w) assert_equal(43, w)
let t: number = 44 var t: number = 44
assert_equal(44, t) assert_equal(44, t)
END END
CheckScriptSuccess(lines) CheckScriptSuccess(lines)
@ -571,80 +572,80 @@ def Mess(): string
enddef enddef
def Test_assignment_failure() def Test_assignment_failure()
CheckDefFailure(['let var=234'], 'E1004:') CheckDefFailure(['var var=234'], 'E1004:')
CheckDefFailure(['let var =234'], 'E1004:') CheckDefFailure(['var var =234'], 'E1004:')
CheckDefFailure(['let var= 234'], 'E1004:') CheckDefFailure(['var var= 234'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var=234'], 'E1004:') CheckScriptFailure(['vim9script', 'var var=234'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var=234'], "before and after '='") CheckScriptFailure(['vim9script', 'var var=234'], "before and after '='")
CheckScriptFailure(['vim9script', 'let var =234'], 'E1004:') CheckScriptFailure(['vim9script', 'var var =234'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var= 234'], 'E1004:') CheckScriptFailure(['vim9script', 'var var= 234'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var = 234', 'var+=234'], 'E1004:') CheckScriptFailure(['vim9script', 'var var = 234', 'var+=234'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var = 234', 'var+=234'], "before and after '+='") CheckScriptFailure(['vim9script', 'var var = 234', 'var+=234'], "before and after '+='")
CheckScriptFailure(['vim9script', 'let var = "x"', 'var..="y"'], 'E1004:') CheckScriptFailure(['vim9script', 'var var = "x"', 'var..="y"'], 'E1004:')
CheckScriptFailure(['vim9script', 'let var = "x"', 'var..="y"'], "before and after '..='") CheckScriptFailure(['vim9script', 'var var = "x"', 'var..="y"'], "before and after '..='")
CheckDefFailure(['let true = 1'], 'E1034:') CheckDefFailure(['var true = 1'], 'E1034:')
CheckDefFailure(['let false = 1'], 'E1034:') CheckDefFailure(['var false = 1'], 'E1034:')
CheckDefFailure(['[a; b; c] = g:list'], 'E452:') CheckDefFailure(['[a; b; c] = g:list'], 'E452:')
CheckDefExecFailure(['let a: number', CheckDefExecFailure(['var a: number',
'[a] = test_null_list()'], 'E1093:') '[a] = test_null_list()'], 'E1093:')
CheckDefExecFailure(['let a: number', CheckDefExecFailure(['var a: number',
'[a] = []'], 'E1093:') '[a] = []'], 'E1093:')
CheckDefExecFailure(['let x: number', CheckDefExecFailure(['var x: number',
'let y: number', 'var y: number',
'[x, y] = [1]'], 'E1093:') '[x, y] = [1]'], 'E1093:')
CheckDefExecFailure(['let x: number', CheckDefExecFailure(['var x: number',
'let y: number', 'var y: number',
'let z: list<number>', 'var z: list<number>',
'[x, y; z] = [1]'], 'E1093:') '[x, y; z] = [1]'], 'E1093:')
CheckDefFailure(['let somevar'], "E1022:") CheckDefFailure(['var somevar'], "E1022:")
CheckDefFailure(['let &tabstop = 4'], 'E1052:') CheckDefFailure(['var &tabstop = 4'], 'E1052:')
CheckDefFailure(['&g:option = 5'], 'E113:') CheckDefFailure(['&g:option = 5'], 'E113:')
CheckScriptFailure(['vim9script', 'let &tabstop = 4'], 'E1052:') CheckScriptFailure(['vim9script', 'var &tabstop = 4'], 'E1052:')
CheckDefFailure(['let $VAR = 5'], 'E1016: Cannot declare an environment variable:') CheckDefFailure(['var $VAR = 5'], 'E1016: Cannot declare an environment variable:')
CheckScriptFailure(['vim9script', 'let $ENV = "xxx"'], 'E1016:') CheckScriptFailure(['vim9script', 'var $ENV = "xxx"'], 'E1016:')
if has('dnd') if has('dnd')
CheckDefFailure(['let @~ = 5'], 'E1066:') CheckDefFailure(['var @~ = 5'], 'E1066:')
else else
CheckDefFailure(['let @~ = 5'], 'E354:') CheckDefFailure(['var @~ = 5'], 'E354:')
CheckDefFailure(['@~ = 5'], 'E354:') CheckDefFailure(['@~ = 5'], 'E354:')
endif endif
CheckDefFailure(['let @a = 5'], 'E1066:') CheckDefFailure(['var @a = 5'], 'E1066:')
CheckDefFailure(['let @/ = "x"'], 'E1066:') CheckDefFailure(['var @/ = "x"'], 'E1066:')
CheckScriptFailure(['vim9script', 'let @a = "abc"'], 'E1066:') CheckScriptFailure(['vim9script', 'var @a = "abc"'], 'E1066:')
CheckDefFailure(['let g:var = 5'], 'E1016: Cannot declare a global variable:') CheckDefFailure(['var g:var = 5'], 'E1016: Cannot declare a global variable:')
CheckDefFailure(['let w:var = 5'], 'E1016: Cannot declare a window variable:') CheckDefFailure(['var w:var = 5'], 'E1016: Cannot declare a window variable:')
CheckDefFailure(['let b:var = 5'], 'E1016: Cannot declare a buffer variable:') CheckDefFailure(['var b:var = 5'], 'E1016: Cannot declare a buffer variable:')
CheckDefFailure(['let t:var = 5'], 'E1016: Cannot declare a tab variable:') CheckDefFailure(['var t:var = 5'], 'E1016: Cannot declare a tab variable:')
CheckDefFailure(['let anr = 4', 'anr ..= "text"'], 'E1019:') CheckDefFailure(['var anr = 4', 'anr ..= "text"'], 'E1019:')
CheckDefFailure(['let xnr += 4'], 'E1020:', 1) CheckDefFailure(['var xnr += 4'], 'E1020:', 1)
CheckScriptFailure(['vim9script', 'let xnr += 4'], 'E1020:') CheckScriptFailure(['vim9script', 'var xnr += 4'], 'E1020:')
CheckDefFailure(["let xnr = xnr + 1"], 'E1001:', 1) CheckDefFailure(["var xnr = xnr + 1"], 'E1001:', 1)
CheckScriptFailure(['vim9script', 'let xnr = xnr + 4'], 'E121:') CheckScriptFailure(['vim9script', 'var xnr = xnr + 4'], 'E121:')
CheckScriptFailure(['vim9script', 'def Func()', 'let dummy = s:notfound', 'enddef', 'defcompile'], 'E1108:') CheckScriptFailure(['vim9script', 'def Func()', 'var dummy = s:notfound', 'enddef', 'defcompile'], 'E1108:')
CheckDefFailure(['let var: list<string> = [123]'], 'expected list<string> but got list<number>') CheckDefFailure(['var var: list<string> = [123]'], 'expected list<string> but got list<number>')
CheckDefFailure(['let var: list<number> = ["xx"]'], 'expected list<number> but got list<string>') CheckDefFailure(['var var: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
CheckDefFailure(['let var: dict<string> = #{key: 123}'], 'expected dict<string> but got dict<number>') CheckDefFailure(['var var: dict<string> = #{key: 123}'], 'expected dict<string> but got dict<number>')
CheckDefFailure(['let var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>') CheckDefFailure(['var var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>')
CheckDefFailure(['let var = feedkeys("0")'], 'E1031:') CheckDefFailure(['var var = feedkeys("0")'], 'E1031:')
CheckDefFailure(['let var: number = feedkeys("0")'], 'expected number but got void') CheckDefFailure(['var var: number = feedkeys("0")'], 'expected number but got void')
CheckDefFailure(['let var: dict <number>'], 'E1068:') CheckDefFailure(['var var: dict <number>'], 'E1068:')
CheckDefFailure(['let var: dict<number'], 'E1009:') CheckDefFailure(['var var: dict<number'], 'E1009:')
assert_fails('s/^/\=Mess()/n', 'E794:') assert_fails('s/^/\=Mess()/n', 'E794:')
CheckDefFailure(['let var: dict<number'], 'E1009:') CheckDefFailure(['var var: dict<number'], 'E1009:')
CheckDefFailure(['w:foo: number = 10'], CheckDefFailure(['w:foo: number = 10'],
'E488: Trailing characters: : number = 1') 'E488: Trailing characters: : number = 1')
@ -657,7 +658,7 @@ def Test_assignment_failure()
enddef enddef
def Test_assign_list() def Test_assign_list()
let l: list<string> = [] var l: list<string> = []
l[0] = 'value' l[0] = 'value'
assert_equal('value', l[0]) assert_equal('value', l[0])
@ -667,7 +668,7 @@ def Test_assign_list()
assert_equal('asdf', l[-1]) assert_equal('asdf', l[-1])
assert_equal('value', l[-2]) assert_equal('value', l[-2])
let nrl: list<number> = [] var nrl: list<number> = []
for i in range(5) for i in range(5)
nrl[i] = i nrl[i] = i
endfor endfor
@ -675,7 +676,7 @@ def Test_assign_list()
enddef enddef
def Test_assign_dict() def Test_assign_dict()
let d: dict<string> = {} var d: dict<string> = {}
d['key'] = 'value' d['key'] = 'value'
assert_equal('value', d['key']) assert_equal('value', d['key'])
@ -683,7 +684,7 @@ def Test_assign_dict()
assert_equal('qwerty', d[123]) assert_equal('qwerty', d[123])
assert_equal('qwerty', d['123']) assert_equal('qwerty', d['123'])
let nrd: dict<number> = {} var nrd: dict<number> = {}
for i in range(3) for i in range(3)
nrd[i] = i nrd[i] = i
endfor endfor
@ -691,12 +692,12 @@ def Test_assign_dict()
enddef enddef
def Test_assign_dict_unknown_type() def Test_assign_dict_unknown_type()
let lines =<< trim END var lines =<< trim END
vim9script vim9script
let mylist = [] var mylist = []
mylist += [#{one: 'one'}] mylist += [#{one: 'one'}]
def Func() def Func()
let dd = mylist[0] var dd = mylist[0]
assert_equal('one', dd.one) assert_equal('one', dd.one)
enddef enddef
Func() Func()
@ -706,10 +707,10 @@ def Test_assign_dict_unknown_type()
# doesn't work yet # doesn't work yet
#lines =<< trim END #lines =<< trim END
# vim9script # vim9script
# let mylist = [[]] # var mylist = [[]]
# mylist[0] += [#{one: 'one'}] # mylist[0] += [#{one: 'one'}]
# def Func() # def Func()
# let dd = mylist[0][0] # var dd = mylist[0][0]
# assert_equal('one', dd.one) # assert_equal('one', dd.one)
# enddef # enddef
# Func() # Func()
@ -719,13 +720,13 @@ enddef
def Test_assign_lambda() def Test_assign_lambda()
# check if assign a lambda to a variable which type is func or any. # check if assign a lambda to a variable which type is func or any.
let lines =<< trim END var lines =<< trim END
vim9script vim9script
let FuncRef = {->123} var FuncRef = {->123}
assert_equal(123, FuncRef()) assert_equal(123, FuncRef())
let FuncRef_Func: func = {->123} var FuncRef_Func: func = {->123}
assert_equal(123, FuncRef_Func()) assert_equal(123, FuncRef_Func())
let FuncRef_Any: any = {->123} var FuncRef_Any: any = {->123}
assert_equal(123, FuncRef_Any()) assert_equal(123, FuncRef_Any())
END END
CheckScriptSuccess(lines) CheckScriptSuccess(lines)

View File

@ -193,21 +193,21 @@ def Test_wrong_type()
enddef enddef
def Test_const() def Test_const()
CheckDefFailure(['const var = 234', 'var = 99'], 'E1018:') CheckDefFailure(['final var = 234', 'var = 99'], 'E1018:')
CheckDefFailure(['const one = 234', 'let one = 99'], 'E1017:') CheckDefFailure(['final one = 234', 'let one = 99'], 'E1017:')
CheckDefFailure(['const list = [1, 2]', 'let list = [3, 4]'], 'E1017:') CheckDefFailure(['final list = [1, 2]', 'let list = [3, 4]'], 'E1017:')
CheckDefFailure(['const two'], 'E1021:') CheckDefFailure(['final two'], 'E1125:')
CheckDefFailure(['const &option'], 'E996:') CheckDefFailure(['final &option'], 'E996:')
let lines =<< trim END let lines =<< trim END
const list = [1, 2, 3] final list = [1, 2, 3]
list[0] = 4 list[0] = 4
list->assert_equal([4, 2, 3]) list->assert_equal([4, 2, 3])
const! other = [5, 6, 7] const other = [5, 6, 7]
other->assert_equal([5, 6, 7]) other->assert_equal([5, 6, 7])
let varlist = [7, 8] let varlist = [7, 8]
const! constlist = [1, varlist, 3] const constlist = [1, varlist, 3]
varlist[0] = 77 varlist[0] = 77
# TODO: does not work yet # TODO: does not work yet
# constlist[1][1] = 88 # constlist[1][1] = 88
@ -216,7 +216,7 @@ def Test_const()
constlist->assert_equal([1, [77, 88], 3]) constlist->assert_equal([1, [77, 88], 3])
let vardict = #{five: 5, six: 6} let vardict = #{five: 5, six: 6}
const! constdict = #{one: 1, two: vardict, three: 3} const constdict = #{one: 1, two: vardict, three: 3}
vardict['five'] = 55 vardict['five'] = 55
# TODO: does not work yet # TODO: does not work yet
# constdict['two']['six'] = 66 # constdict['two']['six'] = 66
@ -229,35 +229,35 @@ enddef
def Test_const_bang() def Test_const_bang()
let lines =<< trim END let lines =<< trim END
const! var = 234 const var = 234
var = 99 var = 99
END END
CheckDefExecFailure(lines, 'E1018:', 2) CheckDefExecFailure(lines, 'E1018:', 2)
CheckScriptFailure(['vim9script'] + lines, 'E46:', 3) CheckScriptFailure(['vim9script'] + lines, 'E46:', 3)
lines =<< trim END lines =<< trim END
const! ll = [2, 3, 4] const ll = [2, 3, 4]
ll[0] = 99 ll[0] = 99
END END
CheckDefExecFailure(lines, 'E1119:', 2) CheckDefExecFailure(lines, 'E1119:', 2)
CheckScriptFailure(['vim9script'] + lines, 'E741:', 3) CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
lines =<< trim END lines =<< trim END
const! ll = [2, 3, 4] const ll = [2, 3, 4]
ll[3] = 99 ll[3] = 99
END END
CheckDefExecFailure(lines, 'E1118:', 2) CheckDefExecFailure(lines, 'E1118:', 2)
CheckScriptFailure(['vim9script'] + lines, 'E684:', 3) CheckScriptFailure(['vim9script'] + lines, 'E684:', 3)
lines =<< trim END lines =<< trim END
const! dd = #{one: 1, two: 2} const dd = #{one: 1, two: 2}
dd["one"] = 99 dd["one"] = 99
END END
CheckDefExecFailure(lines, 'E1121:', 2) CheckDefExecFailure(lines, 'E1121:', 2)
CheckScriptFailure(['vim9script'] + lines, 'E741:', 3) CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
lines =<< trim END lines =<< trim END
const! dd = #{one: 1, two: 2} const dd = #{one: 1, two: 2}
dd["three"] = 99 dd["three"] = 99
END END
CheckDefExecFailure(lines, 'E1120:') CheckDefExecFailure(lines, 'E1120:')
@ -2531,6 +2531,12 @@ enddef
def Test_let_declaration_fails() def Test_let_declaration_fails()
let lines =<< trim END let lines =<< trim END
vim9script
final var: string
END
CheckScriptFailure(lines, 'E1125:')
lines =<< trim END
vim9script vim9script
const var: string const var: string
END END

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1744,
/**/ /**/
1743, 1743,
/**/ /**/

View File

@ -2135,9 +2135,9 @@ typedef enum {
} estack_arg_T; } estack_arg_T;
// Flags for assignment functions. // Flags for assignment functions.
#define LET_IS_CONST 1 // ":const" #define ASSIGN_FINAL 1 // ":final"
#define LET_FORCEIT 2 // ":const!" (LET_IS_CONST is also set) #define ASSIGN_CONST 2 // ":const"
#define LET_NO_COMMAND 4 // "var = expr" without ":let" or ":const" #define ASSIGN_NO_DECL 4 // "name = expr" without ":let" or ":const"
#include "ex_cmds.h" // Ex command defines #include "ex_cmds.h" // Ex command defines
#include "spell.h" // spell checking stuff #include "spell.h" // spell checking stuff

View File

@ -4562,8 +4562,12 @@ vim9_declare_error(char_u *name)
/* /*
* Compile declaration and assignment: * Compile declaration and assignment:
* "let var", "let var = expr", "const var = expr" and "var = expr" * "let name"
* "arg" points to "var". * "var name = expr"
* "final name = expr"
* "const name = expr"
* "name = expr"
* "arg" points to "name".
* Return NULL for an error. * Return NULL for an error.
* Return "arg" if it does not look like a variable list. * Return "arg" if it does not look like a variable list.
*/ */
@ -4588,7 +4592,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
type_T *member_type = &t_any; type_T *member_type = &t_any;
char_u *name = NULL; char_u *name = NULL;
char_u *sp; char_u *sp;
int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; int is_decl = cmdidx == CMD_let || cmdidx == CMD_var
|| cmdidx == CMD_final || cmdidx == CMD_const;
// Skip over the "var" or "[var, var]" to get to any "=". // Skip over the "var" or "[var, var]" to get to any "=".
p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE); p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
@ -4729,7 +4734,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
long numval; long numval;
dest = dest_option; dest = dest_option;
if (cmdidx == CMD_const) if (cmdidx == CMD_final || cmdidx == CMD_const)
{ {
emsg(_(e_const_option)); emsg(_(e_const_option));
goto theend; goto theend;
@ -4968,7 +4973,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
&& var_wrong_func_name(name, TRUE)) && var_wrong_func_name(name, TRUE))
goto theend; goto theend;
lvar = reserve_local(cctx, var_start, varlen, lvar = reserve_local(cctx, var_start, varlen,
cmdidx == CMD_const, type); cmdidx == CMD_final || cmdidx == CMD_const, type);
if (lvar == NULL) if (lvar == NULL)
goto theend; goto theend;
new_local = TRUE; new_local = TRUE;
@ -5119,6 +5124,11 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
cctx, FALSE) == FAIL) cctx, FALSE) == FAIL)
goto theend; goto theend;
} }
else if (cmdidx == CMD_final)
{
emsg(_(e_final_requires_a_value));
goto theend;
}
else if (cmdidx == CMD_const) else if (cmdidx == CMD_const)
{ {
emsg(_(e_const_requires_a_value)); emsg(_(e_const_requires_a_value));
@ -5283,9 +5293,9 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
} }
else else
{ {
if (is_decl && eap->forceit && cmdidx == CMD_const if (is_decl && cmdidx == CMD_const
&& (dest == dest_script || dest == dest_local)) && (dest == dest_script || dest == dest_local))
// ":const! var": lock the value, but not referenced variables // ":const var": lock the value, but not referenced variables
generate_LOCKCONST(cctx); generate_LOCKCONST(cctx);
switch (dest) switch (dest)
@ -6915,7 +6925,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
// Expression or function call. // Expression or function call.
if (ea.cmdidx != CMD_eval) if (ea.cmdidx != CMD_eval)
{ {
// CMD_let cannot happen, compile_assignment() above is used // CMD_var cannot happen, compile_assignment() above is used
iemsg("Command from find_ex_command() not handled"); iemsg("Command from find_ex_command() not handled");
goto erret; goto erret;
} }
@ -6967,6 +6977,8 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
break; break;
case CMD_let: case CMD_let:
case CMD_var:
case CMD_final:
case CMD_const: case CMD_const:
line = compile_assignment(p, &ea, ea.cmdidx, &cctx); line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
if (line == p) if (line == p)

View File

@ -729,7 +729,7 @@ store_var(char_u *name, typval_T *tv)
funccal_entry_T entry; funccal_entry_T entry;
save_funccal(&entry); save_funccal(&entry);
set_var_const(name, NULL, tv, FALSE, LET_NO_COMMAND); set_var_const(name, NULL, tv, FALSE, ASSIGN_NO_DECL);
restore_funccal(); restore_funccal();
} }

View File

@ -97,6 +97,8 @@ ex_export(exarg_T *eap)
switch (eap->cmdidx) switch (eap->cmdidx)
{ {
case CMD_let: case CMD_let:
case CMD_var:
case CMD_final:
case CMD_const: case CMD_const:
case CMD_def: case CMD_def:
// case CMD_class: // case CMD_class:
@ -508,9 +510,12 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg)
int called_emsg_before = called_emsg; int called_emsg_before = called_emsg;
typval_T init_tv; typval_T init_tv;
if (eap->cmdidx == CMD_const) if (eap->cmdidx == CMD_final || eap->cmdidx == CMD_const)
{ {
emsg(_(e_const_requires_a_value)); if (eap->cmdidx == CMD_final)
emsg(_(e_final_requires_a_value));
else
emsg(_(e_const_requires_a_value));
return arg + STRLEN(arg); return arg + STRLEN(arg);
} }