changeset 22391:a9fb7efa31d6 v8.2.1744

patch 8.2.1744: Vim9: using ":const!" is weird Commit: https://github.com/vim/vim/commit/30fd8204cecb317d842b964d624d492088d6d15f Author: Bram Moolenaar <Bram@vim.org> Date: Sat Sep 26 15:09:30 2020 +0200 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.
author Bram Moolenaar <Bram@vim.org>
date Sat, 26 Sep 2020 15:15:05 +0200
parents ee56fb5ea218
children 71c2c6e35e09
files runtime/doc/vim9.txt src/cmdexpand.c src/errors.h src/eval.c src/evalvars.c src/ex_cmdidxs.h src/ex_cmds.h src/ex_docmd.c src/proto/evalvars.pro src/testdir/test_vim9_assign.vim src/testdir/test_vim9_script.vim src/version.c src/vim.h src/vim9compile.c src/vim9execute.c src/vim9script.c
diffstat 16 files changed, 512 insertions(+), 382 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -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
@@ -70,7 +70,7 @@ Comments starting with # ~
 In legacy Vim script comments start with double quote.  In Vim9 script
 comments start with #. >
 	# 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
 places, especially halfway through an expression with a line break, it's hard
@@ -154,31 +154,32 @@ Vim9 script script-local functions are d
 and cannot be deleted or replaced.
 
 
-Variable declarations with :let and :const ~
+Variable declarations with :var, :final and :const ~
 							*vim9-declaration*
-Local variables need to be declared with `:let`.  Local constants need to be
-declared with `:const`.  We refer to both as "variables".
+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
-	let script_var = 123
+	var script_var = 123
 	def SomeFunc()
-	  let func_var = script_var
+	  var func_var = script_var
 	  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
 blocks.  Once the block ends the variable is no longer accessible: >
 	if cond
-	   let inner = 5
+	   var inner = 5
 	else
-	   let inner = 0
+	   var inner = 0
 	endif
 	echo inner  # Error!
 
 The declaration must be done earlier: >
-	let inner: number
+	var inner: number
 	if cond
 	   inner = 5
 	else
@@ -186,10 +187,10 @@ The declaration must be done earlier: >
 	endif
 	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!
@@ -197,9 +198,9 @@ To intentionally avoid a variable being 
 Declaring a variable with a type but without an initializer will initialize to
 zero, false or empty.
 
-An existing variable cannot be assigned to with `:let`, since that implies a
-declaration.  Global, window, tab, buffer and Vim variables can only be used
-without `:let`, because they are not really declared, they can also be deleted
+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.  They can also be deleted
 with `:unlet`.
 
 Variables and functions cannot shadow previously defined or imported variables
@@ -209,51 +210,50 @@ Variables may shadow Ex commands, rename
 Global variables and user defined functions must be prefixed with "g:", also
 at the script level. >
 	vim9script
-	let script_local = 'text'
+	var script_local = 'text'
 	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.
-							*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.
+
+
+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.
 
-One can use `:const!` to make both the variable and the value immutable.  Use
+`: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!
+	muList->add(3)		# Error!
 
-How this works: >
-	vim9script
-	const list = [1, 2]
-	list = [3, 4]	     # Error!
-	list[0] = 2          # OK
+`: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
+	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.
 
 The constant only applies to the value itself, not what it refers to. >
-	cont females = ["Mary"]
-	const! NAMES = [["John", "Peter"], females]
+	final females = ["Mary"]
+	const NAMES = [["John", "Peter"], females]
 	NAMES[0] = ["Jack"]     # Error!
-	NAMES[0][0] = ["Jack"]  # Error!
+	NAMES[0][0] = "Jack"    # Error!
 	NAMES[1] = ["Emma"]     # Error!
 	Names[1][0] = "Emma"    # OK, now females[0] == "Emma"
 
-Rationale: TypeScript has no way to make the value immutable.  One can use
-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*
+<							*E1092*
 Declaring more than one variable at a time, using the unpack notation, is
 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
 isn't that easy.
 
@@ -296,7 +296,7 @@ A user defined function can be used as a
 without `function()`. The argument types and return type will then be checked.
 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
 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
 those cases there is no need to prefix the line with a backslash
 |line-continuation|.  For example, when a list spans multiple lines: >
-	let mylist = [
+	var mylist = [
 		'one',
 		'two',
 		]
 And when a dict spans multiple lines: >
-	let mydict = #{
+	var mydict = #{
 		one: 1,
 		two: 2,
 		}
 Function call: >
-	let result = Func(
+	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: >
-	let text = lead
+	var text = lead
 		   .. middle
 		   .. end
-	let total = start +
+	var total = start +
 	            end -
 		    correction
-	let result = positive
+	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: >
-	let result = GetBuilder()
+	var result = GetBuilder()
 			->BuilderSetWidth(333)
 			->BuilderSetHeight(777)
 			->BuilderBuild()
-	let result = MyDict
+	var result = MyDict
 			.member
 
 <							*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 will add
 "start" and print: >
-	let result = start
+	var result = start
 	+ print
 Like this: >
-	let result = start + print
+	var result = start + print
 
 This will assign "start" and print a line: >
-	let result = start
+	var result = start
 	:+ print
 
 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 
 White space ~
 
 Vim9 script enforces proper use of white space.  This is no longer allowed: >
-	let var=234	# Error!
-	let var= 234	# Error!
-	let var =234	# Error!
+	var name=234	# Error!
+	var name= 234	# Error!
+	var name =234	# Error!
 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
 command: >
-	let var = 234# Error!
-	let var = 234 # OK
+	var name = 234# Error!
+	var name = 234 # OK
 
 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
 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
 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 - ANYTHI
 			It is possible to nest `:def` inside another `:def` or
 			`:function` up to about 50 levels deep.
 
-			[!] is used as with `:function`.  Note that in Vim9
-			script script-local functions cannot be deleted or
-			redefined later in the same script.
+			[!] 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*
 :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: >
 	def EvalString(): list<string>
-	  let list = ['aa', 'bb', 'cc', 'dd']
+	  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 EvalString(): list<string>
-	  let list = ['aa', 'bb', 'cc', 'dd']
+	  var list = ['aa', 'bb', 'cc', 'dd']
 	  return range(1, 2)->map({ _, v -> list[v] })
 	enddef
 
@@ -690,23 +691,23 @@ builtin types added later, similarly to 
 
 And classes and interfaces can be used as types: >
 	:class MyClass
-	:let mine: MyClass
+	:var mine: MyClass
 
 	:interface MyInterface
-	:let mine: MyInterface
+	:var mine: MyInterface
 
 	:class MyTemplate<Targ>
-	:let mine: MyTemplate<number>
-	:let mine: MyTemplate<string>
+	:var mine: MyTemplate<number>
+	:var mine: MyTemplate<string>
 
 	:class MyInterface<Targ>
-	:let mine: MyInterface<number>
-	:let mine: MyInterface<string>
+	:var mine: MyInterface<number>
+	:var mine: MyInterface<string>
 {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
 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
 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
 type cast: >
-	let l: list<number> = [1, <number>g:two]
+	var l: list<number> = [1, <number>g:two]
 <							*type-casting*
 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.
@@ -734,12 +735,12 @@ it to a string, use the |string()| funct
 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
 declaring a variable and giving it a value: >
-	let var = 0		# infers number type
-	let var = 'hello'	# infers string type
+	var name = 0		# infers number type
+	var name = 'hello'	# infers string type
 
 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
@@ -749,8 +750,8 @@ dictionary.  If there is a mix of types,
 	[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
 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
@@ -766,7 +767,7 @@ an error, thus breaking backwards compat
 
 ==============================================================================
 
-5.  Namespace, Import and Export
+5. Namespace, Import and Export
 					*vim9script* *vim9-export* *vim9-import*
 
 THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
@@ -786,7 +787,7 @@ appear as the first statement in the fil
 script in its own namespace, instead of the global namespace.  If a file
 starts with: >
 	vim9script
-	let myvar = 'yes'
+	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.
 
@@ -809,7 +810,9 @@ Export ~
 							*:export* *:exp*
 Exporting an item can be written as: >
 	export const EXPORTED_CONST = 1234
-	export let someValue = ...
+	export var someValue = ...
+	export final someValue = ...
+	export const someValue = ...
 	export def MyFunc() ...
 	export class MyClass ...
 
@@ -880,7 +883,7 @@ 2. In the autoload script do the actual 
 	vim9script
         import FilterFunc from "../import/someother.vim"
 	def searchfor#Stuff(arg: string)
-	  let filtered = FilterFunc(arg)
+	  var filtered = FilterFunc(arg)
 	  ...
 <   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
@@ -889,7 +892,7 @@ 2. In the autoload script do the actual 
 3. Other functionality, possibly shared between plugins, contains the exported
    items and any private items. >
 	vim9script
-	let localVar = 'local'
+	var localVar = 'local'
 	export def FilterFunc(arg: string): string
 	   ...
 <   This goes in .../import/someother.vim.
@@ -909,7 +912,7 @@ namespace will be used for the imported 
 6. Future work: classes					*vim9-classes*
 
 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.
 For now we'll just make sure classes can be added later.
 
@@ -941,7 +944,7 @@ 9. Rationale						*vim9-rationale*
 
 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
 impossible, because of the overhead involved with calling a function, setting
 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,
 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 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
 "function" which clashes with legacy Vim script.
@@ -968,12 +971,12 @@ instruction, at execution time the instr
 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, adding two
-numbers cannot fail.
+given at compile time, no error handling is needed at runtime, since adding
+two numbers cannot fail.
 
-The syntax for types is similar to Java, since 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".
+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 ~
@@ -981,10 +984,10 @@ 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 uses.
+only Vim does.
 
 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:
 - 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:
 - 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
   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 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
 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.
 
-Since Vim already uses `:let` and `:const` and optional type checking is
-desirable, the JavaScript/TypeScript syntax fits best for variable
-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
+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 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
 (esp. builtin functions) depends on that, while static typing allows for much
 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
   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.
+  result as a bool.  TODO: to be reconsidered
 - 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.
@@ -1063,6 +1048,80 @@ Specific items from TypeScript we avoid:
   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 ~
 
 A problem of legacy Vim script is that by default all functions and variables
@@ -1122,7 +1181,7 @@ only reported then.  In case these error
 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
 these interfaces have never become widely used, for various reasons.  When
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -1513,8 +1513,10 @@ set_one_cmd_context(
 	    break;
 #endif
 #ifdef FEAT_EVAL
+	case CMD_final:
 	case CMD_const:
 	case CMD_let:
+	case CMD_var:
 	case CMD_if:
 	case CMD_elseif:
 	case CMD_while:
--- a/src/errors.h
+++ b/src/errors.h
@@ -270,4 +270,8 @@ EXTERN char e_variable_is_locked_str[]
 	INIT(= N_("E1122: Variable is locked: %s"));
 EXTERN char e_missing_comma_before_argument_str[]
 	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
--- a/src/eval.c
+++ b/src/eval.c
@@ -1213,7 +1213,7 @@ set_var_lval(
     char_u	*endp,
     typval_T	*rettv,
     int		copy,
-    int		flags,    // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
+    int		flags,    // ASSIGN_CONST, ASSIGN_NO_DECL
     char_u	*op)
 {
     int		cc;
@@ -1281,7 +1281,7 @@ set_var_lval(
 	{
 	    typval_T tv;
 
-	    if (flags & LET_IS_CONST)
+	    if (flags & ASSIGN_CONST)
 	    {
 		emsg(_(e_cannot_mod));
 		*endp = cc;
@@ -1319,7 +1319,7 @@ set_var_lval(
 	listitem_T *ll_li = lp->ll_li;
 	int	    ll_n1 = lp->ll_n1;
 
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	{
 	    emsg(_("E996: Cannot lock a range"));
 	    return;
@@ -1378,7 +1378,7 @@ set_var_lval(
 	/*
 	 * Assign to a List or Dictionary item.
 	 */
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	{
 	    emsg(_("E996: Cannot lock a list or dict"));
 	    return;
@@ -1688,7 +1688,7 @@ next_for_item(void *fi_void, char_u *arg
 {
     forinfo_T	*fi = (forinfo_T *)fi_void;
     int		result;
-    int		flag = in_vim9script() ?  LET_NO_COMMAND : 0;
+    int		flag = in_vim9script() ?  ASSIGN_NO_DECL : 0;
     listitem_T	*item;
 
     if (fi->fi_blob != NULL)
@@ -1741,11 +1741,12 @@ set_context_for_expression(
     char_u	*arg,
     cmdidx_T	cmdidx)
 {
-    int		got_eq = FALSE;
+    int		has_expr = cmdidx != CMD_let && cmdidx != CMD_var;
     int		c;
     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;
 	if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL)
@@ -1774,8 +1775,7 @@ set_context_for_expression(
 	    if (c == '&')
 	    {
 		++xp->xp_pattern;
-		xp->xp_context = cmdidx != CMD_let || got_eq
-					 ? EXPAND_EXPRESSION : EXPAND_NOTHING;
+		xp->xp_context = has_expr ? EXPAND_EXPRESSION : EXPAND_NOTHING;
 	    }
 	    else if (c != ' ')
 	    {
@@ -1792,7 +1792,7 @@ set_context_for_expression(
 	}
 	else if (c == '=')
 	{
-	    got_eq = TRUE;
+	    has_expr = TRUE;
 	    xp->xp_context = EXPAND_EXPRESSION;
 	}
 	else if (c == '#'
@@ -1808,7 +1808,7 @@ set_context_for_expression(
 	    // Function name can start with "<SNR>" and contain '#'.
 	    break;
 	}
-	else if (cmdidx != CMD_let || got_eq)
+	else if (has_expr)
 	{
 	    if (c == '"')	    // string
 	    {
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -669,6 +669,25 @@ heredoc_get(exarg_T *eap, char_u *cmd, i
 }
 
 /*
+ * 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 var1 var2"		list variable values
  * ":let var = expr"		assignment command.
@@ -683,6 +702,9 @@ heredoc_get(exarg_T *eap, char_u *cmd, i
  * ":let var =<< ..."		heredoc
  * ":let var: string"		Vim9 declaration
  *
+ * ":final var = expr"		assignment command.
+ * ":final [var1, var2] = expr"	unpack list.
+ *
  * ":const"			list all variable values
  * ":const var1 var2"		list variable values
  * ":const var = expr"		assignment command.
@@ -702,14 +724,22 @@ ex_let(exarg_T *eap)
     int		first = TRUE;
     int		concat;
     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();
 
+    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"
     if (eap->arg == eap->cmd)
-	flags |= LET_NO_COMMAND;
-    if (eap->forceit)
-	flags |= LET_FORCEIT;
+	flags |= ASSIGN_NO_DECL;
 
     argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
     if (argend == NULL)
@@ -787,7 +817,7 @@ ex_let(exarg_T *eap)
 	    op[1] = NUL;
 	    if (*expr != '=')
 	    {
-		if (vim9script && (flags & LET_NO_COMMAND) == 0)
+		if (vim9script && (flags & ASSIGN_NO_DECL) == 0)
 		{
 		    // +=, /=, etc. require an existing variable
 		    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		semicolon,	// 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	*arg = arg_start;
@@ -1215,7 +1245,7 @@ ex_let_one(
     char_u	*arg,		// points to variable name
     typval_T	*tv,		// value to assign to variable
     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	*op)		// "+", "-", "."  or NULL
 {
@@ -1227,7 +1257,7 @@ ex_let_one(
     int		opt_flags;
     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)
     {
 	vim9_declare_error(arg);
@@ -1237,7 +1267,7 @@ ex_let_one(
     // ":let $VAR = expr": Set environment variable.
     if (*arg == '$')
     {
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	{
 	    emsg(_("E996: Cannot lock an environment variable"));
 	    return NULL;
@@ -1289,7 +1319,7 @@ ex_let_one(
     // ":let &g:option = expr": Set global option value.
     else if (*arg == '&')
     {
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	{
 	    emsg(_(e_const_option));
 	    return NULL;
@@ -1373,7 +1403,7 @@ ex_let_one(
     // ":let @r = expr": Set register contents.
     else if (*arg == '@')
     {
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	{
 	    emsg(_("E996: Cannot lock a register"));
 	    return NULL;
@@ -2926,7 +2956,7 @@ set_var(
     typval_T	*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,
     typval_T	*tv_arg,
     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	bool_tv;
@@ -2960,7 +2990,7 @@ set_var_const(
 
     if (vim9script
 	    && !is_script_local
-	    && (flags & LET_NO_COMMAND) == 0
+	    && (flags & ASSIGN_NO_DECL) == 0
 	    && name[1] == ':')
     {
 	vim9_declare_error(name);
@@ -2990,7 +3020,7 @@ set_var_const(
     {
 	if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
 	{
-	    if (flags & LET_IS_CONST)
+	    if (flags & ASSIGN_CONST)
 	    {
 		emsg(_(e_cannot_mod));
 		goto failed;
@@ -2998,7 +3028,7 @@ set_var_const(
 
 	    if (is_script_local && vim9script)
 	    {
-		if ((flags & LET_NO_COMMAND) == 0)
+		if ((flags & ASSIGN_NO_DECL) == 0)
 		{
 		    semsg(_(e_redefining_script_item_str), name);
 		    goto failed;
@@ -3094,7 +3124,7 @@ set_var_const(
 	    goto failed;
 	}
 	di->di_flags = DI_FLAGS_ALLOC;
-	if (flags & LET_IS_CONST)
+	if (flags & ASSIGN_CONST)
 	    di->di_flags |= DI_FLAGS_LOCK;
 
 	if (is_script_local && vim9script)
@@ -3113,7 +3143,7 @@ set_var_const(
 		    sv->sv_type = typval2type(tv, &si->sn_type_list);
 		else
 		    sv->sv_type = type;
-		sv->sv_const = (flags & LET_IS_CONST);
+		sv->sv_const = (flags & ASSIGN_CONST);
 		sv->sv_export = is_export;
 		++si->sn_var_vals.ga_len;
 
@@ -3132,8 +3162,8 @@ set_var_const(
 	init_tv(tv);
     }
 
-    // ":const var = val" locks the value; in Vim9 script only with ":const!"
-    if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT)))
+    // ":const var = val" locks the value
+    if (flags & ASSIGN_CONST)
 	// Like :lockvar! name: lock the value and what it contains, but only
 	// if the reference count is up to one.  That locks only literal
 	// values.
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -11,26 +11,26 @@ static const unsigned short cmdidxs1[26]
   /* d */ 108,
   /* e */ 133,
   /* f */ 156,
-  /* g */ 172,
-  /* h */ 178,
-  /* i */ 187,
-  /* j */ 206,
-  /* k */ 208,
-  /* l */ 213,
-  /* m */ 275,
-  /* n */ 293,
-  /* o */ 313,
-  /* p */ 325,
-  /* q */ 364,
-  /* r */ 367,
-  /* s */ 387,
-  /* t */ 456,
-  /* u */ 501,
-  /* v */ 512,
-  /* w */ 531,
-  /* x */ 545,
-  /* y */ 555,
-  /* z */ 556
+  /* g */ 173,
+  /* h */ 179,
+  /* i */ 188,
+  /* j */ 207,
+  /* k */ 209,
+  /* l */ 214,
+  /* m */ 276,
+  /* n */ 294,
+  /* o */ 314,
+  /* p */ 326,
+  /* q */ 365,
+  /* r */ 368,
+  /* s */ 388,
+  /* t */ 457,
+  /* u */ 502,
+  /* v */ 513,
+  /* w */ 533,
+  /* x */ 547,
+  /* y */ 557,
+  /* z */ 558
 };
 
 /*
@@ -46,7 +46,7 @@ static const unsigned char cmdidxs2[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 },
   /* 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 },
-  /* 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 },
   /* 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 },
@@ -62,11 +62,11 @@ static const unsigned char cmdidxs2[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 },
   /* 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 },
-  /* 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 },
   /* 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 },
   /* 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;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -592,6 +592,9 @@ EXCMD(CMD_filter,	"filter",	ex_wrongmodi
 EXCMD(CMD_find,		"find",		ex_find,
 	EX_RANGE|EX_BANG|EX_FILE1|EX_CMDARG|EX_ARGOPT|EX_TRLBAR|EX_NEEDARG,
 	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,
 	EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_NONE),
@@ -1648,6 +1651,9 @@ EXCMD(CMD_update,	"update",	ex_update,
 EXCMD(CMD_vglobal,	"vglobal",	ex_global,
 	EX_RANGE|EX_WHOLEFOLD|EX_EXTRA|EX_DFLALL|EX_CMDWIN|EX_LOCK_OK,
 	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,
 	EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_NONE),
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -2421,6 +2421,7 @@ do_one_cmd(
 	    case CMD_eval:
 	    case CMD_execute:
 	    case CMD_filter:
+	    case CMD_final:
 	    case CMD_help:
 	    case CMD_hide:
 	    case CMD_ijump:
@@ -2442,9 +2443,9 @@ do_one_cmd(
 	    case CMD_noswapfile:
 	    case CMD_perl:
 	    case CMD_psearch:
-	    case CMD_python:
 	    case CMD_py3:
 	    case CMD_python3:
+	    case CMD_python:
 	    case CMD_return:
 	    case CMD_rightbelow:
 	    case CMD_ruby:
@@ -2460,6 +2461,7 @@ do_one_cmd(
 	    case CMD_topleft:
 	    case CMD_unlet:
 	    case CMD_unlockvar:
+	    case CMD_var:
 	    case CMD_verbose:
 	    case CMD_vertical:
 	    case CMD_wincmd:
@@ -3244,7 +3246,7 @@ find_ex_command(
 		if (skip_expr(&after) == OK
 				  && (*after == '='
 				      || (*after != NUL && after[1] == '=')))
-		    eap->cmdidx = CMD_let;
+		    eap->cmdidx = CMD_var;
 		else
 		    eap->cmdidx = CMD_eval;
 		--emsg_silent;
@@ -3268,7 +3270,7 @@ find_ex_command(
 		}
 		if (p > eap->cmd && *skipwhite(p) == '=')
 		{
-		    eap->cmdidx = CMD_let;
+		    eap->cmdidx = CMD_var;
 		    return eap->cmd;
 		}
 	    }
@@ -3287,7 +3289,7 @@ find_ex_command(
 			|| *eap->cmd == '@'
 			|| lookup(eap->cmd, p - eap->cmd, cctx) != NULL)
 		{
-		    eap->cmdidx = CMD_let;
+		    eap->cmdidx = CMD_var;
 		    return eap->cmd;
 		}
 	    }
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -14,6 +14,7 @@ int get_spellword(list_T *list, char_u *
 void prepare_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);
+void ex_var(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);
 char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon, int silent);
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -12,30 +12,30 @@ let g:alist = [7]
 let g:astring = 'text'
 
 def Test_assignment_bool()
-  let bool1: bool = true
+  var bool1: bool = true
   assert_equal(v:true, bool1)
-  let bool2: bool = false
+  var bool2: bool = false
   assert_equal(v:false, bool2)
 
-  let bool3: bool = 0
+  var bool3: bool = 0
   assert_equal(false, bool3)
-  let bool4: bool = 1
+  var bool4: bool = 1
   assert_equal(true, bool4)
 
-  let bool5: bool = 'yes' && 'no'
+  var bool5: bool = 'yes' && 'no'
   assert_equal(true, bool5)
-  let bool6: bool = [] && 99
+  var bool6: bool = [] && 99
   assert_equal(false, bool6)
-  let bool7: bool = [] || #{a: 1} && 99
+  var bool7: bool = [] || #{a: 1} && 99
   assert_equal(true, bool7)
 
-  let lines =<< trim END
+  var lines =<< trim END
     vim9script
     def GetFlag(): bool
-      let flag: bool = 1
+      var flag: bool = 1
       return flag
     enddef
-    let flag: bool = GetFlag()
+    var flag: bool = GetFlag()
     assert_equal(true, flag)
     flag = 0
     assert_equal(false, flag)
@@ -47,41 +47,42 @@ def Test_assignment_bool()
     assert_equal(false, flag)
   END
   CheckScriptSuccess(lines)
-  CheckDefAndScriptFailure(['let x: bool = 2'], 'E1012:')
-  CheckDefAndScriptFailure(['let x: bool = -1'], 'E1012:')
-  CheckDefAndScriptFailure(['let x: bool = [1]'], 'E1012:')
-  CheckDefAndScriptFailure(['let x: bool = {}'], 'E1012:')
-  CheckDefAndScriptFailure(['let x: bool = "x"'], 'E1012:')
+  CheckDefAndScriptFailure(['var x: bool = 2'], 'E1012:')
+  CheckDefAndScriptFailure(['var x: bool = -1'], 'E1012:')
+  CheckDefAndScriptFailure(['var x: bool = [1]'], 'E1012:')
+  CheckDefAndScriptFailure(['var x: bool = {}'], 'E1012:')
+  CheckDefAndScriptFailure(['var x: bool = "x"'], 'E1012:')
 enddef
 
 def Test_syntax()
-  let var = 234
-  let other: list<string> = ['asdf']
+  var var = 234
+  var other: list<string> = ['asdf']
 enddef
 
 def Test_assignment()
-  CheckDefFailure(['let x:string'], 'E1069:')
-  CheckDefFailure(['let x:string = "x"'], 'E1069:')
-  CheckDefFailure(['let a:string = "x"'], 'E1069:')
-  CheckDefFailure(['let lambda = {-> "lambda"}'], 'E704:')
+  CheckDefFailure(['var x:string'], 'E1069:')
+  CheckDefFailure(['var x:string = "x"'], 'E1069:')
+  CheckDefFailure(['var a:string = "x"'], 'E1069:')
+  CheckDefFailure(['var lambda = {-> "lambda"}'], 'E704:')
+  CheckScriptFailure(['var x = "x"'], 'E1124:')
 
-  let nr: number = 1234
-  CheckDefFailure(['let nr: number = "asdf"'], 'E1012:')
+  var nr: number = 1234
+  CheckDefFailure(['var nr: number = "asdf"'], 'E1012:')
 
-  let a: number = 6 #comment
+  var a: number = 6 #comment
   assert_equal(6, a)
 
   if has('channel')
-    let chan1: channel
-    let job1: job
-    let job2: job = job_start('willfail')
+    var chan1: channel
+    var job1: job
+    var job2: job = job_start('willfail')
   endif
   if has('float')
-    let float1: float = 3.4
+    var float1: float = 3.4
   endif
-  let Funky1: func
-  let Funky2: func = function('len')
-  let Party2: func = funcref('g:Test_syntax')
+  var Funky1: func
+  var Funky2: func = function('len')
+  var Party2: func = funcref('g:Test_syntax')
 
   g:newvar = 'new'  #comment
   assert_equal('new', g:newvar)
@@ -97,7 +98,7 @@ def Test_assignment()
   assert_equal('foobar', $ENVVAR)
   $ENVVAR = ''
 
-  let lines =<< trim END
+  var lines =<< trim END
     vim9script
     $ENVVAR = 'barfoo'
     assert_equal('barfoo', $ENVVAR)
@@ -126,15 +127,15 @@ def Test_assignment()
   assert_equal(2, &ts)
 
   if has('float')
-    let f100: float = 100.0
+    var f100: float = 100.0
     f100 /= 5
     assert_equal(20.0, f100)
 
-    let f200: float = 200.0
+    var f200: float = 200.0
     f200 /= 5.0
     assert_equal(40.0, f200)
 
-    CheckDefFailure(['let nr: number = 200', 'nr /= 5.0'], 'E1012:')
+    CheckDefFailure(['var nr: number = 200', 'nr /= 5.0'], 'E1012:')
   endif
 
   lines =<< trim END
@@ -163,11 +164,11 @@ def Test_assignment()
   CheckDefFailure(['&path += 3'], 'E1012:')
   CheckDefExecFailure(['&bs = "asdf"'], 'E474:')
   # test freeing ISN_STOREOPT
-  CheckDefFailure(['&ts = 3', 'let asdf'], 'E1022:')
+  CheckDefFailure(['&ts = 3', 'var asdf'], 'E1022:')
   &ts = 8
 
   lines =<< trim END
-    let save_TI = &t_TI
+    var save_TI = &t_TI
     &t_TI = ''
     assert_equal('', &t_TI)
     &t_TI = 'xxx'
@@ -179,8 +180,8 @@ def Test_assignment()
   CheckDefFailure(['&t_TI = 123'], 'E1012:')
   CheckScriptFailure(['vim9script', '&t_TI = 123'], 'E928:')
 
-  CheckDefFailure(['let s:var = 123'], 'E1101:')
-  CheckDefFailure(['let s:var: number'], 'E1101:')
+  CheckDefFailure(['var s:var = 123'], 'E1101:')
+  CheckDefFailure(['var s:var: number'], 'E1101:')
 
   lines =<< trim END
     vim9script
@@ -217,20 +218,20 @@ def Test_assignment()
 
   # this should not leak
   if 0
-    let text =<< trim END
+    var text =<< trim END
       some text
     END
   endif
 enddef
 
 def Test_extend_list()
-  let lines =<< trim END
+  var lines =<< trim END
       vim9script
-      let l: list<number>
+      var l: list<number>
       l += [123]
       assert_equal([123], l)
 
-      let d: dict<number>
+      var d: dict<number>
       d['one'] = 1
       assert_equal(#{one: 1}, d)
   END
@@ -239,41 +240,41 @@ enddef
 
 def Test_single_letter_vars()
   # single letter variables
-  let a: number = 123
+  var a: number = 123
   a = 123
   assert_equal(123, a)
-  let b: number
+  var b: number
   b = 123
   assert_equal(123, b)
-  let g: number
+  var g: number
   g = 123
   assert_equal(123, g)
-  let s: number
+  var s: number
   s = 123
   assert_equal(123, s)
-  let t: number
+  var t: number
   t = 123
   assert_equal(123, t)
-  let v: number
+  var v: number
   v = 123
   assert_equal(123, v)
-  let w: number
+  var w: number
   w = 123
   assert_equal(123, w)
 enddef
 
 def Test_vim9_single_char_vars()
-  let lines =<< trim END
+  var lines =<< trim END
       vim9script
 
       # single character variable declarations work
-      let a: string
-      let b: number
-      let l: list<any>
-      let s: string
-      let t: number
-      let v: number
-      let w: number
+      var a: string
+      var b: number
+      var l: list<any>
+      var s: string
+      var t: number
+      var v: number
+      var w: number
 
       # script-local variables can be used without s: prefix
       a = 'script-a'
@@ -298,14 +299,14 @@ def Test_vim9_single_char_vars()
 enddef
 
 def Test_assignment_list()
-  let list1: list<bool> = [false, true, false]
-  let list2: list<number> = [1, 2, 3]
-  let list3: list<string> = ['sdf', 'asdf']
-  let list4: list<any> = ['yes', true, 1234]
-  let list5: list<blob> = [0z01, 0z02]
+  var list1: list<bool> = [false, true, false]
+  var list2: list<number> = [1, 2, 3]
+  var list3: list<string> = ['sdf', 'asdf']
+  var list4: list<any> = ['yes', true, 1234]
+  var list5: list<blob> = [0z01, 0z02]
 
-  let listS: list<string> = []
-  let listN: list<number> = []
+  var listS: list<string> = []
+  var listN: list<number> = []
 
   assert_equal([1, 2, 3], list2)
   list2[-1] = 99
@@ -320,19 +321,19 @@ def Test_assignment_list()
   list3 += ['end']
   assert_equal(['sdf', 'asdf', 'end'], list3)
 
-  CheckDefExecFailure(['let ll = [1, 2, 3]', 'll[-4] = 6'], 'E684:')
-  CheckDefExecFailure(['let [v1, v2] = [1, 2]'], 'E1092:')
+  CheckDefExecFailure(['var ll = [1, 2, 3]', 'll[-4] = 6'], 'E684:')
+  CheckDefExecFailure(['var [v1, v2] = [1, 2]'], 'E1092:')
 
   # 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
 
 def Test_assignment_list_vim9script()
-  let lines =<< trim END
+  var lines =<< trim END
     vim9script
-    let v1: number
-    let v2: number
-    let v3: number
+    var v1: number
+    var v2: number
+    var v3: number
     [v1, v2, v3] = [1, 2, 3]
     assert_equal([1, 2, 3], [v1, v2, v3])
   END
@@ -340,27 +341,27 @@ def Test_assignment_list_vim9script()
 enddef
 
 def Test_assignment_dict()
-  let dict1: dict<bool> = #{one: false, two: true}
-  let dict2: dict<number> = #{one: 1, two: 2}
-  let dict3: dict<string> = #{key: 'value'}
-  let dict4: dict<any> = #{one: 1, two: '2'}
-  let dict5: dict<blob> = #{one: 0z01, two: 0z02}
+  var dict1: dict<bool> = #{one: false, two: true}
+  var dict2: dict<number> = #{one: 1, two: 2}
+  var dict3: dict<string> = #{key: 'value'}
+  var dict4: dict<any> = #{one: 1, two: '2'}
+  var dict5: dict<blob> = #{one: 0z01, two: 0z02}
 
   # overwrite
   dict3['key'] = 'another'
 
   # empty key can be used
-  let dd = {}
+  var dd = {}
   dd[""] = 6
   assert_equal({'': 6}, dd)
 
   # 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
-  let lines =<< trim END
+  var lines =<< trim END
     vim9script
-    let test: dict<any> = {}
+    var test: dict<any> = {}
     def FillDict(): dict<any>
       test['a'] = 43
       return test
@@ -371,7 +372,7 @@ def Test_assignment_dict()
 
   lines =<< trim END
     vim9script
-    let test: dict<any>
+    var test: dict<any>
     def FillDict(): dict<any>
       test['a'] = 43
       return test
@@ -408,7 +409,7 @@ enddef
 def Test_assignment_local()
   # Test in a separated file in order not to the current buffer/window/tab is
   # changed.
-  let script_lines: list<string> =<< trim END
+  var script_lines: list<string> =<< trim END
     let b:existing = 'yes'
     let w:existing = 'yes'
     let t:existing = 'yes'
@@ -446,37 +447,37 @@ enddef
 def Test_assignment_default()
 
   # Test default values.
-  let thebool: bool
+  var thebool: bool
   assert_equal(v:false, thebool)
 
-  let thenumber: number
+  var thenumber: number
   assert_equal(0, thenumber)
 
   if has('float')
-    let thefloat: float
+    var thefloat: float
     assert_equal(0.0, thefloat)
   endif
 
-  let thestring: string
+  var thestring: string
   assert_equal('', thestring)
 
-  let theblob: blob
+  var theblob: blob
   assert_equal(0z, theblob)
 
-  let Thefunc: func
+  var Thefunc: func
   assert_equal(test_null_function(), Thefunc)
 
-  let thelist: list<any>
+  var thelist: list<any>
   assert_equal([], thelist)
 
-  let thedict: dict<any>
+  var thedict: dict<any>
   assert_equal({}, thedict)
 
   if has('channel')
-    let thejob: job
+    var thejob: job
     assert_equal(test_null_job(), thejob)
 
-    let thechannel: channel
+    var thechannel: channel
     assert_equal(test_null_channel(), thechannel)
 
     if has('unix') && executable('cat')
@@ -487,14 +488,14 @@ def Test_assignment_default()
     endif
   endif
 
-  let nr = 1234 | nr = 5678
+  var nr = 1234 | nr = 5678
   assert_equal(5678, nr)
 enddef
 
 def Test_assignment_var_list()
-  let v1: string
-  let v2: string
-  let vrem: list<string>
+  var v1: string
+  var v2: string
+  var vrem: list<string>
   [v1] = ['aaa']
   assert_equal('aaa', v1)
 
@@ -519,18 +520,18 @@ def Test_assignment_var_list()
 enddef
 
 def Test_assignment_vim9script()
-  let lines =<< trim END
+  var lines =<< trim END
     vim9script
     def Func(): list<number>
       return [1, 2]
     enddef
-    let var1: number
-    let var2: number
+    var var1: number
+    var var2: number
     [var1, var2] =
           Func()
     assert_equal(1, var1)
     assert_equal(2, var2)
-    let ll =
+    var ll =
           Func()
     assert_equal([1, 2], ll)
 
@@ -551,15 +552,15 @@ def Test_assignment_vim9script()
       assert_equal('plus', @+)
     endif
 
-    let a: number = 123
+    var a: number = 123
     assert_equal(123, a)
-    let s: string = 'yes'
+    var s: string = 'yes'
     assert_equal('yes', s)
-    let b: number = 42
+    var b: number = 42
     assert_equal(42, b)
-    let w: number = 43
+    var w: number = 43
     assert_equal(43, w)
-    let t: number = 44
+    var t: number = 44
     assert_equal(44, t)
   END
   CheckScriptSuccess(lines)
@@ -571,80 +572,80 @@ def Mess(): string
 enddef
 
 def Test_assignment_failure()
-  CheckDefFailure(['let var=234'], 'E1004:')
-  CheckDefFailure(['let var =234'], 'E1004:')
-  CheckDefFailure(['let var= 234'], 'E1004:')
+  CheckDefFailure(['var var=234'], 'E1004:')
+  CheckDefFailure(['var var =234'], 'E1004:')
+  CheckDefFailure(['var var= 234'], 'E1004:')
 
-  CheckScriptFailure(['vim9script', 'let var=234'], 'E1004:')
-  CheckScriptFailure(['vim9script', 'let var=234'], "before and after '='")
-  CheckScriptFailure(['vim9script', 'let var =234'], 'E1004:')
-  CheckScriptFailure(['vim9script', 'let var= 234'], 'E1004:')
-  CheckScriptFailure(['vim9script', 'let var = 234', 'var+=234'], 'E1004:')
-  CheckScriptFailure(['vim9script', 'let var = 234', 'var+=234'], "before and after '+='")
-  CheckScriptFailure(['vim9script', 'let var = "x"', 'var..="y"'], 'E1004:')
-  CheckScriptFailure(['vim9script', 'let var = "x"', 'var..="y"'], "before and after '..='")
+  CheckScriptFailure(['vim9script', 'var var=234'], 'E1004:')
+  CheckScriptFailure(['vim9script', 'var var=234'], "before and after '='")
+  CheckScriptFailure(['vim9script', 'var var =234'], 'E1004:')
+  CheckScriptFailure(['vim9script', 'var var= 234'], 'E1004:')
+  CheckScriptFailure(['vim9script', 'var var = 234', 'var+=234'], 'E1004:')
+  CheckScriptFailure(['vim9script', 'var var = 234', 'var+=234'], "before and after '+='")
+  CheckScriptFailure(['vim9script', 'var var = "x"', 'var..="y"'], 'E1004:')
+  CheckScriptFailure(['vim9script', 'var var = "x"', 'var..="y"'], "before and after '..='")
 
-  CheckDefFailure(['let true = 1'], 'E1034:')
-  CheckDefFailure(['let false = 1'], 'E1034:')
+  CheckDefFailure(['var true = 1'], 'E1034:')
+  CheckDefFailure(['var false = 1'], 'E1034:')
 
   CheckDefFailure(['[a; b; c] = g:list'], 'E452:')
-  CheckDefExecFailure(['let a: number',
+  CheckDefExecFailure(['var a: number',
                        '[a] = test_null_list()'], 'E1093:')
-  CheckDefExecFailure(['let a: number',
+  CheckDefExecFailure(['var a: number',
                        '[a] = []'], 'E1093:')
-  CheckDefExecFailure(['let x: number',
-                       'let y: number',
+  CheckDefExecFailure(['var x: number',
+                       'var y: number',
                        '[x, y] = [1]'], 'E1093:')
-  CheckDefExecFailure(['let x: number',
-                       'let y: number',
-                       'let z: list<number>',
+  CheckDefExecFailure(['var x: number',
+                       'var y: number',
+                       'var z: list<number>',
                        '[x, y; z] = [1]'], 'E1093:')
 
-  CheckDefFailure(['let somevar'], "E1022:")
-  CheckDefFailure(['let &tabstop = 4'], 'E1052:')
+  CheckDefFailure(['var somevar'], "E1022:")
+  CheckDefFailure(['var &tabstop = 4'], 'E1052:')
   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:')
-  CheckScriptFailure(['vim9script', 'let $ENV = "xxx"'], 'E1016:')
+  CheckDefFailure(['var $VAR = 5'], 'E1016: Cannot declare an environment variable:')
+  CheckScriptFailure(['vim9script', 'var $ENV = "xxx"'], 'E1016:')
 
   if has('dnd')
-    CheckDefFailure(['let @~ = 5'], 'E1066:')
+    CheckDefFailure(['var @~ = 5'], 'E1066:')
   else
-    CheckDefFailure(['let @~ = 5'], 'E354:')
+    CheckDefFailure(['var @~ = 5'], 'E354:')
     CheckDefFailure(['@~ = 5'], 'E354:')
   endif
-  CheckDefFailure(['let @a = 5'], 'E1066:')
-  CheckDefFailure(['let @/ = "x"'], 'E1066:')
-  CheckScriptFailure(['vim9script', 'let @a = "abc"'], 'E1066:')
+  CheckDefFailure(['var @a = 5'], 'E1066:')
+  CheckDefFailure(['var @/ = "x"'], 'E1066:')
+  CheckScriptFailure(['vim9script', 'var @a = "abc"'], 'E1066:')
 
-  CheckDefFailure(['let g:var = 5'], 'E1016: Cannot declare a global variable:')
-  CheckDefFailure(['let w:var = 5'], 'E1016: Cannot declare a window variable:')
-  CheckDefFailure(['let b:var = 5'], 'E1016: Cannot declare a buffer variable:')
-  CheckDefFailure(['let t:var = 5'], 'E1016: Cannot declare a tab variable:')
+  CheckDefFailure(['var g:var = 5'], 'E1016: Cannot declare a global variable:')
+  CheckDefFailure(['var w:var = 5'], 'E1016: Cannot declare a window variable:')
+  CheckDefFailure(['var b:var = 5'], 'E1016: Cannot declare a buffer variable:')
+  CheckDefFailure(['var t:var = 5'], 'E1016: Cannot declare a tab variable:')
 
-  CheckDefFailure(['let anr = 4', 'anr ..= "text"'], 'E1019:')
-  CheckDefFailure(['let xnr += 4'], 'E1020:', 1)
-  CheckScriptFailure(['vim9script', 'let xnr += 4'], 'E1020:')
-  CheckDefFailure(["let xnr = xnr + 1"], 'E1001:', 1)
-  CheckScriptFailure(['vim9script', 'let xnr = xnr + 4'], 'E121:')
+  CheckDefFailure(['var anr = 4', 'anr ..= "text"'], 'E1019:')
+  CheckDefFailure(['var xnr += 4'], 'E1020:', 1)
+  CheckScriptFailure(['vim9script', 'var xnr += 4'], 'E1020:')
+  CheckDefFailure(["var xnr = xnr + 1"], 'E1001:', 1)
+  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(['let var: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
+  CheckDefFailure(['var var: list<string> = [123]'], 'expected list<string> but got list<number>')
+  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(['let var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>')
+  CheckDefFailure(['var var: dict<string> = #{key: 123}'], 'expected dict<string> but got dict<number>')
+  CheckDefFailure(['var var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>')
 
-  CheckDefFailure(['let var = feedkeys("0")'], 'E1031:')
-  CheckDefFailure(['let var: number = feedkeys("0")'], 'expected number but got void')
+  CheckDefFailure(['var var = feedkeys("0")'], 'E1031:')
+  CheckDefFailure(['var var: number = feedkeys("0")'], 'expected number but got void')
 
-  CheckDefFailure(['let var: dict <number>'], 'E1068:')
-  CheckDefFailure(['let var: dict<number'], 'E1009:')
+  CheckDefFailure(['var var: dict <number>'], 'E1068:')
+  CheckDefFailure(['var var: dict<number'], 'E1009:')
 
   assert_fails('s/^/\=Mess()/n', 'E794:')
-  CheckDefFailure(['let var: dict<number'], 'E1009:')
+  CheckDefFailure(['var var: dict<number'], 'E1009:')
 
   CheckDefFailure(['w:foo: number = 10'],
                   'E488: Trailing characters: : number = 1')
@@ -657,7 +658,7 @@ def Test_assignment_failure()
 enddef
 
 def Test_assign_list()
-  let l: list<string> = []
+  var l: list<string> = []
   l[0] = 'value'
   assert_equal('value', l[0])
 
@@ -667,7 +668,7 @@ def Test_assign_list()
   assert_equal('asdf', l[-1])
   assert_equal('value', l[-2])
 
-  let nrl: list<number> = []
+  var nrl: list<number> = []
   for i in range(5)
     nrl[i] = i
   endfor
@@ -675,7 +676,7 @@ def Test_assign_list()
 enddef
 
 def Test_assign_dict()
-  let d: dict<string> = {}
+  var d: dict<string> = {}
   d['key'] = 'value'
   assert_equal('value', d['key'])
 
@@ -683,7 +684,7 @@ def Test_assign_dict()
   assert_equal('qwerty', d[123])
   assert_equal('qwerty', d['123'])
 
-  let nrd: dict<number> = {}
+  var nrd: dict<number> = {}
   for i in range(3)
     nrd[i] = i
   endfor
@@ -691,12 +692,12 @@ def Test_assign_dict()
 enddef
 
 def Test_assign_dict_unknown_type()
-  let lines =<< trim END
+  var lines =<< trim END
       vim9script
-      let mylist = []
+      var mylist = []
       mylist += [#{one: 'one'}]
       def Func()
-        let dd = mylist[0]
+        var dd = mylist[0]
         assert_equal('one', dd.one)
       enddef
       Func()
@@ -706,10 +707,10 @@ def Test_assign_dict_unknown_type()
   # doesn't work yet
   #lines =<< trim END
   #    vim9script
-  #    let mylist = [[]]
+  #    var mylist = [[]]
   #    mylist[0] += [#{one: 'one'}]
   #    def Func()
-  #      let dd = mylist[0][0]
+  #      var dd = mylist[0][0]
   #      assert_equal('one', dd.one)
   #    enddef
   #    Func()
@@ -719,13 +720,13 @@ enddef
 
 def Test_assign_lambda()
   # check if assign a lambda to a variable which type is func or any.
-  let lines =<< trim END
+  var lines =<< trim END
       vim9script
-      let FuncRef = {->123}
+      var FuncRef = {->123}
       assert_equal(123, FuncRef())
-      let FuncRef_Func: func = {->123}
+      var FuncRef_Func: func = {->123}
       assert_equal(123, FuncRef_Func())
-      let FuncRef_Any: any = {->123}
+      var FuncRef_Any: any = {->123}
       assert_equal(123, FuncRef_Any())
   END
   CheckScriptSuccess(lines)
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -193,21 +193,21 @@ def Test_wrong_type()
 enddef
 
 def Test_const()
-  CheckDefFailure(['const var = 234', 'var = 99'], 'E1018:')
-  CheckDefFailure(['const one = 234', 'let one = 99'], 'E1017:')
-  CheckDefFailure(['const list = [1, 2]', 'let list = [3, 4]'], 'E1017:')
-  CheckDefFailure(['const two'], 'E1021:')
-  CheckDefFailure(['const &option'], 'E996:')
+  CheckDefFailure(['final var = 234', 'var = 99'], 'E1018:')
+  CheckDefFailure(['final one = 234', 'let one = 99'], 'E1017:')
+  CheckDefFailure(['final list = [1, 2]', 'let list = [3, 4]'], 'E1017:')
+  CheckDefFailure(['final two'], 'E1125:')
+  CheckDefFailure(['final &option'], 'E996:')
 
   let lines =<< trim END
-    const list = [1, 2, 3]
+    final list = [1, 2, 3]
     list[0] = 4
     list->assert_equal([4, 2, 3])
-    const! other = [5, 6, 7]
+    const other = [5, 6, 7]
     other->assert_equal([5, 6, 7])
 
     let varlist = [7, 8]
-    const! constlist = [1, varlist, 3]
+    const constlist = [1, varlist, 3]
     varlist[0] = 77
     # TODO: does not work yet
     # constlist[1][1] = 88
@@ -216,7 +216,7 @@ def Test_const()
     constlist->assert_equal([1, [77, 88], 3])
 
     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
     # TODO: does not work yet
     # constdict['two']['six'] = 66
@@ -229,35 +229,35 @@ enddef
 
 def Test_const_bang()
   let lines =<< trim END
-      const! var = 234
+      const var = 234
       var = 99
   END
   CheckDefExecFailure(lines, 'E1018:', 2)
   CheckScriptFailure(['vim9script'] + lines, 'E46:', 3)
 
   lines =<< trim END
-      const! ll = [2, 3, 4]
+      const ll = [2, 3, 4]
       ll[0] = 99
   END
   CheckDefExecFailure(lines, 'E1119:', 2)
   CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
 
   lines =<< trim END
-      const! ll = [2, 3, 4]
+      const ll = [2, 3, 4]
       ll[3] = 99
   END
   CheckDefExecFailure(lines, 'E1118:', 2)
   CheckScriptFailure(['vim9script'] + lines, 'E684:', 3)
 
   lines =<< trim END
-      const! dd = #{one: 1, two: 2}
+      const dd = #{one: 1, two: 2}
       dd["one"] = 99
   END
   CheckDefExecFailure(lines, 'E1121:', 2)
   CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
 
   lines =<< trim END
-      const! dd = #{one: 1, two: 2}
+      const dd = #{one: 1, two: 2}
       dd["three"] = 99
   END
   CheckDefExecFailure(lines, 'E1120:')
@@ -2532,6 +2532,12 @@ enddef
 def Test_let_declaration_fails()
   let lines =<< trim END
     vim9script
+    final var: string
+  END
+  CheckScriptFailure(lines, 'E1125:')
+
+  lines =<< trim END
+    vim9script
     const var: string
   END
   CheckScriptFailure(lines, 'E1021:')
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1744,
+/**/
     1743,
 /**/
     1742,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2135,9 +2135,9 @@ typedef enum {
 } estack_arg_T;
 
 // Flags for assignment functions.
-#define LET_IS_CONST	1   // ":const"
-#define LET_FORCEIT	2   // ":const!" (LET_IS_CONST is also set)
-#define LET_NO_COMMAND	4   // "var = expr" without ":let" or ":const"
+#define ASSIGN_FINAL	1   // ":final"
+#define ASSIGN_CONST	2   // ":const"
+#define ASSIGN_NO_DECL	4   // "name = expr" without ":let" or ":const"
 
 #include "ex_cmds.h"	    // Ex command defines
 #include "spell.h"	    // spell checking stuff
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -4562,8 +4562,12 @@ vim9_declare_error(char_u *name)
 
 /*
  * Compile declaration and assignment:
- * "let var", "let var = expr", "const var = expr" and "var = expr"
- * "arg" points to "var".
+ * "let name"
+ * "var name = expr"
+ * "final name = expr"
+ * "const name = expr"
+ * "name = expr"
+ * "arg" points to "name".
  * Return NULL for an error.
  * Return "arg" if it does not look like a variable list.
  */
@@ -4588,7 +4592,8 @@ compile_assignment(char_u *arg, exarg_T 
     type_T	*member_type = &t_any;
     char_u	*name = NULL;
     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 "=".
     p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
@@ -4729,7 +4734,7 @@ compile_assignment(char_u *arg, exarg_T 
 		long	    numval;
 
 		dest = dest_option;
-		if (cmdidx == CMD_const)
+		if (cmdidx == CMD_final || cmdidx == CMD_const)
 		{
 		    emsg(_(e_const_option));
 		    goto theend;
@@ -4968,7 +4973,7 @@ compile_assignment(char_u *arg, exarg_T 
 					    && var_wrong_func_name(name, TRUE))
 		goto theend;
 	    lvar = reserve_local(cctx, var_start, varlen,
-						    cmdidx == CMD_const, type);
+			     cmdidx == CMD_final || cmdidx == CMD_const, type);
 	    if (lvar == NULL)
 		goto theend;
 	    new_local = TRUE;
@@ -5119,6 +5124,11 @@ compile_assignment(char_u *arg, exarg_T 
 							  cctx, FALSE) == FAIL)
 		    goto theend;
 	    }
+	    else if (cmdidx == CMD_final)
+	    {
+		emsg(_(e_final_requires_a_value));
+		goto theend;
+	    }
 	    else if (cmdidx == CMD_const)
 	    {
 		emsg(_(e_const_requires_a_value));
@@ -5283,9 +5293,9 @@ compile_assignment(char_u *arg, exarg_T 
 	}
 	else
 	{
-	    if (is_decl && eap->forceit && cmdidx == CMD_const
-		    && (dest == dest_script || dest == dest_local))
-		// ":const! var": lock the value, but not referenced variables
+	    if (is_decl && cmdidx == CMD_const
+				&& (dest == dest_script || dest == dest_local))
+		// ":const var": lock the value, but not referenced variables
 		generate_LOCKCONST(cctx);
 
 	    switch (dest)
@@ -6915,7 +6925,7 @@ compile_def_function(ufunc_T *ufunc, int
 	    // Expression or function call.
 	    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");
 		goto erret;
 	    }
@@ -6967,6 +6977,8 @@ compile_def_function(ufunc_T *ufunc, int
 		    break;
 
 	    case CMD_let:
+	    case CMD_var:
+	    case CMD_final:
 	    case CMD_const:
 		    line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
 		    if (line == p)
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -729,7 +729,7 @@ store_var(char_u *name, typval_T *tv)
     funccal_entry_T 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();
 }
 
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -97,6 +97,8 @@ ex_export(exarg_T *eap)
     switch (eap->cmdidx)
     {
 	case CMD_let:
+	case CMD_var:
+	case CMD_final:
 	case CMD_const:
 	case CMD_def:
 	// case CMD_class:
@@ -508,9 +510,12 @@ vim9_declare_scriptvar(exarg_T *eap, cha
     int		    called_emsg_before = called_emsg;
     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);
     }