changeset 19181:94eda51ba9ba v8.2.0149

patch 8.2.0149: maintaining a Vim9 branch separately is more work Commit: https://github.com/vim/vim/commit/8a7d6542b33e5d2b352262305c3bfdb2d14e1cf8 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jan 26 15:56:19 2020 +0100 patch 8.2.0149: maintaining a Vim9 branch separately is more work Problem: Maintaining a Vim9 branch separately is more work. Solution: Merge the Vim9 script changes.
author Bram Moolenaar <Bram@vim.org>
date Sun, 26 Jan 2020 16:00:05 +0100
parents 8edf0aeb71b9
children 418299e3f4a7
files README.md README_VIM9.md runtime/doc/Makefile runtime/doc/eval.txt runtime/doc/options.txt runtime/doc/vim9.txt runtime/ftplugin/vim.vim runtime/indent/vim.vim runtime/syntax/vim.vim src/Make_cyg_ming.mak src/Make_mvc.mak src/Makefile src/blob.c src/channel.c src/dict.c src/eval.c src/evalbuffer.c src/evalfunc.c src/evalvars.c src/ex_cmdidxs.h src/ex_cmds.h src/ex_docmd.c src/ex_eval.c src/filepath.c src/globals.h src/gui.c src/if_lua.c src/if_py_both.h src/insexpand.c src/json.c src/list.c src/macros.h src/main.c src/message.c src/misc1.c src/proto.h src/proto/blob.pro src/proto/eval.pro src/proto/evalfunc.pro src/proto/evalvars.pro src/proto/ex_docmd.pro src/proto/ex_eval.pro src/proto/list.pro src/proto/message.pro src/proto/scriptfile.pro src/proto/userfunc.pro src/proto/vim9compile.pro src/proto/vim9execute.pro src/proto/vim9script.pro src/scriptfile.c src/session.c src/structs.h src/syntax.c src/testdir/Make_all.mak src/testdir/test_vim9_expr.vim src/testdir/test_vim9_script.vim src/testing.c src/userfunc.c src/version.c src/vim.h src/vim9.h src/vim9compile.c src/vim9execute.c src/vim9script.c src/viminfo.c
diffstat 65 files changed, 11814 insertions(+), 1364 deletions(-) [+]
line wrap: on
line diff
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ flavours of UNIX.  Porting to other syst
 Older versions of Vim run on MS-DOS, MS-Windows 95/98/Me/NT/2000, Amiga DOS,
 Atari MiNT, BeOS, RISC OS and OS/2.  These are no longer maintained.
 
+For Vim9 script see [README_VIM9](README_VIM9.md).
+
 ## Distribution ##
 
 You can often use your favorite package manager to install Vim.  On Mac and
new file mode 100644
--- /dev/null
+++ b/README_VIM9.md
@@ -0,0 +1,344 @@
+![Vim Logo](https://github.com/vim/vim/blob/master/runtime/vimlogo.gif)
+
+# What is Vim9?
+
+This is an experimental side of [Vim](https://github.com/vim/vim).
+It explores ways of making Vim script faster and better.
+
+WARNING: The Vim9 script features are in the early stages of development,
+anything can break!
+
+# Why Vim9?
+
+## 1. FASTER VIM SCRIPT
+
+The third item on the poll results of 2018, after popup windows and text
+properties, is faster Vim script.  So how do we do that?
+
+I have been throwing some ideas around, and soon came to the conclusion
+that the current way functions are called and executed, with
+dictionaries for the arguments and local variables, is never going to be
+very fast.  We're lucky if we can make it twice as fast.  The overhead
+of a function call and executing every line is just too high.
+
+So what then?  We can only make something fast by having a new way of
+defining a function, with similar but different properties of the old
+way:
+* Arguments are only available by name, not through the a: dictionary or
+  the a:000 list.
+* Local variables are not available in an l: dictionary.
+* A few more things that slow us down, such as exception handling details.
+
+I Implemented a "proof of concept" and measured the time to run a simple
+for loop with an addition (Justin used this example in his presentation,
+full code is below):
+
+``` vim
+  let sum = 0
+  for i in range(1, 2999999)
+    let sum += i
+  endfor
+```
+
+| how     | time in sec |
+| --------| -------- |
+| Vim old | 5.018541 |
+| Python  | 0.369598 |
+| Lua     | 0.078817 |
+| Vim new | 0.073595 |
+
+That looks very promising!  It's just one example, but it shows how much
+we can gain, and also that Vim script can be faster than builtin
+interfaces.
+
+In practice the script would not do something useless as counting but change
+the text.  For example, re-indent all the lines:
+
+``` vim
+  let totallen = 0
+  for i in range(1, 100000)
+    call setline(i, '    ' .. getline(i))
+    let totallen += len(getline(i))
+  endfor
+```
+
+| how     | time in sec |
+| --------| -------- |
+| Vim old | 0.853752 |
+| Python  | 0.304584 |
+| Lua     | 0.286573 |
+| Vim new | 0.190276 |
+
+The differences are smaller, but Vim 9 script is clearly the fastest.
+
+How does Vim9 script work?  The function is first compiled into a sequence of
+instructions.  Each instruction has one or two parameters and a stack is
+used to store intermediate results.  Local variables are also on the
+stack, space is reserved during compilation.  This is a fairly normal
+way of compilation into an intermediate format, specialized for Vim,
+e.g. each stack item is a typeval_T.  And one of the instructions is
+"execute Ex command", for commands that are not compiled.
+
+
+## 2. PHASING OUT INTERFACES
+
+Attempts have been made to implement functionality with built-in script
+languages such as Python, Perl, Lua, Tcl and Ruby.  This never gained much
+foothold, for various reasons.
+
+Instead of using script language support in Vim:
+* Encourage implementing external tools in any language and communicate
+  with them.  The job and channel support already makes this possible.
+  Really any language can be used, also Java and Go, which are not
+  available built-in.
+* Phase out the built-in language interfaces, make maintenance a bit easier
+  and executables easier to build.  They will be kept for backwards
+  compatibility, no new features.
+* Improve the Vim script language, it is used to communicate with the external
+  tool and implements the Vim side of the interface.  Also, it can be used when
+  an external tool is undesired.
+
+All together this creates a clear situation: Vim with the +eval feature
+will be sufficient for most plugins, while some plugins require
+installing a tool that can be written in any language.  No confusion
+about having Vim but the plugin not working because some specific
+language is missing.  This is a good long term goal.
+
+Rationale: Why is it better to run a tool separately from Vim than using a
+built-in interface and interpreter?  Take for example something that is
+written in Python:
+* The built-in interface uses the embedded python interpreter.  This is less
+  well maintained than the python command.  Building Vim with it requires
+  installing developer packages.  If loaded dynamically there can be a version
+  mismatch.
+* When running the tool externally the standard python command can be used,
+  which is quite often available by default or can be easily installed.
+* The built-in interface has an API that is unique for Vim with Python. This is
+  an extra API to learn.
+* A .py file can be compiled into a .pyc file and execute much faster.
+* Inside Vim multi-threading can cause problems, since the Vim core is single
+  threaded.  In an external tool there are no such problems.
+* The Vim part is written in .vim files, the Python part is in .py files, this
+  is nicely separated.
+* Disadvantage: An interface needs to be made between Vim and Python.
+  JSON is available for this, and it's fairly easy to use.  But it still
+  requires implementing asynchronous communication.
+
+
+## 3. BETTER VIM SCRIPT
+
+To make Vim faster a new way of defining a function needs to be added.
+While we are doing that, since the lines in this function won't be fully
+backwards compatible anyway, we can also make Vim script easier to use.
+In other words: "less weird".  Making it work more like modern
+programming languages will help.  No surprises.
+
+A good example is how in a function the arguments are prefixed with
+"a:". No other language I know does that, so let's drop it.
+
+Taking this one step further is also dropping "s:" for script-local variables;
+everything at the script level is script-local by default.  Since this is not
+backwards compatible it requires a new script style: Vim9 script!
+
+It should be possible to convert code from other languages to Vim
+script.  We can add functionality to make this easier.  This still needs
+to be discussed, but we can consider adding type checking and a simple
+form of classes.  If you look at JavaScript for example, it has gone
+through these stages over time, adding real class support and now
+TypeScript adds type checking.  But we'll have to see how much of that
+we actually want to include in Vim script.  Ideally a conversion tool
+can take Python, JavaScript or TypeScript code and convert it to Vim
+script, with only some things that cannot be converted.
+
+Vim script won't work the same as any specific language, but we can use
+mechanisms that are commonly known, ideally with the same syntax.  One
+thing I have been thinking of is assignments without ":let".  I often
+make that mistake (after writing JavaScript especially).  I think it is
+possible, if we make local variables shadow commands.  That should be OK,
+if you shadow a command you want to use, just rename the variable.
+Using "let" and "const" to declare a variable, like in JavaScript and
+TypeScript, can work:
+
+
+``` vim
+def MyFunction(arg: number): number
+   let local = 1
+   let todo = arg
+   const ADD = 88
+   while todo > 0
+      local += ADD
+      --todo
+   endwhile
+   return local
+enddef
+```
+
+The similarity with JavaScript/TypeScript can also be used for dependencies
+between files.  Vim currently uses the `:source` command, which has several
+disadvantages:
+*   In the sourced script, is not clear what it provides.  By default all
+    functions are global and can be used elsewhere.
+*   In a script that sources other scripts, it is not clear what function comes
+    from what sourced script.  Finding the implementation is a hassle.
+*   Prevention of loading the whole script twice must be manually implemented.
+
+We can use the `:import` and `:export` commands from the JavaScript standard to
+make this much better.  For example, in script "myfunction.vim" define a
+function and export it:
+
+``` vim
+vim9script  " Vim9 script syntax used here
+
+let local = 'local variable is not exported, script-local'
+
+export def MyFunction()  " exported function
+...
+
+def LocalFunction() " not exported, script-local
+...
+```
+
+And in another script import the function:
+
+``` vim
+vim9script  " Vim9 script syntax used here
+
+import MyFunction from 'myfunction.vim'
+```
+
+This looks like JavaScript/TypeScript, thus many users will understand the
+syntax.
+
+These are ideas, this will take time to design, discuss and implement.
+Eventually this will lead to Vim 9!
+
+
+## Code for sum time measurements
+
+Vim was build with -O2.
+
+``` vim
+func VimOld()
+  let sum = 0
+  for i in range(1, 2999999)
+    let sum += i
+  endfor
+  return sum
+endfunc
+
+func Python()
+  py3 << END
+sum = 0
+for i in range(1, 3000000):
+  sum += i
+END
+  return py3eval('sum')
+endfunc
+
+func Lua()
+  lua << END
+    sum = 0
+    for i = 1, 2999999 do
+      sum = sum + i
+    end
+END
+  return luaeval('sum')
+endfunc
+
+def VimNew()
+  let sum = 0
+  for i in range(1, 2999999)
+    let sum += i
+  endfor
+  return sum
+enddef
+
+let start = reltime()
+echo VimOld()
+echo 'Vim old: ' .. reltimestr(reltime(start))
+
+let start = reltime()
+echo Python()
+echo 'Python: ' .. reltimestr(reltime(start))
+
+let start = reltime()
+echo Lua()
+echo 'Lua: ' .. reltimestr(reltime(start))
+
+let start = reltime()
+echo VimNew()
+echo 'Vim new: ' .. reltimestr(reltime(start))
+```
+
+## Code for indent time measurements
+
+``` vim
+def VimNew(): number
+  let totallen = 0
+  for i in range(1, 100000)
+    setline(i, '    ' .. getline(i))
+    totallen += len(getline(i))
+  endfor
+  return totallen
+enddef
+
+func VimOld()
+  let totallen = 0
+  for i in range(1, 100000)
+    call setline(i, '    ' .. getline(i))
+    let totallen += len(getline(i))
+  endfor
+  return totallen
+endfunc
+
+func Lua()
+  lua << END
+    b = vim.buffer()
+    totallen = 0
+    for i = 1, 100000 do
+      b[i] = "    " .. b[i]
+      totallen = totallen + string.len(b[i])
+    end
+END
+  return luaeval('totallen')
+endfunc
+
+func Python()
+  py3 << END
+cb = vim.current.buffer
+totallen = 0
+for i in range(0, 100000):
+  cb[i] = '    ' + cb[i]
+  totallen += len(cb[i])
+END
+  return py3eval('totallen')
+endfunc
+
+new
+call setline(1, range(100000))
+let start = reltime()
+echo VimOld()
+echo 'Vim old: ' .. reltimestr(reltime(start))
+bwipe!
+
+new
+call setline(1, range(100000))
+let start = reltime()
+echo Python()
+echo 'Python: ' .. reltimestr(reltime(start))
+bwipe!
+ 
+new
+call setline(1, range(100000))
+let start = reltime()
+echo Lua()
+echo 'Lua: ' .. reltimestr(reltime(start))
+bwipe!
+
+new
+call setline(1, range(100000))
+let start = reltime()
+echo VimNew()
+echo 'Vim new: ' .. reltimestr(reltime(start))
+bwipe!
+```
--- a/runtime/doc/Makefile
+++ b/runtime/doc/Makefile
@@ -149,6 +149,7 @@ DOCS = \
 	version7.txt \
 	version8.txt \
 	vi_diff.txt \
+	vim9.txt \
 	visual.txt \
 	windows.txt \
 	workshop.txt
@@ -289,6 +290,7 @@ HTMLS = \
 	version8.html \
 	vi_diff.html \
 	vimindex.html \
+	vim9.html \
 	visual.html \
 	windows.html \
 	workshop.html
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -12,6 +12,10 @@ Note: Expression evaluation can be disab
 done, the features in this document are not available.  See |+eval| and
 |no-eval-feature|.
 
+This file is about the backwards compatible Vim script.  For Vim9 script,
+which executes much faster, supports type checking and much more, see
+|vim9.txt|.
+
 1.  Variables			|variables|
     1.1 Variable types
     1.2 Function references		|Funcref|
@@ -2512,8 +2516,8 @@ haslocaldir([{winnr} [, {tabnr}]])
 					or |:tcd|
 hasmapto({what} [, {mode} [, {abbr}]])
 				Number	|TRUE| if mapping to {what} exists
-histadd({history}, {item})	String	add an item to a history
-histdel({history} [, {item}])	String	remove an item from a history
+histadd({history}, {item})	Number	add an item to a history
+histdel({history} [, {item}])	Number	remove an item from a history
 histget({history} [, {index}])	String	get the item {index} from a history
 histnr({history})		Number	highest index of a history
 hlexists({name})		Number	|TRUE| if highlight group {name} exists
@@ -10894,6 +10898,9 @@ New functions can be defined.  These can
 functions.  The function executes a sequence of Ex commands.  Normal mode
 commands can be executed with the |:normal| command.
 
+This section is about the legacy functions. For the Vim9 functions, which
+execute much faster, support type checking and more, see |vim9.txt|.
+
 The function name must start with an uppercase letter, to avoid confusion with
 builtin functions.  To prevent from using the same name in different scripts
 avoid obvious, short names.  A good habit is to start the function name with
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6221,6 +6221,7 @@ A jump table for the options with a shor
 	  compiler/	compiler files |:compiler|
 	  doc/		documentation |write-local-help|
 	  ftplugin/	filetype plugins |write-filetype-plugin|
+	  import/	files that are found by `:import`
 	  indent/	indent scripts |indent-expression|
 	  keymap/	key mapping files |mbyte-keymap|
 	  lang/		menu translations |:menutrans|
new file mode 100644
--- /dev/null
+++ b/runtime/doc/vim9.txt
@@ -0,0 +1,561 @@
+*vim9.txt*	For Vim version 8.2.  Last change: 2019 Dec 06
+
+
+		  VIM REFERENCE MANUAL	  by Bram Moolenaar
+
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+Vim9 script commands and expressions.
+
+Most expression help is in |eval.txt|.  This file is about the new syntax and
+features in Vim9 script.
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+
+1   What is Vim9 script?		|vim9-script|
+2.  Differences				|vim9-differences|
+3.  New style functions			|fast-functions|
+4.  Types				|vim9-types|
+5.  Namespace, Import and Export	|vim9script|
+
+9.  Rationale				|vim9-rationale|
+
+==============================================================================
+
+1. What is Vim9 script?					*vim9-script*
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+Vim script has been growing over time, while keeping backwards compatibility.
+That means bad choices from the past often can't be changed.  Execution is
+quite slow, every line is parsed every time it is executed.
+
+The main goal of Vim9 script is to drastically improve performance.  An
+increase in execution speed of 10 to 100 times can be expected.  A secondary
+goal is to avoid Vim-specific constructs and get closer to commonly used
+programming languages, such as JavaScript, TypeScript and Java.
+
+The performance improvements can only be achieved by not being 100% backwards
+compatible.  For example, in a function the arguments are not available in the
+"a:" dictionary, as creating that dictionary adds quite a lot of overhead.
+Other differences are more subtle, such as how errors are handled.
+
+The Vim9 script syntax and semantics are used in:
+- a function defined with the `:def` command
+- a script file where the first command is `vim9script`
+
+When using `:function` in a Vim9 script file the legacy syntax is used.
+However, this is discouraged.
+
+Vim9 script and legacy Vim script can be mixed.  There is no need to rewrite
+old scripts, they keep working as before.
+
+==============================================================================
+
+2. Differences from legacy Vim script			*vim9-differences*
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+Vim9 functions ~
+
+`:def` has no extra arguments like `:function` does: "range", "abort", "dict"
+or "closure".  A `:def` function always aborts on an error, does not get a
+range passed and cannot be a "dict" function.
+
+In the function body:
+- Arguments are accessed by name, without "a:".
+- There is no "a:" dictionary or "a:000" list.  Variable arguments are defined
+  with a name and have a list type: >
+  	def MyFunc(...itemlist: list<type>)
+	   for item in itemlist
+	     ...
+
+
+Variable declarations with :let and :const ~
+
+Local variables need to be declared with `:let`.  Local constants need to be
+declared with `:const`.  We refer to both as "variables".
+
+Variables can be local to a script, function or code block: >
+	vim9script
+	let script_var = 123
+	def SomeFunc()
+	  let func_var = script_var
+	  if cond
+	    let 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
+	else
+	   let inner = 0
+	endif
+	echo inner  " Error!
+
+The declaration must be done earlier: >
+	let inner: number
+	if cond
+	   inner = 5
+	else
+	   inner = 0
+	endif
+	echo inner
+
+To intentionally use a variable that won't be available later, a block can be
+used: >
+	{
+	   let temp = 'temp'
+	   ...
+	}
+	echo temp  " Error!
+
+An existing variable cannot be assigend to with `:let`, since that implies a
+declaration.  An exception is global variables: these can be both used with
+and without `:let`, because there is no rule about where they are declared.
+
+Variables cannot shadow previously defined variables.
+Variables may shadow Ex commands, rename the variable if needed.
+
+Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be
+used to repeat a `:substitute` command.
+
+
+Omitting :call and :eval ~
+
+Functions can be called without `:call`: >
+  	writefile(lines, 'file')
+Using `:call` is still posible, but this is discouraged.
+
+A method call without `eval` is possible, so long as the start is an
+identifier or can't be an Ex command.  It does not work for string constants: >
+	myList->add(123)		" works
+	g:myList->add(123)		" works
+	[1, 2, 3]->Process()		" works
+	#{a: 1, b: 2}->Process()	" works
+	{'a': 1, 'b': 2}->Process()	" works
+	"foobar"->Process()		" does NOT work
+	eval "foobar"->Process()	" works
+
+
+No curly braces expansion ~
+
+|curly-braces-names| cannot be used.
+
+
+Comperators ~
+
+The 'ignorecase' option is not used for comperators that use strings.
+
+
+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!
+There must be white space before and after the "=": >
+	let var = 234	" OK
+
+White space is required around most operators.
+
+White space is not allowed:
+- Between a function name and the "(": >
+  	call Func (arg)	   " Error!
+  	call Func
+	     \ (arg)	   " Error!
+  	call Func(arg)	   " OK
+  	call Func(
+	     \ arg)	   " OK
+
+
+Conditions and expressions ~
+
+Conditions and expression 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.
+
+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
+few exceptions.
+
+	type		TRUE when ~
+	bool		v:true
+	number		non-zero
+	float		non-zero
+	string		non-empty
+	blob		non-empty
+	list		non-empty (different from JavaScript)
+	dictionary	non-empty (different from JavaScript)
+	funcref		when not NULL
+	partial		when not NULL
+	special		v:true
+	job		when not NULL
+	channel		when not NULL
+	class		when not NULL
+	object		when not NULL (TODO: when isTrue() returns v:true)
+
+The boolean operators "||" and "&&" do not change the value: >
+	8 || 2   == 8
+	0 || 2   == 2
+	0 || ''  == ''
+	8 && 2   == 2
+	0 && 2   == 0
+	[] && 2  == []
+
+When using `..` for string concatenation the arguments are always converted to
+string. >
+	'hello ' .. 123  == 'hello 123'
+	'hello ' .. v:true  == 'hello true'
+
+In Vim9 script one can use "true" for v:true and "false" for v:false.
+
+
+==============================================================================
+
+3. New style functions					*fast-functions*
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+							*:def*
+:def[!] {name}([arguments])[: {return-type}
+			Define a new function by the name {name}.  The body of
+			the function follows in the next lines, until the
+			matching `:enddef`.
+
+			When {return-type} is omitted the return type will be
+			decided upon by the first encountered `return`
+			statement in the function.  E.g., for: >
+				return 'message'
+<			The return type will be "string".
+			
+			{arguments} is a sequence of zero or more argument
+			declarations.  There are three forms:
+				{name}: {type}
+				{name} = {value}
+				{name}: {type} = {value}
+			The first form is a mandatory argument, the caller
+			must always provide them.
+			The second and third form are optional arguments.
+			When the caller omits an argument the {value} is used.
+
+			[!] is used as with `:function`.
+
+							*:enddef*
+:enddef			End of a function defined with `:def`.
+
+
+==============================================================================
+
+4. Types					*vim9-types*
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+The following builtin types are supported:
+	bool
+	number
+	float
+	string
+	blob
+	list<type>
+	dict<type>
+	(a: type, b: type): type
+	job
+	channel
+
+Not supported yet:
+	tuple<a: type, b: type, ...>
+
+These types can be used in declarations, but no variable will have this type:
+	type|type
+	void
+	any
+
+There is no array type, use list<type> instead.  For a list constant an
+efficient implementation is used that avoids allocating lot of small pieces of
+memory.
+
+A function defined with `:def` must declare the return type.  If there is no
+type then the function doesn't return anything.  "void" is used in type
+declarations.
+
+Custom types can be defined with `:type`: >
+	:type MyList list<string>
+{not implemented yet}
+
+And classes and interfaces can be used as types: >
+	:class MyClass
+	:let mine: MyClass
+
+	:interface MyInterface
+	:let mine: MyInterface
+
+	:class MyTemplate<Targ>
+	:let mine: MyTemplate<number>
+	:let mine: MyTemplate<string>
+
+	:class MyInterface<Targ>
+	:let mine: MyInterface<number>
+	:let mine: MyInterface<string>
+{not implemented yet}
+
+
+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
+
+
+==============================================================================
+
+5.  Namespace, Import and Export
+					*vim9script* *vim9-export* *vim9-import*
+
+THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
+
+A Vim9 script can be written to be imported.  This means that everything in
+the script is local, unless exported.  Those exported items, and only those
+items, can then be imported in another script.
+
+
+Namespace ~
+							*:vim9script* *:vim9*
+To recognize an file that can be imported the `vim9script` statement must
+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
+starts with: >
+	vim9script
+	let 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.
+
+The variables at the file level are very much like the script-local "s:"
+variables in legacy Vim script, but the "s:" is omitted.
+
+In Vim9 script the global "g:" namespace can still be used as before.
+
+A side effect of `:vim9script` is that the 'cpoptions' option is set to the
+Vim default value, like with: >
+	:set cpo&vim
+One of the effects is that |line-continuation| is always enabled.
+The original value of 'cpoptions' is restored at the end of the script.
+
+
+Export ~
+							*:export* *:exp*
+Exporting one item can be written as: >
+	export const EXPORTED_CONST = 1234
+	export let someValue = ...
+	export def MyFunc() ...
+	export class MyClass ...
+
+As this suggests, only constants, variables, `:def` functions and classes can
+be exported.
+
+Alternatively, an export statement can be used to export several already
+defined (otherwise script-local) items: >
+	export {EXPORTED_CONST, someValue, MyFunc, MyClass}
+
+
+Import ~
+							*:import* *:imp*
+The exported items can be imported individually in another Vim9 script: >
+	import EXPORTED_CONST from "thatscript.vim"
+	import MyClass from "myclass.vim"
+
+To import multiple items at the same time: >
+	import {someValue, MyClass} from "thatscript.vim"
+
+In case the name is ambigiuous, another name can be specified: >
+	import MyClass as ThatClass from "myclass.vim"
+	import {someValue, MyClass as ThatClass} from "myclass.vim"
+
+To import all exported items under a specific identifier: >
+	import * as That from 'thatscript.vim'
+
+Then you can use "That.EXPORTED_CONST", "That.someValue", etc.  You are free
+to choose the name "That", but it is highly recommended to use the name of the
+script file to avoid confusion.
+
+The script name after `import` can be:
+- A relative path, starting "." or "..".  This finds a file relative to the
+  location of the script file itself.  This is useful to split up a large
+  plugin into several files.
+- An absolute path, starting with "/" on Unix or "D:/" on MS-Windows.  This
+  will be rarely used.
+- A path not being relative or absolute.  This will be found in the
+  "import" subdirectories of 'runtimepath' entries.  The name will usually be
+  longer and unique, to avoid loading the wrong file.
+
+Once a vim9 script file has been imported, the result is cached and used the
+next time the same script is imported.  It will not be read again.
+							*:import-cycle*
+The `import` commands are executed when encountered.  If that script (directly
+or indirectly) imports the current script, then items defined after the
+`import` won't be processed yet.  Therefore cyclic imports can exist, but may
+result in undefined items.
+
+
+Import in an autoload script ~
+
+For optimal startup speed, loading scripts should be postponed until they are
+actually needed.  A recommended mechamism:
+
+1. In the plugin define user commands, functions and/or mappings that refer to
+   an autoload script. >
+   	command -nargs=1 SearchForStuff call searchfor#Stuff(<f-args>)
+
+<   This goes in .../plugin/anyname.vim.  "anyname.vim" can be freely chosen.
+
+2. In the autocommand script do the actual work.  You can import items from
+   other files to split up functionality in appropriate pieces. >
+	vim9script
+        import FilterFunc from "../import/someother.vim"
+	def searchfor#Stuff(arg: string)
+	  let 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
+   Vim finds the file.
+
+3. Other functionality, possibly shared between plugins, contains the exported
+   items and any private items. >
+	vim9script
+	let localVar = 'local'
+   	export def FilterFunc(arg: string): string
+	   ...
+<   This goes in .../import/someother.vim.
+
+
+Import in legacy Vim script ~
+
+If an `import` statement is used in legacy Vim script, for identifier the
+script-local "s:" namespace will be used, even when "s:" is not specified.
+
+
+==============================================================================
+
+9. Rationale						*vim9-rationale*
+
+The :def command ~
+
+Plugin writers have asked for a much faster Vim script.  Investigation have
+shown that keeping the existing semantics of funtion calls make this close to
+impossible, because of the overhead involved with calling a function, setting
+up the local function scope and executing lines.  There are many details that
+need to be handled, such as error messages and exceptions.  The need to create
+a dictionary for a: and l: scopes, the a:000 list and several others add too
+much overhead that cannot be avoided.
+
+Therefore the `:def` method to define a new-style function had to be added,
+which allows for a function with different semantics.  Most things still work
+as before, but some parts do not.  A new way to define a function was
+considered the best way to separate the old-style code from Vim9 script code.
+
+Using "def" to define a function comes from Python. Other languages use
+"function" which clashes with legacy Vim script.
+
+
+Type checking ~
+
+When compiling lines of Vim commands into instructions as much as possible
+should be done at compile time.  Postponing it to runtime makes the execution
+slower and means mistakes are found only later.  For example, when
+encountering the "+" character and compiling this into a generic add
+instruction, at execution time the instruction would have to inspect the type
+of the arguments and decide what kind of addition to do.  And when the
+type is dictionary throw an error.  If the types are known to be numbers then
+an "add number" instruction can be used, which is faster.  The error can be
+given at compile time, no error handling is needed at runtime.
+
+The syntax for types is similar to Java, since it is easy to understand and
+widely used.  The type names are what was used in Vim before, with some
+additions such as "void" and "bool".
+
+
+JavaScript/TypeScript syntax and semantics ~
+
+Script writers have complained that the Vim script syntax is unexpectedly
+different from what they are used to.  To reduce this complaint popular
+languages will be used as an example.  At the same time, we do not want to
+abondon 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 result = 44
+	...
+	return result || 0	" returns 1
+
+Vim9 script works like JavaScript, keep the value: >
+	let result = 44
+	...
+	return result || 0	" returns 44
+
+On the other hand, overloading "+" to use both for addition and string
+concatenation goes against legacy Vim script and often leads to mistakes.
+For that reason we will keep using ".." for string concatenation.  Lua also
+uses ".." this way.
+
+
+Import and Export ~
+
+A problem of legacy Vim script is that by default all functions and variables
+are global.  It is possible to make them script-local, but then they are not
+available in other scripts.
+
+In Vim9 script a mechanism very similar to the Javascript import and export
+mechanism is supported.  It is a variant to the existing `:source` command
+that works like one would expect:
+- Instead of making everything global by default, everything is script-local,
+  unless exported.
+- When importing a script the symbols that are imported are listed, avoiding
+  name conflicts and failures if later functionality is added.
+- The mechanism allows for writing a big, long script with a very clear API:
+  the exported function(s) and class(es).
+- By using relative paths loading can be much faster for an import inside of a
+  package, no need to search many directories.
+- Once an import has been used, it can be cached and loading it again can be
+  avoided.
+- The Vim-specific use of "s:" to make things script-local can be dropped.
+
+
+Classes ~
+
+Vim supports interfaces to Perl, Python, Lua, Tcl and a few others.  But
+these have never become widespread.  When Vim 9 was designed a decision was
+made to phase out these interfaces and concentrate on Vim script, while
+encouraging plugin authors to write code in any language and run it as an
+external tool, using jobs and channels.
+
+Still, using an external tool has disadvantages.  An alternative is to convert
+the tool into Vim script.  For that to be possible without too much
+translation, and keeping the code fast at the same time, the constructs of the
+tool need to be supported.  Since most languages support classes the lack of
+class support in Vim is then a problem.
+
+Previously Vim supported a kind-of object oriented programming by adding
+methods to a dictionary.  With some care this could be made to work, but it
+does not look like real classes.  On top of that, it's very slow, because of
+the use of dictionaries.
+
+The support of classes in Vim9 script is a "minimal common functionality" of
+class support in most languages.  It works mostly like Java, which is the most
+popular programming language.
+
+
+
+ vim:tw=78:ts=8:noet:ft=help:norl:
--- a/runtime/ftplugin/vim.vim
+++ b/runtime/ftplugin/vim.vim
@@ -84,8 +84,10 @@ if exists("loaded_matchit")
   let b:match_ignorecase = 0
   let b:match_words =
 	\ '\<fu\%[nction]\>:\<retu\%[rn]\>:\<endf\%[unction]\>,' .
+	\ '\<def\>:\<retu\%[rn]\>:\<enddef\>,' .
  	\ '\<\(wh\%[ile]\|for\)\>:\<brea\%[k]\>:\<con\%[tinue]\>:\<end\(w\%[hile]\|fo\%[r]\)\>,' .
 	\ '\<if\>:\<el\%[seif]\>:\<en\%[dif]\>,' .
+	\ '{:},' .
 	\ '\<try\>:\<cat\%[ch]\>:\<fina\%[lly]\>:\<endt\%[ry]\>,' .
 	\ '\<aug\%[roup]\s\+\%(END\>\)\@!\S:\<aug\%[roup]\s\+END\>,'
   " Ignore syntax region commands and settings, any 'en*' would clobber
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -10,7 +10,7 @@ endif
 let b:did_indent = 1
 
 setlocal indentexpr=GetVimIndent()
-setlocal indentkeys+==end,=else,=cat,=fina,=END,0\\,0=\"\\\ 
+setlocal indentkeys+==end,=},=else,=cat,=fina,=END,0\\,0=\"\\\ 
 
 let b:undo_indent = "setl indentkeys< indentexpr<"
 
@@ -92,7 +92,7 @@ function GetVimIndentIntern()
   else
     " A line starting with :au does not increment/decrement indent.
     if prev_text !~ '^\s*au\%[tocmd]'
-      let i = match(prev_text, '\(^\||\)\s*\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\%[lly]\|fu\%[nction]\|el\%[seif]\)\>')
+      let i = match(prev_text, '\(^\||\)\s*\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\%[lly]\|fu\%[nction]\|def\|el\%[seif]\)\>\)')
       if i >= 0
 	let ind += shiftwidth()
 	if strpart(prev_text, i, 1) == '|' && has('syntax_items')
@@ -115,8 +115,8 @@ function GetVimIndentIntern()
 
 
   " Subtract a 'shiftwidth' on a :endif, :endwhile, :catch, :finally, :endtry,
-  " :endfun, :else and :augroup END.
-  if cur_text =~ '^\s*\(ene\@!\|cat\|fina\|el\|aug\%[roup]\s\+[eE][nN][dD]\)'
+  " :endfun, :enddef, :else and :augroup END.
+  if cur_text =~ '^\s*\(ene\@!\|}\|cat\|fina\|el\|aug\%[roup]\s\+[eE][nN][dD]\)'
     let ind = ind - shiftwidth()
   endif
 
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -26,6 +26,7 @@ syn keyword vimCommand contained	abo[vel
 syn keyword vimCommand contained	addd arge[dit] bN[ext] bf[irst] br[ewind] bufdo c[hange] caddf[ile] cbel[ow] ce[nter] cgetb[uffer] chi[story] cn[ext] colo[rscheme] cons[t] cs d[elete] deletel delm[arks] diffo[ff] dir doaut ea el[se] endt[ry] exu[sage] fin[d] foldc[lose] g h[elp] hi if intro k lN[ext] laddb[uffer] lb[uffer] lcl[ose] lex[pr] lgete[xpr] lla[st] lnew[er] lockv[ar] ls lvimgrepa[dd] mat[ch] mksp[ell] n[ext] noa nu[mber] ownsyntax ped[it] prev[ious] ps[earch] ptn[ext] py3 python3 qa[ll] redr[aw] retu[rn] rubyd[o] sIe sN[ext] sb[uffer] sbp[revious] sci scs sf[ind] sgi si sim[alt] sm[agic] sno[magic] spe[llgood] spellu[ndo] sre[wind] srp startr[eplace] sunme sy t tabc[lose] tabl[ast] tabp[revious] tcd ter[minal] tlm tlunmenu tno[remap] ts[elect] undoj[oin] uns[ilent] vert[ical] viu[sage] w[rite] windo wqa[ll] xmapc[lear] xprop y[ank]
 syn keyword vimCommand contained	al[l] argg[lobal] b[uffer] bl[ast] brea[k] buffers ca caf[ter] cbo[ttom] cex[pr] cgete[xpr] cl[ist] cnew[er] com cope[n] cscope debug deletep delp diffp[atch] dj[ump] dp earlier elsei[f] endw[hile] f[ile] fina[lly] foldd[oopen] go[to] ha[rdcopy] hid[e] ij[ump] is[earch] kee[pmarks] lNf[ile] laddf[ile] lbe[fore] lcs lf[ile] lgr[ep] lli[st] lnf[ile] lol[der] lt[ag] lw[indow] menut[ranslate] mkv[imrc] nb[key] noautocmd o[pen] p[rint] perld[o] pro ptN[ext] ptp[revious] py3do pythonx quita[ll] redraws[tatus] rew[ind] rubyf[ile] sIg sa[rgument] sba[ll] sbr[ewind] scl scscope sfir[st] sgl sic sin sm[ap] snoreme spelld[ump] spellw[rong]
 syn match   vimCommand contained	"\<z[-+^.=]\=\>"
+syn keyword vimCommand contained	def endd[ef] disa[ssemble] vim9[script] imp[ort] exp[ort]
 syn keyword vimStdPlugin contained	Arguments Break Cfilter Clear Continue DiffOrig Evaluate Finish Gdb Lfilter Man N[ext] Over P[rint] Program Run S Source Step Stop Termdebug TermdebugCommand TOhtml Winbar XMLent XMLns
 
 " vimOptions are caught only when contained in a vimSet {{{2
@@ -237,16 +238,17 @@ endif
 " =========
 syn cluster	vimFuncList	contains=vimCommand,vimFunctionError,vimFuncKey,Tag,vimFuncSID
 syn cluster	vimFuncBodyList	contains=vimAbb,vimAddress,vimAugroupKey,vimAutoCmd,vimCmplxRepeat,vimComment,vimContinue,vimCtrlChar,vimEcho,vimEchoHL,vimExecute,vimIsCommand,vimFBVar,vimFunc,vimFunction,vimFuncVar,vimGlobal,vimHighlight,vimIsCommand,vimLet,vimLetHereDoc,vimLineComment,vimMap,vimMark,vimNorm,vimNotation,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegion,vimRegister,vimSearch,vimSet,vimSpecFile,vimString,vimSubst,vimSynLine,vimUnmap,vimUserCommand
-syn match	vimFunction	"\<fu\%[nction]!\=\s\+\%(<[sS][iI][dD]>\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*("	contains=@vimFuncList nextgroup=vimFuncBody
+syn match	vimFunction	"\<\(fu\%[nction]\|def\)!\=\s\+\%(<[sS][iI][dD]>\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*("	contains=@vimFuncList nextgroup=vimFuncBody
 
 if exists("g:vimsyn_folding") && g:vimsyn_folding =~# 'f'
- syn region	vimFuncBody  contained	fold start="\ze\s*("	matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\)"		contains=@vimFuncBodyList
+  syn region	vimFuncBody  contained	fold start="\ze\s*("	matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\|enddef\>\)"		contains=@vimFuncBodyList
 else
- syn region	vimFuncBody  contained	start="\ze\s*("	matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\)"		contains=@vimFuncBodyList
+  syn region	vimFuncBody  contained	start="\ze\s*("	matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\|enddef\>\)"		contains=@vimFuncBodyList
 endif
 syn match	vimFuncVar   contained	"a:\(\K\k*\|\d\+\)"
 syn match	vimFuncSID   contained	"\c<sid>\|\<s:"
 syn keyword	vimFuncKey   contained	fu[nction]
+syn keyword	vimFuncKey   contained	def
 syn match	vimFuncBlank contained	"\s\+"
 
 syn keyword	vimPattern   contained	start	skip	end
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -788,6 +788,9 @@ OBJ = \
 	$(OUTDIR)/usercmd.o \
 	$(OUTDIR)/userfunc.o \
 	$(OUTDIR)/version.o \
+	$(OUTDIR)/vim9compile.o \
+	$(OUTDIR)/vim9execute.o \
+	$(OUTDIR)/vim9script.o \
 	$(OUTDIR)/viminfo.o \
 	$(OUTDIR)/winclip.o \
 	$(OUTDIR)/window.o
@@ -1153,6 +1156,12 @@ endif
 
 $(OUTDIR)/version.o: version.c $(INCL) version.h
 
+$(OUTDIR)/vim9compile.o: vim9compile.c $(INCL) version.h
+
+$(OUTDIR)/vim9execute.o: vim9execute.c $(INCL) version.h
+
+$(OUTDIR)/vim9script.o: vim9script.c $(INCL) version.h
+
 $(OUTDIR)/viminfo.o: viminfo.c $(INCL) version.h
 
 $(OUTDIR)/gui_dwrite.o:	gui_dwrite.cpp gui_dwrite.h
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -791,6 +791,9 @@ OBJ = \
 	$(OUTDIR)\undo.obj \
 	$(OUTDIR)\usercmd.obj \
 	$(OUTDIR)\userfunc.obj \
+	$(OUTDIR)\vim9compile.obj \
+	$(OUTDIR)\vim9execute.obj \
+	$(OUTDIR)\vim9script.obj \
 	$(OUTDIR)\viminfo.obj \
 	$(OUTDIR)\winclip.obj \
 	$(OUTDIR)\window.obj \
@@ -1726,6 +1729,12 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l
 
 $(OUTDIR)/version.obj:	$(OUTDIR) version.c  $(INCL) version.h
 
+$(OUTDIR)/vim9compile.obj:	$(OUTDIR) vim9compile.c  $(INCL)
+
+$(OUTDIR)/vim9execute.obj:	$(OUTDIR) vim9execute.c  $(INCL)
+
+$(OUTDIR)/vim9script.obj:	$(OUTDIR) vim9script.c  $(INCL)
+
 $(OUTDIR)/viminfo.obj:	$(OUTDIR) viminfo.c  $(INCL) version.h
 
 $(OUTDIR)/window.obj:	$(OUTDIR) window.c  $(INCL)
@@ -1907,6 +1916,9 @@ proto.h: \
 	proto/undo.pro \
 	proto/usercmd.pro \
 	proto/userfunc.pro \
+	proto/vim9compile.pro \
+	proto/vim9execute.pro \
+	proto/vim9script.pro \
 	proto/viminfo.pro \
 	proto/window.pro \
 	$(SOUND_PRO) \
--- a/src/Makefile
+++ b/src/Makefile
@@ -1623,6 +1623,7 @@ BASIC_SRC = \
 	main.c \
 	map.c \
 	mark.c \
+	mbyte.c \
 	memfile.c \
 	memline.c \
 	menu.c \
@@ -1631,7 +1632,6 @@ BASIC_SRC = \
 	misc2.c \
 	mouse.c \
 	move.c \
-	mbyte.c \
 	normal.c \
 	ops.c \
 	option.c \
@@ -1645,8 +1645,8 @@ BASIC_SRC = \
 	quickfix.c \
 	regexp.c \
 	register.c \
+	screen.c \
 	scriptfile.c \
-	screen.c \
 	search.c \
 	session.c \
 	sha256.c \
@@ -1666,6 +1666,9 @@ BASIC_SRC = \
 	usercmd.c \
 	userfunc.c \
 	version.c \
+	vim9compile.c \
+	vim9execute.c \
+	vim9script.c \
 	viminfo.c \
 	window.c \
 	bufwrite.c \
@@ -1761,13 +1764,13 @@ OBJ_COMMON = \
 	objects/list.o \
 	objects/map.o \
 	objects/mark.o \
+	objects/mbyte.o \
 	objects/memline.o \
 	objects/menu.o \
 	objects/misc1.o \
 	objects/misc2.o \
 	objects/mouse.o \
 	objects/move.o \
-	objects/mbyte.o \
 	objects/normal.o \
 	objects/ops.o \
 	objects/option.o \
@@ -1781,8 +1784,8 @@ OBJ_COMMON = \
 	objects/quickfix.o \
 	objects/regexp.o \
 	objects/register.o \
+	objects/screen.o \
 	objects/scriptfile.o \
-	objects/screen.o \
 	objects/search.o \
 	objects/session.o \
 	objects/sha256.o \
@@ -1802,6 +1805,9 @@ OBJ_COMMON = \
 	objects/usercmd.o \
 	objects/userfunc.o \
 	objects/version.o \
+	objects/vim9compile.o \
+	objects/vim9execute.o \
+	objects/vim9script.o \
 	objects/viminfo.o \
 	objects/window.o \
 	objects/bufwrite.o \
@@ -1873,9 +1879,12 @@ PRO_AUTO = \
 	arabic.pro \
 	arglist.pro \
 	autocmd.pro \
+	beval.pro \
 	blowfish.pro \
 	buffer.pro \
+	bufwrite.pro \
 	change.pro \
+	channel.pro \
 	charset.pro \
 	cindent.pro \
 	cmdexpand.pro \
@@ -1904,6 +1913,7 @@ PRO_AUTO = \
 	findfile.pro \
 	fold.pro \
 	getchar.pro \
+	gui_beval.pro \
 	hardcopy.pro \
 	hashtab.pro \
 	highlight.pro \
@@ -1930,6 +1940,7 @@ PRO_AUTO = \
 	misc2.pro \
 	mouse.pro \
 	move.pro \
+	netbeans.pro \
 	normal.pro \
 	ops.pro \
 	option.pro \
@@ -1943,8 +1954,8 @@ PRO_AUTO = \
 	quickfix.pro \
 	regexp.pro \
 	register.pro \
+	screen.pro \
 	scriptfile.pro \
-	screen.pro \
 	search.pro \
 	session.pro \
 	sha256.pro \
@@ -1965,13 +1976,11 @@ PRO_AUTO = \
 	usercmd.pro \
 	userfunc.pro \
 	version.pro \
+	vim9compile.pro \
+	vim9execute.pro \
+	vim9script.pro \
 	viminfo.pro \
 	window.pro \
-	bufwrite.pro \
-	beval.pro \
-	gui_beval.pro \
-	netbeans.pro \
-	channel.pro \
 	$(ALL_GUI_PRO) \
 	$(TCL_PRO)
 
@@ -3079,6 +3088,9 @@ objects/blowfish.o: blowfish.c
 objects/buffer.o: buffer.c
 	$(CCC) -o $@ buffer.c
 
+objects/bufwrite.o: bufwrite.c
+	$(CCC) -o $@ bufwrite.c
+
 objects/change.o: change.c
 	$(CCC) -o $@ change.c
 
@@ -3433,15 +3445,21 @@ objects/usercmd.o: usercmd.c
 objects/userfunc.o: userfunc.c
 	$(CCC) -o $@ userfunc.c
 
+objects/vim9compile.o: vim9compile.c
+	$(CCC) -o $@ vim9compile.c
+
+objects/vim9execute.o: vim9execute.c
+	$(CCC) -o $@ vim9execute.c
+
+objects/vim9script.o: vim9script.c
+	$(CCC) -o $@ vim9script.c
+
 objects/viminfo.o: viminfo.c
 	$(CCC) -o $@ viminfo.c
 
 objects/window.o: window.c
 	$(CCC) -o $@ window.c
 
-objects/bufwrite.o: bufwrite.c
-	$(CCC) -o $@ bufwrite.c
-
 objects/netbeans.o: netbeans.c
 	$(CCC) -o $@ netbeans.c
 
@@ -3787,6 +3805,10 @@ objects/mark.o: mark.c vim.h protodef.h 
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/mbyte.o: mbyte.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/memfile.o: memfile.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3819,10 +3841,6 @@ objects/move.o: move.c vim.h protodef.h 
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
-objects/mbyte.o: mbyte.c vim.h protodef.h auto/config.h feature.h os_unix.h \
- auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
- proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
- proto.h globals.h
 objects/normal.o: normal.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3875,14 +3893,14 @@ objects/register.o: register.c vim.h pro
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/screen.o: screen.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/scriptfile.o: scriptfile.c vim.h protodef.h auto/config.h feature.h \
  os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
-objects/screen.o: screen.c vim.h protodef.h auto/config.h feature.h os_unix.h \
- auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
- proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
- proto.h globals.h
 objects/search.o: search.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3961,6 +3979,18 @@ objects/version.o: version.c vim.h proto
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h version.h
+objects/vim9compile.o: vim9compile.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
+objects/vim9execute.o: vim9execute.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
+objects/vim9script.o: vim9script.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
 objects/viminfo.o: viminfo.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
--- a/src/blob.c
+++ b/src/blob.c
@@ -58,24 +58,24 @@ rettv_blob_set(typval_T *rettv, blob_T *
 }
 
     int
-blob_copy(typval_T *from, typval_T *to)
+blob_copy(blob_T *from, typval_T *to)
 {
     int	    ret = OK;
 
     to->v_type = VAR_BLOB;
     to->v_lock = 0;
-    if (from->vval.v_blob == NULL)
+    if (from == NULL)
 	to->vval.v_blob = NULL;
     else if (rettv_blob_alloc(to) == FAIL)
 	ret = FAIL;
     else
     {
-	int  len = from->vval.v_blob->bv_ga.ga_len;
+	int  len = from->bv_ga.ga_len;
 
 	if (len > 0)
 	{
 	    to->vval.v_blob->bv_ga.ga_data =
-			    vim_memsave(from->vval.v_blob->bv_ga.ga_data, len);
+					 vim_memsave(from->bv_ga.ga_data, len);
 	    if (to->vval.v_blob->bv_ga.ga_data == NULL)
 		len = 0;
 	}
--- a/src/channel.c
+++ b/src/channel.c
@@ -2263,7 +2263,10 @@ channel_get_json(
     while (item != NULL)
     {
 	list_T	    *l = item->jq_value->vval.v_list;
-	typval_T    *tv = &l->lv_first->li_tv;
+	typval_T    *tv;
+
+	range_list_materialize(l);
+	tv = &l->lv_first->li_tv;
 
 	if ((without_callback || !item->jq_no_callback)
 	    && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id)
--- a/src/dict.c
+++ b/src/dict.c
@@ -826,7 +826,7 @@ eval_dict(char_u **arg, typval_T *rettv,
 
 	if (**arg != ':')
 	{
-	    semsg(_("E720: Missing colon in Dictionary: %s"), *arg);
+	    semsg(_(e_missing_dict_colon), *arg);
 	    clear_tv(&tvkey);
 	    goto failret;
 	}
@@ -853,7 +853,7 @@ eval_dict(char_u **arg, typval_T *rettv,
 	    item = dict_find(d, key, -1);
 	    if (item != NULL)
 	    {
-		semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key);
+		semsg(_(e_duplicate_key), key);
 		clear_tv(&tvkey);
 		clear_tv(&tv);
 		goto failret;
@@ -873,7 +873,7 @@ eval_dict(char_u **arg, typval_T *rettv,
 	    break;
 	if (**arg != ',')
 	{
-	    semsg(_("E722: Missing comma in Dictionary: %s"), *arg);
+	    semsg(_(e_missing_dict_comma), *arg);
 	    goto failret;
 	}
 	*arg = skipwhite(*arg + 1);
@@ -881,7 +881,7 @@ eval_dict(char_u **arg, typval_T *rettv,
 
     if (**arg != '}')
     {
-	semsg(_("E723: Missing end of Dictionary '}': %s"), *arg);
+	semsg(_(e_missing_dict_end), *arg);
 failret:
 	if (d != NULL)
 	    dict_free(d);
--- a/src/eval.c
+++ b/src/eval.c
@@ -20,12 +20,10 @@
 # include <float.h>
 #endif
 
-static char *e_missbrac = N_("E111: Missing ']'");
 static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
 #ifdef FEAT_FLOAT
 static char *e_float_as_string = N_("E806: using Float as a String");
 #endif
-static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis");
 
 #define NAMESPACE_CHAR	(char_u *)"abglstvw"
 
@@ -60,10 +58,7 @@ static int eval6(char_u **arg, typval_T 
 static int eval7(char_u **arg, typval_T *rettv, int evaluate, int want_string);
 static int eval7_leader(typval_T *rettv, char_u *start_leader, char_u **end_leaderp);
 
-static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate);
-static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate);
 static int free_unref_items(int copyID);
-static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate);
 static char_u *make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end);
 static int tv_check_lock(typval_T *tv, char_u *name, int use_gettext);
 
@@ -222,6 +217,11 @@ eval1_emsg(char_u **arg, typval_T *rettv
     return ret;
 }
 
+/*
+ * Evaluate an expression, which can be a function, partial or string.
+ * Pass arguments "argv[argc]".
+ * Return the result in "rettv" and OK or FAIL.
+ */
     int
 eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
 {
@@ -243,14 +243,22 @@ eval_expr_typval(typval_T *expr, typval_
     {
 	partial_T   *partial = expr->vval.v_partial;
 
-	s = partial_name(partial);
-	if (s == NULL || *s == NUL)
-	    return FAIL;
-	vim_memset(&funcexe, 0, sizeof(funcexe));
-	funcexe.evaluate = TRUE;
-	funcexe.partial = partial;
-	if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL)
-	    return FAIL;
+	if (partial->pt_func != NULL && partial->pt_func->uf_dfunc_idx >= 0)
+	{
+	    if (call_def_function(partial->pt_func, argc, argv, rettv) == FAIL)
+		return FAIL;
+	}
+	else
+	{
+	    s = partial_name(partial);
+	    if (s == NULL || *s == NUL)
+		return FAIL;
+	    vim_memset(&funcexe, 0, sizeof(funcexe));
+	    funcexe.evaluate = TRUE;
+	    funcexe.partial = partial;
+	    if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL)
+		return FAIL;
+	}
     }
     else
     {
@@ -652,6 +660,7 @@ get_lval(
 
     // Find the end of the name.
     p = find_name_end(name, &expr_start, &expr_end, fne_flags);
+    lp->ll_name_end = p;
     if (expr_start != NULL)
     {
 	// Don't expand the name when we already know there is an error.
@@ -678,8 +687,20 @@ get_lval(
 	lp->ll_name = lp->ll_exp_name;
     }
     else
+    {
 	lp->ll_name = name;
 
+	if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && *p == ':')
+	{
+	    scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+	    char_u	 *tp = skipwhite(p + 1);
+
+	    // parse the type after the name
+	    lp->ll_type = parse_type(&tp, &si->sn_type_list);
+	    lp->ll_name_end = tp;
+	}
+    }
+
     // Without [idx] or .key we are done.
     if ((*p != '[' && *p != '.') || lp->ll_name == NULL)
 	return p;
@@ -1002,6 +1023,7 @@ get_lval(
     }
 
     clear_tv(&var1);
+    lp->ll_name_end = p;
     return p;
 }
 
@@ -1027,7 +1049,7 @@ set_var_lval(
     char_u	*endp,
     typval_T	*rettv,
     int		copy,
-    int		is_const,    // Disallow to modify existing variable for :const
+    int		flags,    // LET_IS_CONST and/or LET_NO_COMMAND
     char_u	*op)
 {
     int		cc;
@@ -1093,7 +1115,7 @@ set_var_lval(
 	{
 	    typval_T tv;
 
-	    if (is_const)
+	    if (flags & LET_IS_CONST)
 	    {
 		emsg(_(e_cannot_mod));
 		*endp = cc;
@@ -1114,7 +1136,7 @@ set_var_lval(
 	    }
 	}
 	else
-	    set_var_const(lp->ll_name, rettv, copy, is_const);
+	    set_var_const(lp->ll_name, lp->ll_type, rettv, copy, flags);
 	*endp = cc;
     }
     else if (var_check_lock(lp->ll_newkey == NULL
@@ -1126,7 +1148,7 @@ set_var_lval(
 	listitem_T *ll_li = lp->ll_li;
 	int	    ll_n1 = lp->ll_n1;
 
-	if (is_const)
+	if (flags & LET_IS_CONST)
 	{
 	    emsg(_("E996: Cannot lock a range"));
 	    return;
@@ -1185,7 +1207,7 @@ set_var_lval(
 	/*
 	 * Assign to a List or Dictionary item.
 	 */
-	if (is_const)
+	if (flags & LET_IS_CONST)
 	{
 	    emsg(_("E996: Cannot lock a list or dict"));
 	    return;
@@ -1250,6 +1272,7 @@ tv_op(typval_T *tv1, typval_T *tv2, char
 	switch (tv1->v_type)
 	{
 	    case VAR_UNKNOWN:
+	    case VAR_VOID:
 	    case VAR_DICT:
 	    case VAR_FUNC:
 	    case VAR_PARTIAL:
@@ -1392,14 +1415,14 @@ eval_for_line(
     if (fi == NULL)
 	return NULL;
 
-    expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon);
+    expr = skip_var_list(arg, TRUE, &fi->fi_varcount, &fi->fi_semicolon);
     if (expr == NULL)
 	return fi;
 
     expr = skipwhite(expr);
     if (expr[0] != 'i' || expr[1] != 'n' || !VIM_ISWHITE(expr[2]))
     {
-	emsg(_("E690: Missing \"in\" after :for"));
+	emsg(_(e_missing_in));
 	return fi;
     }
 
@@ -1420,6 +1443,9 @@ eval_for_line(
 		}
 		else
 		{
+		    // Need a real list here.
+		    range_list_materialize(l);
+
 		    // No need to increment the refcount, it's already set for
 		    // the list being used in "tv".
 		    fi->fi_list = l;
@@ -1436,7 +1462,7 @@ eval_for_line(
 
 		    // Make a copy, so that the iteration still works when the
 		    // blob is changed.
-		    blob_copy(&tv, &btv);
+		    blob_copy(tv.vval.v_blob, &btv);
 		    fi->fi_blob = btv.vval.v_blob;
 		}
 		clear_tv(&tv);
@@ -1478,7 +1504,7 @@ next_for_item(void *fi_void, char_u *arg
 	tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi);
 	++fi->fi_bi;
 	return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
-					   fi->fi_varcount, FALSE, NULL) == OK;
+					       fi->fi_varcount, 0, NULL) == OK;
     }
 
     item = fi->fi_lw.lw_item;
@@ -1488,7 +1514,7 @@ next_for_item(void *fi_void, char_u *arg
     {
 	fi->fi_lw.lw_item = item->li_next;
 	result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon,
-					  fi->fi_varcount, FALSE, NULL) == OK);
+					  fi->fi_varcount, 0, NULL) == OK);
     }
     return result;
 }
@@ -1814,7 +1840,7 @@ eval1(char_u **arg, typval_T *rettv, int
 	 */
 	if ((*arg)[0] != ':')
 	{
-	    emsg(_("E109: Missing ':' after '?'"));
+	    emsg(_(e_missing_colon));
 	    if (evaluate && result)
 		clear_tv(rettv);
 	    return FAIL;
@@ -2089,6 +2115,43 @@ eval4(char_u **arg, typval_T *rettv, int
     return OK;
 }
 
+    void
+eval_addblob(typval_T *tv1, typval_T *tv2)
+{
+    blob_T  *b1 = tv1->vval.v_blob;
+    blob_T  *b2 = tv2->vval.v_blob;
+    blob_T  *b = blob_alloc();
+    int	    i;
+
+    if (b != NULL)
+    {
+	for (i = 0; i < blob_len(b1); i++)
+	    ga_append(&b->bv_ga, blob_get(b1, i));
+	for (i = 0; i < blob_len(b2); i++)
+	    ga_append(&b->bv_ga, blob_get(b2, i));
+
+	clear_tv(tv1);
+	rettv_blob_set(tv1, b);
+    }
+}
+
+    int
+eval_addlist(typval_T *tv1, typval_T *tv2)
+{
+    typval_T var3;
+
+    // concatenate Lists
+    if (list_concat(tv1->vval.v_list, tv2->vval.v_list, &var3) == FAIL)
+    {
+	clear_tv(tv1);
+	clear_tv(tv2);
+	return FAIL;
+    }
+    clear_tv(tv1);
+    *tv1 = var3;
+    return OK;
+}
+
 /*
  * Handle fourth level expression:
  *	+	number addition
@@ -2105,7 +2168,6 @@ eval4(char_u **arg, typval_T *rettv, int
 eval5(char_u **arg, typval_T *rettv, int evaluate)
 {
     typval_T	var2;
-    typval_T	var3;
     int		op;
     varnumber_T	n1, n2;
 #ifdef FEAT_FLOAT
@@ -2189,36 +2251,12 @@ eval5(char_u **arg, typval_T *rettv, int
 	    }
 	    else if (op == '+' && rettv->v_type == VAR_BLOB
 						   && var2.v_type == VAR_BLOB)
-	    {
-		blob_T  *b1 = rettv->vval.v_blob;
-		blob_T  *b2 = var2.vval.v_blob;
-		blob_T	*b = blob_alloc();
-		int	i;
-
-		if (b != NULL)
-		{
-		    for (i = 0; i < blob_len(b1); i++)
-			ga_append(&b->bv_ga, blob_get(b1, i));
-		    for (i = 0; i < blob_len(b2); i++)
-			ga_append(&b->bv_ga, blob_get(b2, i));
-
-		    clear_tv(rettv);
-		    rettv_blob_set(rettv, b);
-		}
-	    }
+		eval_addblob(rettv, &var2);
 	    else if (op == '+' && rettv->v_type == VAR_LIST
 						   && var2.v_type == VAR_LIST)
 	    {
-		// concatenate Lists
-		if (list_concat(rettv->vval.v_list, var2.vval.v_list,
-							       &var3) == FAIL)
-		{
-		    clear_tv(rettv);
-		    clear_tv(&var2);
+		if (eval_addlist(rettv, &var2) == FAIL)
 		    return FAIL;
-		}
-		clear_tv(rettv);
-		*rettv = var3;
 	    }
 	    else
 	    {
@@ -2424,7 +2462,7 @@ eval6(
 		}
 		else
 		{
-		    emsg(_("E804: Cannot use '%' with Float"));
+		    emsg(_(e_modulus));
 		    return FAIL;
 		}
 		rettv->v_type = VAR_FLOAT;
@@ -2462,6 +2500,7 @@ eval6(
  *  $VAR		environment variable
  *  (expression)	nested expression
  *  [expr, expr]	List
+ *  {arg, arg -> expr}	Lambda
  *  {key: val, key: val}   Dictionary
  *  #{key: val, key: val}  Dictionary with literal keys
  *
@@ -2483,9 +2522,8 @@ eval7(
     char_u	**arg,
     typval_T	*rettv,
     int		evaluate,
-    int		want_string UNUSED)	// after "." operator
+    int		want_string)	// after "." operator
 {
-    varnumber_T	n;
     int		len;
     char_u	*s;
     char_u	*start_leader, *end_leader;
@@ -2532,105 +2570,8 @@ eval7(
     case '7':
     case '8':
     case '9':
-    case '.':
-	{
-#ifdef FEAT_FLOAT
-		char_u *p;
-		int    get_float = FALSE;
-
-		// We accept a float when the format matches
-		// "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?".  This is very
-		// strict to avoid backwards compatibility problems.
-		// With script version 2 and later the leading digit can be
-		// omitted.
-		// Don't look for a float after the "." operator, so that
-		// ":let vers = 1.2.3" doesn't fail.
-		if (**arg == '.')
-		    p = *arg;
-		else
-		    p = skipdigits(*arg + 1);
-		if (!want_string && p[0] == '.' && vim_isdigit(p[1]))
-		{
-		    get_float = TRUE;
-		    p = skipdigits(p + 2);
-		    if (*p == 'e' || *p == 'E')
-		    {
-			++p;
-			if (*p == '-' || *p == '+')
-			    ++p;
-			if (!vim_isdigit(*p))
-			    get_float = FALSE;
-			else
-			    p = skipdigits(p + 1);
-		    }
-		    if (ASCII_ISALPHA(*p) || *p == '.')
-			get_float = FALSE;
-		}
-		if (get_float)
-		{
-		    float_T	f;
-
-		    *arg += string2float(*arg, &f);
-		    if (evaluate)
-		    {
-			rettv->v_type = VAR_FLOAT;
-			rettv->vval.v_float = f;
-		    }
-		}
-		else
-#endif
-		if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z'))
-		{
-		    char_u  *bp;
-		    blob_T  *blob = NULL;  // init for gcc
-
-		    // Blob constant: 0z0123456789abcdef
-		    if (evaluate)
-			blob = blob_alloc();
-		    for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2)
-		    {
-			if (!vim_isxdigit(bp[1]))
-			{
-			    if (blob != NULL)
-			    {
-				emsg(_("E973: Blob literal should have an even number of hex characters"));
-				ga_clear(&blob->bv_ga);
-				VIM_CLEAR(blob);
-			    }
-			    ret = FAIL;
-			    break;
-			}
-			if (blob != NULL)
-			    ga_append(&blob->bv_ga,
-					 (hex2nr(*bp) << 4) + hex2nr(*(bp+1)));
-			if (bp[2] == '.' && vim_isxdigit(bp[3]))
-			    ++bp;
-		    }
-		    if (blob != NULL)
-			rettv_blob_set(rettv, blob);
-		    *arg = bp;
-		}
-		else
-		{
-		    // decimal, hex or octal number
-		    vim_str2nr(*arg, NULL, &len, current_sctx.sc_version >= 4
-				  ? STR2NR_NO_OCT + STR2NR_QUOTE
-				  : STR2NR_ALL, &n, NULL, 0, TRUE);
-		    if (len == 0)
-		    {
-			semsg(_(e_invexpr2), *arg);
-			ret = FAIL;
-			break;
-		    }
-		    *arg += len;
-		    if (evaluate)
-		    {
-			rettv->v_type = VAR_NUMBER;
-			rettv->vval.v_number = n;
-		    }
-		}
+    case '.':	ret = get_number_tv(arg, rettv, evaluate, want_string);
 		break;
-	}
 
     /*
      * String constant: "string".
@@ -2647,7 +2588,7 @@ eval7(
     /*
      * List: [expr, expr]
      */
-    case '[':	ret = get_list_tv(arg, rettv, evaluate);
+    case '[':	ret = get_list_tv(arg, rettv, evaluate, TRUE);
 		break;
 
     /*
@@ -2706,7 +2647,7 @@ eval7(
 		    ++*arg;
 		else if (ret == OK)
 		{
-		    emsg(_("E110: Missing ')'"));
+		    emsg(_(e_missing_close));
 		    clear_tv(rettv);
 		    ret = FAIL;
 		}
@@ -2907,7 +2848,7 @@ eval_lambda(
 	    if (*skipwhite(*arg) == '(')
 		semsg(_(e_nowhitespace));
 	    else
-		semsg(_(e_missingparen), "lambda");
+		semsg(_(e_missing_paren), "lambda");
 	}
 	clear_tv(rettv);
 	ret = FAIL;
@@ -2961,7 +2902,7 @@ eval_method(
 	if (**arg != '(')
 	{
 	    if (verbose)
-		semsg(_(e_missingparen), name);
+		semsg(_(e_missing_paren), name);
 	    ret = FAIL;
 	}
 	else if (VIM_ISWHITE((*arg)[-1]))
@@ -3024,6 +2965,7 @@ eval_index(
 		emsg(_("E909: Cannot index a special variable"));
 	    return FAIL;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    if (evaluate)
 		return FAIL;
 	    // FALLTHROUGH
@@ -3129,6 +3071,7 @@ eval_index(
 	switch (rettv->v_type)
 	{
 	    case VAR_UNKNOWN:
+	    case VAR_VOID:
 	    case VAR_FUNC:
 	    case VAR_PARTIAL:
 	    case VAR_FLOAT:
@@ -3377,7 +3320,7 @@ get_option_tv(
     if (opt_type == -3)			// invalid name
     {
 	if (rettv != NULL)
-	    semsg(_("E113: Unknown option: %s"), *arg);
+	    semsg(_(e_unknown_option), *arg);
 	ret = FAIL;
     }
     else if (rettv != NULL)
@@ -3413,10 +3356,120 @@ get_option_tv(
 }
 
 /*
+ * Allocate a variable for a number constant.  Also deals with "0z" for blob.
+ * Return OK or FAIL.
+ */
+    int
+get_number_tv(
+	char_u	    **arg,
+	typval_T    *rettv,
+	int	    evaluate,
+	int	    want_string UNUSED)
+{
+    int		len;
+#ifdef FEAT_FLOAT
+    char_u	*p;
+    int		get_float = FALSE;
+
+    // We accept a float when the format matches
+    // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?".  This is very
+    // strict to avoid backwards compatibility problems.
+    // With script version 2 and later the leading digit can be
+    // omitted.
+    // Don't look for a float after the "." operator, so that
+    // ":let vers = 1.2.3" doesn't fail.
+    if (**arg == '.')
+	p = *arg;
+    else
+	p = skipdigits(*arg + 1);
+    if (!want_string && p[0] == '.' && vim_isdigit(p[1]))
+    {
+	get_float = TRUE;
+	p = skipdigits(p + 2);
+	if (*p == 'e' || *p == 'E')
+	{
+	    ++p;
+	    if (*p == '-' || *p == '+')
+		++p;
+	    if (!vim_isdigit(*p))
+		get_float = FALSE;
+	    else
+		p = skipdigits(p + 1);
+	}
+	if (ASCII_ISALPHA(*p) || *p == '.')
+	    get_float = FALSE;
+    }
+    if (get_float)
+    {
+	float_T	f;
+
+	*arg += string2float(*arg, &f);
+	if (evaluate)
+	{
+	    rettv->v_type = VAR_FLOAT;
+	    rettv->vval.v_float = f;
+	}
+    }
+    else
+#endif
+    if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z'))
+    {
+	char_u  *bp;
+	blob_T  *blob = NULL;  // init for gcc
+
+	// Blob constant: 0z0123456789abcdef
+	if (evaluate)
+	    blob = blob_alloc();
+	for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2)
+	{
+	    if (!vim_isxdigit(bp[1]))
+	    {
+		if (blob != NULL)
+		{
+		    emsg(_("E973: Blob literal should have an even number of hex characters"));
+		    ga_clear(&blob->bv_ga);
+		    VIM_CLEAR(blob);
+		}
+		return FAIL;
+	    }
+	    if (blob != NULL)
+		ga_append(&blob->bv_ga,
+			     (hex2nr(*bp) << 4) + hex2nr(*(bp+1)));
+	    if (bp[2] == '.' && vim_isxdigit(bp[3]))
+		++bp;
+	}
+	if (blob != NULL)
+	    rettv_blob_set(rettv, blob);
+	*arg = bp;
+    }
+    else
+    {
+	varnumber_T	n;
+
+	// decimal, hex or octal number
+	vim_str2nr(*arg, NULL, &len, current_sctx.sc_version >= 4
+		      ? STR2NR_NO_OCT + STR2NR_QUOTE
+		      : STR2NR_ALL, &n, NULL, 0, TRUE);
+	if (len == 0)
+	{
+	    semsg(_(e_invexpr2), *arg);
+	    return FAIL;
+	}
+	*arg += len;
+	if (evaluate)
+	{
+	    rettv->v_type = VAR_NUMBER;
+	    rettv->vval.v_number = n;
+	}
+    }
+    return OK;
+}
+
+/*
  * Allocate a variable for a string constant.
  * Return OK or FAIL.
  */
-    static int
+    int
 get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
 {
     char_u	*p;
@@ -3553,7 +3606,7 @@ get_string_tv(char_u **arg, typval_T *re
  * Allocate a variable for a 'str''ing' constant.
  * Return OK or FAIL.
  */
-    static int
+    int
 get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
 {
     char_u	*p;
@@ -3772,6 +3825,8 @@ tv_equal(
 	    return blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
 
 	case VAR_NUMBER:
+	case VAR_BOOL:
+	case VAR_SPECIAL:
 	    return tv1->vval.v_number == tv2->vval.v_number;
 
 	case VAR_STRING:
@@ -3779,10 +3834,6 @@ tv_equal(
 	    s2 = tv_get_string_buf(tv2, buf2);
 	    return ((ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2)) == 0);
 
-	case VAR_BOOL:
-	case VAR_SPECIAL:
-	    return tv1->vval.v_number == tv2->vval.v_number;
-
 	case VAR_FLOAT:
 #ifdef FEAT_FLOAT
 	    return tv1->vval.v_float == tv2->vval.v_float;
@@ -3795,9 +3846,11 @@ tv_equal(
 #ifdef FEAT_JOB_CHANNEL
 	    return tv1->vval.v_channel == tv2->vval.v_channel;
 #endif
+
 	case VAR_FUNC:
 	case VAR_PARTIAL:
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    break;
     }
 
@@ -4511,6 +4564,7 @@ echo_string_core(
 
 	case VAR_NUMBER:
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    *tofree = NULL;
 	    r = tv_get_string_buf(tv, numbuf);
 	    break;
@@ -4668,7 +4722,7 @@ string2float(
  * If the environment variable was not set, silently assume it is empty.
  * Return FAIL if the name is invalid.
  */
-    static int
+    int
 get_env_tv(char_u **arg, typval_T *rettv, int evaluate)
 {
     char_u	*string = NULL;
@@ -5363,6 +5417,7 @@ free_tv(typval_T *varp)
 	    case VAR_NUMBER:
 	    case VAR_FLOAT:
 	    case VAR_UNKNOWN:
+	    case VAR_VOID:
 	    case VAR_BOOL:
 	    case VAR_SPECIAL:
 		break;
@@ -5425,6 +5480,7 @@ clear_tv(typval_T *varp)
 		varp->vval.v_channel = NULL;
 #endif
 	    case VAR_UNKNOWN:
+	    case VAR_VOID:
 		break;
 	}
 	varp->v_lock = 0;
@@ -5503,6 +5559,7 @@ tv_get_number_chk(typval_T *varp, int *d
 	    emsg(_("E974: Using a Blob as a Number"));
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("tv_get_number(UNKNOWN)");
 	    break;
     }
@@ -5556,6 +5613,7 @@ tv_get_float(typval_T *varp)
 	    emsg(_("E975: Using a Blob as a Float"));
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("tv_get_float(UNKNOWN)");
 	    break;
     }
@@ -5678,6 +5736,7 @@ tv_get_string_buf_chk(typval_T *varp, ch
 #endif
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    emsg(_(e_inval_string));
 	    break;
     }
@@ -5826,6 +5885,7 @@ copy_tv(typval_T *from, typval_T *to)
 	    }
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("copy_tv(UNKNOWN)");
 	    break;
     }
@@ -5885,7 +5945,7 @@ item_copy(
 		ret = FAIL;
 	    break;
 	case VAR_BLOB:
-	    ret = blob_copy(from, to);
+	    ret = blob_copy(from->vval.v_blob, to);
 	    break;
 	case VAR_DICT:
 	    to->v_type = VAR_DICT;
@@ -5904,6 +5964,7 @@ item_copy(
 		ret = FAIL;
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("item_copy(UNKNOWN)");
 	    ret = FAIL;
     }
@@ -5911,6 +5972,59 @@ item_copy(
     return ret;
 }
 
+    void
+echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr)
+{
+    char_u	*tofree;
+    char_u	numbuf[NUMBUFLEN];
+    char_u	*p = echo_string(rettv, &tofree, numbuf, get_copyID());
+
+    if (*atstart)
+    {
+	*atstart = FALSE;
+	// Call msg_start() after eval1(), evaluating the expression
+	// may cause a message to appear.
+	if (with_space)
+	{
+	    // Mark the saved text as finishing the line, so that what
+	    // follows is displayed on a new line when scrolling back
+	    // at the more prompt.
+	    msg_sb_eol();
+	    msg_start();
+	}
+    }
+    else if (with_space)
+	msg_puts_attr(" ", echo_attr);
+
+    if (p != NULL)
+	for ( ; *p != NUL && !got_int; ++p)
+	{
+	    if (*p == '\n' || *p == '\r' || *p == TAB)
+	    {
+		if (*p != TAB && *needclr)
+		{
+		    // remove any text still there from the command
+		    msg_clr_eos();
+		    *needclr = FALSE;
+		}
+		msg_putchar_attr(*p, echo_attr);
+	    }
+	    else
+	    {
+		if (has_mbyte)
+		{
+		    int i = (*mb_ptr2len)(p);
+
+		    (void)msg_outtrans_len_attr(p, i, echo_attr);
+		    p += i - 1;
+		}
+		else
+		    (void)msg_outtrans_len_attr(p, 1, echo_attr);
+	    }
+	}
+    vim_free(tofree);
+}
+
 /*
  * ":echo expr1 ..."	print each argument separated with a space, add a
  *			newline at the end.
@@ -5921,11 +6035,9 @@ ex_echo(exarg_T *eap)
 {
     char_u	*arg = eap->arg;
     typval_T	rettv;
-    char_u	*tofree;
     char_u	*p;
     int		needclr = TRUE;
     int		atstart = TRUE;
-    char_u	numbuf[NUMBUFLEN];
     int		did_emsg_before = did_emsg;
     int		called_emsg_before = called_emsg;
 
@@ -5954,52 +6066,8 @@ ex_echo(exarg_T *eap)
 	need_clr_eos = FALSE;
 
 	if (!eap->skip)
-	{
-	    if (atstart)
-	    {
-		atstart = FALSE;
-		// Call msg_start() after eval1(), evaluating the expression
-		// may cause a message to appear.
-		if (eap->cmdidx == CMD_echo)
-		{
-		    // Mark the saved text as finishing the line, so that what
-		    // follows is displayed on a new line when scrolling back
-		    // at the more prompt.
-		    msg_sb_eol();
-		    msg_start();
-		}
-	    }
-	    else if (eap->cmdidx == CMD_echo)
-		msg_puts_attr(" ", echo_attr);
-	    p = echo_string(&rettv, &tofree, numbuf, get_copyID());
-	    if (p != NULL)
-		for ( ; *p != NUL && !got_int; ++p)
-		{
-		    if (*p == '\n' || *p == '\r' || *p == TAB)
-		    {
-			if (*p != TAB && needclr)
-			{
-			    // remove any text still there from the command
-			    msg_clr_eos();
-			    needclr = FALSE;
-			}
-			msg_putchar_attr(*p, echo_attr);
-		    }
-		    else
-		    {
-			if (has_mbyte)
-			{
-			    int i = (*mb_ptr2len)(p);
-
-			    (void)msg_outtrans_len_attr(p, i, echo_attr);
-			    p += i - 1;
-			}
-			else
-			    (void)msg_outtrans_len_attr(p, 1, echo_attr);
-		    }
-		}
-	    vim_free(tofree);
-	}
+	    echo_one(&rettv, eap->cmdidx == CMD_echo, &atstart, &needclr);
+
 	clear_tv(&rettv);
 	arg = skipwhite(arg);
     }
@@ -6369,7 +6437,7 @@ typval_compare(
 	    case EXPR_SEQUAL:   n1 = (f1 <= f2); break;
 	    case EXPR_UNKNOWN:
 	    case EXPR_MATCH:
-	    case EXPR_NOMATCH:  break;  // avoid gcc warning
+	    default:  break;  // avoid gcc warning
 	}
     }
 #endif
@@ -6395,7 +6463,7 @@ typval_compare(
 	    case EXPR_SEQUAL:   n1 = (n1 <= n2); break;
 	    case EXPR_UNKNOWN:
 	    case EXPR_MATCH:
-	    case EXPR_NOMATCH:  break;  // avoid gcc warning
+	    default:  break;  // avoid gcc warning
 	}
     }
     else
@@ -6425,7 +6493,7 @@ typval_compare(
 			n1 = !n1;
 		    break;
 
-	    case EXPR_UNKNOWN:  break;  // avoid gcc warning
+	    default:  break;  // avoid gcc warning
 	}
     }
     clear_tv(typ1);
--- a/src/evalbuffer.c
+++ b/src/evalbuffer.c
@@ -176,6 +176,7 @@ set_buffer_lines(
     if (lines->v_type == VAR_LIST)
     {
 	l = lines->vval.v_list;
+	range_list_materialize(l);
 	li = l->lv_first;
     }
     else
@@ -689,10 +690,16 @@ get_buffer_lines(
 {
     char_u	*p;
 
-    rettv->v_type = VAR_STRING;
-    rettv->vval.v_string = NULL;
-    if (retlist && rettv_list_alloc(rettv) == FAIL)
-	return;
+    if (retlist)
+    {
+	if (rettv_list_alloc(rettv) == FAIL)
+	    return;
+    }
+    else
+    {
+	rettv->v_type = VAR_STRING;
+	rettv->vval.v_string = NULL;
+    }
 
     if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0)
 	return;
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -290,6 +290,7 @@ typedef struct
     char	f_min_argc;	// minimal number of arguments
     char	f_max_argc;	// maximal number of arguments
     char	f_argtype;	// for method: FEARG_ values
+    type_T	*f_rettype;	// return type
     void	(*f_func)(typval_T *args, typval_T *rvar);
 				// implementation of function
 } funcentry_T;
@@ -304,580 +305,580 @@ typedef struct
 static funcentry_T global_functions[] =
 {
 #ifdef FEAT_FLOAT
-    {"abs",		1, 1, FEARG_1,	  f_abs},
-    {"acos",		1, 1, FEARG_1,	  f_acos},	// WJMc
-#endif
-    {"add",		2, 2, FEARG_1,	  f_add},
-    {"and",		2, 2, FEARG_1,	  f_and},
-    {"append",		2, 2, FEARG_LAST, f_append},
-    {"appendbufline",	3, 3, FEARG_LAST, f_appendbufline},
-    {"argc",		0, 1, 0,	  f_argc},
-    {"argidx",		0, 0, 0,	  f_argidx},
-    {"arglistid",	0, 2, 0,	  f_arglistid},
-    {"argv",		0, 2, 0,	  f_argv},
+    {"abs",		1, 1, FEARG_1,	  &t_any,	f_abs},
+    {"acos",		1, 1, FEARG_1,	  &t_float,	f_acos},	// WJMc
+#endif
+    {"add",		2, 2, FEARG_1,	  &t_any,	f_add},
+    {"and",		2, 2, FEARG_1,	  &t_number,	f_and},
+    {"append",		2, 2, FEARG_LAST, &t_number,	f_append},
+    {"appendbufline",	3, 3, FEARG_LAST, &t_number,	f_appendbufline},
+    {"argc",		0, 1, 0,	  &t_number,	f_argc},
+    {"argidx",		0, 0, 0,	  &t_number,	f_argidx},
+    {"arglistid",	0, 2, 0,	  &t_number,	f_arglistid},
+    {"argv",		0, 2, 0,	  &t_any,	f_argv},
 #ifdef FEAT_FLOAT
-    {"asin",		1, 1, FEARG_1,	  f_asin},	// WJMc
-#endif
-    {"assert_beeps",	1, 2, FEARG_1,	  f_assert_beeps},
-    {"assert_equal",	2, 3, FEARG_2,	  f_assert_equal},
-    {"assert_equalfile", 2, 2, FEARG_1,	  f_assert_equalfile},
-    {"assert_exception", 1, 2, 0,	  f_assert_exception},
-    {"assert_fails",	1, 3, FEARG_1,	  f_assert_fails},
-    {"assert_false",	1, 2, FEARG_1,	  f_assert_false},
-    {"assert_inrange",	3, 4, FEARG_3,	  f_assert_inrange},
-    {"assert_match",	2, 3, FEARG_2,	  f_assert_match},
-    {"assert_notequal",	2, 3, FEARG_2,	  f_assert_notequal},
-    {"assert_notmatch",	2, 3, FEARG_2,	  f_assert_notmatch},
-    {"assert_report",	1, 1, FEARG_1,	  f_assert_report},
-    {"assert_true",	1, 2, FEARG_1,	  f_assert_true},
+    {"asin",		1, 1, FEARG_1,	  &t_float,	f_asin},	// WJMc
+#endif
+    {"assert_beeps",	1, 2, FEARG_1,	  &t_number,	f_assert_beeps},
+    {"assert_equal",	2, 3, FEARG_2,	  &t_number,	f_assert_equal},
+    {"assert_equalfile", 2, 2, FEARG_1,	  &t_number,	f_assert_equalfile},
+    {"assert_exception", 1, 2, 0,	  &t_number,	f_assert_exception},
+    {"assert_fails",	1, 3, FEARG_1,	  &t_number,	f_assert_fails},
+    {"assert_false",	1, 2, FEARG_1,	  &t_number,	f_assert_false},
+    {"assert_inrange",	3, 4, FEARG_3,	  &t_number,	f_assert_inrange},
+    {"assert_match",	2, 3, FEARG_2,	  &t_number,	f_assert_match},
+    {"assert_notequal",	2, 3, FEARG_2,	  &t_number,	f_assert_notequal},
+    {"assert_notmatch",	2, 3, FEARG_2,	  &t_number,	f_assert_notmatch},
+    {"assert_report",	1, 1, FEARG_1,	  &t_number,	f_assert_report},
+    {"assert_true",	1, 2, FEARG_1,	  &t_number,	f_assert_true},
 #ifdef FEAT_FLOAT
-    {"atan",		1, 1, FEARG_1,	  f_atan},
-    {"atan2",		2, 2, FEARG_1,	  f_atan2},
+    {"atan",		1, 1, FEARG_1,	  &t_float,	f_atan},
+    {"atan2",		2, 2, FEARG_1,	  &t_float,	f_atan2},
 #endif
 #ifdef FEAT_BEVAL
-    {"balloon_gettext",	0, 0, 0,	  f_balloon_gettext},
-    {"balloon_show",	1, 1, FEARG_1,	  f_balloon_show},
+    {"balloon_gettext",	0, 0, 0,	  &t_string,	f_balloon_gettext},
+    {"balloon_show",	1, 1, FEARG_1,	  &t_void,	f_balloon_show},
 # if defined(FEAT_BEVAL_TERM)
-    {"balloon_split",	1, 1, FEARG_1,	  f_balloon_split},
+    {"balloon_split",	1, 1, FEARG_1,	  &t_list_string, f_balloon_split},
 # endif
 #endif
-    {"browse",		4, 4, 0,	  f_browse},
-    {"browsedir",	2, 2, 0,	  f_browsedir},
-    {"bufadd",		1, 1, FEARG_1,	  f_bufadd},
-    {"bufexists",	1, 1, FEARG_1,	  f_bufexists},
-    {"buffer_exists",	1, 1, FEARG_1,	  f_bufexists},	// obsolete
-    {"buffer_name",	0, 1, FEARG_1,	  f_bufname},	// obsolete
-    {"buffer_number",	0, 1, FEARG_1,	  f_bufnr},	// obsolete
-    {"buflisted",	1, 1, FEARG_1,	  f_buflisted},
-    {"bufload",		1, 1, FEARG_1,	  f_bufload},
-    {"bufloaded",	1, 1, FEARG_1,	  f_bufloaded},
-    {"bufname",		0, 1, FEARG_1,	  f_bufname},
-    {"bufnr",		0, 2, FEARG_1,	  f_bufnr},
-    {"bufwinid",	1, 1, FEARG_1,	  f_bufwinid},
-    {"bufwinnr",	1, 1, FEARG_1,	  f_bufwinnr},
-    {"byte2line",	1, 1, FEARG_1,	  f_byte2line},
-    {"byteidx",		2, 2, FEARG_1,	  f_byteidx},
-    {"byteidxcomp",	2, 2, FEARG_1,	  f_byteidxcomp},
-    {"call",		2, 3, FEARG_1,	  f_call},
+    {"browse",		4, 4, 0,	  &t_string,	f_browse},
+    {"browsedir",	2, 2, 0,	  &t_string,	f_browsedir},
+    {"bufadd",		1, 1, FEARG_1,	  &t_number,	f_bufadd},
+    {"bufexists",	1, 1, FEARG_1,	  &t_number,	f_bufexists},
+    {"buffer_exists",	1, 1, FEARG_1,	  &t_number,	f_bufexists},	// obsolete
+    {"buffer_name",	0, 1, FEARG_1,	  &t_string,	f_bufname},	// obsolete
+    {"buffer_number",	0, 1, FEARG_1,	  &t_number,	f_bufnr},	// obsolete
+    {"buflisted",	1, 1, FEARG_1,	  &t_number,	f_buflisted},
+    {"bufload",		1, 1, FEARG_1,	  &t_void,	f_bufload},
+    {"bufloaded",	1, 1, FEARG_1,	  &t_number,	f_bufloaded},
+    {"bufname",		0, 1, FEARG_1,	  &t_string,	f_bufname},
+    {"bufnr",		0, 2, FEARG_1,	  &t_number,	f_bufnr},
+    {"bufwinid",	1, 1, FEARG_1,	  &t_number,	f_bufwinid},
+    {"bufwinnr",	1, 1, FEARG_1,	  &t_number,	f_bufwinnr},
+    {"byte2line",	1, 1, FEARG_1,	  &t_number,	f_byte2line},
+    {"byteidx",		2, 2, FEARG_1,	  &t_number,	f_byteidx},
+    {"byteidxcomp",	2, 2, FEARG_1,	  &t_number,	f_byteidxcomp},
+    {"call",		2, 3, FEARG_1,	  &t_any,	f_call},
 #ifdef FEAT_FLOAT
-    {"ceil",		1, 1, FEARG_1,	  f_ceil},
+    {"ceil",		1, 1, FEARG_1,	  &t_float,	f_ceil},
 #endif
 #ifdef FEAT_JOB_CHANNEL
-    {"ch_canread",	1, 1, FEARG_1,	  f_ch_canread},
-    {"ch_close",	1, 1, FEARG_1,	  f_ch_close},
-    {"ch_close_in",	1, 1, FEARG_1,	  f_ch_close_in},
-    {"ch_evalexpr",	2, 3, FEARG_1,	  f_ch_evalexpr},
-    {"ch_evalraw",	2, 3, FEARG_1,	  f_ch_evalraw},
-    {"ch_getbufnr",	2, 2, FEARG_1,	  f_ch_getbufnr},
-    {"ch_getjob",	1, 1, FEARG_1,	  f_ch_getjob},
-    {"ch_info",		1, 1, FEARG_1,	  f_ch_info},
-    {"ch_log",		1, 2, FEARG_1,	  f_ch_log},
-    {"ch_logfile",	1, 2, FEARG_1,	  f_ch_logfile},
-    {"ch_open",		1, 2, FEARG_1,	  f_ch_open},
-    {"ch_read",		1, 2, FEARG_1,	  f_ch_read},
-    {"ch_readblob",	1, 2, FEARG_1,	  f_ch_readblob},
-    {"ch_readraw",	1, 2, FEARG_1,	  f_ch_readraw},
-    {"ch_sendexpr",	2, 3, FEARG_1,	  f_ch_sendexpr},
-    {"ch_sendraw",	2, 3, FEARG_1,	  f_ch_sendraw},
-    {"ch_setoptions",	2, 2, FEARG_1,	  f_ch_setoptions},
-    {"ch_status",	1, 2, FEARG_1,	  f_ch_status},
-#endif
-    {"changenr",	0, 0, 0,	  f_changenr},
-    {"char2nr",		1, 2, FEARG_1,	  f_char2nr},
-    {"chdir",		1, 1, FEARG_1,	  f_chdir},
-    {"cindent",		1, 1, FEARG_1,	  f_cindent},
-    {"clearmatches",	0, 1, FEARG_1,	  f_clearmatches},
-    {"col",		1, 1, FEARG_1,	  f_col},
-    {"complete",	2, 2, FEARG_2,	  f_complete},
-    {"complete_add",	1, 1, FEARG_1,	  f_complete_add},
-    {"complete_check",	0, 0, 0,	  f_complete_check},
-    {"complete_info",	0, 1, FEARG_1,	  f_complete_info},
-    {"confirm",		1, 4, FEARG_1,	  f_confirm},
-    {"copy",		1, 1, FEARG_1,	  f_copy},
+    {"ch_canread",	1, 1, FEARG_1,	  &t_number,	f_ch_canread},
+    {"ch_close",	1, 1, FEARG_1,	  &t_void,	f_ch_close},
+    {"ch_close_in",	1, 1, FEARG_1,	  &t_void,	f_ch_close_in},
+    {"ch_evalexpr",	2, 3, FEARG_1,	  &t_any,	f_ch_evalexpr},
+    {"ch_evalraw",	2, 3, FEARG_1,	  &t_any,	f_ch_evalraw},
+    {"ch_getbufnr",	2, 2, FEARG_1,	  &t_number,	f_ch_getbufnr},
+    {"ch_getjob",	1, 1, FEARG_1,	  &t_job,	f_ch_getjob},
+    {"ch_info",		1, 1, FEARG_1,	  &t_dict_any,	f_ch_info},
+    {"ch_log",		1, 2, FEARG_1,	  &t_void,	f_ch_log},
+    {"ch_logfile",	1, 2, FEARG_1,	  &t_void,	f_ch_logfile},
+    {"ch_open",		1, 2, FEARG_1,	  &t_channel,	f_ch_open},
+    {"ch_read",		1, 2, FEARG_1,	  &t_string,	f_ch_read},
+    {"ch_readblob",	1, 2, FEARG_1,	  &t_blob,	f_ch_readblob},
+    {"ch_readraw",	1, 2, FEARG_1,	  &t_string,	f_ch_readraw},
+    {"ch_sendexpr",	2, 3, FEARG_1,	  &t_void,	f_ch_sendexpr},
+    {"ch_sendraw",	2, 3, FEARG_1,	  &t_void,	f_ch_sendraw},
+    {"ch_setoptions",	2, 2, FEARG_1,	  &t_void,	f_ch_setoptions},
+    {"ch_status",	1, 2, FEARG_1,	  &t_string,	f_ch_status},
+#endif
+    {"changenr",	0, 0, 0,	  &t_number,	f_changenr},
+    {"char2nr",		1, 2, FEARG_1,	  &t_number,	f_char2nr},
+    {"chdir",		1, 1, FEARG_1,	  &t_string,	f_chdir},
+    {"cindent",		1, 1, FEARG_1,	  &t_number,	f_cindent},
+    {"clearmatches",	0, 1, FEARG_1,	  &t_void,	f_clearmatches},
+    {"col",		1, 1, FEARG_1,	  &t_number,	f_col},
+    {"complete",	2, 2, FEARG_2,	  &t_void,	f_complete},
+    {"complete_add",	1, 1, FEARG_1,	  &t_number,	f_complete_add},
+    {"complete_check",	0, 0, 0,	  &t_number,	f_complete_check},
+    {"complete_info",	0, 1, FEARG_1,	  &t_dict_any,	f_complete_info},
+    {"confirm",		1, 4, FEARG_1,	  &t_number,	f_confirm},
+    {"copy",		1, 1, FEARG_1,	  &t_any,	f_copy},
 #ifdef FEAT_FLOAT
-    {"cos",		1, 1, FEARG_1,	  f_cos},
-    {"cosh",		1, 1, FEARG_1,	  f_cosh},
-#endif
-    {"count",		2, 4, FEARG_1,	  f_count},
-    {"cscope_connection",0,3, 0,	  f_cscope_connection},
-    {"cursor",		1, 3, FEARG_1,	  f_cursor},
+    {"cos",		1, 1, FEARG_1,	  &t_float,	f_cos},
+    {"cosh",		1, 1, FEARG_1,	  &t_float,	f_cosh},
+#endif
+    {"count",		2, 4, FEARG_1,	  &t_number,	f_count},
+    {"cscope_connection",0,3, 0,	  &t_number,	f_cscope_connection},
+    {"cursor",		1, 3, FEARG_1,	  &t_number,	f_cursor},
 #ifdef MSWIN
-    {"debugbreak",	1, 1, FEARG_1,	  f_debugbreak},
-#endif
-    {"deepcopy",	1, 2, FEARG_1,	  f_deepcopy},
-    {"delete",		1, 2, FEARG_1,	  f_delete},
-    {"deletebufline",	2, 3, FEARG_1,	  f_deletebufline},
-    {"did_filetype",	0, 0, 0,	  f_did_filetype},
-    {"diff_filler",	1, 1, FEARG_1,	  f_diff_filler},
-    {"diff_hlID",	2, 2, FEARG_1,	  f_diff_hlID},
-    {"empty",		1, 1, FEARG_1,	  f_empty},
-    {"environ",		0, 0, 0,	  f_environ},
-    {"escape",		2, 2, FEARG_1,	  f_escape},
-    {"eval",		1, 1, FEARG_1,	  f_eval},
-    {"eventhandler",	0, 0, 0,	  f_eventhandler},
-    {"executable",	1, 1, FEARG_1,	  f_executable},
-    {"execute",		1, 2, FEARG_1,	  f_execute},
-    {"exepath",		1, 1, FEARG_1,	  f_exepath},
-    {"exists",		1, 1, FEARG_1,	  f_exists},
+    {"debugbreak",	1, 1, FEARG_1,	  &t_number,	f_debugbreak},
+#endif
+    {"deepcopy",	1, 2, FEARG_1,	  &t_any,	f_deepcopy},
+    {"delete",		1, 2, FEARG_1,	  &t_number,	f_delete},
+    {"deletebufline",	2, 3, FEARG_1,	  &t_number,	f_deletebufline},
+    {"did_filetype",	0, 0, 0,	  &t_number,	f_did_filetype},
+    {"diff_filler",	1, 1, FEARG_1,	  &t_number,	f_diff_filler},
+    {"diff_hlID",	2, 2, FEARG_1,	  &t_number,	f_diff_hlID},
+    {"empty",		1, 1, FEARG_1,	  &t_number,	f_empty},
+    {"environ",		0, 0, 0,	  &t_dict_string, f_environ},
+    {"escape",		2, 2, FEARG_1,	  &t_string,	f_escape},
+    {"eval",		1, 1, FEARG_1,	  &t_any,	f_eval},
+    {"eventhandler",	0, 0, 0,	  &t_number,	f_eventhandler},
+    {"executable",	1, 1, FEARG_1,	  &t_number,	f_executable},
+    {"execute",		1, 2, FEARG_1,	  &t_string,	f_execute},
+    {"exepath",		1, 1, FEARG_1,	  &t_string,	f_exepath},
+    {"exists",		1, 1, FEARG_1,	  &t_number,	f_exists},
 #ifdef FEAT_FLOAT
-    {"exp",		1, 1, FEARG_1,	  f_exp},
-#endif
-    {"expand",		1, 3, FEARG_1,	  f_expand},
-    {"expandcmd",	1, 1, FEARG_1,	  f_expandcmd},
-    {"extend",		2, 3, FEARG_1,	  f_extend},
-    {"feedkeys",	1, 2, FEARG_1,	  f_feedkeys},
-    {"file_readable",	1, 1, FEARG_1,	  f_filereadable},	// obsolete
-    {"filereadable",	1, 1, FEARG_1,	  f_filereadable},
-    {"filewritable",	1, 1, FEARG_1,	  f_filewritable},
-    {"filter",		2, 2, FEARG_1,	  f_filter},
-    {"finddir",		1, 3, FEARG_1,	  f_finddir},
-    {"findfile",	1, 3, FEARG_1,	  f_findfile},
+    {"exp",		1, 1, FEARG_1,	  &t_float,	f_exp},
+#endif
+    {"expand",		1, 3, FEARG_1,	  &t_any,	f_expand},
+    {"expandcmd",	1, 1, FEARG_1,	  &t_string,	f_expandcmd},
+    {"extend",		2, 3, FEARG_1,	  &t_any,	f_extend},
+    {"feedkeys",	1, 2, FEARG_1,	  &t_void,	f_feedkeys},
+    {"file_readable",	1, 1, FEARG_1,	  &t_number,	f_filereadable}, // obsolete
+    {"filereadable",	1, 1, FEARG_1,	  &t_number,	f_filereadable},
+    {"filewritable",	1, 1, FEARG_1,	  &t_number,	f_filewritable},
+    {"filter",		2, 2, FEARG_1,	  &t_any,	f_filter},
+    {"finddir",		1, 3, FEARG_1,	  &t_string,	f_finddir},
+    {"findfile",	1, 3, FEARG_1,	  &t_string,	f_findfile},
 #ifdef FEAT_FLOAT
-    {"float2nr",	1, 1, FEARG_1,	  f_float2nr},
-    {"floor",		1, 1, FEARG_1,	  f_floor},
-    {"fmod",		2, 2, FEARG_1,	  f_fmod},
-#endif
-    {"fnameescape",	1, 1, FEARG_1,	  f_fnameescape},
-    {"fnamemodify",	2, 2, FEARG_1,	  f_fnamemodify},
-    {"foldclosed",	1, 1, FEARG_1,	  f_foldclosed},
-    {"foldclosedend",	1, 1, FEARG_1,	  f_foldclosedend},
-    {"foldlevel",	1, 1, FEARG_1,	  f_foldlevel},
-    {"foldtext",	0, 0, 0,	  f_foldtext},
-    {"foldtextresult",	1, 1, FEARG_1,	  f_foldtextresult},
-    {"foreground",	0, 0, 0,	  f_foreground},
-    {"funcref",		1, 3, FEARG_1,	  f_funcref},
-    {"function",	1, 3, FEARG_1,	  f_function},
-    {"garbagecollect",	0, 1, 0,	  f_garbagecollect},
-    {"get",		2, 3, FEARG_1,	  f_get},
-    {"getbufinfo",	0, 1, 0,	  f_getbufinfo},
-    {"getbufline",	2, 3, FEARG_1,	  f_getbufline},
-    {"getbufvar",	2, 3, FEARG_1,	  f_getbufvar},
-    {"getchangelist",	0, 1, FEARG_1,	  f_getchangelist},
-    {"getchar",		0, 1, 0,	  f_getchar},
-    {"getcharmod",	0, 0, 0,	  f_getcharmod},
-    {"getcharsearch",	0, 0, 0,	  f_getcharsearch},
-    {"getcmdline",	0, 0, 0,	  f_getcmdline},
-    {"getcmdpos",	0, 0, 0,	  f_getcmdpos},
-    {"getcmdtype",	0, 0, 0,	  f_getcmdtype},
-    {"getcmdwintype",	0, 0, 0,	  f_getcmdwintype},
-    {"getcompletion",	2, 3, FEARG_1,	  f_getcompletion},
-    {"getcurpos",	0, 0, 0,	  f_getcurpos},
-    {"getcwd",		0, 2, FEARG_1,	  f_getcwd},
-    {"getenv",		1, 1, FEARG_1,	  f_getenv},
-    {"getfontname",	0, 1, 0,	  f_getfontname},
-    {"getfperm",	1, 1, FEARG_1,	  f_getfperm},
-    {"getfsize",	1, 1, FEARG_1,	  f_getfsize},
-    {"getftime",	1, 1, FEARG_1,	  f_getftime},
-    {"getftype",	1, 1, FEARG_1,	  f_getftype},
-    {"getimstatus",	0, 0, 0,	  f_getimstatus},
-    {"getjumplist",	0, 2, FEARG_1,	  f_getjumplist},
-    {"getline",		1, 2, FEARG_1,	  f_getline},
-    {"getloclist",	1, 2, 0,	  f_getloclist},
-    {"getmatches",	0, 1, 0,	  f_getmatches},
-    {"getmousepos",	0, 0, 0,	  f_getmousepos},
-    {"getpid",		0, 0, 0,	  f_getpid},
-    {"getpos",		1, 1, FEARG_1,	  f_getpos},
-    {"getqflist",	0, 1, 0,	  f_getqflist},
-    {"getreg",		0, 3, FEARG_1,	  f_getreg},
-    {"getregtype",	0, 1, FEARG_1,	  f_getregtype},
-    {"gettabinfo",	0, 1, FEARG_1,	  f_gettabinfo},
-    {"gettabvar",	2, 3, FEARG_1,	  f_gettabvar},
-    {"gettabwinvar",	3, 4, FEARG_1,	  f_gettabwinvar},
-    {"gettagstack",	0, 1, FEARG_1,	  f_gettagstack},
-    {"getwininfo",	0, 1, FEARG_1,	  f_getwininfo},
-    {"getwinpos",	0, 1, FEARG_1,	  f_getwinpos},
-    {"getwinposx",	0, 0, 0,	  f_getwinposx},
-    {"getwinposy",	0, 0, 0,	  f_getwinposy},
-    {"getwinvar",	2, 3, FEARG_1,	  f_getwinvar},
-    {"glob",		1, 4, FEARG_1,	  f_glob},
-    {"glob2regpat",	1, 1, FEARG_1,	  f_glob2regpat},
-    {"globpath",	2, 5, FEARG_2,	  f_globpath},
-    {"has",		1, 1, 0,	  f_has},
-    {"has_key",		2, 2, FEARG_1,	  f_has_key},
-    {"haslocaldir",	0, 2, FEARG_1,	  f_haslocaldir},
-    {"hasmapto",	1, 3, FEARG_1,	  f_hasmapto},
-    {"highlightID",	1, 1, FEARG_1,	  f_hlID},	// obsolete
-    {"highlight_exists",1, 1, FEARG_1,	  f_hlexists},	// obsolete
-    {"histadd",		2, 2, FEARG_2,	  f_histadd},
-    {"histdel",		1, 2, FEARG_1,	  f_histdel},
-    {"histget",		1, 2, FEARG_1,	  f_histget},
-    {"histnr",		1, 1, FEARG_1,	  f_histnr},
-    {"hlID",		1, 1, FEARG_1,	  f_hlID},
-    {"hlexists",	1, 1, FEARG_1,	  f_hlexists},
-    {"hostname",	0, 0, 0,	  f_hostname},
-    {"iconv",		3, 3, FEARG_1,	  f_iconv},
-    {"indent",		1, 1, FEARG_1,	  f_indent},
-    {"index",		2, 4, FEARG_1,	  f_index},
-    {"input",		1, 3, FEARG_1,	  f_input},
-    {"inputdialog",	1, 3, FEARG_1,	  f_inputdialog},
-    {"inputlist",	1, 1, FEARG_1,	  f_inputlist},
-    {"inputrestore",	0, 0, 0,	  f_inputrestore},
-    {"inputsave",	0, 0, 0,	  f_inputsave},
-    {"inputsecret",	1, 2, FEARG_1,	  f_inputsecret},
-    {"insert",		2, 3, FEARG_1,	  f_insert},
-    {"interrupt",	0, 0, 0,	  f_interrupt},
-    {"invert",		1, 1, FEARG_1,	  f_invert},
-    {"isdirectory",	1, 1, FEARG_1,	  f_isdirectory},
+    {"float2nr",	1, 1, FEARG_1,	  &t_number,	f_float2nr},
+    {"floor",		1, 1, FEARG_1,	  &t_float,	f_floor},
+    {"fmod",		2, 2, FEARG_1,	  &t_float,	f_fmod},
+#endif
+    {"fnameescape",	1, 1, FEARG_1,	  &t_string,	f_fnameescape},
+    {"fnamemodify",	2, 2, FEARG_1,	  &t_string,	f_fnamemodify},
+    {"foldclosed",	1, 1, FEARG_1,	  &t_number,	f_foldclosed},
+    {"foldclosedend",	1, 1, FEARG_1,	  &t_number,	f_foldclosedend},
+    {"foldlevel",	1, 1, FEARG_1,	  &t_number,	f_foldlevel},
+    {"foldtext",	0, 0, 0,	  &t_string,	f_foldtext},
+    {"foldtextresult",	1, 1, FEARG_1,	  &t_string,	f_foldtextresult},
+    {"foreground",	0, 0, 0,	  &t_void,	f_foreground},
+    {"funcref",		1, 3, FEARG_1,	  &t_any,	f_funcref},
+    {"function",	1, 3, FEARG_1,	  &t_any,	f_function},
+    {"garbagecollect",	0, 1, 0,	  &t_void,	f_garbagecollect},
+    {"get",		2, 3, FEARG_1,	  &t_any,	f_get},
+    {"getbufinfo",	0, 1, 0,	  &t_list_dict_any, f_getbufinfo},
+    {"getbufline",	2, 3, FEARG_1,	  &t_list_string, f_getbufline},
+    {"getbufvar",	2, 3, FEARG_1,	  &t_any,	f_getbufvar},
+    {"getchangelist",	0, 1, FEARG_1,	  &t_list_any,	f_getchangelist},
+    {"getchar",		0, 1, 0,	  &t_number,	f_getchar},
+    {"getcharmod",	0, 0, 0,	  &t_number,	f_getcharmod},
+    {"getcharsearch",	0, 0, 0,	  &t_dict_any,	f_getcharsearch},
+    {"getcmdline",	0, 0, 0,	  &t_string,	f_getcmdline},
+    {"getcmdpos",	0, 0, 0,	  &t_number,	f_getcmdpos},
+    {"getcmdtype",	0, 0, 0,	  &t_string,	f_getcmdtype},
+    {"getcmdwintype",	0, 0, 0,	  &t_string,	f_getcmdwintype},
+    {"getcompletion",	2, 3, FEARG_1,	  &t_list_string, f_getcompletion},
+    {"getcurpos",	0, 0, 0,	  &t_list_number, f_getcurpos},
+    {"getcwd",		0, 2, FEARG_1,	  &t_string,	f_getcwd},
+    {"getenv",		1, 1, FEARG_1,	  &t_string,	f_getenv},
+    {"getfontname",	0, 1, 0,	  &t_string,	f_getfontname},
+    {"getfperm",	1, 1, FEARG_1,	  &t_string,	f_getfperm},
+    {"getfsize",	1, 1, FEARG_1,	  &t_number,	f_getfsize},
+    {"getftime",	1, 1, FEARG_1,	  &t_number,	f_getftime},
+    {"getftype",	1, 1, FEARG_1,	  &t_string,	f_getftype},
+    {"getimstatus",	0, 0, 0,	  &t_number,	f_getimstatus},
+    {"getjumplist",	0, 2, FEARG_1,	  &t_list_any,	f_getjumplist},
+    {"getline",		1, 2, FEARG_1,	  &t_string,	f_getline},
+    {"getloclist",	1, 2, 0,	  &t_list_dict_any, f_getloclist},
+    {"getmatches",	0, 1, 0,	  &t_list_dict_any, f_getmatches},
+    {"getmousepos",	0, 0, 0,	  &t_dict_number, f_getmousepos},
+    {"getpid",		0, 0, 0,	  &t_number,	f_getpid},
+    {"getpos",		1, 1, FEARG_1,	  &t_list_number,	f_getpos},
+    {"getqflist",	0, 1, 0,	  &t_list_dict_any,	f_getqflist},
+    {"getreg",		0, 3, FEARG_1,	  &t_string,	f_getreg},
+    {"getregtype",	0, 1, FEARG_1,	  &t_string,	f_getregtype},
+    {"gettabinfo",	0, 1, FEARG_1,	  &t_list_dict_any,	f_gettabinfo},
+    {"gettabvar",	2, 3, FEARG_1,	  &t_any,	f_gettabvar},
+    {"gettabwinvar",	3, 4, FEARG_1,	  &t_any,	f_gettabwinvar},
+    {"gettagstack",	0, 1, FEARG_1,	  &t_dict_any,	f_gettagstack},
+    {"getwininfo",	0, 1, FEARG_1,	  &t_list_dict_any,	f_getwininfo},
+    {"getwinpos",	0, 1, FEARG_1,	  &t_list_number,	f_getwinpos},
+    {"getwinposx",	0, 0, 0,	  &t_number,	f_getwinposx},
+    {"getwinposy",	0, 0, 0,	  &t_number,	f_getwinposy},
+    {"getwinvar",	2, 3, FEARG_1,	  &t_any,	f_getwinvar},
+    {"glob",		1, 4, FEARG_1,	  &t_any,	f_glob},
+    {"glob2regpat",	1, 1, FEARG_1,	  &t_string,	f_glob2regpat},
+    {"globpath",	2, 5, FEARG_2,	  &t_any,	f_globpath},
+    {"has",		1, 1, 0,	  &t_number,	f_has},
+    {"has_key",		2, 2, FEARG_1,	  &t_number,	f_has_key},
+    {"haslocaldir",	0, 2, FEARG_1,	  &t_number,	f_haslocaldir},
+    {"hasmapto",	1, 3, FEARG_1,	  &t_number,	f_hasmapto},
+    {"highlightID",	1, 1, FEARG_1,	  &t_number,	f_hlID},	// obsolete
+    {"highlight_exists",1, 1, FEARG_1,	  &t_number,	f_hlexists},	// obsolete
+    {"histadd",		2, 2, FEARG_2,	  &t_number,	f_histadd},
+    {"histdel",		1, 2, FEARG_1,	  &t_number,	f_histdel},
+    {"histget",		1, 2, FEARG_1,	  &t_string,	f_histget},
+    {"histnr",		1, 1, FEARG_1,	  &t_number,	f_histnr},
+    {"hlID",		1, 1, FEARG_1,	  &t_number,	f_hlID},
+    {"hlexists",	1, 1, FEARG_1,	  &t_number,	f_hlexists},
+    {"hostname",	0, 0, 0,	  &t_string,	f_hostname},
+    {"iconv",		3, 3, FEARG_1,	  &t_string,	f_iconv},
+    {"indent",		1, 1, FEARG_1,	  &t_number,	f_indent},
+    {"index",		2, 4, FEARG_1,	  &t_number,	f_index},
+    {"input",		1, 3, FEARG_1,	  &t_string,	f_input},
+    {"inputdialog",	1, 3, FEARG_1,	  &t_string,	f_inputdialog},
+    {"inputlist",	1, 1, FEARG_1,	  &t_number,	f_inputlist},
+    {"inputrestore",	0, 0, 0,	  &t_number,	f_inputrestore},
+    {"inputsave",	0, 0, 0,	  &t_number,	f_inputsave},
+    {"inputsecret",	1, 2, FEARG_1,	  &t_string,	f_inputsecret},
+    {"insert",		2, 3, FEARG_1,	  &t_any,	f_insert},
+    {"interrupt",	0, 0, 0,	  &t_void,	f_interrupt},
+    {"invert",		1, 1, FEARG_1,	  &t_number,	f_invert},
+    {"isdirectory",	1, 1, FEARG_1,	  &t_number,	f_isdirectory},
 #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
-    {"isinf",		1, 1, FEARG_1,	  f_isinf},
-#endif
-    {"islocked",	1, 1, FEARG_1,	  f_islocked},
+    {"isinf",		1, 1, FEARG_1,	  &t_number,	f_isinf},
+#endif
+    {"islocked",	1, 1, FEARG_1,	  &t_number,	f_islocked},
 #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
-    {"isnan",		1, 1, FEARG_1,	  f_isnan},
-#endif
-    {"items",		1, 1, FEARG_1,	  f_items},
+    {"isnan",		1, 1, FEARG_1,	  &t_number,	f_isnan},
+#endif
+    {"items",		1, 1, FEARG_1,	  &t_list_any,	f_items},
 #ifdef FEAT_JOB_CHANNEL
-    {"job_getchannel",	1, 1, FEARG_1,	  f_job_getchannel},
-    {"job_info",	0, 1, FEARG_1,	  f_job_info},
-    {"job_setoptions",	2, 2, FEARG_1,	  f_job_setoptions},
-    {"job_start",	1, 2, FEARG_1,	  f_job_start},
-    {"job_status",	1, 1, FEARG_1,	  f_job_status},
-    {"job_stop",	1, 2, FEARG_1,	  f_job_stop},
-#endif
-    {"join",		1, 2, FEARG_1,	  f_join},
-    {"js_decode",	1, 1, FEARG_1,	  f_js_decode},
-    {"js_encode",	1, 1, FEARG_1,	  f_js_encode},
-    {"json_decode",	1, 1, FEARG_1,	  f_json_decode},
-    {"json_encode",	1, 1, FEARG_1,	  f_json_encode},
-    {"keys",		1, 1, FEARG_1,	  f_keys},
-    {"last_buffer_nr",	0, 0, 0,	  f_last_buffer_nr}, // obsolete
-    {"len",		1, 1, FEARG_1,	  f_len},
-    {"libcall",		3, 3, FEARG_3,	  f_libcall},
-    {"libcallnr",	3, 3, FEARG_3,	  f_libcallnr},
-    {"line",		1, 2, FEARG_1,	  f_line},
-    {"line2byte",	1, 1, FEARG_1,	  f_line2byte},
-    {"lispindent",	1, 1, FEARG_1,	  f_lispindent},
-    {"list2str",	1, 2, FEARG_1,	  f_list2str},
-    {"listener_add",	1, 2, FEARG_2,	  f_listener_add},
-    {"listener_flush",	0, 1, FEARG_1,	  f_listener_flush},
-    {"listener_remove",	1, 1, FEARG_1,	  f_listener_remove},
-    {"localtime",	0, 0, 0,	  f_localtime},
+    {"job_getchannel",	1, 1, FEARG_1,	  &t_channel,	f_job_getchannel},
+    {"job_info",	0, 1, FEARG_1,	  &t_dict_any,	f_job_info},
+    {"job_setoptions",	2, 2, FEARG_1,	  &t_void,	f_job_setoptions},
+    {"job_start",	1, 2, FEARG_1,	  &t_job,	f_job_start},
+    {"job_status",	1, 1, FEARG_1,	  &t_string,	f_job_status},
+    {"job_stop",	1, 2, FEARG_1,	  &t_number,	f_job_stop},
+#endif
+    {"join",		1, 2, FEARG_1,	  &t_string,	f_join},
+    {"js_decode",	1, 1, FEARG_1,	  &t_any,	f_js_decode},
+    {"js_encode",	1, 1, FEARG_1,	  &t_string,	f_js_encode},
+    {"json_decode",	1, 1, FEARG_1,	  &t_any,	f_json_decode},
+    {"json_encode",	1, 1, FEARG_1,	  &t_string,	f_json_encode},
+    {"keys",		1, 1, FEARG_1,	  &t_list_any,	f_keys},
+    {"last_buffer_nr",	0, 0, 0,	  &t_number,	f_last_buffer_nr}, // obsolete
+    {"len",		1, 1, FEARG_1,	  &t_number,	f_len},
+    {"libcall",		3, 3, FEARG_3,	  &t_string,	f_libcall},
+    {"libcallnr",	3, 3, FEARG_3,	  &t_number,	f_libcallnr},
+    {"line",		1, 2, FEARG_1,	  &t_number,	f_line},
+    {"line2byte",	1, 1, FEARG_1,	  &t_number,	f_line2byte},
+    {"lispindent",	1, 1, FEARG_1,	  &t_number,	f_lispindent},
+    {"list2str",	1, 2, FEARG_1,	  &t_string,	f_list2str},
+    {"listener_add",	1, 2, FEARG_2,	  &t_number,	f_listener_add},
+    {"listener_flush",	0, 1, FEARG_1,	  &t_void,	f_listener_flush},
+    {"listener_remove",	1, 1, FEARG_1,	  &t_number,	f_listener_remove},
+    {"localtime",	0, 0, 0,	  &t_number,	f_localtime},
 #ifdef FEAT_FLOAT
-    {"log",		1, 1, FEARG_1,	  f_log},
-    {"log10",		1, 1, FEARG_1,	  f_log10},
+    {"log",		1, 1, FEARG_1,	  &t_float,	f_log},
+    {"log10",		1, 1, FEARG_1,	  &t_float,	f_log10},
 #endif
 #ifdef FEAT_LUA
-    {"luaeval",		1, 2, FEARG_1,	  f_luaeval},
-#endif
-    {"map",		2, 2, FEARG_1,	  f_map},
-    {"maparg",		1, 4, FEARG_1,	  f_maparg},
-    {"mapcheck",	1, 3, FEARG_1,	  f_mapcheck},
-    {"match",		2, 4, FEARG_1,	  f_match},
-    {"matchadd",	2, 5, FEARG_1,	  f_matchadd},
-    {"matchaddpos",	2, 5, FEARG_1,	  f_matchaddpos},
-    {"matcharg",	1, 1, FEARG_1,	  f_matcharg},
-    {"matchdelete",	1, 2, FEARG_1,	  f_matchdelete},
-    {"matchend",	2, 4, FEARG_1,	  f_matchend},
-    {"matchlist",	2, 4, FEARG_1,	  f_matchlist},
-    {"matchstr",	2, 4, FEARG_1,	  f_matchstr},
-    {"matchstrpos",	2, 4, FEARG_1,	  f_matchstrpos},
-    {"max",		1, 1, FEARG_1,	  f_max},
-    {"min",		1, 1, FEARG_1,	  f_min},
-    {"mkdir",		1, 3, FEARG_1,	  f_mkdir},
-    {"mode",		0, 1, FEARG_1,	  f_mode},
+    {"luaeval",		1, 2, FEARG_1,	  &t_any,	f_luaeval},
+#endif
+    {"map",		2, 2, FEARG_1,	  &t_any,	f_map},
+    {"maparg",		1, 4, FEARG_1,	  &t_string,	f_maparg},
+    {"mapcheck",	1, 3, FEARG_1,	  &t_string,	f_mapcheck},
+    {"match",		2, 4, FEARG_1,	  &t_any,	f_match},
+    {"matchadd",	2, 5, FEARG_1,	  &t_number,	f_matchadd},
+    {"matchaddpos",	2, 5, FEARG_1,	  &t_number,	f_matchaddpos},
+    {"matcharg",	1, 1, FEARG_1,	  &t_list_string, f_matcharg},
+    {"matchdelete",	1, 2, FEARG_1,	  &t_number,	f_matchdelete},
+    {"matchend",	2, 4, FEARG_1,	  &t_number,	f_matchend},
+    {"matchlist",	2, 4, FEARG_1,	  &t_list_any,	f_matchlist},
+    {"matchstr",	2, 4, FEARG_1,	  &t_string,	f_matchstr},
+    {"matchstrpos",	2, 4, FEARG_1,	  &t_list_any,	f_matchstrpos},
+    {"max",		1, 1, FEARG_1,	  &t_any,	f_max},
+    {"min",		1, 1, FEARG_1,	  &t_any,	f_min},
+    {"mkdir",		1, 3, FEARG_1,	  &t_number,	f_mkdir},
+    {"mode",		0, 1, FEARG_1,	  &t_string,	f_mode},
 #ifdef FEAT_MZSCHEME
-    {"mzeval",		1, 1, FEARG_1,	  f_mzeval},
-#endif
-    {"nextnonblank",	1, 1, FEARG_1,	  f_nextnonblank},
-    {"nr2char",		1, 2, FEARG_1,	  f_nr2char},
-    {"or",		2, 2, FEARG_1,	  f_or},
-    {"pathshorten",	1, 1, FEARG_1,	  f_pathshorten},
+    {"mzeval",		1, 1, FEARG_1,	  &t_any,	f_mzeval},
+#endif
+    {"nextnonblank",	1, 1, FEARG_1,	  &t_number,	f_nextnonblank},
+    {"nr2char",		1, 2, FEARG_1,	  &t_string,	f_nr2char},
+    {"or",		2, 2, FEARG_1,	  &t_number,	f_or},
+    {"pathshorten",	1, 1, FEARG_1,	  &t_string,	f_pathshorten},
 #ifdef FEAT_PERL
-    {"perleval",	1, 1, FEARG_1,	  f_perleval},
+    {"perleval",	1, 1, FEARG_1,	  &t_any,	f_perleval},
 #endif
 #ifdef FEAT_PROP_POPUP
-    {"popup_atcursor",	2, 2, FEARG_1,	  f_popup_atcursor},
-    {"popup_beval",	2, 2, FEARG_1,	  f_popup_beval},
-    {"popup_clear",	0, 0, 0,	  f_popup_clear},
-    {"popup_close",	1, 2, FEARG_1,	  f_popup_close},
-    {"popup_create",	2, 2, FEARG_1,	  f_popup_create},
-    {"popup_dialog",	2, 2, FEARG_1,	  f_popup_dialog},
-    {"popup_filter_menu", 2, 2, 0,	  f_popup_filter_menu},
-    {"popup_filter_yesno", 2, 2, 0,	  f_popup_filter_yesno},
-    {"popup_findinfo",	0, 0, 0,	  f_popup_findinfo},
-    {"popup_findpreview", 0, 0, 0,	  f_popup_findpreview},
-    {"popup_getoptions", 1, 1, FEARG_1,	  f_popup_getoptions},
-    {"popup_getpos",	1, 1, FEARG_1,	  f_popup_getpos},
-    {"popup_hide",	1, 1, FEARG_1,	  f_popup_hide},
-    {"popup_locate",	2, 2, 0,	  f_popup_locate},
-    {"popup_menu",	2, 2, FEARG_1,	  f_popup_menu},
-    {"popup_move",	2, 2, FEARG_1,	  f_popup_move},
-    {"popup_notification", 2, 2, FEARG_1, f_popup_notification},
-    {"popup_setoptions", 2, 2, FEARG_1,	  f_popup_setoptions},
-    {"popup_settext",	2, 2, FEARG_1,	  f_popup_settext},
-    {"popup_show",	1, 1, FEARG_1,	  f_popup_show},
+    {"popup_atcursor",	2, 2, FEARG_1,	  &t_number,	f_popup_atcursor},
+    {"popup_beval",	2, 2, FEARG_1,	  &t_number,	f_popup_beval},
+    {"popup_clear",	0, 0, 0,	  &t_void,	f_popup_clear},
+    {"popup_close",	1, 2, FEARG_1,	  &t_void,	f_popup_close},
+    {"popup_create",	2, 2, FEARG_1,	  &t_number,	f_popup_create},
+    {"popup_dialog",	2, 2, FEARG_1,	  &t_number,	f_popup_dialog},
+    {"popup_filter_menu", 2, 2, 0,	  &t_number,	f_popup_filter_menu},
+    {"popup_filter_yesno", 2, 2, 0,	  &t_number,	f_popup_filter_yesno},
+    {"popup_findinfo",	0, 0, 0,	  &t_number,	f_popup_findinfo},
+    {"popup_findpreview", 0, 0, 0,	  &t_number,	f_popup_findpreview},
+    {"popup_getoptions", 1, 1, FEARG_1,	  &t_dict_any,	f_popup_getoptions},
+    {"popup_getpos",	1, 1, FEARG_1,	  &t_dict_any,	f_popup_getpos},
+    {"popup_hide",	1, 1, FEARG_1,	  &t_void,	f_popup_hide},
+    {"popup_locate",	2, 2, 0,	  &t_number,	f_popup_locate},
+    {"popup_menu",	2, 2, FEARG_1,	  &t_number,	f_popup_menu},
+    {"popup_move",	2, 2, FEARG_1,	  &t_void,	f_popup_move},
+    {"popup_notification", 2, 2, FEARG_1, &t_number,	f_popup_notification},
+    {"popup_setoptions", 2, 2, FEARG_1,	  &t_void,	f_popup_setoptions},
+    {"popup_settext",	2, 2, FEARG_1,	  &t_void,	f_popup_settext},
+    {"popup_show",	1, 1, FEARG_1,	  &t_void,	f_popup_show},
 #endif
 #ifdef FEAT_FLOAT
-    {"pow",		2, 2, FEARG_1,	  f_pow},
-#endif
-    {"prevnonblank",	1, 1, FEARG_1,	  f_prevnonblank},
-    {"printf",		1, 19, FEARG_2,	  f_printf},
+    {"pow",		2, 2, FEARG_1,	  &t_float,	f_pow},
+#endif
+    {"prevnonblank",	1, 1, FEARG_1,	  &t_number,	f_prevnonblank},
+    {"printf",		1, 19, FEARG_2,	  &t_string,	f_printf},
 #ifdef FEAT_JOB_CHANNEL
-    {"prompt_setcallback", 2, 2, FEARG_1,  f_prompt_setcallback},
-    {"prompt_setinterrupt", 2, 2, FEARG_1, f_prompt_setinterrupt},
-    {"prompt_setprompt", 2, 2, FEARG_1,	   f_prompt_setprompt},
+    {"prompt_setcallback", 2, 2, FEARG_1, &t_void,	 f_prompt_setcallback},
+    {"prompt_setinterrupt", 2, 2, FEARG_1,&t_void,	 f_prompt_setinterrupt},
+    {"prompt_setprompt", 2, 2, FEARG_1,	  &t_void,	 f_prompt_setprompt},
 #endif
 #ifdef FEAT_PROP_POPUP
-    {"prop_add",	3, 3, FEARG_1,	  f_prop_add},
-    {"prop_clear",	1, 3, FEARG_1,	  f_prop_clear},
-    {"prop_find",	1, 2, FEARG_1,	  f_prop_find},
-    {"prop_list",	1, 2, FEARG_1,	  f_prop_list},
-    {"prop_remove",	1, 3, FEARG_1,	  f_prop_remove},
-    {"prop_type_add",	2, 2, FEARG_1,	  f_prop_type_add},
-    {"prop_type_change", 2, 2, FEARG_1,	  f_prop_type_change},
-    {"prop_type_delete", 1, 2, FEARG_1,	  f_prop_type_delete},
-    {"prop_type_get",	1, 2, FEARG_1,	  f_prop_type_get},
-    {"prop_type_list",	0, 1, FEARG_1,	  f_prop_type_list},
-#endif
-    {"pum_getpos",	0, 0, 0,	  f_pum_getpos},
-    {"pumvisible",	0, 0, 0,	  f_pumvisible},
+    {"prop_add",	3, 3, FEARG_1,	  &t_void,	f_prop_add},
+    {"prop_clear",	1, 3, FEARG_1,	  &t_void,	f_prop_clear},
+    {"prop_find",	1, 2, FEARG_1,	  &t_dict_any,	f_prop_find},
+    {"prop_list",	1, 2, FEARG_1,	  &t_list_any,	f_prop_list},
+    {"prop_remove",	1, 3, FEARG_1,	  &t_number,	f_prop_remove},
+    {"prop_type_add",	2, 2, FEARG_1,	  &t_void,	f_prop_type_add},
+    {"prop_type_change", 2, 2, FEARG_1,	  &t_void,	f_prop_type_change},
+    {"prop_type_delete", 1, 2, FEARG_1,	  &t_void,	f_prop_type_delete},
+    {"prop_type_get",	1, 2, FEARG_1,	  &t_dict_any,	f_prop_type_get},
+    {"prop_type_list",	0, 1, FEARG_1,	  &t_list_string, f_prop_type_list},
+#endif
+    {"pum_getpos",	0, 0, 0,	  &t_dict_number, f_pum_getpos},
+    {"pumvisible",	0, 0, 0,	  &t_number,	f_pumvisible},
 #ifdef FEAT_PYTHON3
-    {"py3eval",		1, 1, FEARG_1,	  f_py3eval},
+    {"py3eval",		1, 1, FEARG_1,	  &t_any,	f_py3eval},
 #endif
 #ifdef FEAT_PYTHON
-    {"pyeval",		1, 1, FEARG_1,	  f_pyeval},
+    {"pyeval",		1, 1, FEARG_1,	  &t_any,	f_pyeval},
 #endif
 #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
-    {"pyxeval",		1, 1, FEARG_1,	  f_pyxeval},
-#endif
-    {"rand",		0, 1, FEARG_1,	  f_rand},
-    {"range",		1, 3, FEARG_1,	  f_range},
-    {"readdir",		1, 2, FEARG_1,	  f_readdir},
-    {"readfile",	1, 3, FEARG_1,	  f_readfile},
-    {"reg_executing",	0, 0, 0,	  f_reg_executing},
-    {"reg_recording",	0, 0, 0,	  f_reg_recording},
-    {"reltime",		0, 2, FEARG_1,	  f_reltime},
+    {"pyxeval",		1, 1, FEARG_1,	  &t_any,	f_pyxeval},
+#endif
+    {"rand",		0, 1, FEARG_1,	  &t_number,	f_rand},
+    {"range",		1, 3, FEARG_1,	  &t_list_number, f_range},
+    {"readdir",		1, 2, FEARG_1,	  &t_list_string, f_readdir},
+    {"readfile",	1, 3, FEARG_1,	  &t_any,	f_readfile},
+    {"reg_executing",	0, 0, 0,	  &t_string,	f_reg_executing},
+    {"reg_recording",	0, 0, 0,	  &t_string,	f_reg_recording},
+    {"reltime",		0, 2, FEARG_1,	  &t_list_any,	f_reltime},
 #ifdef FEAT_FLOAT
-    {"reltimefloat",	1, 1, FEARG_1,	  f_reltimefloat},
-#endif
-    {"reltimestr",	1, 1, FEARG_1,	  f_reltimestr},
-    {"remote_expr",	2, 4, FEARG_1,	  f_remote_expr},
-    {"remote_foreground", 1, 1, FEARG_1,  f_remote_foreground},
-    {"remote_peek",	1, 2, FEARG_1,	  f_remote_peek},
-    {"remote_read",	1, 2, FEARG_1,	  f_remote_read},
-    {"remote_send",	2, 3, FEARG_1,	  f_remote_send},
-    {"remote_startserver", 1, 1, FEARG_1,  f_remote_startserver},
-    {"remove",		2, 3, FEARG_1,	  f_remove},
-    {"rename",		2, 2, FEARG_1,	  f_rename},
-    {"repeat",		2, 2, FEARG_1,	  f_repeat},
-    {"resolve",		1, 1, FEARG_1,	  f_resolve},
-    {"reverse",		1, 1, FEARG_1,	  f_reverse},
+    {"reltimefloat",	1, 1, FEARG_1,	  &t_float,	f_reltimefloat},
+#endif
+    {"reltimestr",	1, 1, FEARG_1,	  &t_string,	f_reltimestr},
+    {"remote_expr",	2, 4, FEARG_1,	  &t_string,	f_remote_expr},
+    {"remote_foreground", 1, 1, FEARG_1,  &t_string,	f_remote_foreground},
+    {"remote_peek",	1, 2, FEARG_1,	  &t_number,	f_remote_peek},
+    {"remote_read",	1, 2, FEARG_1,	  &t_string,	f_remote_read},
+    {"remote_send",	2, 3, FEARG_1,	  &t_string,	f_remote_send},
+    {"remote_startserver", 1, 1, FEARG_1, &t_void,	 f_remote_startserver},
+    {"remove",		2, 3, FEARG_1,	  &t_any,	f_remove},
+    {"rename",		2, 2, FEARG_1,	  &t_number,	f_rename},
+    {"repeat",		2, 2, FEARG_1,	  &t_any,	f_repeat},
+    {"resolve",		1, 1, FEARG_1,	  &t_string,	f_resolve},
+    {"reverse",		1, 1, FEARG_1,	  &t_any,	f_reverse},
 #ifdef FEAT_FLOAT
-    {"round",		1, 1, FEARG_1,	  f_round},
+    {"round",		1, 1, FEARG_1,	  &t_float,	f_round},
 #endif
 #ifdef FEAT_RUBY
-    {"rubyeval",	1, 1, FEARG_1,	  f_rubyeval},
-#endif
-    {"screenattr",	2, 2, FEARG_1,	  f_screenattr},
-    {"screenchar",	2, 2, FEARG_1,	  f_screenchar},
-    {"screenchars",	2, 2, FEARG_1,	  f_screenchars},
-    {"screencol",	0, 0, 0,	  f_screencol},
-    {"screenpos",	3, 3, FEARG_1,	  f_screenpos},
-    {"screenrow",	0, 0, 0,	  f_screenrow},
-    {"screenstring",	2, 2, FEARG_1,	  f_screenstring},
-    {"search",		1, 4, FEARG_1,	  f_search},
-    {"searchdecl",	1, 3, FEARG_1,	  f_searchdecl},
-    {"searchpair",	3, 7, 0,	  f_searchpair},
-    {"searchpairpos",	3, 7, 0,	  f_searchpairpos},
-    {"searchpos",	1, 4, FEARG_1,	  f_searchpos},
-    {"server2client",	2, 2, FEARG_1,	  f_server2client},
-    {"serverlist",	0, 0, 0,	  f_serverlist},
-    {"setbufline",	3, 3, FEARG_3,	  f_setbufline},
-    {"setbufvar",	3, 3, FEARG_3,	  f_setbufvar},
-    {"setcharsearch",	1, 1, FEARG_1,	  f_setcharsearch},
-    {"setcmdpos",	1, 1, FEARG_1,	  f_setcmdpos},
-    {"setenv",		2, 2, FEARG_2,	  f_setenv},
-    {"setfperm",	2, 2, FEARG_1,	  f_setfperm},
-    {"setline",		2, 2, FEARG_2,	  f_setline},
-    {"setloclist",	2, 4, FEARG_2,	  f_setloclist},
-    {"setmatches",	1, 2, FEARG_1,	  f_setmatches},
-    {"setpos",		2, 2, FEARG_2,	  f_setpos},
-    {"setqflist",	1, 3, FEARG_1,	  f_setqflist},
-    {"setreg",		2, 3, FEARG_2,	  f_setreg},
-    {"settabvar",	3, 3, FEARG_3,	  f_settabvar},
-    {"settabwinvar",	4, 4, FEARG_4,	  f_settabwinvar},
-    {"settagstack",	2, 3, FEARG_2,	  f_settagstack},
-    {"setwinvar",	3, 3, FEARG_3,	  f_setwinvar},
+    {"rubyeval",	1, 1, FEARG_1,	  &t_any,	f_rubyeval},
+#endif
+    {"screenattr",	2, 2, FEARG_1,	  &t_number,	f_screenattr},
+    {"screenchar",	2, 2, FEARG_1,	  &t_number,	f_screenchar},
+    {"screenchars",	2, 2, FEARG_1,	  &t_list_number, f_screenchars},
+    {"screencol",	0, 0, 0,	  &t_number,	f_screencol},
+    {"screenpos",	3, 3, FEARG_1,	  &t_dict_number, f_screenpos},
+    {"screenrow",	0, 0, 0,	  &t_number,	f_screenrow},
+    {"screenstring",	2, 2, FEARG_1,	  &t_string,	f_screenstring},
+    {"search",		1, 4, FEARG_1,	  &t_number,	f_search},
+    {"searchdecl",	1, 3, FEARG_1,	  &t_number,	f_searchdecl},
+    {"searchpair",	3, 7, 0,	  &t_number,	f_searchpair},
+    {"searchpairpos",	3, 7, 0,	  &t_list_number, f_searchpairpos},
+    {"searchpos",	1, 4, FEARG_1,	  &t_list_number, f_searchpos},
+    {"server2client",	2, 2, FEARG_1,	  &t_number,	f_server2client},
+    {"serverlist",	0, 0, 0,	  &t_string,	f_serverlist},
+    {"setbufline",	3, 3, FEARG_3,	  &t_number,	f_setbufline},
+    {"setbufvar",	3, 3, FEARG_3,	  &t_void,	f_setbufvar},
+    {"setcharsearch",	1, 1, FEARG_1,	  &t_void,	f_setcharsearch},
+    {"setcmdpos",	1, 1, FEARG_1,	  &t_number,	f_setcmdpos},
+    {"setenv",		2, 2, FEARG_2,	  &t_void,	f_setenv},
+    {"setfperm",	2, 2, FEARG_1,	  &t_number,	f_setfperm},
+    {"setline",		2, 2, FEARG_2,	  &t_number,	f_setline},
+    {"setloclist",	2, 4, FEARG_2,	  &t_number,	f_setloclist},
+    {"setmatches",	1, 2, FEARG_1,	  &t_number,	f_setmatches},
+    {"setpos",		2, 2, FEARG_2,	  &t_number,	f_setpos},
+    {"setqflist",	1, 3, FEARG_1,	  &t_number,	f_setqflist},
+    {"setreg",		2, 3, FEARG_2,	  &t_number,	f_setreg},
+    {"settabvar",	3, 3, FEARG_3,	  &t_void,	f_settabvar},
+    {"settabwinvar",	4, 4, FEARG_4,	  &t_void,	f_settabwinvar},
+    {"settagstack",	2, 3, FEARG_2,	  &t_number,	f_settagstack},
+    {"setwinvar",	3, 3, FEARG_3,	  &t_void,	f_setwinvar},
 #ifdef FEAT_CRYPT
-    {"sha256",		1, 1, FEARG_1,	  f_sha256},
-#endif
-    {"shellescape",	1, 2, FEARG_1,	  f_shellescape},
-    {"shiftwidth",	0, 1, FEARG_1,	  f_shiftwidth},
+    {"sha256",		1, 1, FEARG_1,	  &t_string,	f_sha256},
+#endif
+    {"shellescape",	1, 2, FEARG_1,	  &t_string,	f_shellescape},
+    {"shiftwidth",	0, 1, FEARG_1,	  &t_number,	f_shiftwidth},
 #ifdef FEAT_SIGNS
-    {"sign_define",	1, 2, FEARG_1,	  f_sign_define},
-    {"sign_getdefined",	0, 1, FEARG_1,	  f_sign_getdefined},
-    {"sign_getplaced",	0, 2, FEARG_1,	  f_sign_getplaced},
-    {"sign_jump",	3, 3, FEARG_1,	  f_sign_jump},
-    {"sign_place",	4, 5, FEARG_1,	  f_sign_place},
-    {"sign_placelist",	1, 1, FEARG_1,	  f_sign_placelist},
-    {"sign_undefine",	0, 1, FEARG_1,	  f_sign_undefine},
-    {"sign_unplace",	1, 2, FEARG_1,	  f_sign_unplace},
-    {"sign_unplacelist", 1, 2, FEARG_1,	  f_sign_unplacelist},
-#endif
-    {"simplify",	1, 1, 0,	  f_simplify},
+    {"sign_define",	1, 2, FEARG_1,	  &t_any,	f_sign_define},
+    {"sign_getdefined",	0, 1, FEARG_1,	  &t_list_dict_any, f_sign_getdefined},
+    {"sign_getplaced",	0, 2, FEARG_1,	  &t_list_dict_any, f_sign_getplaced},
+    {"sign_jump",	3, 3, FEARG_1,	  &t_number,	f_sign_jump},
+    {"sign_place",	4, 5, FEARG_1,	  &t_number,	f_sign_place},
+    {"sign_placelist",	1, 1, FEARG_1,	  &t_list_number, f_sign_placelist},
+    {"sign_undefine",	0, 1, FEARG_1,	  &t_number,	f_sign_undefine},
+    {"sign_unplace",	1, 2, FEARG_1,	  &t_number,	f_sign_unplace},
+    {"sign_unplacelist", 1, 2, FEARG_1,	  &t_list_number, f_sign_unplacelist},
+#endif
+    {"simplify",	1, 1, 0,	  &t_string,	f_simplify},
 #ifdef FEAT_FLOAT
-    {"sin",		1, 1, FEARG_1,	  f_sin},
-    {"sinh",		1, 1, FEARG_1,	  f_sinh},
-#endif
-    {"sort",		1, 3, FEARG_1,	  f_sort},
+    {"sin",		1, 1, FEARG_1,	  &t_float,	f_sin},
+    {"sinh",		1, 1, FEARG_1,	  &t_float,	f_sinh},
+#endif
+    {"sort",		1, 3, FEARG_1,	  &t_list_any,	f_sort},
 #ifdef FEAT_SOUND
-    {"sound_clear",	0, 0, 0,	  f_sound_clear},
-    {"sound_playevent",	1, 2, FEARG_1,	  f_sound_playevent},
-    {"sound_playfile",	1, 2, FEARG_1,	  f_sound_playfile},
-    {"sound_stop",	1, 1, FEARG_1,	  f_sound_stop},
-#endif
-    {"soundfold",	1, 1, FEARG_1,	  f_soundfold},
-    {"spellbadword",	0, 1, FEARG_1,	  f_spellbadword},
-    {"spellsuggest",	1, 3, FEARG_1,	  f_spellsuggest},
-    {"split",		1, 3, FEARG_1,	  f_split},
+    {"sound_clear",	0, 0, 0,	  &t_void,	f_sound_clear},
+    {"sound_playevent",	1, 2, FEARG_1,	  &t_number,	f_sound_playevent},
+    {"sound_playfile",	1, 2, FEARG_1,	  &t_number,	f_sound_playfile},
+    {"sound_stop",	1, 1, FEARG_1,	  &t_void,	f_sound_stop},
+#endif
+    {"soundfold",	1, 1, FEARG_1,	  &t_string,	f_soundfold},
+    {"spellbadword",	0, 1, FEARG_1,	  &t_list_string, f_spellbadword},
+    {"spellsuggest",	1, 3, FEARG_1,	  &t_list_string, f_spellsuggest},
+    {"split",		1, 3, FEARG_1,	  &t_list_string, f_split},
 #ifdef FEAT_FLOAT
-    {"sqrt",		1, 1, FEARG_1,	  f_sqrt},
-#endif
-    {"srand",		0, 1, FEARG_1,	  f_srand},
-    {"state",		0, 1, FEARG_1,	  f_state},
+    {"sqrt",		1, 1, FEARG_1,	  &t_float,	f_sqrt},
+#endif
+    {"srand",		0, 1, FEARG_1,	  &t_list_number, f_srand},
+    {"state",		0, 1, FEARG_1,	  &t_string,	f_state},
 #ifdef FEAT_FLOAT
-    {"str2float",	1, 1, FEARG_1,	  f_str2float},
-#endif
-    {"str2list",	1, 2, FEARG_1,	  f_str2list},
-    {"str2nr",		1, 3, FEARG_1,	  f_str2nr},
-    {"strcharpart",	2, 3, FEARG_1,	  f_strcharpart},
-    {"strchars",	1, 2, FEARG_1,	  f_strchars},
-    {"strdisplaywidth",	1, 2, FEARG_1,	  f_strdisplaywidth},
+    {"str2float",	1, 1, FEARG_1,	  &t_float,	f_str2float},
+#endif
+    {"str2list",	1, 2, FEARG_1,	  &t_list_number, f_str2list},
+    {"str2nr",		1, 3, FEARG_1,	  &t_number,	f_str2nr},
+    {"strcharpart",	2, 3, FEARG_1,	  &t_string,	f_strcharpart},
+    {"strchars",	1, 2, FEARG_1,	  &t_number,	f_strchars},
+    {"strdisplaywidth",	1, 2, FEARG_1,	  &t_number,	f_strdisplaywidth},
 #ifdef HAVE_STRFTIME
-    {"strftime",	1, 2, FEARG_1,	  f_strftime},
-#endif
-    {"strgetchar",	2, 2, FEARG_1,	  f_strgetchar},
-    {"stridx",		2, 3, FEARG_1,	  f_stridx},
-    {"string",		1, 1, FEARG_1,	  f_string},
-    {"strlen",		1, 1, FEARG_1,	  f_strlen},
-    {"strpart",		2, 3, FEARG_1,	  f_strpart},
+    {"strftime",	1, 2, FEARG_1,	  &t_string,	f_strftime},
+#endif
+    {"strgetchar",	2, 2, FEARG_1,	  &t_number,	f_strgetchar},
+    {"stridx",		2, 3, FEARG_1,	  &t_number,	f_stridx},
+    {"string",		1, 1, FEARG_1,	  &t_string,	f_string},
+    {"strlen",		1, 1, FEARG_1,	  &t_number,	f_strlen},
+    {"strpart",		2, 3, FEARG_1,	  &t_string,	f_strpart},
 #ifdef HAVE_STRPTIME
-    {"strptime",	2, 2, FEARG_1,	  f_strptime},
-#endif
-    {"strridx",		2, 3, FEARG_1,	  f_strridx},
-    {"strtrans",	1, 1, FEARG_1,	  f_strtrans},
-    {"strwidth",	1, 1, FEARG_1,	  f_strwidth},
-    {"submatch",	1, 2, FEARG_1,	  f_submatch},
-    {"substitute",	4, 4, FEARG_1,	  f_substitute},
-    {"swapinfo",	1, 1, FEARG_1,	  f_swapinfo},
-    {"swapname",	1, 1, FEARG_1,	  f_swapname},
-    {"synID",		3, 3, 0,	  f_synID},
-    {"synIDattr",	2, 3, FEARG_1,	  f_synIDattr},
-    {"synIDtrans",	1, 1, FEARG_1,	  f_synIDtrans},
-    {"synconcealed",	2, 2, 0,	  f_synconcealed},
-    {"synstack",	2, 2, 0,	  f_synstack},
-    {"system",		1, 2, FEARG_1,	  f_system},
-    {"systemlist",	1, 2, FEARG_1,	  f_systemlist},
-    {"tabpagebuflist",	0, 1, FEARG_1,	  f_tabpagebuflist},
-    {"tabpagenr",	0, 1, 0,	  f_tabpagenr},
-    {"tabpagewinnr",	1, 2, FEARG_1,	  f_tabpagewinnr},
-    {"tagfiles",	0, 0, 0,	  f_tagfiles},
-    {"taglist",		1, 2, FEARG_1,	  f_taglist},
+    {"strptime",	2, 2, FEARG_1,	  &t_number,	f_strptime},
+#endif
+    {"strridx",		2, 3, FEARG_1,	  &t_number,	f_strridx},
+    {"strtrans",	1, 1, FEARG_1,	  &t_string,	f_strtrans},
+    {"strwidth",	1, 1, FEARG_1,	  &t_number,	f_strwidth},
+    {"submatch",	1, 2, FEARG_1,	  &t_string,	f_submatch},
+    {"substitute",	4, 4, FEARG_1,	  &t_string,	f_substitute},
+    {"swapinfo",	1, 1, FEARG_1,	  &t_dict_any,	f_swapinfo},
+    {"swapname",	1, 1, FEARG_1,	  &t_string,	f_swapname},
+    {"synID",		3, 3, 0,	  &t_number,	f_synID},
+    {"synIDattr",	2, 3, FEARG_1,	  &t_string,	f_synIDattr},
+    {"synIDtrans",	1, 1, FEARG_1,	  &t_number,	f_synIDtrans},
+    {"synconcealed",	2, 2, 0,	  &t_list_any,	f_synconcealed},
+    {"synstack",	2, 2, 0,	  &t_list_number, f_synstack},
+    {"system",		1, 2, FEARG_1,	  &t_string,	f_system},
+    {"systemlist",	1, 2, FEARG_1,	  &t_list_string, f_systemlist},
+    {"tabpagebuflist",	0, 1, FEARG_1,	  &t_list_number, f_tabpagebuflist},
+    {"tabpagenr",	0, 1, 0,	  &t_number,	f_tabpagenr},
+    {"tabpagewinnr",	1, 2, FEARG_1,	  &t_number,	f_tabpagewinnr},
+    {"tagfiles",	0, 0, 0,	  &t_list_string, f_tagfiles},
+    {"taglist",		1, 2, FEARG_1,	  &t_list_dict_any, f_taglist},
 #ifdef FEAT_FLOAT
-    {"tan",		1, 1, FEARG_1,	  f_tan},
-    {"tanh",		1, 1, FEARG_1,	  f_tanh},
-#endif
-    {"tempname",	0, 0, 0,	  f_tempname},
+    {"tan",		1, 1, FEARG_1,	  &t_float,	f_tan},
+    {"tanh",		1, 1, FEARG_1,	  &t_float,	f_tanh},
+#endif
+    {"tempname",	0, 0, 0,	  &t_string,	f_tempname},
 #ifdef FEAT_TERMINAL
-    {"term_dumpdiff",	2, 3, FEARG_1,	  f_term_dumpdiff},
-    {"term_dumpload",	1, 2, FEARG_1,	  f_term_dumpload},
-    {"term_dumpwrite",	2, 3, FEARG_2,	  f_term_dumpwrite},
-    {"term_getaltscreen", 1, 1, FEARG_1,  f_term_getaltscreen},
+    {"term_dumpdiff",	2, 3, FEARG_1,	  &t_number,	f_term_dumpdiff},
+    {"term_dumpload",	1, 2, FEARG_1,	  &t_number,	f_term_dumpload},
+    {"term_dumpwrite",	2, 3, FEARG_2,	  &t_void,	f_term_dumpwrite},
+    {"term_getaltscreen", 1, 1, FEARG_1,  &t_number,	f_term_getaltscreen},
 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
-    {"term_getansicolors", 1, 1, FEARG_1, f_term_getansicolors},
+    {"term_getansicolors", 1, 1, FEARG_1, &t_list_string, f_term_getansicolors},
 # endif
-    {"term_getattr",	2, 2, FEARG_1,	  f_term_getattr},
-    {"term_getcursor",	1, 1, FEARG_1,	  f_term_getcursor},
-    {"term_getjob",	1, 1, FEARG_1,	  f_term_getjob},
-    {"term_getline",	2, 2, FEARG_1,	  f_term_getline},
-    {"term_getscrolled", 1, 1, FEARG_1,	  f_term_getscrolled},
-    {"term_getsize",	1, 1, FEARG_1,	  f_term_getsize},
-    {"term_getstatus",	1, 1, FEARG_1,	  f_term_getstatus},
-    {"term_gettitle",	1, 1, FEARG_1,	  f_term_gettitle},
-    {"term_gettty",	1, 2, FEARG_1,	  f_term_gettty},
-    {"term_list",	0, 0, 0,	  f_term_list},
-    {"term_scrape",	2, 2, FEARG_1,	  f_term_scrape},
-    {"term_sendkeys",	2, 2, FEARG_1,	  f_term_sendkeys},
+    {"term_getattr",	2, 2, FEARG_1,	  &t_number,	f_term_getattr},
+    {"term_getcursor",	1, 1, FEARG_1,	  &t_list_any,	f_term_getcursor},
+    {"term_getjob",	1, 1, FEARG_1,	  &t_job,	f_term_getjob},
+    {"term_getline",	2, 2, FEARG_1,	  &t_string,	f_term_getline},
+    {"term_getscrolled", 1, 1, FEARG_1,	  &t_number,	f_term_getscrolled},
+    {"term_getsize",	1, 1, FEARG_1,	  &t_list_number, f_term_getsize},
+    {"term_getstatus",	1, 1, FEARG_1,	  &t_string,	f_term_getstatus},
+    {"term_gettitle",	1, 1, FEARG_1,	  &t_string,	f_term_gettitle},
+    {"term_gettty",	1, 2, FEARG_1,	  &t_string,	f_term_gettty},
+    {"term_list",	0, 0, 0,	  &t_list_number, f_term_list},
+    {"term_scrape",	2, 2, FEARG_1,	  &t_list_dict_any, f_term_scrape},
+    {"term_sendkeys",	2, 2, FEARG_1,	  &t_void,	f_term_sendkeys},
 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
-    {"term_setansicolors", 2, 2, FEARG_1, f_term_setansicolors},
+    {"term_setansicolors", 2, 2, FEARG_1, &t_void,	f_term_setansicolors},
 # endif
-    {"term_setapi",	2, 2, FEARG_1,	  f_term_setapi},
-    {"term_setkill",	2, 2, FEARG_1,	  f_term_setkill},
-    {"term_setrestore",	2, 2, FEARG_1,	  f_term_setrestore},
-    {"term_setsize",	3, 3, FEARG_1,	  f_term_setsize},
-    {"term_start",	1, 2, FEARG_1,	  f_term_start},
-    {"term_wait",	1, 2, FEARG_1,	  f_term_wait},
-#endif
-    {"test_alloc_fail",	3, 3, FEARG_1,	  f_test_alloc_fail},
-    {"test_autochdir",	0, 0, 0,	  f_test_autochdir},
-    {"test_feedinput",	1, 1, FEARG_1,	  f_test_feedinput},
-    {"test_garbagecollect_now",	0, 0, 0,  f_test_garbagecollect_now},
-    {"test_garbagecollect_soon", 0, 0, 0, f_test_garbagecollect_soon},
-    {"test_getvalue",	1, 1, FEARG_1,	  f_test_getvalue},
-    {"test_ignore_error", 1, 1, FEARG_1,  f_test_ignore_error},
-    {"test_null_blob",	0, 0, 0,	  f_test_null_blob},
+    {"term_setapi",	2, 2, FEARG_1,	  &t_void,	f_term_setapi},
+    {"term_setkill",	2, 2, FEARG_1,	  &t_void,	f_term_setkill},
+    {"term_setrestore",	2, 2, FEARG_1,	  &t_void,	f_term_setrestore},
+    {"term_setsize",	3, 3, FEARG_1,	  &t_void,	f_term_setsize},
+    {"term_start",	1, 2, FEARG_1,	  &t_number,	f_term_start},
+    {"term_wait",	1, 2, FEARG_1,	  &t_void,	f_term_wait},
+#endif
+    {"test_alloc_fail",	3, 3, FEARG_1,	  &t_void,	f_test_alloc_fail},
+    {"test_autochdir",	0, 0, 0,	  &t_void,	f_test_autochdir},
+    {"test_feedinput",	1, 1, FEARG_1,	  &t_void,	f_test_feedinput},
+    {"test_garbagecollect_now",	0, 0, 0,  &t_void,	f_test_garbagecollect_now},
+    {"test_garbagecollect_soon", 0, 0, 0, &t_void,	f_test_garbagecollect_soon},
+    {"test_getvalue",	1, 1, FEARG_1,	  &t_number,	f_test_getvalue},
+    {"test_ignore_error", 1, 1, FEARG_1,  &t_void,	f_test_ignore_error},
+    {"test_null_blob",	0, 0, 0,	  &t_blob,	f_test_null_blob},
 #ifdef FEAT_JOB_CHANNEL
-    {"test_null_channel", 0, 0, 0,	  f_test_null_channel},
-#endif
-    {"test_null_dict",	0, 0, 0,	  f_test_null_dict},
+    {"test_null_channel", 0, 0, 0,	  &t_channel,	f_test_null_channel},
+#endif
+    {"test_null_dict",	0, 0, 0,	  &t_dict_any,	f_test_null_dict},
 #ifdef FEAT_JOB_CHANNEL
-    {"test_null_job",	0, 0, 0,	  f_test_null_job},
-#endif
-    {"test_null_list",	0, 0, 0,	  f_test_null_list},
-    {"test_null_partial", 0, 0, 0,	  f_test_null_partial},
-    {"test_null_string", 0, 0, 0,	  f_test_null_string},
-    {"test_option_not_set", 1, 1, FEARG_1, f_test_option_not_set},
-    {"test_override",	2, 2, FEARG_2,	  f_test_override},
-    {"test_refcount",	1, 1, FEARG_1,	  f_test_refcount},
+    {"test_null_job",	0, 0, 0,	  &t_job,	f_test_null_job},
+#endif
+    {"test_null_list",	0, 0, 0,	  &t_list_any,	f_test_null_list},
+    {"test_null_partial", 0, 0, 0,	  &t_partial_void, f_test_null_partial},
+    {"test_null_string", 0, 0, 0,	  &t_string,	f_test_null_string},
+    {"test_option_not_set", 1, 1, FEARG_1,&t_void,	 f_test_option_not_set},
+    {"test_override",	2, 2, FEARG_2,	  &t_void,	f_test_override},
+    {"test_refcount",	1, 1, FEARG_1,	  &t_number,	f_test_refcount},
 #ifdef FEAT_GUI
-    {"test_scrollbar",	3, 3, FEARG_2,	  f_test_scrollbar},
-#endif
-    {"test_setmouse",	2, 2, 0,	  f_test_setmouse},
-    {"test_settime",	1, 1, FEARG_1,	  f_test_settime},
+    {"test_scrollbar",	3, 3, FEARG_2,	  &t_void,	f_test_scrollbar},
+#endif
+    {"test_setmouse",	2, 2, 0,	  &t_void,	f_test_setmouse},
+    {"test_settime",	1, 1, FEARG_1,	  &t_void,	f_test_settime},
 #ifdef FEAT_TIMERS
-    {"timer_info",	0, 1, FEARG_1,	  f_timer_info},
-    {"timer_pause",	2, 2, FEARG_1,	  f_timer_pause},
-    {"timer_start",	2, 3, FEARG_1,	  f_timer_start},
-    {"timer_stop",	1, 1, FEARG_1,	  f_timer_stop},
-    {"timer_stopall",	0, 0, 0,	  f_timer_stopall},
-#endif
-    {"tolower",		1, 1, FEARG_1,	  f_tolower},
-    {"toupper",		1, 1, FEARG_1,	  f_toupper},
-    {"tr",		3, 3, FEARG_1,	  f_tr},
-    {"trim",		1, 2, FEARG_1,	  f_trim},
+    {"timer_info",	0, 1, FEARG_1,	  &t_list_dict_any, f_timer_info},
+    {"timer_pause",	2, 2, FEARG_1,	  &t_void,	f_timer_pause},
+    {"timer_start",	2, 3, FEARG_1,	  &t_number,	f_timer_start},
+    {"timer_stop",	1, 1, FEARG_1,	  &t_void,	f_timer_stop},
+    {"timer_stopall",	0, 0, 0,	  &t_void,	f_timer_stopall},
+#endif
+    {"tolower",		1, 1, FEARG_1,	  &t_string,	f_tolower},
+    {"toupper",		1, 1, FEARG_1,	  &t_string,	f_toupper},
+    {"tr",		3, 3, FEARG_1,	  &t_string,	f_tr},
+    {"trim",		1, 2, FEARG_1,	  &t_string,	f_trim},
 #ifdef FEAT_FLOAT
-    {"trunc",		1, 1, FEARG_1,	  f_trunc},
-#endif
-    {"type",		1, 1, FEARG_1,	  f_type},
-    {"undofile",	1, 1, FEARG_1,	  f_undofile},
-    {"undotree",	0, 0, 0,	  f_undotree},
-    {"uniq",		1, 3, FEARG_1,	  f_uniq},
-    {"values",		1, 1, FEARG_1,	  f_values},
-    {"virtcol",		1, 1, FEARG_1,	  f_virtcol},
-    {"visualmode",	0, 1, 0,	  f_visualmode},
-    {"wildmenumode",	0, 0, 0,	  f_wildmenumode},
-    {"win_execute",	2, 3, FEARG_2,	  f_win_execute},
-    {"win_findbuf",	1, 1, FEARG_1,	  f_win_findbuf},
-    {"win_getid",	0, 2, FEARG_1,	  f_win_getid},
-    {"win_gotoid",	1, 1, FEARG_1,	  f_win_gotoid},
-    {"win_id2tabwin",	1, 1, FEARG_1,	  f_win_id2tabwin},
-    {"win_id2win",	1, 1, FEARG_1,	  f_win_id2win},
-    {"win_screenpos",	1, 1, FEARG_1,	  f_win_screenpos},
-    {"win_splitmove",   2, 3, FEARG_1,    f_win_splitmove},
-    {"winbufnr",	1, 1, FEARG_1,	  f_winbufnr},
-    {"wincol",		0, 0, 0,	  f_wincol},
-    {"windowsversion",	0, 0, 0,	  f_windowsversion},
-    {"winheight",	1, 1, FEARG_1,	  f_winheight},
-    {"winlayout",	0, 1, FEARG_1,	  f_winlayout},
-    {"winline",		0, 0, 0,	  f_winline},
-    {"winnr",		0, 1, FEARG_1,	  f_winnr},
-    {"winrestcmd",	0, 0, 0,	  f_winrestcmd},
-    {"winrestview",	1, 1, FEARG_1,	  f_winrestview},
-    {"winsaveview",	0, 0, 0,	  f_winsaveview},
-    {"winwidth",	1, 1, FEARG_1,	  f_winwidth},
-    {"wordcount",	0, 0, 0,	  f_wordcount},
-    {"writefile",	2, 3, FEARG_1,	  f_writefile},
-    {"xor",		2, 2, FEARG_1,	  f_xor},
+    {"trunc",		1, 1, FEARG_1,	  &t_float,	f_trunc},
+#endif
+    {"type",		1, 1, FEARG_1,	  &t_number,	f_type},
+    {"undofile",	1, 1, FEARG_1,	  &t_string,	f_undofile},
+    {"undotree",	0, 0, 0,	  &t_dict_any,	f_undotree},
+    {"uniq",		1, 3, FEARG_1,	  &t_list_any,	f_uniq},
+    {"values",		1, 1, FEARG_1,	  &t_list_any,	f_values},
+    {"virtcol",		1, 1, FEARG_1,	  &t_number,	f_virtcol},
+    {"visualmode",	0, 1, 0,	  &t_string,	f_visualmode},
+    {"wildmenumode",	0, 0, 0,	  &t_number,	f_wildmenumode},
+    {"win_execute",	2, 3, FEARG_2,	  &t_string,	f_win_execute},
+    {"win_findbuf",	1, 1, FEARG_1,	  &t_list_number, f_win_findbuf},
+    {"win_getid",	0, 2, FEARG_1,	  &t_number,	f_win_getid},
+    {"win_gotoid",	1, 1, FEARG_1,	  &t_number,	f_win_gotoid},
+    {"win_id2tabwin",	1, 1, FEARG_1,	  &t_list_number, f_win_id2tabwin},
+    {"win_id2win",	1, 1, FEARG_1,	  &t_number,	f_win_id2win},
+    {"win_screenpos",	1, 1, FEARG_1,	  &t_list_number, f_win_screenpos},
+    {"win_splitmove",   2, 3, FEARG_1,    &t_number,	f_win_splitmove},
+    {"winbufnr",	1, 1, FEARG_1,	  &t_number,	f_winbufnr},
+    {"wincol",		0, 0, 0,	  &t_number,	f_wincol},
+    {"windowsversion",	0, 0, 0,	  &t_string,	f_windowsversion},
+    {"winheight",	1, 1, FEARG_1,	  &t_number,	f_winheight},
+    {"winlayout",	0, 1, FEARG_1,	  &t_list_any,	f_winlayout},
+    {"winline",		0, 0, 0,	  &t_number,	f_winline},
+    {"winnr",		0, 1, FEARG_1,	  &t_number,	f_winnr},
+    {"winrestcmd",	0, 0, 0,	  &t_string,	f_winrestcmd},
+    {"winrestview",	1, 1, FEARG_1,	  &t_void,	f_winrestview},
+    {"winsaveview",	0, 0, 0,	  &t_dict_any,	f_winsaveview},
+    {"winwidth",	1, 1, FEARG_1,	  &t_number,	f_winwidth},
+    {"wordcount",	0, 0, 0,	  &t_dict_number, f_wordcount},
+    {"writefile",	2, 3, FEARG_1,	  &t_number,	f_writefile},
+    {"xor",		2, 2, FEARG_1,	  &t_number,	f_xor},
 };
 
 /*
@@ -935,7 +936,7 @@ get_expr_name(expand_T *xp, int idx)
  * Find internal function "name" in table "global_functions".
  * Return index, or -1 if not found
  */
-    static int
+    int
 find_internal_func(char_u *name)
 {
     int		first = 0;
@@ -966,6 +967,47 @@ has_internal_func(char_u *name)
     return find_internal_func(name) >= 0;
 }
 
+    char *
+internal_func_name(int idx)
+{
+    return global_functions[idx].f_name;
+}
+
+    type_T *
+internal_func_ret_type(int idx, int argcount)
+{
+    funcentry_T *fe = &global_functions[idx];
+
+    if (fe->f_func == f_getline)
+	return argcount == 1 ? &t_string : &t_list_string;
+    return fe->f_rettype;
+}
+
+/*
+ * Check the argument count to use for internal function "idx".
+ * Returns OK or FAIL;
+ */
+    int
+check_internal_func(int idx, int argcount)
+{
+    int	    res;
+    char    *name;
+
+    if (argcount < global_functions[idx].f_min_argc)
+	res = FCERR_TOOFEW;
+    else if (argcount > global_functions[idx].f_max_argc)
+	res = FCERR_TOOMANY;
+    else
+	return OK;
+
+    name = internal_func_name(idx);
+    if (res == FCERR_TOOMANY)
+	semsg(_(e_toomanyarg), name);
+    else
+	semsg(_(e_toofewarg), name);
+    return FAIL;
+}
+
     int
 call_internal_func(
 	char_u	    *name,
@@ -987,6 +1029,15 @@ call_internal_func(
     return FCERR_NONE;
 }
 
+    void
+call_internal_func_by_idx(
+	int	    idx,
+	typval_T    *argvars,
+	typval_T    *rettv)
+{
+    global_functions[idx].f_func(argvars, rettv);
+}
+
 /*
  * Invoke a method for base->method().
  */
@@ -1834,6 +1885,7 @@ f_empty(typval_T *argvars, typval_T *ret
 	    break;
 #endif
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("f_empty(UNKNOWN)");
 	    n = TRUE;
 	    break;
@@ -2487,7 +2539,7 @@ common_function(typval_T *argvars, typva
 	semsg(_(e_invarg2), use_string ? tv_get_string(&argvars[0]) : s);
     // Don't check an autoload name for existence here.
     else if (trans_name != NULL && (is_funcref
-				? find_func(trans_name) == NULL
+				? find_func(trans_name, NULL) == NULL
 				: !translated_function_exists(trans_name)))
 	semsg(_("E700: Unknown function: %s"), s);
     else
@@ -2623,7 +2675,7 @@ common_function(typval_T *argvars, typva
 		}
 		else if (is_funcref)
 		{
-		    pt->pt_func = find_func(trans_name);
+		    pt->pt_func = find_func(trans_name, NULL);
 		    func_ptr_ref(pt->pt_func);
 		    vim_free(name);
 		}
@@ -4319,6 +4371,7 @@ f_len(typval_T *argvars, typval_T *rettv
 	    rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
 	    break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	case VAR_BOOL:
 	case VAR_SPECIAL:
 	case VAR_FLOAT:
@@ -5237,7 +5290,6 @@ f_range(typval_T *argvars, typval_T *ret
     varnumber_T	start;
     varnumber_T	end;
     varnumber_T	stride = 1;
-    varnumber_T	i;
     int		error = FALSE;
 
     start = tv_get_number_chk(&argvars[0], &error);
@@ -5259,13 +5311,41 @@ f_range(typval_T *argvars, typval_T *ret
 	emsg(_("E726: Stride is zero"));
     else if (stride > 0 ? end + 1 < start : end - 1 > start)
 	emsg(_("E727: Start past end"));
-    else
-    {
-	if (rettv_list_alloc(rettv) == OK)
-	    for (i = start; stride > 0 ? i <= end : i >= end; i += stride)
-		if (list_append_number(rettv->vval.v_list,
-						      (varnumber_T)i) == FAIL)
-		    break;
+    else if (rettv_list_alloc(rettv) == OK)
+    {
+	list_T *list = rettv->vval.v_list;
+
+	// Create a non-materialized list.  This is much more efficient and
+	// works with ":for".  If used otherwise range_list_materialize() must
+	// be called.
+	list->lv_first = &range_list_item;
+	list->lv_start = start;
+	list->lv_end = end;
+	list->lv_stride = stride;
+	list->lv_len = (end - start + 1) / stride;
+    }
+}
+
+/*
+ * If "list" is a non-materialized list then materialize it now.
+ */
+    void
+range_list_materialize(list_T *list)
+{
+    if (list->lv_first == &range_list_item)
+    {
+	varnumber_T start = list->lv_start;
+	varnumber_T end = list->lv_end;
+	int	    stride = list->lv_stride;
+	varnumber_T i;
+
+	list->lv_first = NULL;
+	list->lv_last = NULL;
+	list->lv_len = 0;
+	list->lv_idx_item = NULL;
+	for (i = start; stride > 0 ? i <= end : i >= end; i += stride)
+	    if (list_append_number(list, (varnumber_T)i) == FAIL)
+		break;
     }
 }
 
@@ -8356,6 +8436,7 @@ f_type(typval_T *argvars, typval_T *rett
 	case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break;
 	case VAR_BLOB:    n = VAR_TYPE_BLOB; break;
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	     internal_error("f_type(UNKNOWN)");
 	     n = -1;
 	     break;
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -15,8 +15,6 @@
 
 #if defined(FEAT_EVAL) || defined(PROTO)
 
-static char *e_letunexp	= N_("E18: Unexpected characters in :let");
-
 static dictitem_T	globvars_var;		// variable used for g:
 static dict_T		globvardict;		// Dictionary with g: variables
 #define globvarht globvardict.dv_hashtab
@@ -41,6 +39,8 @@ static hashtab_T	compat_hashtab;
 
 #define VV_NAME(s, t)	s, {{t, 0, {0}}, 0, {0}}
 
+typedef struct vimvar vimvar_T;
+
 static struct vimvar
 {
     char	*vv_name;	// name of variable, without v:
@@ -163,17 +163,14 @@ static dict_T		vimvardict;		// Dictionar
 // for VIM_VERSION_ defines
 #include "version.h"
 
-#define SCRIPT_SV(id) (SCRIPT_ITEM(id).sn_vars)
-#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
-
 static void ex_let_const(exarg_T *eap, int is_const);
-static char_u *skip_var_one(char_u *arg);
+static char_u *skip_var_one(char_u *arg, int include_type);
 static void list_glob_vars(int *first);
 static void list_buf_vars(int *first);
 static void list_win_vars(int *first);
 static void list_tab_vars(int *first);
 static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first);
-static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int is_const, char_u *endchars, char_u *op);
+static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op);
 static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep);
 static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit);
 static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock);
@@ -544,7 +541,7 @@ list_script_vars(int *first)
  * indentation in the 'cmd' line) is stripped.
  * Returns a List with {lines} or NULL.
  */
-    static list_T *
+    list_T *
 heredoc_get(exarg_T *eap, char_u *cmd)
 {
     char_u	*theline;
@@ -669,6 +666,7 @@ heredoc_get(exarg_T *eap, char_u *cmd)
  * ":let var .= expr"		assignment command.
  * ":let var ..= expr"		assignment command.
  * ":let [var1, var2] = expr"	unpack list.
+ * ":let var =<< ..."		heredoc
  */
     void
 ex_let(exarg_T *eap)
@@ -701,8 +699,13 @@ ex_let_const(exarg_T *eap, int is_const)
     char_u	*argend;
     int		first = TRUE;
     int		concat;
-
-    argend = skip_var_list(arg, &var_count, &semicolon);
+    int		flags = is_const ? LET_IS_CONST : 0;
+
+    // detect Vim9 assignment without ":let" or ":const"
+    if (eap->arg == eap->cmd)
+	flags |= LET_NO_COMMAND;
+
+    argend = skip_var_list(arg, TRUE, &var_count, &semicolon);
     if (argend == NULL)
 	return;
     if (argend > arg && argend[-1] == '.')  // for var.='str'
@@ -749,7 +752,7 @@ ex_let_const(exarg_T *eap, int is_const)
 		op[0] = '=';
 		op[1] = NUL;
 		(void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
-								is_const, op);
+								flags, op);
 	    }
 	    clear_tv(&rettv);
 	}
@@ -783,7 +786,7 @@ ex_let_const(exarg_T *eap, int is_const)
 	else if (i != FAIL)
 	{
 	    (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
-								 is_const, op);
+								 flags, op);
 	    clear_tv(&rettv);
 	}
     }
@@ -804,7 +807,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		is_const,	// lock variables for const
+    int		flags,		// LET_IS_CONST and/or LET_NO_COMMAND
     char_u	*op)
 {
     char_u	*arg = arg_start;
@@ -816,7 +819,7 @@ ex_let_vars(
     if (*arg != '[')
     {
 	// ":let var = expr" or ":for var in list"
-	if (ex_let_one(arg, tv, copy, is_const, op, op) == NULL)
+	if (ex_let_one(arg, tv, copy, flags, op, op) == NULL)
 	    return FAIL;
 	return OK;
     }
@@ -844,8 +847,7 @@ ex_let_vars(
     while (*arg != ']')
     {
 	arg = skipwhite(arg + 1);
-	arg = ex_let_one(arg, &item->li_tv, TRUE, is_const,
-							  (char_u *)",;]", op);
+	arg = ex_let_one(arg, &item->li_tv, TRUE, flags, (char_u *)",;]", op);
 	item = item->li_next;
 	if (arg == NULL)
 	    return FAIL;
@@ -869,7 +871,7 @@ ex_let_vars(
 	    ltv.vval.v_list = l;
 	    l->lv_refcount = 1;
 
-	    arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE, is_const,
+	    arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE, flags,
 							    (char_u *)"]", op);
 	    clear_tv(&ltv);
 	    if (arg == NULL)
@@ -896,6 +898,7 @@ ex_let_vars(
     char_u *
 skip_var_list(
     char_u	*arg,
+    int		include_type,
     int		*var_count,
     int		*semicolon)
 {
@@ -908,7 +911,7 @@ skip_var_list(
 	for (;;)
 	{
 	    p = skipwhite(p + 1);	// skip whites after '[', ';' or ','
-	    s = skip_var_one(p);
+	    s = skip_var_one(p, TRUE);
 	    if (s == p)
 	    {
 		semsg(_(e_invarg2), p);
@@ -937,20 +940,29 @@ skip_var_list(
 	return p + 1;
     }
     else
-	return skip_var_one(arg);
+	return skip_var_one(arg, include_type);
 }
 
 /*
  * Skip one (assignable) variable name, including @r, $VAR, &option, d.key,
  * l[idx].
+ * In Vim9 script also skip over ": type" if "include_type" is TRUE.
  */
     static char_u *
-skip_var_one(char_u *arg)
+skip_var_one(char_u *arg, int include_type)
 {
+    char_u *end;
+
     if (*arg == '@' && arg[1] != NUL)
 	return arg + 2;
-    return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
+    end = find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
 				   NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
+    if (include_type && current_sctx.sc_version == SCRIPT_VERSION_VIM9
+								&& *end == ':')
+    {
+	end = skip_type(skipwhite(end + 1));
+    }
+    return end;
 }
 
 /*
@@ -1141,7 +1153,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		is_const,	// lock variable for const
+    int		flags,		// LET_IS_CONST and/or LET_NO_COMMAND
     char_u	*endchars,	// valid chars after variable name  or NULL
     char_u	*op)		// "+", "-", "."  or NULL
 {
@@ -1156,7 +1168,7 @@ ex_let_one(
     // ":let $VAR = expr": Set environment variable.
     if (*arg == '$')
     {
-	if (is_const)
+	if (flags & LET_IS_CONST)
 	{
 	    emsg(_("E996: Cannot lock an environment variable"));
 	    return NULL;
@@ -1214,9 +1226,9 @@ ex_let_one(
     // ":let &g:option = expr": Set global option value.
     else if (*arg == '&')
     {
-	if (is_const)
+	if (flags & LET_IS_CONST)
 	{
-	    emsg(_("E996: Cannot lock an option"));
+	    emsg(_(e_const_option));
 	    return NULL;
 	}
 	// Find the end of the name.
@@ -1281,7 +1293,7 @@ ex_let_one(
     // ":let @r = expr": Set register contents.
     else if (*arg == '@')
     {
-	if (is_const)
+	if (flags & LET_IS_CONST)
 	{
 	    emsg(_("E996: Cannot lock a register"));
 	    return NULL;
@@ -1317,6 +1329,7 @@ ex_let_one(
     }
 
     // ":let var = expr": Set internal variable.
+    // ":let var: type = expr": Set internal variable with type.
     // ":let {expr} = expr": Idem, name made with curly braces
     else if (eval_isnamec1(*arg) || *arg == '{')
     {
@@ -1325,11 +1338,12 @@ ex_let_one(
 	p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START);
 	if (p != NULL && lv.ll_name != NULL)
 	{
-	    if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL)
+	    if (endchars != NULL && vim_strchr(endchars,
+					   *skipwhite(lv.ll_name_end)) == NULL)
 		emsg(_(e_letunexp));
 	    else
 	    {
-		set_var_lval(&lv, p, tv, copy, is_const, op);
+		set_var_lval(&lv, p, tv, copy, flags, op);
 		arg_end = p;
 	    }
 	}
@@ -1657,12 +1671,13 @@ item_lock(typval_T *tv, int deep, int lo
     switch (tv->v_type)
     {
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	case VAR_NUMBER:
+	case VAR_BOOL:
 	case VAR_STRING:
 	case VAR_FUNC:
 	case VAR_PARTIAL:
 	case VAR_FLOAT:
-	case VAR_BOOL:
 	case VAR_SPECIAL:
 	case VAR_JOB:
 	case VAR_CHANNEL:
@@ -1901,6 +1916,22 @@ get_vimvar_dict(void)
 }
 
 /*
+ * Returns the index of a v:variable.  Negative if not found.
+ */
+    int
+find_vim_var(char_u *name)
+{
+    dictitem_T *di = find_var_in_ht(&vimvarht, 0, name, TRUE);
+    struct vimvar *vv;
+
+    if (di == NULL)
+	return -1;
+    vv = (struct vimvar *)((char *)di - offsetof(vimvar_T, vv_di));
+    return (int)(vv - vimvars);
+}
+
+
+/*
  * Set type of v: variable to "type".
  */
     void
@@ -1919,6 +1950,12 @@ set_vim_var_nr(int idx, varnumber_T val)
     vimvars[idx].vv_nr = val;
 }
 
+    char *
+get_vim_var_name(int idx)
+{
+    return vimvars[idx].vv_name;
+}
+
 /*
  * Get typval_T v: variable value.
  */
@@ -2245,6 +2282,20 @@ get_var_tv(
 	    *dip = v;
     }
 
+    if (tv == NULL && current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+    {
+	imported_T *import = find_imported(name, NULL);
+
+	// imported variable from another script
+	if (import != NULL)
+	{
+	    scriptitem_T    *si = &SCRIPT_ITEM(import->imp_sid);
+	    svar_T	    *sv = ((svar_T *)si->sn_var_vals.ga_data)
+						    + import->imp_var_vals_idx;
+	    tv = sv->sv_tv;
+	}
+    }
+
     if (tv == NULL)
     {
 	if (rettv != NULL && verbose)
@@ -2366,6 +2417,58 @@ find_var_in_ht(
 }
 
 /*
+ * Get the script-local hashtab.  NULL if not in a script context.
+ */
+    hashtab_T *
+get_script_local_ht(void)
+{
+    scid_T sid = current_sctx.sc_sid;
+
+    if (sid > 0 && sid <= script_items.ga_len)
+	return &SCRIPT_VARS(sid);
+    return NULL;
+}
+
+/*
+ * Look for "name[len]" in script-local variables.
+ * Return -1 when not found.
+ */
+    int
+lookup_scriptvar(char_u *name, size_t len, cctx_T *dummy UNUSED)
+{
+    hashtab_T	*ht = get_script_local_ht();
+    char_u	buffer[30];
+    char_u	*p;
+    int		res;
+    hashitem_T	*hi;
+
+    if (ht == NULL)
+	return -1;
+    if (len < sizeof(buffer) - 1)
+    {
+	vim_strncpy(buffer, name, len);
+	p = buffer;
+    }
+    else
+    {
+	p = vim_strnsave(name, (int)len);
+	if (p == NULL)
+	    return -1;
+    }
+
+    hi = hash_find(ht, p);
+    res = HASHITEM_EMPTY(hi) ? -1 : 1;
+
+    // if not script-local, then perhaps imported
+    if (res == -1 && find_imported(p, NULL) != NULL)
+	res = 1;
+
+    if (p != buffer)
+	vim_free(p);
+    return res;
+}
+
+/*
  * Find the hashtab used for a variable name.
  * Return NULL if the name is not valid.
  * Set "varname" to the start of name without ':'.
@@ -2395,9 +2498,18 @@ find_var_ht(char_u *name, char_u **varna
 	}
 
 	ht = get_funccal_local_ht();
-	if (ht == NULL)
-	    return &globvarht;			// global variable
-	return ht;				// local variable
+	if (ht != NULL)
+	    return ht;				// local variable
+
+	// in Vim9 script items at the script level are script-local
+	if (current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+	{
+	    ht = get_script_local_ht();
+	    if (ht != NULL)
+		return ht;
+	}
+
+	return &globvarht;			// global variable
     }
     *varname = name + 2;
     if (*name == 'g')				// global variable
@@ -2414,14 +2526,19 @@ find_var_ht(char_u *name, char_u **varna
 	return &curtab->tp_vars->dv_hashtab;
     if (*name == 'v')				// v: variable
 	return &vimvarht;
-    if (*name == 'a')				// a: function argument
-	return get_funccal_args_ht();
-    if (*name == 'l')				// l: local function variable
-	return get_funccal_local_ht();
-    if (*name == 's'				// script variable
-	    && current_sctx.sc_sid > 0
-	    && current_sctx.sc_sid <= script_items.ga_len)
-	return &SCRIPT_VARS(current_sctx.sc_sid);
+    if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
+    {
+	if (*name == 'a')			// a: function argument
+	    return get_funccal_args_ht();
+	if (*name == 'l')			// l: local function variable
+	    return get_funccal_local_ht();
+    }
+    if (*name == 's')				// script variable
+    {
+	ht = get_script_local_ht();
+	if (ht != NULL)
+	    return ht;
+    }
     return NULL;
 }
 
@@ -2617,7 +2734,7 @@ set_var(
     typval_T	*tv,
     int		copy)	    // make copy of value in "tv"
 {
-    set_var_const(name, tv, copy, FALSE);
+    set_var_const(name, NULL, tv, copy, 0);
 }
 
 /*
@@ -2628,13 +2745,15 @@ set_var(
     void
 set_var_const(
     char_u	*name,
+    type_T	*type,
     typval_T	*tv,
     int		copy,	    // make copy of value in "tv"
-    int		is_const)   // disallow to modify existing variable
+    int		flags)	    // LET_IS_CONST and/or LET_NO_COMMAND
 {
-    dictitem_T	*v;
+    dictitem_T	*di;
     char_u	*varname;
     hashtab_T	*ht;
+    int		is_script_local;
 
     ht = find_var_ht(name, &varname);
     if (ht == NULL || *varname == NUL)
@@ -2642,75 +2761,92 @@ set_var_const(
 	semsg(_(e_illvar), name);
 	return;
     }
-    v = find_var_in_ht(ht, 0, varname, TRUE);
+    is_script_local = ht == get_script_local_ht();
+
+    di = find_var_in_ht(ht, 0, varname, TRUE);
 
     // Search in parent scope which is possible to reference from lambda
-    if (v == NULL)
-	v = find_var_in_scoped_ht(name, TRUE);
+    if (di == NULL)
+	di = find_var_in_scoped_ht(name, TRUE);
 
     if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
-				      && var_check_func_name(name, v == NULL))
+				      && var_check_func_name(name, di == NULL))
 	return;
 
-    if (v != NULL)
+    if (di != NULL)
     {
-	if (is_const)
+	if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
 	{
-	    emsg(_(e_cannot_mod));
-	    return;
+	    if (flags & LET_IS_CONST)
+	    {
+		emsg(_(e_cannot_mod));
+		return;
+	    }
+
+	    if (var_check_ro(di->di_flags, name, FALSE)
+			       || var_check_lock(di->di_tv.v_lock, name, FALSE))
+		return;
+
+	    if ((flags & LET_NO_COMMAND) == 0
+		    && is_script_local
+		    && current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+	    {
+		semsg(_("E1041: Redefining script item %s"), name);
+		return;
+	    }
 	}
+	else
+	    // can only redefine once
+	    di->di_flags &= ~DI_FLAGS_RELOAD;
 
 	// existing variable, need to clear the value
-	if (var_check_ro(v->di_flags, name, FALSE)
-			      || var_check_lock(v->di_tv.v_lock, name, FALSE))
-	    return;
-
-	// Handle setting internal v: variables separately where needed to
+
+	// Handle setting internal di: variables separately where needed to
 	// prevent changing the type.
 	if (ht == &vimvarht)
 	{
-	    if (v->di_tv.v_type == VAR_STRING)
+	    if (di->di_tv.v_type == VAR_STRING)
 	    {
-		VIM_CLEAR(v->di_tv.vval.v_string);
+		VIM_CLEAR(di->di_tv.vval.v_string);
 		if (copy || tv->v_type != VAR_STRING)
 		{
 		    char_u *val = tv_get_string(tv);
 
 		    // Careful: when assigning to v:errmsg and tv_get_string()
 		    // causes an error message the variable will alrady be set.
-		    if (v->di_tv.vval.v_string == NULL)
-			v->di_tv.vval.v_string = vim_strsave(val);
+		    if (di->di_tv.vval.v_string == NULL)
+			di->di_tv.vval.v_string = vim_strsave(val);
 		}
 		else
 		{
 		    // Take over the string to avoid an extra alloc/free.
-		    v->di_tv.vval.v_string = tv->vval.v_string;
+		    di->di_tv.vval.v_string = tv->vval.v_string;
 		    tv->vval.v_string = NULL;
 		}
 		return;
 	    }
-	    else if (v->di_tv.v_type == VAR_NUMBER)
+	    else if (di->di_tv.v_type == VAR_NUMBER)
 	    {
-		v->di_tv.vval.v_number = tv_get_number(tv);
+		di->di_tv.vval.v_number = tv_get_number(tv);
 		if (STRCMP(varname, "searchforward") == 0)
-		    set_search_direction(v->di_tv.vval.v_number ? '/' : '?');
+		    set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
 #ifdef FEAT_SEARCH_EXTRA
 		else if (STRCMP(varname, "hlsearch") == 0)
 		{
-		    no_hlsearch = !v->di_tv.vval.v_number;
+		    no_hlsearch = !di->di_tv.vval.v_number;
 		    redraw_all_later(SOME_VALID);
 		}
 #endif
 		return;
 	    }
-	    else if (v->di_tv.v_type != tv->v_type)
+	    else if (di->di_tv.v_type != tv->v_type)
 	    {
 		semsg(_("E963: setting %s to value with wrong type"), name);
 		return;
 	    }
 	}
 
-	clear_tv(&v->di_tv);
+	clear_tv(&di->di_tv);
     }
     else		    // add a new variable
     {
@@ -2725,31 +2861,53 @@ set_var_const(
 	if (!valid_varname(varname))
 	    return;
 
-	v = alloc(sizeof(dictitem_T) + STRLEN(varname));
-	if (v == NULL)
+	di = alloc(sizeof(dictitem_T) + STRLEN(varname));
+	if (di == NULL)
 	    return;
-	STRCPY(v->di_key, varname);
-	if (hash_add(ht, DI2HIKEY(v)) == FAIL)
+	STRCPY(di->di_key, varname);
+	if (hash_add(ht, DI2HIKEY(di)) == FAIL)
 	{
-	    vim_free(v);
+	    vim_free(di);
 	    return;
 	}
-	v->di_flags = DI_FLAGS_ALLOC;
-	if (is_const)
-	    v->di_flags |= DI_FLAGS_LOCK;
+	di->di_flags = DI_FLAGS_ALLOC;
+	if (flags & LET_IS_CONST)
+	    di->di_flags |= DI_FLAGS_LOCK;
+
+	if (is_script_local && current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+	{
+	    scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+
+	    // Store a pointer to the typval_T, so that it can be found by
+	    // index instead of using a hastab lookup.
+	    if (ga_grow(&si->sn_var_vals, 1) == OK)
+	    {
+		svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+						      + si->sn_var_vals.ga_len;
+		sv->sv_name = di->di_key;
+		sv->sv_tv = &di->di_tv;
+		sv->sv_type = type == NULL ? &t_any : type;
+		sv->sv_const = (flags & LET_IS_CONST);
+		sv->sv_export = is_export;
+		++si->sn_var_vals.ga_len;
+
+		// let ex_export() know the export worked.
+		is_export = FALSE;
+	    }
+	}
     }
 
     if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
-	copy_tv(tv, &v->di_tv);
+	copy_tv(tv, &di->di_tv);
     else
     {
-	v->di_tv = *tv;
-	v->di_tv.v_lock = 0;
+	di->di_tv = *tv;
+	di->di_tv.v_lock = 0;
 	init_tv(tv);
     }
 
-    if (is_const)
-	v->di_tv.v_lock |= VAR_LOCKED;
+    if (flags & LET_IS_CONST)
+	di->di_tv.v_lock |= VAR_LOCKED;
 }
 
 /*
@@ -3130,9 +3288,9 @@ var_redir_start(char_u *name, int append
     tv.v_type = VAR_STRING;
     tv.vval.v_string = (char_u *)"";
     if (append)
-	set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)".");
+	set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)".");
     else
-	set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)"=");
+	set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)"=");
     clear_lval(redir_lval);
     err = did_emsg;
     did_emsg |= save_emsg;
@@ -3205,7 +3363,7 @@ var_redir_stop(void)
 	    redir_endp = get_lval(redir_varname, NULL, redir_lval,
 					FALSE, FALSE, 0, FNE_CHECK_START);
 	    if (redir_endp != NULL && redir_lval->ll_name != NULL)
-		set_var_lval(redir_lval, redir_endp, &tv, FALSE, FALSE,
+		set_var_lval(redir_lval, redir_endp, &tv, FALSE, 0,
 								(char_u *)".");
 	    clear_lval(redir_lval);
 	}
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -9,28 +9,28 @@ static const unsigned short cmdidxs1[26]
   /* b */ 19,
   /* c */ 42,
   /* d */ 108,
-  /* e */ 130,
-  /* f */ 151,
-  /* g */ 167,
-  /* h */ 173,
-  /* i */ 182,
-  /* j */ 200,
-  /* k */ 202,
-  /* l */ 207,
-  /* m */ 269,
-  /* n */ 287,
-  /* o */ 307,
-  /* p */ 319,
-  /* q */ 358,
-  /* r */ 361,
-  /* s */ 381,
-  /* t */ 450,
-  /* u */ 495,
-  /* v */ 506,
-  /* w */ 524,
-  /* x */ 538,
-  /* y */ 548,
-  /* z */ 549
+  /* e */ 132,
+  /* f */ 155,
+  /* g */ 171,
+  /* h */ 177,
+  /* i */ 186,
+  /* j */ 205,
+  /* k */ 207,
+  /* l */ 212,
+  /* m */ 274,
+  /* n */ 292,
+  /* o */ 312,
+  /* p */ 324,
+  /* q */ 363,
+  /* r */ 366,
+  /* s */ 386,
+  /* t */ 455,
+  /* u */ 500,
+  /* v */ 511,
+  /* w */ 530,
+  /* x */ 544,
+  /* y */ 554,
+  /* z */ 555
 };
 
 /*
@@ -44,12 +44,12 @@ static const unsigned char cmdidxs2[26][
   /* a */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  4,  5,  6,  0,  0,  0,  7, 15,  0, 16,  0,  0,  0,  0,  0 },
   /* b */ {  2,  0,  0,  4,  5,  7,  0,  0,  0,  0,  0,  8,  9, 10, 11, 12,  0, 13,  0,  0,  0,  0, 22,  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,  6, 15,  0, 16,  0,  0, 17,  0,  0, 19, 20,  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, 16,  0, 17,  0,  0 },
+  /* d */ {  0,  0,  0,  0,  0,  0,  0,  0,  7, 17,  0, 18,  0,  0, 19,  0,  0, 21, 22,  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 },
   /* 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, 13,  0, 15,  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 },
   /* j */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 },
   /* k */ {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* l */ {  3, 11, 15, 19, 20, 24, 27, 32,  0,  0,  0, 34, 37, 40, 44, 50,  0, 52, 61, 53, 54, 58, 60,  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,  9, 12,  0,  0,  0,  0, 15,  0, 16,  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 },
   /* 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 = 562;
+static const int command_count = 568;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -442,6 +442,9 @@ EXCMD(CMD_debug,	"debug",	ex_debug,
 EXCMD(CMD_debuggreedy,	"debuggreedy",	ex_debuggreedy,
 	EX_RANGE|EX_ZEROR|EX_TRLBAR|EX_CMDWIN,
 	ADDR_OTHER),
+EXCMD(CMD_def,		"def",		ex_function,
+	EX_EXTRA|EX_BANG|EX_SBOXOK|EX_CMDWIN,
+	ADDR_NONE),
 EXCMD(CMD_delcommand,	"delcommand",	ex_delcommand,
 	EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN,
 	ADDR_NONE),
@@ -475,6 +478,9 @@ EXCMD(CMD_diffthis,	"diffthis",	ex_difft
 EXCMD(CMD_digraphs,	"digraphs",	ex_digraphs,
 	EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN,
 	ADDR_NONE),
+EXCMD(CMD_disassemble,	"disassemble",	ex_disassemble,
+	EX_EXTRA|EX_TRLBAR|EX_CMDWIN,
+	ADDR_NONE),
 EXCMD(CMD_djump,	"djump",	ex_findpat,
 	EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA,
 	ADDR_LINES),
@@ -529,6 +535,9 @@ EXCMD(CMD_emenu,	"emenu",	ex_emenu,
 EXCMD(CMD_endif,	"endif",	ex_endif,
 	EX_TRLBAR|EX_SBOXOK|EX_CMDWIN,
 	ADDR_NONE),
+EXCMD(CMD_enddef,	"enddef",	ex_endfunction,
+	EX_TRLBAR|EX_CMDWIN,
+	ADDR_NONE),
 EXCMD(CMD_endfunction,	"endfunction",	ex_endfunction,
 	EX_TRLBAR|EX_CMDWIN,
 	ADDR_NONE),
@@ -556,6 +565,9 @@ EXCMD(CMD_execute,	"execute",	ex_execute
 EXCMD(CMD_exit,		"exit",		ex_exit,
 	EX_RANGE|EX_WHOLEFOLD|EX_BANG|EX_FILE1|EX_ARGOPT|EX_DFLALL|EX_TRLBAR|EX_CMDWIN,
 	ADDR_LINES),
+EXCMD(CMD_export,	"export",	ex_export,
+	EX_EXTRA|EX_NOTRLCOM,
+	ADDR_NONE),
 EXCMD(CMD_exusage,	"exusage",	ex_exusage,
 	EX_TRLBAR,
 	ADDR_NONE),
@@ -679,6 +691,9 @@ EXCMD(CMD_imapclear,	"imapclear",	ex_map
 EXCMD(CMD_imenu,	"imenu",	ex_menu,
 	EX_RANGE|EX_ZEROR|EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN,
 	ADDR_OTHER),
+EXCMD(CMD_import,	"import",	ex_import,
+	EX_EXTRA|EX_NOTRLCOM,
+	ADDR_NONE),
 EXCMD(CMD_inoremap,	"inoremap",	ex_map,
 	EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN,
 	ADDR_NONE),
@@ -1648,6 +1663,9 @@ EXCMD(CMD_vimgrep,	"vimgrep",	ex_vimgrep
 EXCMD(CMD_vimgrepadd,	"vimgrepadd",	ex_vimgrep,
 	EX_RANGE|EX_BANG|EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_TRLBAR|EX_XFILE,
 	ADDR_OTHER),
+EXCMD(CMD_vim9script,	"vim9script",	ex_vim9script,
+	0,
+	ADDR_NONE),
 EXCMD(CMD_viusage,	"viusage",	ex_viusage,
 	EX_TRLBAR,
 	ADDR_NONE),
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -27,7 +27,6 @@ static int	if_level = 0;		// depth in :i
 #endif
 static void	free_cmdmod(void);
 static void	append_command(char_u *cmd);
-static char_u	*find_command(exarg_T *eap, int *full);
 
 #ifndef FEAT_MENU
 # define ex_emenu		ex_ni
@@ -275,6 +274,7 @@ static void	ex_tag_cmd(exarg_T *eap, cha
 # define ex_debug		ex_ni
 # define ex_debuggreedy		ex_ni
 # define ex_delfunction		ex_ni
+# define ex_disassemble		ex_ni
 # define ex_echo		ex_ni
 # define ex_echohl		ex_ni
 # define ex_else		ex_ni
@@ -300,7 +300,10 @@ static void	ex_tag_cmd(exarg_T *eap, cha
 # define ex_try			ex_ni
 # define ex_unlet		ex_ni
 # define ex_unlockvar		ex_ni
+# define ex_vim9script		ex_ni
 # define ex_while		ex_ni
+# define ex_import		ex_ni
+# define ex_export		ex_ni
 #endif
 #ifndef FEAT_SESSION
 # define ex_loadview		ex_ni
@@ -1708,7 +1711,13 @@ do_one_cmd(
     ea.cmd = skip_range(ea.cmd, NULL);
     if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL)
 	ea.cmd = skipwhite(ea.cmd + 1);
-    p = find_command(&ea, NULL);
+
+#ifdef FEAT_EVAL
+    if (current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+	p = find_ex_command(&ea, NULL, lookup_scriptvar, NULL);
+    else
+#endif
+	p = find_ex_command(&ea, NULL, NULL, NULL);
 
 #ifdef FEAT_EVAL
 # ifdef FEAT_PROFILE
@@ -1876,7 +1885,7 @@ do_one_cmd(
 #ifdef FEAT_EVAL
 		&& !aborting()
 #endif
-		) ? find_command(&ea, NULL) : ea.cmd;
+		) ? find_ex_command(&ea, NULL, NULL, NULL) : ea.cmd;
     }
 
     if (p == NULL)
@@ -2485,6 +2494,10 @@ do_one_cmd(
     }
 
 #ifdef FEAT_EVAL
+    // Set flag that any command was executed, used by ex_vim9script().
+    if (getline_equal(ea.getline, ea.cookie, getsourceline))
+	SCRIPT_ITEM(current_sctx.sc_sid).sn_had_command = TRUE;
+
     /*
      * If the command just executed called do_cmdline(), any throw or ":return"
      * or ":finish" encountered there must also check the cstack of the still
@@ -3108,15 +3121,64 @@ append_command(char_u *cmd)
  * Start of the name can be found at eap->cmd.
  * Sets eap->cmdidx and returns a pointer to char after the command name.
  * "full" is set to TRUE if the whole command name matched.
+ *
+ * If "lookup" is not NULL recognize expression without "eval" or "call" and
+ * assignment without "let".  Sets eap->cmdidx to the command while returning
+ * "eap->cmd".
+ *
  * Returns NULL for an ambiguous user command.
  */
-    static char_u *
-find_command(exarg_T *eap, int *full UNUSED)
+    char_u *
+find_ex_command(
+	exarg_T *eap,
+	int *full UNUSED,
+	int (*lookup)(char_u *, size_t, cctx_T *) UNUSED,
+	cctx_T *cctx UNUSED)
 {
     int		len;
     char_u	*p;
     int		i;
 
+#ifdef FEAT_EVAL
+    /*
+     * Recognize a Vim9 script function/method call and assignment:
+     * "lvar = value", "lvar(arg)", "[1, 2 3]->Func()"
+     */
+    if (lookup != NULL && (p = to_name_const_end(eap->cmd)) > eap->cmd
+								  && *p != NUL)
+    {
+	int oplen;
+	int heredoc;
+
+	// "funcname(" is always a function call.
+	// "varname[]" is an expression.
+	// "g:varname" is an expression.
+	// "varname->expr" is an expression.
+	if (*p == '('
+		|| *p == '['
+		|| p[1] == ':'
+		|| (*p == '-' && p[1] == '>'))
+	{
+	    eap->cmdidx = CMD_eval;
+	    return eap->cmd;
+	}
+
+	oplen = assignment_len(skipwhite(p), &heredoc);
+	if (oplen > 0)
+	{
+	    // Recognize an assignment if we recognize the variable name:
+	    // "g:var = expr"
+	    // "var = expr"  where "var" is a local var name.
+	    if (((p - eap->cmd) > 2 && eap->cmd[1] == ':')
+		    || lookup(eap->cmd, p - eap->cmd, cctx) >= 0)
+	    {
+		eap->cmdidx = CMD_let;
+		return eap->cmd;
+	    }
+	}
+    }
+#endif
+
     /*
      * Isolate the command and search for it in the command table.
      * Exceptions:
@@ -3149,8 +3211,17 @@ find_command(exarg_T *eap, int *full UNU
 	    ++p;
 	// for python 3.x support ":py3", ":python3", ":py3file", etc.
 	if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y')
+	{
 	    while (ASCII_ISALNUM(*p))
 		++p;
+	}
+	else if (*p == '9' && STRNCMP("vim9", eap->cmd, 4) == 0)
+	{
+	    // include "9" for "vim9script"
+	    ++p;
+	    while (ASCII_ISALPHA(*p))
+		++p;
+	}
 
 	// check for non-alpha command
 	if (p == eap->cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL)
@@ -3307,7 +3378,7 @@ cmd_exists(char_u *name)
     // For ":2match" and ":3match" we need to skip the number.
     ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
     ea.cmdidx = (cmdidx_T)0;
-    p = find_command(&ea, &full);
+    p = find_ex_command(&ea, &full, NULL, NULL);
     if (p == NULL)
 	return 3;
     if (vim_isdigit(*name) && ea.cmdidx != CMD_match)
@@ -8558,7 +8629,7 @@ ex_folddo(exarg_T *eap)
 }
 #endif
 
-#ifdef FEAT_QUICKFIX
+#if defined(FEAT_QUICKFIX) || defined(PROTO)
 /*
  * Returns TRUE if the supplied Ex cmdidx is for a location list command
  * instead of a quickfix command.
--- a/src/ex_eval.c
+++ b/src/ex_eval.c
@@ -15,7 +15,6 @@
 
 #if defined(FEAT_EVAL) || defined(PROTO)
 
-static int	throw_exception(void *, except_type_T, char_u *);
 static char	*get_end_emsg(cstack_T *cstack);
 
 /*
@@ -498,7 +497,7 @@ get_exception_string(
  * user or interrupt exception, or points to a message list in case of an
  * error exception.
  */
-    static int
+    int
 throw_exception(void *value, except_type_T type, char_u *cmdname)
 {
     except_T	*excp;
@@ -649,7 +648,7 @@ discard_current_exception(void)
 /*
  * Put an exception on the caught stack.
  */
-    static void
+    void
 catch_exception(except_T *excp)
 {
     excp->caught = caught_stack;
@@ -932,7 +931,7 @@ ex_endif(exarg_T *eap)
     if (eap->cstack->cs_idx < 0
 	    || (eap->cstack->cs_flags[eap->cstack->cs_idx]
 					   & (CSF_WHILE | CSF_FOR | CSF_TRY)))
-	eap->errmsg = N_("E580: :endif without :if");
+	eap->errmsg = N_(e_endif_without_if);
     else
     {
 	/*
@@ -976,10 +975,10 @@ ex_else(exarg_T *eap)
     {
 	if (eap->cmdidx == CMD_else)
 	{
-	    eap->errmsg = N_("E581: :else without :if");
+	    eap->errmsg = N_(e_else_without_if);
 	    return;
 	}
-	eap->errmsg = N_("E582: :elseif without :if");
+	eap->errmsg = N_(e_elseif_without_if);
 	skip = TRUE;
     }
     else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE)
@@ -1152,7 +1151,7 @@ ex_continue(exarg_T *eap)
     cstack_T	*cstack = eap->cstack;
 
     if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
-	eap->errmsg = N_("E586: :continue without :while or :for");
+	eap->errmsg = N_(e_continue);
     else
     {
 	// Try to find the matching ":while".  This might stop at a try
@@ -1190,7 +1189,7 @@ ex_break(exarg_T *eap)
     cstack_T	*cstack = eap->cstack;
 
     if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
-	eap->errmsg = N_("E587: :break without :while or :for");
+	eap->errmsg = N_(e_break);
     else
     {
 	// Inactivate conditionals until the matching ":while" or a try
@@ -1492,7 +1491,7 @@ ex_catch(exarg_T *eap)
 
     if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
     {
-	eap->errmsg = N_("E603: :catch without :try");
+	eap->errmsg = e_catch;
 	give_up = TRUE;
     }
     else
@@ -1648,7 +1647,7 @@ ex_finally(exarg_T *eap)
     cstack_T	*cstack = eap->cstack;
 
     if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
-	eap->errmsg = N_("E606: :finally without :try");
+	eap->errmsg = e_finally;
     else
     {
 	if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
@@ -1668,7 +1667,7 @@ ex_finally(exarg_T *eap)
 	if (cstack->cs_flags[idx] & CSF_FINALLY)
 	{
 	    // Give up for a multiple ":finally" and ignore it.
-	    eap->errmsg = N_("E607: multiple :finally");
+	    eap->errmsg = e_finally_dup;
 	    return;
 	}
 	rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
@@ -1777,7 +1776,7 @@ ex_endtry(exarg_T *eap)
     cstack_T	*cstack = eap->cstack;
 
     if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
-	eap->errmsg = N_("E602: :endtry without :try");
+	eap->errmsg = e_no_endtry;
     else
     {
 	/*
--- a/src/filepath.c
+++ b/src/filepath.c
@@ -1892,6 +1892,7 @@ f_writefile(typval_T *argvars, typval_T 
 	list = argvars[0].vval.v_list;
 	if (list == NULL)
 	    return;
+	range_list_materialize(list);
 	for (li = list->lv_first; li != NULL; li = li->li_next)
 	    if (tv_get_string_chk(&li->li_tv) == NULL)
 		return;
--- a/src/globals.h
+++ b/src/globals.h
@@ -286,8 +286,11 @@ EXTERN int	debug_backtrace_level INIT(= 
 EXTERN int	do_profiling INIT(= PROF_NONE);	// PROF_ values
 # endif
 EXTERN garray_T script_items INIT5(0, 0, sizeof(scriptitem_T), 4, NULL);
-#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
-#define FUNCLINE(fp, j)	((char_u **)(fp->uf_lines.ga_data))[j]
+# define SCRIPT_ITEM(id)    (((scriptitem_T *)script_items.ga_data)[(id) - 1])
+# define SCRIPT_SV(id)	    (SCRIPT_ITEM(id).sn_vars)
+# define SCRIPT_VARS(id)    (SCRIPT_SV(id)->sv_dict.dv_hashtab)
+
+# define FUNCLINE(fp, j)	((char_u **)(fp->uf_lines.ga_data))[j]
 
 /*
  * The exception currently being thrown.  Used to pass an exception to
@@ -359,9 +362,6 @@ EXTERN int suppress_errthrow INIT(= FALS
  */
 EXTERN except_T *caught_stack INIT(= NULL);
 
-#endif
-
-#ifdef FEAT_EVAL
 /*
  * Garbage collection can only take place when we are sure there are no Lists
  * or Dictionaries being used internally.  This is flagged with
@@ -376,6 +376,39 @@ EXTERN int	garbage_collect_at_exit INIT(
 
 // Script CTX being sourced or was sourced to define the current function.
 EXTERN sctx_T	current_sctx INIT4(0, 0, 0, 0);
+
+
+// Commonly used types.
+EXTERN type_T t_any INIT4(VAR_UNKNOWN, 0, NULL, NULL);
+EXTERN type_T t_void INIT4(VAR_VOID, 0, NULL, NULL);
+EXTERN type_T t_bool INIT4(VAR_BOOL, 0, NULL, NULL);
+EXTERN type_T t_special INIT4(VAR_SPECIAL, 0, NULL, NULL);
+EXTERN type_T t_number INIT4(VAR_NUMBER, 0, NULL, NULL);
+#ifdef FEAT_FLOAT
+EXTERN type_T t_float INIT4(VAR_FLOAT, 0, NULL, NULL);
+#endif
+EXTERN type_T t_string INIT4(VAR_STRING, 0, NULL, NULL);
+EXTERN type_T t_blob INIT4(VAR_BLOB, 0, NULL, NULL);
+EXTERN type_T t_job INIT4(VAR_JOB, 0, NULL, NULL);
+EXTERN type_T t_channel INIT4(VAR_CHANNEL, 0, NULL, NULL);
+
+EXTERN type_T t_func_void INIT4(VAR_FUNC, -1, &t_void, NULL);
+EXTERN type_T t_func_any INIT4(VAR_FUNC, -1, &t_any, NULL);
+
+EXTERN type_T t_partial_void INIT4(VAR_PARTIAL, -1, &t_void, NULL);
+EXTERN type_T t_partial_any INIT4(VAR_PARTIAL, -1, &t_any, NULL);
+
+EXTERN type_T t_list_any INIT4(VAR_LIST, 0, &t_any, NULL);
+EXTERN type_T t_dict_any INIT4(VAR_DICT, 0, &t_any, NULL);
+
+EXTERN type_T t_list_number INIT4(VAR_LIST, 0, &t_number, NULL);
+EXTERN type_T t_list_string INIT4(VAR_LIST, 0, &t_string, NULL);
+EXTERN type_T t_list_dict_any INIT4(VAR_LIST, 0, &t_dict_any, NULL);
+
+EXTERN type_T t_dict_number INIT4(VAR_DICT, 0, &t_number, NULL);
+EXTERN type_T t_dict_string INIT4(VAR_DICT, 0, &t_string, NULL);
+
+
 #endif
 
 EXTERN int	did_source_packages INIT(= FALSE);
@@ -1038,6 +1071,8 @@ EXTERN int	ctrl_c_interrupts INIT(= TRUE
 
 EXTERN cmdmod_T	cmdmod;			// Ex command modifiers
 
+EXTERN int	is_export INIT(= FALSE);    // :export {cmd}
+
 EXTERN int	msg_silent INIT(= 0);	// don't print messages
 EXTERN int	emsg_silent INIT(= 0);	// don't print error messages
 EXTERN int	emsg_noredir INIT(= 0);	// don't redirect error messages
@@ -1465,9 +1500,13 @@ EXTERN char e_cmdwin[]	INIT(= N_("E11: I
 EXTERN char e_curdir[]	INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search"));
 #ifdef FEAT_EVAL
 EXTERN char e_endif[]		INIT(= N_("E171: Missing :endif"));
-EXTERN char e_endtry[]	INIT(= N_("E600: Missing :endtry"));
+EXTERN char e_catch[]		INIT(= N_("E603: :catch without :try"));
+EXTERN char e_finally[]		INIT(= N_("E606: :finally without :try"));
+EXTERN char e_finally_dup[]	INIT(= N_("E607: multiple :finally"));
+EXTERN char e_endtry[]		INIT(= N_("E600: Missing :endtry"));
+EXTERN char e_no_endtry[]	INIT(= N_("E602: :endtry without :try"));
 EXTERN char e_endwhile[]	INIT(= N_("E170: Missing :endwhile"));
-EXTERN char e_endfor[]	INIT(= N_("E170: Missing :endfor"));
+EXTERN char e_endfor[]		INIT(= N_("E170: Missing :endfor"));
 EXTERN char e_while[]		INIT(= N_("E588: :endwhile without :while"));
 EXTERN char e_for[]		INIT(= N_("E588: :endfor without :for"));
 #endif
@@ -1556,7 +1595,7 @@ EXTERN char e_notmp[]		INIT(= N_("E483: 
 EXTERN char e_notopen[]	INIT(= N_("E484: Can't open file %s"));
 EXTERN char e_notread[]	INIT(= N_("E485: Can't read file %s"));
 EXTERN char e_null[]		INIT(= N_("E38: Null argument"));
-#if defined(FEAT_DIGRAPHS) || defined(FEAT_TIMERS)
+#if defined(FEAT_DIGRAPHS) || defined(FEAT_TIMERS) || defined(FEAT_EVAL)
 EXTERN char e_number_exp[]	INIT(= N_("E39: Number expected"));
 #endif
 #ifdef FEAT_QUICKFIX
@@ -1575,10 +1614,10 @@ EXTERN char e_prev_dir[]	INIT(= N_("E459
 
 #ifdef FEAT_QUICKFIX
 EXTERN char e_quickfix[]	INIT(= N_("E42: No Errors"));
-EXTERN char e_loclist[]	INIT(= N_("E776: No location list"));
+EXTERN char e_loclist[]		INIT(= N_("E776: No location list"));
 #endif
-EXTERN char e_re_damg[]	INIT(= N_("E43: Damaged match string"));
-EXTERN char e_re_corr[]	INIT(= N_("E44: Corrupted regexp program"));
+EXTERN char e_re_damg[]		INIT(= N_("E43: Damaged match string"));
+EXTERN char e_re_corr[]		INIT(= N_("E44: Corrupted regexp program"));
 EXTERN char e_readonly[]	INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
 #ifdef FEAT_EVAL
 EXTERN char e_undefvar[]	INIT(= N_("E121: Undefined variable: %s"));
@@ -1589,17 +1628,23 @@ EXTERN char e_readonlyvar[]	INIT(= N_("E
 EXTERN char e_readonlysbx[]	INIT(= N_("E794: Cannot set variable in the sandbox: \"%s\""));
 EXTERN char e_stringreq[]	INIT(= N_("E928: String required"));
 EXTERN char e_emptykey[]	INIT(= N_("E713: Cannot use empty key for Dictionary"));
-EXTERN char e_dictreq[]	INIT(= N_("E715: Dictionary required"));
-EXTERN char e_listidx[]	INIT(= N_("E684: list index out of range: %ld"));
-EXTERN char e_blobidx[]	INIT(= N_("E979: Blob index out of range: %ld"));
+EXTERN char e_dictreq[]		INIT(= N_("E715: Dictionary required"));
+EXTERN char e_listidx[]		INIT(= N_("E684: list index out of range: %ld"));
+EXTERN char e_blobidx[]		INIT(= N_("E979: Blob index out of range: %ld"));
 EXTERN char e_invalblob[]	INIT(= N_("E978: Invalid operation for Blob"));
 EXTERN char e_toomanyarg[]	INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN char e_toofewarg[]	INIT(= N_("E119: Not enough arguments for function: %s"));
+EXTERN char e_func_deleted[]	INIT(= N_("E933: Function was deleted: %s"));
 EXTERN char e_dictkey[]	INIT(= N_("E716: Key not present in Dictionary: %s"));
-EXTERN char e_listreq[]	INIT(= N_("E714: List required"));
+EXTERN char e_listreq[]		INIT(= N_("E714: List required"));
 EXTERN char e_listblobreq[]	INIT(= N_("E897: List or Blob required"));
 EXTERN char e_listdictarg[]	INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
 EXTERN char e_listdictblobarg[]	INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
+EXTERN char e_modulus[]		INIT(= N_("E804: Cannot use '%' with Float"));
 EXTERN char e_inval_string[]	INIT(= N_("E908: using an invalid value as a String"));
+EXTERN char e_const_option[]	INIT(= N_("E996: Cannot lock an option"));
+EXTERN char e_unknown_option[]	INIT(= N_("E113: Unknown option: %s"));
+EXTERN char e_letunexp[]	INIT(= N_("E18: Unexpected characters in :let"));
 #endif
 #ifdef FEAT_QUICKFIX
 EXTERN char e_readerrf[]	INIT(= N_("E47: Error while reading errorfile"));
@@ -1632,7 +1677,12 @@ EXTERN char e_write[]		INIT(= N_("E80: E
 EXTERN char e_zerocount[]	INIT(= N_("E939: Positive count required"));
 #ifdef FEAT_EVAL
 EXTERN char e_usingsid[]	INIT(= N_("E81: Using <SID> not in a script context"));
-EXTERN char e_missingparen[]	INIT(= N_("E107: Missing parentheses: %s"));
+EXTERN char e_missing_paren[]	INIT(= N_("E107: Missing parentheses: %s"));
+EXTERN char e_missing_close[]	INIT(= N_("E110: Missing ')'"));
+EXTERN char e_missing_dict_colon[] INIT(= N_("E720: Missing colon in Dictionary: %s"));
+EXTERN char e_duplicate_key[]	INIT(= N_("E721: Duplicate key in Dictionary: \"%s\""));
+EXTERN char e_missing_dict_comma[] INIT(= N_("E722: Missing comma in Dictionary: %s"));
+EXTERN char e_missing_dict_end[]    INIT(= N_("E723: Missing end of Dictionary '}': %s"));
 #endif
 #ifdef FEAT_CLIENTSERVER
 EXTERN char e_invexprmsg[]	INIT(= N_("E449: Invalid expression received"));
@@ -1660,6 +1710,18 @@ EXTERN char e_menuothermode[]	INIT(= N_(
 #endif
 EXTERN char e_invalwindow[]	INIT(= N_("E957: Invalid window number"));
 EXTERN char e_listarg[]		INIT(= N_("E686: Argument of %s must be a List"));
+#ifdef FEAT_EVAL
+EXTERN char e_missing_colon[]	INIT(= N_("E109: Missing ':' after '?'"));
+EXTERN char e_missing_in[]	INIT(= N_("E690: Missing \"in\" after :for"));
+EXTERN char e_unknownfunc[]	INIT(= N_("E117: Unknown function: %s"));
+EXTERN char e_missbrac[]	INIT(= N_("E111: Missing ']'"));
+EXTERN char e_else_without_if[] INIT(= N_("E581: :else without :if"));
+EXTERN char e_elseif_without_if[] INIT(= N_("E582: :elseif without :if"));
+EXTERN char e_endif_without_if[] INIT(= N_("E580: :endif without :if"));
+EXTERN char e_continue[]	INIT(= N_("E586: :continue without :while or :for"));
+EXTERN char e_break[]		INIT(= N_("E587: :break without :while or :for"));
+EXTERN char e_nowhitespace[]	INIT(= N_("E274: No white space allowed before parenthesis"));
+#endif
 
 #ifdef FEAT_GUI_MAC
 EXTERN short disallow_gui	INIT(= FALSE);
@@ -1735,6 +1797,9 @@ EXTERN int *eval_lavars_used INIT(= NULL
 
 // Only filled for Win32.
 EXTERN char windowsVersion[20] INIT(= {0});
+
+// Used for a non-materialized range() list.
+EXTERN listitem_T range_list_item;
 #endif
 
 #ifdef MSWIN
--- a/src/gui.c
+++ b/src/gui.c
@@ -519,7 +519,7 @@ gui_init(void)
 	if (vim_strchr(p_go, GO_NOSYSMENU) == NULL)
 	{
 	    sys_menu = TRUE;
-	    do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE);
+	    do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE, NULL);
 	    sys_menu = FALSE;
 	}
 #endif
@@ -540,8 +540,8 @@ gui_init(void)
 	{
 	    if (STRCMP(use_gvimrc, "NONE") != 0
 		    && STRCMP(use_gvimrc, "NORC") != 0
-		    && do_source(use_gvimrc, FALSE, DOSO_NONE) != OK)
-		semsg(_("E230: Cannot read from \"%s\""), use_gvimrc);
+		    && do_source(use_gvimrc, FALSE, DOSO_NONE, NULL) != OK)
+		semsg(_("E230: Cannot read from \"%s\""), use_gvimrc, NULL);
 	}
 	else
 	{
@@ -549,7 +549,7 @@ gui_init(void)
 	     * Get system wide defaults for gvim, only when file name defined.
 	     */
 #ifdef SYS_GVIMRC_FILE
-	    do_source((char_u *)SYS_GVIMRC_FILE, FALSE, DOSO_NONE);
+	    do_source((char_u *)SYS_GVIMRC_FILE, FALSE, DOSO_NONE, NULL);
 #endif
 
 	    /*
@@ -563,19 +563,20 @@ gui_init(void)
 	     */
 	    if (process_env((char_u *)"GVIMINIT", FALSE) == FAIL
 		 && do_source((char_u *)USR_GVIMRC_FILE, TRUE,
-							  DOSO_GVIMRC) == FAIL
+						     DOSO_GVIMRC, NULL) == FAIL
 #ifdef USR_GVIMRC_FILE2
 		 && do_source((char_u *)USR_GVIMRC_FILE2, TRUE,
-							  DOSO_GVIMRC) == FAIL
+						     DOSO_GVIMRC, NULL) == FAIL
 #endif
 #ifdef USR_GVIMRC_FILE3
 		 && do_source((char_u *)USR_GVIMRC_FILE3, TRUE,
-							  DOSO_GVIMRC) == FAIL
+						     DOSO_GVIMRC, NULL) == FAIL
 #endif
 				)
 	    {
 #ifdef USR_GVIMRC_FILE4
-		(void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE, DOSO_GVIMRC);
+		(void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE,
+							    DOSO_GVIMRC, NULL);
 #endif
 	    }
 
@@ -623,7 +624,7 @@ gui_init(void)
 				(char_u *)GVIMRC_FILE, FALSE, TRUE) != FPC_SAME
 #endif
 			)
-		    do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC);
+		    do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC, NULL);
 
 		if (secure == 2)
 		    need_wait_return = TRUE;
--- a/src/if_lua.c
+++ b/src/if_lua.c
@@ -841,8 +841,7 @@ luaV_list_newindex(lua_State *L)
     if (lua_isnil(L, 3)) // remove?
     {
 	vimlist_remove(l, li, li);
-	clear_tv(&li->li_tv);
-	vim_free(li);
+	listitem_free(l, li);
     }
     else
     {
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -785,6 +785,7 @@ VimToPython(typval_T *our_tv, int depth,
 	    return NULL;
 	}
 
+	range_list_materialize(list);
 	for (curr = list->lv_first; curr != NULL; curr = curr->li_next)
 	{
 	    if (!(newObj = VimToPython(&curr->li_tv, depth + 1, lookup_dict)))
@@ -2255,6 +2256,7 @@ ListNew(PyTypeObject *subtype, list_T *l
 	return NULL;
     self->list = list;
     ++list->lv_refcount;
+    range_list_materialize(list);
 
     pyll_add((PyObject *)(self), &self->ref, &lastlist);
 
@@ -2302,7 +2304,7 @@ list_py_concat(list_T *l, PyObject *obj,
 	{
 	    Py_DECREF(item);
 	    Py_DECREF(iterator);
-	    listitem_free(li);
+	    listitem_free(l, li);
 	    return -1;
 	}
 
@@ -2662,7 +2664,7 @@ ListAssSlice(ListObject *self, Py_ssize_
     }
 
     for (i = 0; i < numreplaced; i++)
-	listitem_free(lis[i]);
+	listitem_free(l, lis[i]);
     if (step == 1)
 	for (i = numreplaced; i < slicelen; i++)
 	    listitem_remove(l, lis[i]);
@@ -2822,6 +2824,7 @@ ListIter(ListObject *self)
 	return NULL;
     }
 
+    range_list_materialize(l);
     list_add_watch(l, &lii->lw);
     lii->lw.lw_item = l->lv_first;
     lii->list = l;
@@ -3018,6 +3021,7 @@ FunctionConstructor(PyTypeObject *subtyp
 		return NULL;
 	    }
 	    argslist = argstv.vval.v_list;
+	    range_list_materialize(argslist);
 
 	    argc = argslist->lv_len;
 	    if (argc != 0)
@@ -6386,6 +6390,7 @@ ConvertToPyObject(typval_T *tv)
 		(char*) tv->vval.v_blob->bv_ga.ga_data,
 		(Py_ssize_t) tv->vval.v_blob->bv_ga.ga_len);
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	case VAR_CHANNEL:
 	case VAR_JOB:
 	    Py_INCREF(Py_None);
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -576,8 +576,8 @@ ins_compl_add(
     char_u	*str,
     int		len,
     char_u	*fname,
-    char_u	**cptext,	// extra text for popup menu or NULL
-    typval_T	*user_data,	// "user_data" entry or NULL
+    char_u	**cptext,	    // extra text for popup menu or NULL
+    typval_T	*user_data UNUSED,  // "user_data" entry or NULL
     int		cdir,
     int		flags_arg,
     int		adup)		// accept duplicate match
--- a/src/json.c
+++ b/src/json.c
@@ -215,7 +215,7 @@ json_encode_item(garray_T *gap, typval_T
 
 	case VAR_NUMBER:
 	    vim_snprintf((char *)numbuf, NUMBUFLEN, "%lld",
-						(long_long_T)val->vval.v_number);
+					      (long_long_T)val->vval.v_number);
 	    ga_concat(gap, numbuf);
 	    break;
 
@@ -350,6 +350,7 @@ json_encode_item(garray_T *gap, typval_T
 	    break;
 #endif
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	    internal_error("json_encode_item()");
 	    return FAIL;
     }
--- a/src/list.c
+++ b/src/list.c
@@ -65,6 +65,17 @@ list_fix_watch(list_T *l, listitem_T *it
 	    lw->lw_item = item->li_next;
 }
 
+    static void
+list_init(list_T *l)
+{
+    // Prepend the list to the list of lists for garbage collection.
+    if (first_list != NULL)
+	first_list->lv_used_prev = l;
+    l->lv_used_prev = NULL;
+    l->lv_used_next = first_list;
+    first_list = l;
+}
+
 /*
  * Allocate an empty header for a list.
  * Caller should take care of the reference count.
@@ -76,14 +87,7 @@ list_alloc(void)
 
     l = ALLOC_CLEAR_ONE(list_T);
     if (l != NULL)
-    {
-	// Prepend the list to the list of lists for garbage collection.
-	if (first_list != NULL)
-	    first_list->lv_used_prev = l;
-	l->lv_used_prev = NULL;
-	l->lv_used_next = first_list;
-	first_list = l;
-    }
+	list_init(l);
     return l;
 }
 
@@ -101,6 +105,59 @@ list_alloc_id(alloc_id_T id UNUSED)
 }
 
 /*
+ * Allocate space for a list, plus "count" items.
+ * Next list_set_item() must be called for each item.
+ */
+    list_T *
+list_alloc_with_items(int count)
+{
+    list_T	*l;
+
+    l = (list_T *)alloc_clear(sizeof(list_T) + count * sizeof(listitem_T));
+    if (l != NULL)
+    {
+	list_init(l);
+
+	if (count > 0)
+	{
+	    listitem_T	*li = (listitem_T *)(l + 1);
+	    int		i;
+
+	    l->lv_len = count;
+	    l->lv_with_items = count;
+	    l->lv_first = li;
+	    l->lv_last = li + count - 1;
+	    for (i = 0; i < count; ++i)
+	    {
+		if (i == 0)
+		    li->li_prev = NULL;
+		else
+		    li->li_prev = li - 1;
+		if (i == count - 1)
+		    li->li_next = NULL;
+		else
+		    li->li_next = li + 1;
+		++li;
+	    }
+	}
+    }
+    return l;
+}
+
+/*
+ * Set item "idx" for a list previously allocated with list_alloc_with_items().
+ * The contents of "tv" is moved into the list item.
+ * Each item must be set exactly once.
+ */
+    void
+list_set_item(list_T *l, int idx, typval_T *tv)
+{
+    listitem_T	*li = (listitem_T *)(l + 1) + idx;
+
+    li->li_tv = *tv;
+}
+
+/*
  * Allocate an empty list for a return value, with reference count set.
  * Returns OK or FAIL.
  */
@@ -163,13 +220,14 @@ list_free_contents(list_T *l)
 {
     listitem_T *item;
 
-    for (item = l->lv_first; item != NULL; item = l->lv_first)
-    {
-	// Remove the item before deleting it.
-	l->lv_first = item->li_next;
-	clear_tv(&item->li_tv);
-	vim_free(item);
-    }
+    if (l->lv_first != &range_list_item)
+	for (item = l->lv_first; item != NULL; item = l->lv_first)
+	{
+	    // Remove the item before deleting it.
+	    l->lv_first = item->li_next;
+	    clear_tv(&item->li_tv);
+	    list_free_item(l, item);
+	}
 }
 
 /*
@@ -250,13 +308,26 @@ listitem_alloc(void)
 }
 
 /*
- * Free a list item.  Also clears the value.  Does not notify watchers.
+ * Free a list item, unless it was allocated together with the list itself.
+ * Does not clear the value.  Does not notify watchers.
  */
     void
-listitem_free(listitem_T *item)
+list_free_item(list_T *l, listitem_T *item)
+{
+    if (l->lv_with_items == 0 || item < (listitem_T *)l
+			   || item >= (listitem_T *)(l + 1) + l->lv_with_items)
+	vim_free(item);
+}
+
+/*
+ * Free a list item, unless it was allocated together with the list itself.
+ * Also clears the value.  Does not notify watchers.
+ */
+    void
+listitem_free(list_T *l, listitem_T *item)
 {
     clear_tv(&item->li_tv);
-    vim_free(item);
+    list_free_item(l, item);
 }
 
 /*
@@ -266,7 +337,7 @@ listitem_free(listitem_T *item)
 listitem_remove(list_T *l, listitem_T *item)
 {
     vimlist_remove(l, item, item);
-    listitem_free(item);
+    listitem_free(l, item);
 }
 
 /*
@@ -299,6 +370,9 @@ list_equal(
     if (list_len(l1) != list_len(l2))
 	return FALSE;
 
+    range_list_materialize(l1);
+    range_list_materialize(l2);
+
     for (item1 = l1->lv_first, item2 = l2->lv_first;
 	    item1 != NULL && item2 != NULL;
 			       item1 = item1->li_next, item2 = item2->li_next)
@@ -329,6 +403,8 @@ list_find(list_T *l, long n)
     if (n < 0 || n >= l->lv_len)
 	return NULL;
 
+    range_list_materialize(l);
+
     // When there is a cached index may start search from there.
     if (l->lv_idx_item != NULL)
     {
@@ -398,6 +474,26 @@ list_find_nr(
 {
     listitem_T	*li;
 
+    if (l != NULL && l->lv_first == &range_list_item)
+    {
+	long	    n = idx;
+
+	// not materialized range() list: compute the value.
+	// Negative index is relative to the end.
+	if (n < 0)
+	    n = l->lv_len + n;
+
+	// Check for index out of range.
+	if (n < 0 || n >= l->lv_len)
+	{
+	    if (errorp != NULL)
+		*errorp = TRUE;
+	    return -1L;
+	}
+
+	return l->lv_start + n * l->lv_stride;
+    }
+
     li = list_find(l, idx);
     if (li == NULL)
     {
@@ -437,6 +533,7 @@ list_idx_of_item(list_T *l, listitem_T *
 
     if (l == NULL)
 	return -1;
+    range_list_materialize(l);
     idx = 0;
     for (li = l->lv_first; li != NULL && li != item; li = li->li_next)
 	++idx;
@@ -451,6 +548,7 @@ list_idx_of_item(list_T *l, listitem_T *
     void
 list_append(list_T *l, listitem_T *item)
 {
+    range_list_materialize(l);
     if (l->lv_last == NULL)
     {
 	// empty list
@@ -469,7 +567,7 @@ list_append(list_T *l, listitem_T *item)
 }
 
 /*
- * Append typval_T "tv" to the end of list "l".
+ * Append typval_T "tv" to the end of list "l".  "tv" is copied.
  * Return FAIL when out of memory.
  */
     int
@@ -485,6 +583,22 @@ list_append_tv(list_T *l, typval_T *tv)
 }
 
 /*
+ * As list_append_tv() but move the value instead of copying it.
+ * Return FAIL when out of memory.
+ */
+    int
+list_append_tv_move(list_T *l, typval_T *tv)
+{
+    listitem_T	*li = listitem_alloc();
+
+    if (li == NULL)
+	return FAIL;
+    li->li_tv = *tv;
+    list_append(l, li);
+    return OK;
+}
+
+/*
  * Add a dictionary to a list.  Used by getqflist().
  * Return FAIL when out of memory.
  */
@@ -584,6 +698,7 @@ list_insert_tv(list_T *l, typval_T *tv, 
     void
 list_insert(list_T *l, listitem_T *ni, listitem_T *item)
 {
+    range_list_materialize(l);
     if (item == NULL)
 	// Append new item at end of list.
 	list_append(l, ni);
@@ -618,6 +733,9 @@ list_extend(list_T *l1, list_T *l2, list
     listitem_T	*item;
     int		todo = l2->lv_len;
 
+    range_list_materialize(l1);
+    range_list_materialize(l2);
+
     // We also quit the loop when we have inserted the original item count of
     // the list, avoid a hang when we extend a list with itself.
     for (item = l2->lv_first; item != NULL && --todo >= 0; item = item->li_next)
@@ -675,6 +793,7 @@ list_copy(list_T *orig, int deep, int co
 	    orig->lv_copyID = copyID;
 	    orig->lv_copylist = copy;
 	}
+	range_list_materialize(orig);
 	for (item = orig->lv_first; item != NULL && !got_int;
 							 item = item->li_next)
 	{
@@ -715,6 +834,8 @@ vimlist_remove(list_T *l, listitem_T *it
 {
     listitem_T	*ip;
 
+    range_list_materialize(l);
+
     // notify watchers
     for (ip = item; ip != NULL; ip = ip->li_next)
     {
@@ -748,6 +869,7 @@ list2string(typval_T *tv, int copyID, in
 	return NULL;
     ga_init2(&ga, (int)sizeof(char), 80);
     ga_append(&ga, '[');
+    range_list_materialize(tv->vval.v_list);
     if (list_join(&ga, tv->vval.v_list, (char_u *)", ",
 				       FALSE, restore_copyID, copyID) == FAIL)
     {
@@ -785,6 +907,7 @@ list_join_inner(
     char_u	*s;
 
     // Stringify each item in the list.
+    range_list_materialize(l);
     for (item = l->lv_first; item != NULL && !got_int; item = item->li_next)
     {
 	s = echo_string_core(&item->li_tv, &tofree, numbuf, copyID,
@@ -915,7 +1038,7 @@ f_join(typval_T *argvars, typval_T *rett
  * Return OK or FAIL.
  */
     int
-get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
+get_list_tv(char_u **arg, typval_T *rettv, int evaluate, int do_error)
 {
     list_T	*l = NULL;
     typval_T	tv;
@@ -950,7 +1073,8 @@ get_list_tv(char_u **arg, typval_T *rett
 	    break;
 	if (**arg != ',')
 	{
-	    semsg(_("E696: Missing comma in List: %s"), *arg);
+	    if (do_error)
+		semsg(_("E696: Missing comma in List: %s"), *arg);
 	    goto failret;
 	}
 	*arg = skipwhite(*arg + 1);
@@ -958,7 +1082,8 @@ get_list_tv(char_u **arg, typval_T *rett
 
     if (**arg != ']')
     {
-	semsg(_("E697: Missing end of List ']': %s"), *arg);
+	if (do_error)
+	    semsg(_("E697: Missing end of List ']': %s"), *arg);
 failret:
 	if (evaluate)
 	    list_free(l);
@@ -983,6 +1108,7 @@ write_list(FILE *fd, list_T *list, int b
     int		ret = OK;
     char_u	*s;
 
+    range_list_materialize(list);
     for (li = list->lv_first; li != NULL; li = li->li_next)
     {
 	for (s = tv_get_string(&li->li_tv); *s != NUL; ++s)
@@ -1069,6 +1195,7 @@ f_list2str(typval_T *argvars, typval_T *
     if (argvars[1].v_type != VAR_UNKNOWN)
 	utf8 = (int)tv_get_number_chk(&argvars[1], NULL);
 
+    range_list_materialize(l);
     ga_init2(&ga, 1, 80);
     if (has_mbyte || utf8)
     {
@@ -1123,7 +1250,7 @@ list_remove(typval_T *argvars, typval_T 
 	    // Remove one item, return its value.
 	    vimlist_remove(l, item, item);
 	    *rettv = item->li_tv;
-	    vim_free(item);
+	    list_free_item(l, item);
 	}
 	else
 	{
@@ -1361,6 +1488,7 @@ do_sort_uniq(typval_T *argvars, typval_T
 									TRUE))
 	    goto theend;
 	rettv_list_set(rettv, l);
+	range_list_materialize(l);
 
 	len = list_len(l);
 	if (len <= 1)
@@ -1519,7 +1647,7 @@ do_sort_uniq(typval_T *argvars, typval_T
 		    else
 			l->lv_last = ptrs[i].item;
 		    list_fix_watch(l, li);
-		    listitem_free(li);
+		    listitem_free(l, li);
 		    l->lv_len--;
 		}
 	    }
@@ -1729,6 +1857,7 @@ filter_map(typval_T *argvars, typval_T *
 	    // set_vim_var_nr() doesn't set the type
 	    set_vim_var_type(VV_KEY, VAR_NUMBER);
 
+	    range_list_materialize(l);
 	    for (li = l->lv_first; li != NULL; li = nli)
 	    {
 		if (map && var_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE))
--- a/src/macros.h
+++ b/src/macros.h
@@ -302,6 +302,10 @@
 # endif
 #endif
 
+#ifdef FEAT_EVAL
+# define FUNCARG(fp, j)	((char_u **)(fp->uf_args.ga_data))[j]
+#endif
+
 /*
  * In a hashtab item "hi_key" points to "di_key" in a dictitem.
  * This avoids adding a pointer to the hashtab item.
--- a/src/main.c
+++ b/src/main.c
@@ -482,7 +482,7 @@ vim_main2(void)
 # else
 		(char_u *)"plugin/**/*.vim",
 # endif
-		DIP_ALL | DIP_NOAFTER);
+		DIP_ALL | DIP_NOAFTER, NULL);
 	TIME_MSG("loading plugins");
 	vim_free(rtp_copy);
 
@@ -3169,7 +3169,7 @@ source_startup_scripts(mparm_T *parmp)
      */
     if (parmp->evim_mode)
     {
-	(void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE);
+	(void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE, NULL);
 	TIME_MSG("source evim file");
     }
 
@@ -3180,7 +3180,7 @@ source_startup_scripts(mparm_T *parmp)
     if (parmp->use_vimrc != NULL)
     {
 	if (STRCMP(parmp->use_vimrc, "DEFAULTS") == 0)
-	    do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE);
+	    do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE, NULL);
 	else if (STRCMP(parmp->use_vimrc, "NONE") == 0
 				     || STRCMP(parmp->use_vimrc, "NORC") == 0)
 	{
@@ -3191,7 +3191,7 @@ source_startup_scripts(mparm_T *parmp)
 	}
 	else
 	{
-	    if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE) != OK)
+	    if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE, NULL) != OK)
 		semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
 	}
     }
@@ -3209,10 +3209,11 @@ source_startup_scripts(mparm_T *parmp)
 	 * Get system wide defaults, if the file name is defined.
 	 */
 #ifdef SYS_VIMRC_FILE
-	(void)do_source((char_u *)SYS_VIMRC_FILE, FALSE, DOSO_NONE);
+	(void)do_source((char_u *)SYS_VIMRC_FILE, FALSE, DOSO_NONE, NULL);
 #endif
 #ifdef MACOS_X
-	(void)do_source((char_u *)"$VIMRUNTIME/macmap.vim", FALSE, DOSO_NONE);
+	(void)do_source((char_u *)"$VIMRUNTIME/macmap.vim", FALSE,
+							      DOSO_NONE, NULL);
 #endif
 
 	/*
@@ -3227,28 +3228,31 @@ source_startup_scripts(mparm_T *parmp)
 	 */
 	if (process_env((char_u *)"VIMINIT", TRUE) != OK)
 	{
-	    if (do_source((char_u *)USR_VIMRC_FILE, TRUE, DOSO_VIMRC) == FAIL
+	    if (do_source((char_u *)USR_VIMRC_FILE, TRUE,
+						      DOSO_VIMRC, NULL) == FAIL
 #ifdef USR_VIMRC_FILE2
 		&& do_source((char_u *)USR_VIMRC_FILE2, TRUE,
-							   DOSO_VIMRC) == FAIL
+						      DOSO_VIMRC, NULL) == FAIL
 #endif
 #ifdef USR_VIMRC_FILE3
 		&& do_source((char_u *)USR_VIMRC_FILE3, TRUE,
-							   DOSO_VIMRC) == FAIL
+						      DOSO_VIMRC, NULL) == FAIL
 #endif
 #ifdef USR_VIMRC_FILE4
 		&& do_source((char_u *)USR_VIMRC_FILE4, TRUE,
-							   DOSO_VIMRC) == FAIL
+						      DOSO_VIMRC, NULL) == FAIL
 #endif
 		&& process_env((char_u *)"EXINIT", FALSE) == FAIL
-		&& do_source((char_u *)USR_EXRC_FILE, FALSE, DOSO_NONE) == FAIL
+		&& do_source((char_u *)USR_EXRC_FILE, FALSE,
+						       DOSO_NONE, NULL) == FAIL
 #ifdef USR_EXRC_FILE2
-		&& do_source((char_u *)USR_EXRC_FILE2, FALSE, DOSO_NONE) == FAIL
+		&& do_source((char_u *)USR_EXRC_FILE2, FALSE,
+						       DOSO_NONE, NULL) == FAIL
 #endif
 		&& !has_dash_c_arg)
 	    {
 		// When no .vimrc file was found: source defaults.vim.
-		do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE);
+		do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE, NULL);
 	    }
 	}
 
@@ -3285,7 +3289,7 @@ source_startup_scripts(mparm_T *parmp)
 				(char_u *)VIMRC_FILE, FALSE, TRUE) != FPC_SAME
 #endif
 				)
-		i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC);
+		i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC, NULL);
 
 	    if (i == FAIL)
 	    {
@@ -3303,7 +3307,8 @@ source_startup_scripts(mparm_T *parmp)
 				(char_u *)EXRC_FILE, FALSE, TRUE) != FPC_SAME
 #endif
 				)
-		    (void)do_source((char_u *)EXRC_FILE, FALSE, DOSO_NONE);
+		    (void)do_source((char_u *)EXRC_FILE, FALSE,
+							      DOSO_NONE, NULL);
 	    }
 	}
 	if (secure == 2)
@@ -3334,7 +3339,7 @@ main_start_gui(void)
 #endif  // NO_VIM_MAIN
 
 /*
- * Get an environment variable, and execute it as Ex commands.
+ * Get an environment variable and execute it as Ex commands.
  * Returns FAIL if the environment variable was not executed, OK otherwise.
  */
     int
--- a/src/message.c
+++ b/src/message.c
@@ -847,6 +847,17 @@ emsg_invreg(int name)
 }
 
 /*
+ * Give an error message which contains %s for "name[len]".
+ */
+    void
+emsg_namelen(char *msg, char_u *name, int len)
+{
+    char_u *copy = vim_strnsave((char_u *)name, len);
+
+    semsg(msg, copy == NULL ? "NULL" : (char *)copy);
+}
+
+/*
  * Like msg(), but truncate to a single line if p_shm contains 't', or when
  * "force" is TRUE.  This truncates in another way as for normal messages.
  * Careful: The string may be changed by msg_may_trunc()!
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2067,13 +2067,17 @@ match_user(char_u *name)
 concat_str(char_u *str1, char_u *str2)
 {
     char_u  *dest;
-    size_t  l = STRLEN(str1);
+    size_t  l = str1 == NULL ? 0 : STRLEN(str1);
 
-    dest = alloc(l + STRLEN(str2) + 1L);
+    dest = alloc(l + (str2 == NULL ? 0 : STRLEN(str2)) + 1L);
     if (dest != NULL)
     {
-	STRCPY(dest, str1);
-	STRCPY(dest + l, str2);
+	if (str1 == NULL)
+	    *dest = NUL;
+	else
+	    STRCPY(dest, str1);
+	if (str2 != NULL)
+	    STRCPY(dest + l, str2);
     }
     return dest;
 }
--- a/src/proto.h
+++ b/src/proto.h
@@ -226,6 +226,11 @@ void mbyte_im_set_active(int active_arg)
 # include "usercmd.pro"
 # include "userfunc.pro"
 # include "version.pro"
+# ifdef FEAT_EVAL
+#  include "vim9compile.pro"
+#  include "vim9execute.pro"
+#  include "vim9script.pro"
+# endif
 # include "window.pro"
 
 # ifdef FEAT_LUA
--- a/src/proto/blob.pro
+++ b/src/proto/blob.pro
@@ -2,7 +2,7 @@
 blob_T *blob_alloc(void);
 int rettv_blob_alloc(typval_T *rettv);
 void rettv_blob_set(typval_T *rettv, blob_T *b);
-int blob_copy(typval_T *from, typval_T *to);
+int blob_copy(blob_T *from, typval_T *to);
 void blob_free(blob_T *b);
 void blob_unref(blob_T *b);
 long blob_len(blob_T *b);
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -27,7 +27,12 @@ void set_context_for_expression(expand_T
 int pattern_match(char_u *pat, char_u *text, int ic);
 int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate);
 int eval1(char_u **arg, typval_T *rettv, int evaluate);
+void eval_addblob(typval_T *tv1, typval_T *tv2);
+int eval_addlist(typval_T *tv1, typval_T *tv2);
 int get_option_tv(char_u **arg, typval_T *rettv, int evaluate);
+int get_number_tv(char_u **arg, typval_T *rettv, int evaluate, int want_string);
+int get_string_tv(char_u **arg, typval_T *rettv, int evaluate);
+int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate);
 char_u *partial_name(partial_T *pt);
 void partial_unref(partial_T *pt);
 int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive);
@@ -43,6 +48,7 @@ char_u *echo_string(typval_T *tv, char_u
 char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
 char_u *string_quote(char_u *str, int function);
 int string2float(char_u *text, float_T *value);
+int get_env_tv(char_u **arg, typval_T *rettv, int evaluate);
 pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum);
 int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp);
 int get_env_len(char_u **arg);
@@ -66,6 +72,7 @@ char_u *tv_get_string_chk(typval_T *varp
 char_u *tv_get_string_buf_chk(typval_T *varp, char_u *buf);
 void copy_tv(typval_T *from, typval_T *to);
 int item_copy(typval_T *from, typval_T *to, int deep, int copyID);
+void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr);
 void ex_echo(exarg_T *eap);
 void ex_echohl(exarg_T *eap);
 int get_echo_attr(void);
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -1,8 +1,13 @@
 /* evalfunc.c */
 char_u *get_function_name(expand_T *xp, int idx);
 char_u *get_expr_name(expand_T *xp, int idx);
+int find_internal_func(char_u *name);
 int has_internal_func(char_u *name);
+char *internal_func_name(int idx);
+type_T *internal_func_ret_type(int idx, int argcount);
+int check_internal_func(int idx, int argcount);
 int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
+void call_internal_func_by_idx(int idx, typval_T *argvars, typval_T *rettv);
 int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv);
 int non_zero_arg(typval_T *argvars);
 linenr_T tv_get_lnum(typval_T *argvars);
@@ -13,6 +18,7 @@ win_T *get_optional_window(typval_T *arg
 void execute_redir_str(char_u *value, int value_len);
 void execute_common(typval_T *argvars, typval_T *rettv, int arg_off);
 void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
+void range_list_materialize(list_T *list);
 float_T vim_round(float_T f);
 long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit);
 void f_string(typval_T *argvars, typval_T *rettv);
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -13,10 +13,11 @@ list_T *eval_spell_expr(char_u *badword,
 int get_spellword(list_T *list, char_u **pp);
 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);
 void ex_let(exarg_T *eap);
 void ex_const(exarg_T *eap);
-int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int is_const, char_u *op);
-char_u *skip_var_list(char_u *arg, int *var_count, int *semicolon);
+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);
 void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first);
 void ex_unlet(exarg_T *eap);
 void ex_lockvar(exarg_T *eap);
@@ -27,8 +28,10 @@ char *get_var_special_name(int nr);
 dict_T *get_globvar_dict(void);
 hashtab_T *get_globvar_ht(void);
 dict_T *get_vimvar_dict(void);
+int find_vim_var(char_u *name);
 void set_vim_var_type(int idx, vartype_T type);
 void set_vim_var_nr(int idx, varnumber_T val);
+char *get_vim_var_name(int idx);
 typval_T *get_vim_var_tv(int idx);
 varnumber_T get_vim_var_nr(int idx);
 char_u *get_vim_var_str(int idx);
@@ -50,6 +53,8 @@ int get_var_tv(char_u *name, int len, ty
 void check_vars(char_u *name, int len);
 dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload);
 dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload);
+hashtab_T *get_script_local_ht(void);
+int lookup_scriptvar(char_u *name, size_t len, cctx_T *dummy);
 hashtab_T *find_var_ht(char_u *name, char_u **varname);
 char_u *get_var_value(char_u *name);
 void new_script_vars(scid_T id);
@@ -58,7 +63,7 @@ void unref_var_dict(dict_T *dict);
 void vars_clear(hashtab_T *ht);
 void vars_clear_ext(hashtab_T *ht, int free_val);
 void set_var(char_u *name, typval_T *tv, int copy);
-void set_var_const(char_u *name, typval_T *tv, int copy, int is_const);
+void set_var_const(char_u *name, type_T *type, typval_T *tv, int copy, int flags);
 int var_check_ro(int flags, char_u *name, int use_gettext);
 int var_check_fixed(int flags, char_u *name, int use_gettext);
 int var_check_func_name(char_u *name, int new_var);
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -7,6 +7,7 @@ void *getline_cookie(char_u *(*fgetline)
 int parse_command_modifiers(exarg_T *eap, char **errormsg, int skip_only);
 int parse_cmd_address(exarg_T *eap, char **errormsg, int silent);
 int checkforcmd(char_u **pp, char *cmd, int len);
+char_u *find_ex_command(exarg_T *eap, int *full, int (*lookup)(char_u *, size_t, cctx_T *), cctx_T *cctx);
 int modifier_len(char_u *cmd);
 int cmd_exists(char_u *name);
 cmdidx_T excmd_get_cmdidx(char_u *cmd, int len);
--- a/src/proto/ex_eval.pro
+++ b/src/proto/ex_eval.pro
@@ -8,7 +8,9 @@ void free_global_msglist(void);
 void do_errthrow(cstack_T *cstack, char_u *cmdname);
 int do_intthrow(cstack_T *cstack);
 char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int *should_free);
+int throw_exception(void *value, except_type_T type, char_u *cmdname);
 void discard_current_exception(void);
+void catch_exception(except_T *excp);
 void report_make_pending(int pending, void *value);
 void ex_eval(exarg_T *eap);
 void ex_if(exarg_T *eap);
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -3,6 +3,8 @@ void list_add_watch(list_T *l, listwatch
 void list_rem_watch(list_T *l, listwatch_T *lwrem);
 list_T *list_alloc(void);
 list_T *list_alloc_id(alloc_id_T id);
+list_T *list_alloc_with_items(int count);
+void list_set_item(list_T *l, int idx, typval_T *tv);
 int rettv_list_alloc(typval_T *rettv);
 int rettv_list_alloc_id(typval_T *rettv, alloc_id_T id);
 void rettv_list_set(typval_T *rettv, list_T *l);
@@ -11,7 +13,8 @@ int list_free_nonref(int copyID);
 void list_free_items(int copyID);
 void list_free(list_T *l);
 listitem_T *listitem_alloc(void);
-void listitem_free(listitem_T *item);
+void list_free_item(list_T *l, listitem_T *item);
+void listitem_free(list_T *l, listitem_T *item);
 void listitem_remove(list_T *l, listitem_T *item);
 long list_len(list_T *l);
 int list_equal(list_T *l1, list_T *l2, int ic, int recursive);
@@ -21,6 +24,7 @@ char_u *list_find_str(list_T *l, long id
 long list_idx_of_item(list_T *l, listitem_T *item);
 void list_append(list_T *l, listitem_T *item);
 int list_append_tv(list_T *l, typval_T *tv);
+int list_append_tv_move(list_T *l, typval_T *tv);
 int list_append_dict(list_T *list, dict_T *dict);
 int list_append_list(list_T *list1, list_T *list2);
 int list_append_string(list_T *l, char_u *str, int len);
@@ -34,7 +38,7 @@ void vimlist_remove(list_T *l, listitem_
 char_u *list2string(typval_T *tv, int copyID, int restore_copyID);
 int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID);
 void f_join(typval_T *argvars, typval_T *rettv);
-int get_list_tv(char_u **arg, typval_T *rettv, int evaluate);
+int get_list_tv(char_u **arg, typval_T *rettv, int evaluate, int do_error);
 int write_list(FILE *fd, list_T *list, int binary);
 void init_static_list(staticList10_T *sl);
 void f_list2str(typval_T *argvars, typval_T *rettv);
--- a/src/proto/message.pro
+++ b/src/proto/message.pro
@@ -13,6 +13,7 @@ int emsg(char *s);
 void iemsg(char *s);
 void internal_error(char *where);
 void emsg_invreg(int name);
+void emsg_namelen(char *msg, char_u *name, int len);
 char *msg_trunc_attr(char *s, int force, int attr);
 char_u *msg_may_trunc(int force, char_u *s);
 int delete_first_msg(void);
--- a/src/proto/scriptfile.pro
+++ b/src/proto/scriptfile.pro
@@ -8,7 +8,7 @@ void ex_runtime(exarg_T *eap);
 int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie);
 int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie);
 int source_runtime(char_u *name, int flags);
-int source_in_path(char_u *path, char_u *name, int flags);
+int source_in_path(char_u *path, char_u *name, int flags, int *ret_sid);
 void add_pack_start_dirs(void);
 void load_start_packages(void);
 void ex_packloadall(exarg_T *eap);
@@ -21,7 +21,7 @@ void ex_options(exarg_T *eap);
 linenr_T *source_breakpoint(void *cookie);
 int *source_dbg_tick(void *cookie);
 int source_level(void *cookie);
-int do_source(char_u *fname, int check_other, int is_vimrc);
+int do_source(char_u *fname, int check_other, int is_vimrc, int *ret_sid);
 void ex_scriptnames(exarg_T *eap);
 void scriptnames_slash_adjust(void);
 char_u *get_scriptname(scid_T id);
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -1,26 +1,32 @@
 /* userfunc.c */
 void func_init(void);
 hashtab_T *func_tbl_get(void);
+int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip);
 int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
 char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
 void emsg_funcname(char *ermsg, char_u *name);
 int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe);
-ufunc_T *find_func(char_u *name);
+ufunc_T *find_func(char_u *name, cctx_T *cctx);
+int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict);
 void save_funccal(funccal_entry_T *entry);
 void restore_funccal(void);
 funccall_T *get_current_funccal(void);
 void free_all_functions(void);
+int builtin_function(char_u *name, int len);
 int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv);
 int get_callback_depth(void);
 int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
+void user_func_error(int error, char_u *name);
 int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
 char_u *trans_function_name(char_u **pp, int skip, int flags, funcdict_T *fdp, partial_T **partial);
 void ex_function(exarg_T *eap);
 int eval_fname_script(char_u *p);
 int translated_function_exists(char_u *name);
+int has_varargs(ufunc_T *ufunc);
 int function_exists(char_u *name, int no_deref);
 char_u *get_expanded_name(char_u *name, int check);
 char_u *get_user_func_name(expand_T *xp, int idx);
+void clean_script_functions(int sid);
 void ex_delfunction(exarg_T *eap);
 void func_unref(char_u *name);
 void func_ptr_unref(ufunc_T *fp);
new file mode 100644
--- /dev/null
+++ b/src/proto/vim9compile.pro
@@ -0,0 +1,14 @@
+/* vim9compile.c */
+char_u *skip_type(char_u *start);
+type_T *parse_type(char_u **arg, garray_T *type_list);
+char *vartype_name(vartype_T type);
+char *type_name(type_T *type, char **tofree);
+int get_script_item_idx(int sid, char_u *name, int check_writable);
+imported_T *find_imported(char_u *name, cctx_T *cctx);
+char_u *to_name_end(char_u *arg);
+char_u *to_name_const_end(char_u *arg);
+int assignment_len(char_u *p, int *heredoc);
+void compile_def_function(ufunc_T *ufunc, int set_return_type);
+void delete_def_function(ufunc_T *ufunc);
+void free_def_functions(void);
+/* vim: set ft=c : */
new file mode 100644
--- /dev/null
+++ b/src/proto/vim9execute.pro
@@ -0,0 +1,6 @@
+/* vim9execute.c */
+int call_def_function(ufunc_T *ufunc, int argc, typval_T *argv, typval_T *rettv);
+void ex_disassemble(exarg_T *eap);
+int tv2bool(typval_T *tv);
+int check_not_string(typval_T *tv);
+/* vim: set ft=c : */
new file mode 100644
--- /dev/null
+++ b/src/proto/vim9script.pro
@@ -0,0 +1,8 @@
+/* vim9script.c */
+int in_vim9script(void);
+void ex_vim9script(exarg_T *eap);
+void ex_export(exarg_T *eap);
+void free_imports(int sid);
+void ex_import(exarg_T *eap);
+char_u *handle_import(char_u *arg_start, garray_T *gap, int sid);
+/* vim: set ft=c : */
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -182,9 +182,9 @@ ex_runtime(exarg_T *eap)
 }
 
     static void
-source_callback(char_u *fname, void *cookie UNUSED)
+source_callback(char_u *fname, void *cookie)
 {
-    (void)do_source(fname, FALSE, DOSO_NONE);
+    (void)do_source(fname, FALSE, DOSO_NONE, cookie);
 }
 
 /*
@@ -399,16 +399,16 @@ do_in_runtimepath(
     int
 source_runtime(char_u *name, int flags)
 {
-    return source_in_path(p_rtp, name, flags);
+    return source_in_path(p_rtp, name, flags, NULL);
 }
 
 /*
  * Just like source_runtime(), but use "path" instead of 'runtimepath'.
  */
     int
-source_in_path(char_u *path, char_u *name, int flags)
+source_in_path(char_u *path, char_u *name, int flags, int *ret_sid)
 {
-    return do_in_path_and_pp(path, name, flags, source_callback, NULL);
+    return do_in_path_and_pp(path, name, flags, source_callback, ret_sid);
 }
 
 
@@ -427,7 +427,7 @@ source_all_matches(char_u *pat)
     if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK)
     {
 	for (i = 0; i < num_files; ++i)
-	    (void)do_source(files[i], FALSE, DOSO_NONE);
+	    (void)do_source(files[i], FALSE, DOSO_NONE, NULL);
 	FreeWild(num_files, files);
     }
 }
@@ -930,7 +930,7 @@ cmd_source(char_u *fname, exarg_T *eap)
 						 );
 
     // ":source" read ex commands
-    else if (do_source(fname, FALSE, DOSO_NONE) == FAIL)
+    else if (do_source(fname, FALSE, DOSO_NONE, NULL) == FAIL)
 	semsg(_(e_notopen), fname);
 }
 
@@ -1063,16 +1063,20 @@ fopen_noinh_readbin(char *filename)
 
 /*
  * do_source: Read the file "fname" and execute its lines as EX commands.
+ * When "ret_sid" is not NULL and we loaded the script before, don't load it
+ * again.
  *
  * This function may be called recursively!
  *
- * return FAIL if file could not be opened, OK otherwise
+ * Return FAIL if file could not be opened, OK otherwise.
+ * If a scriptitem_T was found or created "*ret_sid" is set to the SID.
  */
     int
 do_source(
     char_u	*fname,
     int		check_other,	    // check for .vimrc and _vimrc
-    int		is_vimrc)	    // DOSO_ value
+    int		is_vimrc,	    // DOSO_ value
+    int		*ret_sid UNUSED)
 {
     struct source_cookie    cookie;
     char_u		    *p;
@@ -1085,6 +1089,7 @@ do_source(
     static int		    last_current_SID_seq = 0;
     funccal_entry_T	    funccalp_entry;
     int			    save_debug_break_level = debug_break_level;
+    int			    sid;
     scriptitem_T	    *si = NULL;
 # ifdef UNIX
     stat_T		    st;
@@ -1114,6 +1119,37 @@ do_source(
 	goto theend;
     }
 
+#ifdef FEAT_EVAL
+    // See if we loaded this script before.
+# ifdef UNIX
+    stat_ok = (mch_stat((char *)fname_exp, &st) >= 0);
+# endif
+    for (sid = script_items.ga_len; sid > 0; --sid)
+    {
+	si = &SCRIPT_ITEM(sid);
+	if (si->sn_name != NULL)
+	{
+# ifdef UNIX
+	    // Compare dev/ino when possible, it catches symbolic links.  Also
+	    // compare file names, the inode may change when the file was
+	    // edited or it may be re-used for another script (esp. in tests).
+	    if ((stat_ok && si->sn_dev_valid)
+		       && (si->sn_dev != st.st_dev || si->sn_ino != st.st_ino))
+		continue;
+# endif
+	    if (fnamecmp(si->sn_name, fname_exp) == 0)
+		// Found it!
+		break;
+	}
+    }
+    if (sid > 0 && ret_sid != NULL)
+    {
+	// Already loaded and no need to load again, return here.
+	*ret_sid = sid;
+	return OK;
+    }
+#endif
+
     // Apply SourceCmd autocommands, they should get the file and source it.
     if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL)
 	    && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp,
@@ -1239,33 +1275,32 @@ do_source(
     current_sctx.sc_version = 1;  // default script version
 
     // Check if this script was sourced before to finds its SID.
-    // If it's new, generate a new SID.
     // Always use a new sequence number.
     current_sctx.sc_seq = ++last_current_SID_seq;
-# ifdef UNIX
-    stat_ok = (mch_stat((char *)fname_exp, &st) >= 0);
-# endif
-    for (current_sctx.sc_sid = script_items.ga_len; current_sctx.sc_sid > 0;
-							 --current_sctx.sc_sid)
+    if (sid > 0)
     {
-	si = &SCRIPT_ITEM(current_sctx.sc_sid);
-	if (si->sn_name != NULL)
-	{
-# ifdef UNIX
-	    // Compare dev/ino when possible, it catches symbolic links.  Also
-	    // compare file names, the inode may change when the file was
-	    // edited or it may be re-used for another script (esp. in tests).
-	    if ((stat_ok && si->sn_dev_valid)
-		       && (si->sn_dev != st.st_dev || si->sn_ino != st.st_ino))
-		continue;
-# endif
-	    if (fnamecmp(si->sn_name, fname_exp) == 0)
-		// Found it!
-		break;
-	}
+	hashtab_T	*ht;
+	hashitem_T	*hi;
+	dictitem_T	*di;
+	int		todo;
+
+	// loading the same script again
+	si->sn_had_command = FALSE;
+	current_sctx.sc_sid = sid;
+
+	ht = &SCRIPT_VARS(sid);
+	todo = (int)ht->ht_used;
+	for (hi = ht->ht_array; todo > 0; ++hi)
+	    if (!HASHITEM_EMPTY(hi))
+	    {
+		--todo;
+		di = HI2DI(hi);
+		di->di_flags |= DI_FLAGS_RELOAD;
+	    }
     }
-    if (current_sctx.sc_sid == 0)
+    else
     {
+	// It's new, generate a new SID.
 	current_sctx.sc_sid = ++last_current_SID;
 	if (ga_grow(&script_items,
 		     (int)(current_sctx.sc_sid - script_items.ga_len)) == FAIL)
@@ -1273,13 +1308,17 @@ do_source(
 	while (script_items.ga_len < current_sctx.sc_sid)
 	{
 	    ++script_items.ga_len;
-	    SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
-	    SCRIPT_ITEM(script_items.ga_len).sn_version = 1;
+	    si = &SCRIPT_ITEM(script_items.ga_len);
+	    si->sn_name = NULL;
+	    si->sn_version = 1;
 
 	    // Allocate the local script variables to use for this script.
 	    new_script_vars(script_items.ga_len);
+	    ga_init2(&si->sn_var_vals, sizeof(typval_T), 10);
+	    ga_init2(&si->sn_imports, sizeof(imported_T), 10);
+	    ga_init2(&si->sn_type_list, sizeof(type_T), 10);
 # ifdef FEAT_PROFILE
-	    SCRIPT_ITEM(script_items.ga_len).sn_prof_on = FALSE;
+	    si->sn_prof_on = FALSE;
 # endif
 	}
 	si = &SCRIPT_ITEM(current_sctx.sc_sid);
@@ -1295,6 +1334,8 @@ do_source(
 	else
 	    si->sn_dev_valid = FALSE;
 # endif
+	if (ret_sid != NULL)
+	    *ret_sid = current_sctx.sc_sid;
     }
 
 # ifdef FEAT_PROFILE
@@ -1392,6 +1433,15 @@ do_source(
 
 #ifdef FEAT_EVAL
 almosttheend:
+    // Get "si" again, "script_items" may have been reallocated.
+    si = &SCRIPT_ITEM(current_sctx.sc_sid);
+    if (si->sn_save_cpo != NULL)
+    {
+	free_string_option(p_cpo);
+	p_cpo = si->sn_save_cpo;
+	si->sn_save_cpo = NULL;
+    }
+
     current_sctx = save_current_sctx;
     restore_funccal();
 # ifdef FEAT_PROFILE
@@ -1488,7 +1538,9 @@ free_scriptnames(void)
     {
 	// the variables themselves are cleared in evalvars_clear()
 	vim_free(SCRIPT_ITEM(i).sn_vars);
+
 	vim_free(SCRIPT_ITEM(i).sn_name);
+	free_string_option(SCRIPT_ITEM(i).sn_save_cpo);
 #  ifdef FEAT_PROFILE
 	ga_clear(&SCRIPT_ITEM(i).sn_prl_ga);
 #  endif
@@ -1789,6 +1841,11 @@ ex_scriptversion(exarg_T *eap UNUSED)
 	emsg(_("E984: :scriptversion used outside of a sourced file"));
 	return;
     }
+    if (current_sctx.sc_version == SCRIPT_VERSION_VIM9)
+    {
+	emsg(_("E1040: Cannot use :scriptversion after :vim9script"));
+	return;
+    }
 
     nr = getdigits(&eap->arg);
     if (nr == 0 || *eap->arg != NUL)
--- a/src/session.c
+++ b/src/session.c
@@ -980,7 +980,7 @@ ex_loadview(exarg_T *eap)
     fname = get_view_file(*eap->arg);
     if (fname != NULL)
     {
-	do_source(fname, FALSE, DOSO_NONE);
+	do_source(fname, FALSE, DOSO_NONE, NULL);
 	vim_free(fname);
     }
 }
--- a/src/structs.h
+++ b/src/structs.h
@@ -67,6 +67,9 @@ typedef struct terminal_S	term_T;
 typedef struct VimMenu vimmenu_T;
 #endif
 
+// value for sc_version in a Vim9 script file
+#define SCRIPT_VERSION_VIM9 999999
+
 /*
  * SCript ConteXt (SCTX): identifies a script line.
  * When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current
@@ -1298,30 +1301,43 @@ typedef struct {
     int		cb_free_name;	    // cb_name was allocated
 } callback_T;
 
+typedef struct dfunc_S dfunc_T;	    // :def function
+
 typedef struct jobvar_S job_T;
 typedef struct readq_S readq_T;
 typedef struct writeq_S writeq_T;
 typedef struct jsonq_S jsonq_T;
 typedef struct cbq_S cbq_T;
 typedef struct channel_S channel_T;
+typedef struct cctx_S cctx_T;
 
 typedef enum
 {
-    VAR_UNKNOWN = 0,
-    VAR_NUMBER,	 // "v_number" is used
-    VAR_STRING,	 // "v_string" is used
-    VAR_FUNC,	 // "v_string" is function name
-    VAR_PARTIAL, // "v_partial" is used
-    VAR_LIST,	 // "v_list" is used
-    VAR_DICT,	 // "v_dict" is used
-    VAR_FLOAT,	 // "v_float" is used
-    VAR_BOOL,	 // "v_number" is VVAL_FALSE or VVAL_TRUE
-    VAR_SPECIAL, // "v_number" is VVAL_NONE or VVAL_NULL
-    VAR_JOB,	 // "v_job" is used
-    VAR_CHANNEL, // "v_channel" is used
-    VAR_BLOB,	 // "v_blob" is used
+    VAR_UNKNOWN = 0,	// not set, also used for "any" type
+    VAR_VOID,		// no value
+    VAR_BOOL,		// "v_number" is used: VVAL_TRUE or VVAL_FALSE
+    VAR_SPECIAL,	// "v_number" is used: VVAL_NULL or VVAL_NONE
+    VAR_NUMBER,		// "v_number" is used
+    VAR_FLOAT,		// "v_float" is used
+    VAR_STRING,		// "v_string" is used
+    VAR_BLOB,		// "v_blob" is used
+    VAR_FUNC,		// "v_string" is function name
+    VAR_PARTIAL,	// "v_partial" is used
+    VAR_LIST,		// "v_list" is used
+    VAR_DICT,		// "v_dict" is used
+    VAR_JOB,		// "v_job" is used
+    VAR_CHANNEL,	// "v_channel" is used
 } vartype_T;
 
+// A type specification.
+typedef struct type_S type_T;
+struct type_S {
+    vartype_T	    tt_type;
+    short	    tt_argcount;    // for func, partial, -1 for unknown
+    type_T	    *tt_member;	    // for list, dict, func return type
+    type_T	    *tt_args;	    // func arguments
+};
+
 /*
  * Structure to hold an internal variable without a name.
  */
@@ -1380,19 +1396,34 @@ struct listwatch_S
 /*
  * Structure to hold info about a list.
  * Order of members is optimized to reduce padding.
+ * When created by range() it will at first have special value:
+ *  lv_first == &range_list_item;
+ * and use lv_start, lv_end, lv_stride.
  */
 struct listvar_S
 {
     listitem_T	*lv_first;	// first item, NULL if none
-    listitem_T	*lv_last;	// last item, NULL if none
     listwatch_T	*lv_watch;	// first watcher, NULL if none
-    listitem_T	*lv_idx_item;	// when not NULL item at index "lv_idx"
+    union {
+	struct {	// used for non-materialized range list:
+			// "lv_first" is &range_list_item
+	    varnumber_T lv_start;
+	    varnumber_T lv_end;
+	    int		lv_stride;
+	};
+	struct {	// used for materialized list
+	    listitem_T	*lv_last;	// last item, NULL if none
+	    listitem_T	*lv_idx_item;	// when not NULL item at index "lv_idx"
+	    int		lv_idx;		// cached index of an item
+	};
+    };
     list_T	*lv_copylist;	// copied list used by deepcopy()
     list_T	*lv_used_next;	// next list in used lists list
     list_T	*lv_used_prev;	// previous list in used lists list
     int		lv_refcount;	// reference count
     int		lv_len;		// number of items
-    int		lv_idx;		// cached index of an item
+    int		lv_with_items;	// number of items following this struct that
+				// should not be freed
     int		lv_copyID;	// ID used by deepcopy()
     char	lv_lock;	// zero, VAR_LOCKED, VAR_FIXED
 };
@@ -1413,7 +1444,7 @@ typedef struct {
 struct dictitem_S
 {
     typval_T	di_tv;		// type and value of the variable
-    char_u	di_flags;	// flags (only used for variable)
+    char_u	di_flags;	// DI_FLAGS_ flags (only used for variable)
     char_u	di_key[1];	// key (actually longer!)
 };
 typedef struct dictitem_S dictitem_T;
@@ -1426,16 +1457,18 @@ typedef struct dictitem_S dictitem_T;
 struct dictitem16_S
 {
     typval_T	di_tv;		// type and value of the variable
-    char_u	di_flags;	// flags (only used for variable)
+    char_u	di_flags;	// DI_FLAGS_ flags (only used for variable)
     char_u	di_key[DICTITEM16_KEY_LEN + 1];	// key
 };
 typedef struct dictitem16_S dictitem16_T;
 
-#define DI_FLAGS_RO	1  // "di_flags" value: read-only variable
-#define DI_FLAGS_RO_SBX 2  // "di_flags" value: read-only in the sandbox
-#define DI_FLAGS_FIX	4  // "di_flags" value: fixed: no :unlet or remove()
-#define DI_FLAGS_LOCK	8  // "di_flags" value: locked variable
-#define DI_FLAGS_ALLOC	16 // "di_flags" value: separately allocated
+// Flags for "di_flags"
+#define DI_FLAGS_RO	   0x01	    // read-only variable
+#define DI_FLAGS_RO_SBX	   0x02	    // read-only in the sandbox
+#define DI_FLAGS_FIX	   0x04	    // fixed: no :unlet or remove()
+#define DI_FLAGS_LOCK	   0x08	    // locked variable
+#define DI_FLAGS_ALLOC	   0x10	    // separately allocated
+#define DI_FLAGS_RELOAD	   0x20	    // set when script sourced again
 
 /*
  * Structure to hold info about a Dictionary.
@@ -1470,12 +1503,21 @@ typedef struct funccall_S funccall_T;
  */
 typedef struct
 {
-    int		uf_varargs;	// variable nr of arguments
-    int		uf_flags;
+    int		uf_varargs;	// variable nr of arguments (old style)
+    int		uf_flags;	// FC_ flags
     int		uf_calls;	// nr of active calls
     int		uf_cleared;	// func_clear() was already called
+    int		uf_dfunc_idx;	// >= 0 for :def function only
     garray_T	uf_args;	// arguments
     garray_T	uf_def_args;	// default argument expressions
+
+    // for :def (for :function uf_ret_type is NULL)
+    type_T	**uf_arg_types;	// argument types (count == uf_args.ga_len)
+    type_T	*uf_ret_type;	// return type
+    garray_T	uf_type_list;	// types used in arg and return types
+    char_u	*uf_va_name;	// name from "...name" or NULL
+    type_T	*uf_va_type;	// type from "...name: type" or NULL
+
     garray_T	uf_lines;	// function lines
 # ifdef FEAT_PROFILE
     int		uf_profiling;	// TRUE when func is being profiled
@@ -1578,17 +1620,50 @@ typedef struct
 } scriptvar_T;
 
 /*
+ * Entry for "sn_var_vals".  Used for script-local variables.
+ */
+typedef struct {
+    char_u	*sv_name;	// points into "sn_vars" di_key
+    typval_T	*sv_tv;		// points into "sn_vars" di_tv
+    type_T	*sv_type;
+    int		sv_const;
+    int		sv_export;	// "export let var = val"
+} svar_T;
+
+typedef struct {
+    char_u	*imp_name;	    // name imported as (allocated)
+    int		imp_sid;	    // script ID of "from"
+
+    // for "import * as Name", "imp_name" is "Name"
+    int		imp_all;
+
+    // for variable
+    type_T	*imp_type;
+    int		imp_var_vals_idx;   // index in sn_var_vals of "from"
+
+    // for function
+    char_u	*imp_funcname;	    // user func name (NOT allocated)
+} imported_T;
+
+/*
  * Growarray to store info about already sourced scripts.
  * For Unix also store the dev/ino, so that we don't have to stat() each
  * script when going through the list.
  */
 typedef struct
 {
+    char_u	*sn_name;
+
     scriptvar_T	*sn_vars;	// stores s: variables for this script
-
-    char_u	*sn_name;
+    garray_T	sn_var_vals;	// same variables as a list of svar_T
+
+    garray_T	sn_imports;	// imported items, imported_T
+
+    garray_T	sn_type_list;	// keeps types used by variables
 
     int		sn_version;	// :scriptversion
+    int		sn_had_command;	// TRUE if any command was executed
+    char_u	*sn_save_cpo;	// 'cpo' value when :vim9script found
 
 # ifdef UNIX
     int		sn_dev_valid;
@@ -3691,6 +3766,12 @@ typedef enum
     EXPR_NOMATCH,	// !~
     EXPR_IS,		// is
     EXPR_ISNOT,		// isnot
+    // used with ISN_OPNR
+    EXPR_ADD,		// +
+    EXPR_SUB,		// -
+    EXPR_MULT,		// *
+    EXPR_DIV,		// /
+    EXPR_REM,		// %
 } exptype_T;
 
 /*
@@ -3804,6 +3885,8 @@ typedef struct
 typedef struct lval_S
 {
     char_u	*ll_name;	// start of variable name (can be NULL)
+    char_u	*ll_name_end;	// end of variable name (can be NULL)
+    type_T	*ll_type;	// type of variable (can be NULL)
     char_u	*ll_exp_name;	// NULL or expanded name in allocated memory.
     typval_T	*ll_tv;		// Typeval of item being used.  If "newkey"
 				// isn't NULL it's the Dict to which to add
--- a/src/syntax.c
+++ b/src/syntax.c
@@ -4736,7 +4736,7 @@ syn_cmd_include(exarg_T *eap, int syncin
     current_syn_inc_tag = ++running_syn_inc_tag;
     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
     curwin->w_s->b_syn_topgrp = sgl_id;
-    if (source ? do_source(eap->arg, FALSE, DOSO_NONE) == FAIL
+    if (source ? do_source(eap->arg, FALSE, DOSO_NONE, NULL) == FAIL
 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
 	semsg(_(e_notopen), eap->arg);
     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -90,10 +90,10 @@ NEW_TESTS = \
 	test_digraph \
 	test_display \
 	test_edit \
+	test_environ \
 	test_erasebackword \
 	test_escaped_glob \
 	test_eval_stuff \
-	test_environ \
 	test_ex_equal \
 	test_ex_undo \
 	test_ex_z \
@@ -268,6 +268,8 @@ NEW_TESTS = \
 	test_utf8 \
 	test_utf8_comparisons \
 	test_vartabs \
+	test_vim9_expr \
+	test_vim9_script \
 	test_viminfo \
 	test_vimscript \
 	test_virtualedit \
@@ -435,6 +437,8 @@ NEW_TESTS_RES = \
 	test_user_func.res \
 	test_usercommands.res \
 	test_vartabs.res \
+	test_vim9_expr.res \
+	test_vim9_script.res \
 	test_viminfo.res \
 	test_vimscript.res \
 	test_visual.res \
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_vim9_expr.vim
@@ -0,0 +1,734 @@
+" Tests for Vim9 script expressions
+
+source check.vim
+
+" Check that "line" inside ":def" results in an "error" message.
+func CheckDefFailure(line, error)
+  call writefile(['def! Func()', a:line, 'enddef'], 'Xdef')
+  call assert_fails('so Xdef', a:error, a:line)
+  call delete('Xdef')
+endfunc
+
+func CheckDefFailureList(lines, error)
+  call writefile(['def! Func()'] + a:lines + ['enddef'], 'Xdef')
+  call assert_fails('so Xdef', a:error, string(a:lines))
+  call delete('Xdef')
+endfunc
+
+" test cond ? expr : expr
+def Test_expr1()
+  assert_equal('one', true ? 'one' : 'two')
+  assert_equal('one', 1 ? 'one' : 'two')
+  assert_equal('one', 0.1 ? 'one' : 'two')
+  assert_equal('one', 'x' ? 'one' : 'two')
+"  assert_equal('one', 0z1234 ? 'one' : 'two')
+  assert_equal('one', [0] ? 'one' : 'two')
+"  assert_equal('one', #{x: 0} ? 'one' : 'two')
+  let var = 1
+  assert_equal('one', var ? 'one' : 'two')
+
+  assert_equal('two', false ? 'one' : 'two')
+  assert_equal('two', 0 ? 'one' : 'two')
+  assert_equal('two', 0.0 ? 'one' : 'two')
+  assert_equal('two', '' ? 'one' : 'two')
+"  assert_equal('one', 0z ? 'one' : 'two')
+  assert_equal('two', [] ? 'one' : 'two')
+"  assert_equal('two', {} ? 'one' : 'two')
+  var = 0
+  assert_equal('two', var ? 'one' : 'two')
+enddef
+
+func Test_expr1_fails()
+  call CheckDefFailure("let x = 1 ? 'one'", "Missing ':' after '?'")
+
+  let msg = "white space required before and after '?'"
+  call CheckDefFailure("let x = 1? 'one' : 'two'", msg)
+  call CheckDefFailure("let x = 1 ?'one' : 'two'", msg)
+  call CheckDefFailure("let x = 1?'one' : 'two'", msg)
+
+  let msg = "white space required before and after ':'"
+  call CheckDefFailure("let x = 1 ? 'one': 'two'", msg)
+  call CheckDefFailure("let x = 1 ? 'one' :'two'", msg)
+  call CheckDefFailure("let x = 1 ? 'one':'two'", msg)
+endfunc
+
+" TODO: define inside test function
+def Record(val: any): any
+  g:vals->add(val)
+  return val
+enddef
+
+" test ||
+def Test_expr2()
+  assert_equal(2, 2 || 0)
+  assert_equal(7, 0 || 0 || 7)
+  assert_equal(0, 0 || 0)
+  assert_equal('', 0 || '')
+
+  g:vals = []
+  assert_equal(3, Record(3) || Record(1))
+  assert_equal([3], g:vals)
+
+  g:vals = []
+  assert_equal(5, Record(0) || Record(5))
+  assert_equal([0, 5], g:vals)
+
+  g:vals = []
+  assert_equal(4, Record(0) || Record(4) || Record(0))
+  assert_equal([0, 4], g:vals)
+
+  g:vals = []
+  assert_equal(0, Record([]) || Record('') || Record(0))
+  assert_equal([[], '', 0], g:vals)
+enddef
+
+func Test_expr2_fails()
+  let msg = "white space required before and after '||'"
+  call CheckDefFailure("let x = 1||2", msg)
+  call CheckDefFailure("let x = 1 ||2", msg)
+  call CheckDefFailure("let x = 1|| 2", msg)
+endfunc
+
+" test &&
+def Test_expr3()
+  assert_equal(0, 2 && 0)
+  assert_equal(0, 0 && 0 && 7)
+  assert_equal(7, 2 && 3 && 7)
+  assert_equal(0, 0 && 0)
+  assert_equal(0, 0 && '')
+  assert_equal('', 8 && '')
+
+  g:vals = []
+  assert_equal(1, Record(3) && Record(1))
+  assert_equal([3, 1], g:vals)
+
+  g:vals = []
+  assert_equal(0, Record(0) && Record(5))
+  assert_equal([0], g:vals)
+
+  g:vals = []
+  assert_equal(0, Record(0) && Record(4) && Record(0))
+  assert_equal([0], g:vals)
+
+  g:vals = []
+  assert_equal(0, Record(8) && Record(4) && Record(0))
+  assert_equal([8, 4, 0], g:vals)
+
+  g:vals = []
+  assert_equal(0, Record([1]) && Record('z') && Record(0))
+  assert_equal([[1], 'z', 0], g:vals)
+enddef
+
+func Test_expr3_fails()
+  let msg = "white space required before and after '&&'"
+  call CheckDefFailure("let x = 1&&2", msg)
+  call CheckDefFailure("let x = 1 &&2", msg)
+  call CheckDefFailure("let x = 1&& 2", msg)
+endfunc
+
+let atrue = v:true
+let afalse = v:false
+let anone = v:none
+let anull = v:null
+let anint = 10
+let alsoint = 4
+if has('float')
+  let afloat = 0.1
+endif
+let astring = 'asdf'
+let ablob = 0z01ab
+let alist = [2, 3, 4]
+let adict = #{aaa: 2, bbb: 8}
+
+" test == comperator
+def Test_expr4_equal()
+  assert_equal(true, true == true)
+  assert_equal(false, true == false)
+  assert_equal(true, true == g:atrue)
+  assert_equal(false, g:atrue == false)
+
+  assert_equal(true, v:none == v:none)
+  assert_equal(false, v:none == v:null)
+  assert_equal(true, g:anone == v:none)
+  assert_equal(false, v:none == g:anull)
+
+  assert_equal(false, 2 == 0)
+  assert_equal(true, 61 == 61)
+  assert_equal(true, g:anint == 10)
+  assert_equal(false, 61 == g:anint)
+
+  if has('float')
+    assert_equal(true, 0.3 == 0.3)
+    assert_equal(false, 0.4 == 0.3)
+    assert_equal(true, 0.1 == g:afloat)
+    assert_equal(false, g:afloat == 0.3)
+
+    assert_equal(true, 3.0 == 3)
+    assert_equal(true, 3 == 3.0)
+    assert_equal(false, 3.1 == 3)
+    assert_equal(false, 3 == 3.1)
+  endif
+
+  assert_equal(true, 'abc' == 'abc')
+  assert_equal(false, 'xyz' == 'abc')
+  assert_equal(true, g:astring == 'asdf')
+  assert_equal(false, 'xyz' == g:astring)
+
+  assert_equal(false, 'abc' == 'ABC')
+  set ignorecase
+  assert_equal(false, 'abc' == 'ABC')
+  set noignorecase
+
+  assert_equal(true, 0z3f == 0z3f)
+  assert_equal(false, 0z3f == 0z4f)
+  assert_equal(true, g:ablob == 0z01ab)
+  assert_equal(false, 0z3f == g:ablob)
+
+  assert_equal(true, [1, 2, 3] == [1, 2, 3])
+  assert_equal(false, [1, 2, 3] == [2, 3, 1])
+  assert_equal(true, [2, 3, 4] == g:alist)
+  assert_equal(false, g:alist == [2, 3, 1])
+  assert_equal(false, [1, 2, 3] == [])
+  assert_equal(false, [1, 2, 3] == ['1', '2', '3'])
+
+  assert_equal(true, #{one: 1, two: 2} == #{one: 1, two: 2})
+  assert_equal(false, #{one: 1, two: 2} == #{one: 2, two: 2})
+  assert_equal(false, #{one: 1, two: 2} == #{two: 2})
+  assert_equal(false, #{one: 1, two: 2} == #{})
+  assert_equal(true, g:adict == #{bbb: 8, aaa: 2})
+  assert_equal(false, #{ccc: 9, aaa: 2} == g:adict)
+
+  assert_equal(true, function('Test_expr4_equal') == function('Test_expr4_equal'))
+  assert_equal(false, function('Test_expr4_equal') == function('Test_expr4_is'))
+
+  assert_equal(true, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [123]))
+  assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_is', [123]))
+  assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [999]))
+enddef
+
+" test != comperator
+def Test_expr4_notequal()
+  assert_equal(false, true != true)
+  assert_equal(true, true != false)
+  assert_equal(false, true != g:atrue)
+  assert_equal(true, g:atrue != false)
+
+  assert_equal(false, v:none != v:none)
+  assert_equal(true, v:none != v:null)
+  assert_equal(false, g:anone != v:none)
+  assert_equal(true, v:none != g:anull)
+
+  assert_equal(true, 2 != 0)
+  assert_equal(false, 55 != 55)
+  assert_equal(false, g:anint != 10)
+  assert_equal(true, 61 != g:anint)
+
+  if has('float')
+    assert_equal(false, 0.3 != 0.3)
+    assert_equal(true, 0.4 != 0.3)
+    assert_equal(false, 0.1 != g:afloat)
+    assert_equal(true, g:afloat != 0.3)
+
+    assert_equal(false, 3.0 != 3)
+    assert_equal(false, 3 != 3.0)
+    assert_equal(true, 3.1 != 3)
+    assert_equal(true, 3 != 3.1)
+  endif
+
+  assert_equal(false, 'abc' != 'abc')
+  assert_equal(true, 'xyz' != 'abc')
+  assert_equal(false, g:astring != 'asdf')
+  assert_equal(true, 'xyz' != g:astring)
+
+  assert_equal(true, 'abc' != 'ABC')
+  set ignorecase
+  assert_equal(true, 'abc' != 'ABC')
+  set noignorecase
+
+  assert_equal(false, 0z3f != 0z3f)
+  assert_equal(true, 0z3f != 0z4f)
+  assert_equal(false, g:ablob != 0z01ab)
+  assert_equal(true, 0z3f != g:ablob)
+
+  assert_equal(false, [1, 2, 3] != [1, 2, 3])
+  assert_equal(true, [1, 2, 3] != [2, 3, 1])
+  assert_equal(false, [2, 3, 4] != g:alist)
+  assert_equal(true, g:alist != [2, 3, 1])
+  assert_equal(true, [1, 2, 3] != [])
+  assert_equal(true, [1, 2, 3] != ['1', '2', '3'])
+
+  assert_equal(false, #{one: 1, two: 2} != #{one: 1, two: 2})
+  assert_equal(true, #{one: 1, two: 2} != #{one: 2, two: 2})
+  assert_equal(true, #{one: 1, two: 2} != #{two: 2})
+  assert_equal(true, #{one: 1, two: 2} != #{})
+  assert_equal(false, g:adict != #{bbb: 8, aaa: 2})
+  assert_equal(true, #{ccc: 9, aaa: 2} != g:adict)
+
+  assert_equal(false, function('Test_expr4_equal') != function('Test_expr4_equal'))
+  assert_equal(true, function('Test_expr4_equal') != function('Test_expr4_is'))
+
+  assert_equal(false, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [123]))
+  assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_is', [123]))
+  assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [999]))
+enddef
+
+" test > comperator
+def Test_expr4_greater()
+  assert_equal(true, 2 > 0)
+  assert_equal(true, 2 > 1)
+  assert_equal(false, 2 > 2)
+  assert_equal(false, 2 > 3)
+enddef
+
+" test >= comperator
+def Test_expr4_greaterequal()
+  assert_equal(true, 2 >= 0)
+  assert_equal(true, 2 >= 2)
+  assert_equal(false, 2 >= 3)
+enddef
+
+" test < comperator
+def Test_expr4_smaller()
+  assert_equal(false, 2 < 0)
+  assert_equal(false, 2 < 2)
+  assert_equal(true, 2 < 3)
+enddef
+
+" test <= comperator
+def Test_expr4_smallerequal()
+  assert_equal(false, 2 <= 0)
+  assert_equal(false, 2 <= 1)
+  assert_equal(true, 2 <= 2)
+  assert_equal(true, 2 <= 3)
+enddef
+
+" test =~ comperator
+def Test_expr4_match()
+  assert_equal(false, '2' =~ '0')
+  assert_equal(true, '2' =~ '[0-9]')
+enddef
+
+" test !~ comperator
+def Test_expr4_nomatch()
+  assert_equal(true, '2' !~ '0')
+  assert_equal(false, '2' !~ '[0-9]')
+enddef
+
+" test is comperator
+def Test_expr4_is()
+  let mylist = [2]
+  assert_equal(false, mylist is [2])
+  let other = mylist
+  assert_equal(true, mylist is other)
+enddef
+
+" test isnot comperator
+def Test_expr4_isnot()
+  let mylist = [2]
+  assert_equal(true, '2' isnot '0')
+  assert_equal(true, mylist isnot [2])
+  let other = mylist
+  assert_equal(false, mylist isnot other)
+enddef
+
+def RetVoid()
+  let x = 1
+enddef
+
+func Test_expr4_fails()
+  let msg = "white space required before and after '>'"
+  call CheckDefFailure("let x = 1>2", msg)
+  call CheckDefFailure("let x = 1 >2", msg)
+  call CheckDefFailure("let x = 1> 2", msg)
+
+  let msg = "white space required before and after '=='"
+  call CheckDefFailure("let x = 1==2", msg)
+  call CheckDefFailure("let x = 1 ==2", msg)
+  call CheckDefFailure("let x = 1== 2", msg)
+
+  let msg = "white space required before and after 'is'"
+  call CheckDefFailure("let x = '1'is'2'", msg)
+  call CheckDefFailure("let x = '1' is'2'", msg)
+  call CheckDefFailure("let x = '1'is '2'", msg)
+
+  let msg = "white space required before and after 'isnot'"
+  call CheckDefFailure("let x = '1'isnot'2'", msg)
+  call CheckDefFailure("let x = '1' isnot'2'", msg)
+  call CheckDefFailure("let x = '1'isnot '2'", msg)
+
+  call CheckDefFailure("let x = 1 is# 2", 'E15:')
+  call CheckDefFailure("let x = 1 is? 2", 'E15:')
+  call CheckDefFailure("let x = 1 isnot# 2", 'E15:')
+  call CheckDefFailure("let x = 1 isnot? 2", 'E15:')
+
+  call CheckDefFailure("let x = 1 == '2'", 'Cannot compare number with string')
+  call CheckDefFailure("let x = '1' == 2", 'Cannot compare string with number')
+  call CheckDefFailure("let x = 1 == RetVoid()", 'Cannot use void value')
+  call CheckDefFailure("let x = RetVoid() == 1", 'Cannot compare void with number')
+
+  call CheckDefFailure("let x = true > false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true >= false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true < false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true <= false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true =~ false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true !~ false", 'Cannot compare bool with bool')
+  call CheckDefFailure("let x = true is false", 'Cannot use "is" with bool')
+  call CheckDefFailure("let x = true isnot false", 'Cannot use "isnot" with bool')
+
+  call CheckDefFailure("let x = v:none is v:null", 'Cannot use "is" with special')
+  call CheckDefFailure("let x = v:none isnot v:null", 'Cannot use "isnot" with special')
+  call CheckDefFailure("let x = 123 is 123", 'Cannot use "is" with number')
+  call CheckDefFailure("let x = 123 isnot 123", 'Cannot use "isnot" with number')
+  if has('float')
+    call CheckDefFailure("let x = 1.3 is 1.3", 'Cannot use "is" with float')
+    call CheckDefFailure("let x = 1.3 isnot 1.3", 'Cannot use "isnot" with float')
+  endif
+
+  call CheckDefFailure("let x = 0za1 > 0z34", 'Cannot compare blob with blob')
+  call CheckDefFailure("let x = 0za1 >= 0z34", 'Cannot compare blob with blob')
+  call CheckDefFailure("let x = 0za1 < 0z34", 'Cannot compare blob with blob')
+  call CheckDefFailure("let x = 0za1 <= 0z34", 'Cannot compare blob with blob')
+  call CheckDefFailure("let x = 0za1 =~ 0z34", 'Cannot compare blob with blob')
+  call CheckDefFailure("let x = 0za1 !~ 0z34", 'Cannot compare blob with blob')
+
+  call CheckDefFailure("let x = [13] > [88]", 'Cannot compare list with list')
+  call CheckDefFailure("let x = [13] >= [88]", 'Cannot compare list with list')
+  call CheckDefFailure("let x = [13] < [88]", 'Cannot compare list with list')
+  call CheckDefFailure("let x = [13] <= [88]", 'Cannot compare list with list')
+  call CheckDefFailure("let x = [13] =~ [88]", 'Cannot compare list with list')
+  call CheckDefFailure("let x = [13] !~ [88]", 'Cannot compare list with list')
+endfunc
+
+" test addition, subtraction, concatenation
+def Test_expr5()
+  assert_equal(66, 60 + 6)
+  assert_equal(70, 60 + g:anint)
+  assert_equal(9, g:alsoint + 5)
+  assert_equal(14, g:alsoint + g:anint)
+
+  assert_equal(54, 60 - 6)
+  assert_equal(50, 60 - g:anint)
+  assert_equal(-1, g:alsoint - 5)
+  assert_equal(-6, g:alsoint - g:anint)
+
+  assert_equal('hello', 'hel' .. 'lo')
+  assert_equal('hello 123', 'hello ' .. 123)
+  assert_equal('123 hello', 123 .. ' hello')
+  assert_equal('123456', 123 .. 456)
+enddef
+
+def Test_expr5_float()
+  CheckFeature float
+  assert_equal(66.0, 60.0 + 6.0)
+  assert_equal(66.0, 60.0 + 6)
+  assert_equal(66.0, 60 + 6.0)
+  assert_equal(5.1, g:afloat + 5)
+  assert_equal(8.1, 8 + g:afloat)
+  assert_equal(10.1, g:anint + g:afloat)
+  assert_equal(10.1, g:afloat + g:anint)
+
+  assert_equal(54.0, 60.0 - 6.0)
+  assert_equal(54.0, 60.0 - 6)
+  assert_equal(54.0, 60 - 6.0)
+  assert_equal(-4.9, g:afloat - 5)
+  assert_equal(7.9, 8 - g:afloat)
+  assert_equal(9.9, g:anint - g:afloat)
+  assert_equal(-9.9, g:afloat - g:anint)
+enddef
+
+func Test_expr5_fails()
+  let msg = "white space required before and after '+'"
+  call CheckDefFailure("let x = 1+2", msg)
+  call CheckDefFailure("let x = 1 +2", msg)
+  call CheckDefFailure("let x = 1+ 2", msg)
+
+  let msg = "white space required before and after '-'"
+  call CheckDefFailure("let x = 1-2", msg)
+  call CheckDefFailure("let x = 1 -2", msg)
+  call CheckDefFailure("let x = 1- 2", msg)
+
+  let msg = "white space required before and after '..'"
+  call CheckDefFailure("let x = '1'..'2'", msg)
+  call CheckDefFailure("let x = '1' ..'2'", msg)
+  call CheckDefFailure("let x = '1'.. '2'", msg)
+endfunc
+
+" test multiply, divide, modulo
+def Test_expr6()
+  assert_equal(36, 6 * 6)
+  assert_equal(24, 6 * g:alsoint)
+  assert_equal(24, g:alsoint * 6)
+  assert_equal(40, g:anint * g:alsoint)
+
+  assert_equal(10, 60 / 6)
+  assert_equal(6, 60 / g:anint)
+  assert_equal(1, g:anint / 6)
+  assert_equal(2, g:anint / g:alsoint)
+
+  assert_equal(5, 11 % 6)
+  assert_equal(4, g:anint % 6)
+  assert_equal(3, 13 % g:anint)
+  assert_equal(2, g:anint % g:alsoint)
+
+  assert_equal(4, 6 * 4 / 6)
+enddef
+
+def Test_expr6_float()
+  CheckFeature float
+
+  assert_equal(36.0, 6.0 * 6)
+  assert_equal(36.0, 6 * 6.0)
+  assert_equal(36.0, 6.0 * 6.0)
+  assert_equal(1.0, g:afloat * g:anint)
+
+  assert_equal(10.0, 60 / 6.0)
+  assert_equal(10.0, 60.0 / 6)
+  assert_equal(10.0, 60.0 / 6.0)
+  assert_equal(0.01, g:afloat / g:anint)
+
+  assert_equal(4.0, 6.0 * 4 / 6)
+  assert_equal(4.0, 6 * 4.0 / 6)
+  assert_equal(4.0, 6 * 4 / 6.0)
+  assert_equal(4.0, 6.0 * 4.0 / 6)
+  assert_equal(4.0, 6 * 4.0 / 6.0)
+  assert_equal(4.0, 6.0 * 4 / 6.0)
+  assert_equal(4.0, 6.0 * 4.0 / 6.0)
+
+  assert_equal(4.0, 6.0 * 4.0 / 6.0)
+enddef
+
+func Test_expr6_fails()
+  let msg = "white space required before and after '*'"
+  call CheckDefFailure("let x = 1*2", msg)
+  call CheckDefFailure("let x = 1 *2", msg)
+  call CheckDefFailure("let x = 1* 2", msg)
+
+  let msg = "white space required before and after '/'"
+  call CheckDefFailure("let x = 1/2", msg)
+  call CheckDefFailure("let x = 1 /2", msg)
+  call CheckDefFailure("let x = 1/ 2", msg)
+
+  let msg = "white space required before and after '%'"
+  call CheckDefFailure("let x = 1%2", msg)
+  call CheckDefFailure("let x = 1 %2", msg)
+  call CheckDefFailure("let x = 1% 2", msg)
+
+  call CheckDefFailure("let x = '1' * '2'", 'E1036:')
+  call CheckDefFailure("let x = '1' / '2'", 'E1036:')
+  call CheckDefFailure("let x = '1' % '2'", 'E1035:')
+
+  call CheckDefFailure("let x = 0z01 * 0z12", 'E1036:')
+  call CheckDefFailure("let x = 0z01 / 0z12", 'E1036:')
+  call CheckDefFailure("let x = 0z01 % 0z12", 'E1035:')
+
+  call CheckDefFailure("let x = [1] * [2]", 'E1036:')
+  call CheckDefFailure("let x = [1] / [2]", 'E1036:')
+  call CheckDefFailure("let x = [1] % [2]", 'E1035:')
+
+  call CheckDefFailure("let x = #{one: 1} * #{two: 2}", 'E1036:')
+  call CheckDefFailure("let x = #{one: 1} / #{two: 2}", 'E1036:')
+  call CheckDefFailure("let x = #{one: 1} % #{two: 2}", 'E1035:')
+
+endfunc
+
+func Test_expr6_float_fails()
+  CheckFeature float
+  call CheckDefFailure("let x = 1.0 % 2", 'E1035:')
+endfunc
+
+" define here to use old style parsing
+if has('float')
+  let g:float_zero = 0.0
+  let g:float_neg = -9.8
+  let g:float_big = 9.9e99
+endif
+let g:blob_empty = 0z
+let g:blob_one = 0z01
+let g:blob_long = 0z0102.0304
+
+let g:string_empty = ''
+let g:string_short = 'x'
+let g:string_long = 'abcdefghijklm'
+let g:string_special = "ab\ncd\ref\ekk"
+
+let g:special_true = v:true
+let g:special_false = v:false
+let g:special_null = v:null
+let g:special_none = v:none
+
+let g:list_empty = []
+let g:list_mixed = [1, 'b', v:false]
+
+let g:dict_empty = {}
+let g:dict_one = #{one: 1}
+
+let $TESTVAR = 'testvar'
+
+let @a = 'register a'
+
+" test low level expression
+def Test_expr7_number()
+  " number constant
+  assert_equal(0, 0)
+  assert_equal(654, 0654)
+
+  assert_equal(6, 0x6)
+  assert_equal(15, 0xf)
+  assert_equal(255, 0xff)
+enddef
+
+def Test_expr7_float()
+  " float constant
+  if has('float')
+    assert_equal(g:float_zero, .0)
+    assert_equal(g:float_zero, 0.0)
+    assert_equal(g:float_neg, -9.8)
+    assert_equal(g:float_big, 9.9e99)
+  endif
+enddef
+
+def Test_expr7_blob()
+  " blob constant
+  assert_equal(g:blob_empty, 0z)
+  assert_equal(g:blob_one, 0z01)
+  assert_equal(g:blob_long, 0z0102.0304)
+enddef
+
+def Test_expr7_string()
+  " string constant
+  assert_equal(g:string_empty, '')
+  assert_equal(g:string_empty, "")
+  assert_equal(g:string_short, 'x')
+  assert_equal(g:string_short, "x")
+  assert_equal(g:string_long, 'abcdefghijklm')
+  assert_equal(g:string_long, "abcdefghijklm")
+  assert_equal(g:string_special, "ab\ncd\ref\ekk")
+enddef
+
+def Test_expr7_special()
+  " special constant
+  assert_equal(g:special_true, true)
+  assert_equal(g:special_false, false)
+  assert_equal(g:special_null, v:null)
+  assert_equal(g:special_none, v:none)
+enddef
+
+def Test_expr7_list()
+  " list
+  assert_equal(g:list_empty, [])
+  assert_equal(g:list_empty, [  ])
+  assert_equal(g:list_mixed, [1, 'b', false])
+enddef
+
+def Test_expr7_lambda()
+  " lambda
+  let La = { -> 'result'}
+  assert_equal('result', La())
+  assert_equal([1, 3, 5], [1, 2, 3]->map({key, val -> key + val}))
+enddef
+
+def Test_expr7_dict()
+  " dictionary
+  assert_equal(g:dict_empty, {})
+  assert_equal(g:dict_empty, {  })
+  assert_equal(g:dict_one, {'one': 1})
+  let key = 'one'
+  let val = 1
+  assert_equal(g:dict_one, {key: val})
+enddef
+
+def Test_expr7_option()
+  " option
+  set ts=11
+  assert_equal(11, &ts)
+  set ts=8
+  set grepprg=some\ text
+  assert_equal('some text', &grepprg)
+  set grepprg&
+enddef
+
+def Test_expr7_environment()
+  " environment variable
+  assert_equal('testvar', $TESTVAR)
+  assert_equal('', $ASDF_ASD_XXX)
+enddef
+
+def Test_expr7_register()
+  " register
+  assert_equal('register a', @a)
+enddef
+
+def Test_expr7_parens()
+  " (expr)
+  assert_equal(4, (6 * 4) / 6)
+  assert_equal(0, 6 * ( 4 / 6 ))
+
+  assert_equal(6, +6)
+  assert_equal(-6, -6)
+  assert_equal(6, --6)
+  assert_equal(6, -+-6)
+  assert_equal(-6, ---6)
+enddef
+
+def Test_expr7_not()
+  assert_equal(true, !'')
+  assert_equal(true, ![])
+  assert_equal(false, !'asdf')
+  assert_equal(false, ![2])
+  assert_equal(true, !!'asdf')
+  assert_equal(true, !![2])
+enddef
+
+func Test_expr7_fails()
+  call CheckDefFailure("let x = (12", "E110:")
+
+  call CheckDefFailure("let x = -'xx'", "E1030:")
+  call CheckDefFailure("let x = +'xx'", "E1030:")
+
+  call CheckDefFailure("let x = @", "E1002:")
+  call CheckDefFailure("let x = @<", "E354:")
+endfunc
+
+let g:Funcrefs = [function('add')]
+
+func CallMe(arg)
+  return a:arg
+endfunc
+
+def Test_expr7_trailing()
+  " user function call
+  assert_equal(123, CallMe(123))
+  assert_equal('nothing', CallMe('nothing'))
+
+  " partial call
+  let Part = function('CallMe')
+  assert_equal('yes', Part('yes'))
+
+  " funcref call, using list index
+  let l = []
+  g:Funcrefs[0](l, 2)
+  assert_equal([2], l)
+
+  " method call
+  l = [2, 5, 6]
+  l->map({k, v -> k + v})
+  assert_equal([2, 6, 8], l)
+
+  " lambda method call
+  l = [2, 5]
+  l->{l -> add(l, 8)}()
+  assert_equal([2, 5, 8], l)
+
+  " dict member
+  let d = #{key: 123}
+  assert_equal(123, d.key)
+enddef
+
+func Test_expr7_trailing_fails()
+  call CheckDefFailureList(['let l = [2]', 'l->{l -> add(l, 8)}'], 'E107')
+endfunc
+
+func Test_expr_fails()
+  call CheckDefFailure("let x = '1'is2", 'E488:')
+  call CheckDefFailure("let x = '1'isnot2", 'E488:')
+endfunc
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_vim9_script.vim
@@ -0,0 +1,359 @@
+" Test various aspects of the Vim9 script language.
+
+" Check that "lines" inside ":def" results in an "error" message.
+func CheckDefFailure(lines, error)
+  call writefile(['def! Func()'] + a:lines + ['enddef'], 'Xdef')
+  call assert_fails('so Xdef', a:error, a:lines)
+  call delete('Xdef')
+endfunc
+
+func CheckScriptFailure(lines, error)
+  call writefile(a:lines, 'Xdef')
+  call assert_fails('so Xdef', a:error, a:lines)
+  call delete('Xdef')
+endfunc
+
+def Test_syntax()
+  let var = 234
+  let other: list<string> = ['asdf']
+enddef
+
+func Test_def_basic()
+  def SomeFunc(): string
+    return 'yes'
+  enddef
+  call assert_equal('yes', SomeFunc())
+endfunc
+
+def Test_assignment()
+  let bool1: bool = true
+  assert_equal(v:true, bool1)
+  let bool2: bool = false
+  assert_equal(v:false, bool2)
+
+  let list1: list<string> = ['sdf', 'asdf']
+  let list2: list<number> = [1, 2, 3]
+
+  " TODO: does not work yet
+  " let listS: list<string> = []
+  " let listN: list<number> = []
+
+  let dict1: dict<string> = #{key: 'value'}
+  let dict2: dict<number> = #{one: 1, two: 2}
+enddef
+
+func Test_assignment_failure()
+  call CheckDefFailure(['let var=234'], 'E1004:')
+  call CheckDefFailure(['let var =234'], 'E1004:')
+  call CheckDefFailure(['let var= 234'], 'E1004:')
+
+  call CheckDefFailure(['let true = 1'], 'E1034:')
+  call CheckDefFailure(['let false = 1'], 'E1034:')
+
+  call CheckDefFailure(['let var: list<string> = [123]'], 'expected list<string> but got list<number>')
+  call CheckDefFailure(['let var: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
+
+  call CheckDefFailure(['let var: dict<string> = #{key: 123}'], 'expected dict<string> but got dict<number>')
+  call CheckDefFailure(['let var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>')
+
+  call CheckDefFailure(['let var = feedkeys("0")'], 'E1031:')
+  call CheckDefFailure(['let var: number = feedkeys("0")'], 'expected number but got void')
+endfunc
+
+func Test_const()
+  call CheckDefFailure(['const var = 234', 'var = 99'], 'E1018:')
+  call CheckDefFailure(['const one = 234', 'let one = 99'], 'E1017:')
+  call CheckDefFailure(['const two'], 'E1021:')
+endfunc
+
+def Test_block()
+  let outer = 1
+  {
+    let inner = 2
+    assert_equal(1, outer)
+    assert_equal(2, inner)
+  }
+  assert_equal(1, outer)
+enddef
+
+func Test_block_failure()
+  call CheckDefFailure(['{', 'let inner = 1', '}', 'echo inner'], 'E1001:')
+endfunc
+
+def ReturnString(): string
+  return 'string'
+enddef
+
+def ReturnNumber(): number
+  return 123
+enddef
+
+def Test_return_string()
+  assert_equal('string', ReturnString())
+  assert_equal(123, ReturnNumber())
+enddef
+
+func Increment()
+  let g:counter += 1
+endfunc
+
+def Test_call_ufunc_count()
+  g:counter = 1
+  Increment()
+  Increment()
+  Increment()
+  " works with and without :call
+  assert_equal(4, g:counter)
+  call assert_equal(4, g:counter)
+  unlet g:counter
+enddef
+
+def MyVarargs(arg: string, ...rest: list<string>): string
+  let res = arg
+  for s in rest
+    res ..= ',' .. s
+  endfor
+  return res
+enddef
+
+def Test_call_varargs()
+  assert_equal('one', MyVarargs('one'))
+  assert_equal('one,two', MyVarargs('one', 'two'))
+  assert_equal('one,two,three', MyVarargs('one', 'two', 'three'))
+enddef
+
+def Test_return_type_wrong()
+  " TODO: why is ! needed for Mac and FreeBSD?
+  CheckScriptFailure(['def! Func(): number', 'return "a"', 'enddef'], 'expected number but got string')
+  CheckScriptFailure(['def! Func(): string', 'return 1', 'enddef'], 'expected string but got number')
+  CheckScriptFailure(['def! Func(): void', 'return "a"', 'enddef'], 'expected void but got string')
+  CheckScriptFailure(['def! Func()', 'return "a"', 'enddef'], 'expected void but got string')
+enddef
+
+def Test_try_catch()
+  let l = []
+  try
+    add(l, '1')
+    throw 'wrong'
+    add(l, '2')
+  catch
+    add(l, v:exception)
+  finally
+    add(l, '3')
+  endtry
+  assert_equal(['1', 'wrong', '3'], l)
+enddef
+
+let s:export_script_lines =<< trim END
+  vim9script
+  let name: string = 'bob'
+  def Concat(arg: string): string
+    return name .. arg
+  enddef
+  let g:result = Concat('bie')
+  let g:localname = name
+
+  export const CONST = 1234
+  export let exported = 9876
+  export def Exported(): string
+    return 'Exported'
+  enddef
+END
+
+def Test_vim9script()
+  let import_script_lines =<< trim END
+    vim9script
+    import {exported, Exported} from './Xexport.vim'
+    g:imported = exported
+    g:imported_func = Exported()
+  END
+
+  writefile(import_script_lines, 'Ximport.vim')
+  writefile(s:export_script_lines, 'Xexport.vim')
+
+  source Ximport.vim
+
+  assert_equal('bobbie', g:result)
+  assert_equal('bob', g:localname)
+  assert_equal(9876, g:imported)
+  assert_equal('Exported', g:imported_func)
+  assert_false(exists('g:name'))
+
+  unlet g:result
+  unlet g:localname
+  unlet g:imported
+  unlet g:imported_func
+  delete('Ximport.vim')
+  delete('Xexport.vim')
+
+  CheckScriptFailure(['scriptversion 2', 'vim9script'], 'E1039:')
+  CheckScriptFailure(['vim9script', 'scriptversion 2'], 'E1040:')
+enddef
+
+def Test_vim9script_call()
+  let lines =<< trim END
+    vim9script
+    let var = ''
+    def MyFunc(arg: string)
+       var = arg
+    enddef
+    MyFunc('foobar')
+    assert_equal('foobar', var)
+
+    let str = 'barfoo'
+    str->MyFunc()
+    assert_equal('barfoo', var)
+
+    let g:value = 'value'
+    g:value->MyFunc()
+    assert_equal('value', var)
+
+    let listvar = []
+    def ListFunc(arg: list<number>)
+       listvar = arg
+    enddef
+    [1, 2, 3]->ListFunc()
+    assert_equal([1, 2, 3], listvar)
+
+    let dictvar = {}
+    def DictFunc(arg: dict<number>)
+       dictvar = arg
+    enddef
+    {'a': 1, 'b': 2}->DictFunc()
+    assert_equal(#{a: 1, b: 2}, dictvar)
+    #{a: 3, b: 4}->DictFunc()
+    assert_equal(#{a: 3, b: 4}, dictvar)
+  END
+  writefile(lines, 'Xcall.vim')
+  source Xcall.vim
+  delete('Xcall.vim')
+enddef
+
+def Test_vim9script_call_fail_decl()
+  let lines =<< trim END
+    vim9script
+    let var = ''
+    def MyFunc(arg: string)
+       let var = 123
+    enddef
+  END
+  writefile(lines, 'Xcall_decl.vim')
+  assert_fails('source Xcall_decl.vim', 'E1054:')
+  delete('Xcall_decl.vim')
+enddef
+
+def Test_vim9script_call_fail_const()
+  let lines =<< trim END
+    vim9script
+    const var = ''
+    def MyFunc(arg: string)
+       var = 'asdf'
+    enddef
+  END
+  writefile(lines, 'Xcall_const.vim')
+  assert_fails('source Xcall_const.vim', 'E46:')
+  delete('Xcall_const.vim')
+enddef
+
+def Test_vim9script_reload()
+  let lines =<< trim END
+    vim9script
+    const var = ''
+    let valone = 1234
+    def MyFunc(arg: string)
+       valone = 5678
+    enddef
+  END
+  let morelines =<< trim END
+    let valtwo = 222
+    export def GetValtwo(): number
+      return valtwo
+    enddef
+  END
+  writefile(lines + morelines, 'Xreload.vim')
+  source Xreload.vim
+  source Xreload.vim
+  source Xreload.vim
+
+  let testlines =<< trim END
+    vim9script
+    def TheFunc()
+      import GetValtwo from './Xreload.vim'
+      assert_equal(222, GetValtwo())
+    enddef
+    TheFunc()
+  END
+  writefile(testlines, 'Ximport.vim')
+  source Ximport.vim
+
+  " test that when not using "morelines" valtwo is still defined
+  " need to source Xreload.vim again, import doesn't reload a script
+  writefile(lines, 'Xreload.vim')
+  source Xreload.vim
+  source Ximport.vim
+
+  " cannot declare a var twice
+  lines =<< trim END
+    vim9script
+    let valone = 1234
+    let valone = 5678
+  END
+  writefile(lines, 'Xreload.vim')
+  assert_fails('source Xreload.vim', 'E1041:')
+
+  delete('Xreload.vim')
+  delete('Ximport.vim')
+enddef
+
+def Test_import_absolute()
+  let import_lines = [
+        \ 'vim9script',
+        \ 'import exported from "' .. escape(getcwd(), '\') .. '/Xexport_abs.vim"',
+        \ 'g:imported_abs = exported',
+        \ ]
+  writefile(import_lines, 'Ximport_abs.vim')
+  writefile(s:export_script_lines, 'Xexport_abs.vim')
+
+  source Ximport_abs.vim
+
+  assert_equal(9876, g:imported_abs)
+  unlet g:imported_abs
+
+  delete('Ximport_abs.vim')
+  delete('Xexport_abs.vim')
+enddef
+
+def Test_import_rtp()
+  let import_lines = [
+        \ 'vim9script',
+        \ 'import exported from "Xexport_rtp.vim"',
+        \ 'g:imported_rtp = exported',
+        \ ]
+  writefile(import_lines, 'Ximport_rtp.vim')
+  mkdir('import')
+  writefile(s:export_script_lines, 'import/Xexport_rtp.vim')
+
+  let save_rtp = &rtp
+  &rtp = getcwd()
+  source Ximport_rtp.vim
+  &rtp = save_rtp
+
+  assert_equal(9876, g:imported_rtp)
+  unlet g:imported_rtp
+
+  delete('Ximport_rtp.vim')
+  delete('import/Xexport_rtp.vim')
+  delete('import', 'd')
+enddef
+
+def Test_fixed_size_list()
+  " will be allocated as one piece of memory, check that changes work
+  let l = [1, 2, 3, 4]
+  l->remove(0)
+  l->add(5)
+  l->insert(99, 1)
+  call assert_equal([2, 99, 3, 4, 5], l)
+enddef
+
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
--- a/src/testing.c
+++ b/src/testing.c
@@ -758,9 +758,10 @@ f_test_refcount(typval_T *argvars, typva
     switch (argvars[0].v_type)
     {
 	case VAR_UNKNOWN:
+	case VAR_VOID:
 	case VAR_NUMBER:
+	case VAR_BOOL:
 	case VAR_FLOAT:
-	case VAR_BOOL:
 	case VAR_SPECIAL:
 	case VAR_STRING:
 	    break;
@@ -781,7 +782,7 @@ f_test_refcount(typval_T *argvars, typva
 	    {
 		ufunc_T *fp;
 
-		fp = find_func(argvars[0].vval.v_string);
+		fp = find_func(argvars[0].vval.v_string, NULL);
 		if (fp != NULL)
 		    retval = fp->uf_refcount;
 	    }
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -22,8 +22,8 @@
 #define FC_DELETED  0x10	// :delfunction used while uf_refcount > 0
 #define FC_REMOVED  0x20	// function redefined while uf_refcount > 0
 #define FC_SANDBOX  0x40	// function defined in the sandbox
-
-#define FUNCARG(fp, j)	((char_u **)(fp->uf_args.ga_data))[j]
+#define FC_DEAD	    0x80	// function kept only for reference to dfunc
+#define FC_EXPORT   0x100	// "export def Func()"
 
 /*
  * All user-defined functions are found in this hashtable.
@@ -63,13 +63,84 @@ func_tbl_get(void)
 }
 
 /*
+ * Get one function argument and an optional type: "arg: type".
+ * Return a pointer to after the type.
+ * When something is wrong return "arg".
+ */
+    static char_u *
+one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip)
+{
+    char_u *p = arg;
+
+    while (ASCII_ISALNUM(*p) || *p == '_')
+	++p;
+    if (arg == p || isdigit(*arg)
+	    || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+	    || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
+    {
+	if (!skip)
+	    semsg(_("E125: Illegal argument: %s"), arg);
+	return arg;
+    }
+    if (newargs != NULL && ga_grow(newargs, 1) == FAIL)
+	return arg;
+    if (newargs != NULL)
+    {
+	char_u	*arg_copy;
+	int	c;
+	int	i;
+
+	c = *p;
+	*p = NUL;
+	arg_copy = vim_strsave(arg);
+	if (arg_copy == NULL)
+	{
+	    *p = c;
+	    return arg;
+	}
+
+	// Check for duplicate argument name.
+	for (i = 0; i < newargs->ga_len; ++i)
+	    if (STRCMP(((char_u **)(newargs->ga_data))[i], arg_copy) == 0)
+	    {
+		semsg(_("E853: Duplicate argument name: %s"), arg_copy);
+		vim_free(arg_copy);
+		return arg;
+	    }
+	((char_u **)(newargs->ga_data))[newargs->ga_len] = arg_copy;
+	newargs->ga_len++;
+
+	*p = c;
+    }
+
+    // get any type from "arg: type"
+    if (argtypes != NULL && ga_grow(argtypes, 1) == OK)
+    {
+	char_u *type = NULL;
+
+	if (*p == ':')
+	{
+	    type = skipwhite(p + 1);
+	    p = skip_type(type);
+	    type = vim_strnsave(type, p - type);
+	}
+	else if (*skipwhite(p) == ':')
+	    emsg(_("E1059: No white space allowed before :"));
+	((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type;
+    }
+
+    return p;
+}
+
+/*
  * Get function arguments.
  */
-    static int
+    int
 get_function_args(
     char_u	**argp,
     char_u	endchar,
     garray_T	*newargs,
+    garray_T	*argtypes,	// NULL unless using :def
     int		*varargs,
     garray_T	*default_args,
     int		skip)
@@ -78,12 +149,13 @@ get_function_args(
     char_u	*arg = *argp;
     char_u	*p = arg;
     int		c;
-    int		i;
     int		any_default = FALSE;
     char_u	*expr;
 
     if (newargs != NULL)
 	ga_init2(newargs, (int)sizeof(char_u *), 3);
+    if (argtypes != NULL)
+	ga_init2(argtypes, (int)sizeof(char_u *), 3);
     if (default_args != NULL)
 	ga_init2(default_args, (int)sizeof(char_u *), 3);
 
@@ -101,46 +173,29 @@ get_function_args(
 		*varargs = TRUE;
 	    p += 3;
 	    mustend = TRUE;
+
+	    if (argtypes != NULL)
+	    {
+		// ...name: list<type>
+		if (!ASCII_ISALPHA(*p))
+		{
+		    emsg(_("E1055: Missing name after ..."));
+		    break;
+		}
+
+		arg = p;
+		p = one_function_arg(p, newargs, argtypes, skip);
+		if (p == arg)
+		    break;
+	    }
 	}
 	else
 	{
 	    arg = p;
-	    while (ASCII_ISALNUM(*p) || *p == '_')
-		++p;
-	    if (arg == p || isdigit(*arg)
-		    || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
-		    || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
-	    {
-		if (!skip)
-		    semsg(_("E125: Illegal argument: %s"), arg);
+	    p = one_function_arg(p, newargs, argtypes, skip);
+	    if (p == arg)
 		break;
-	    }
-	    if (newargs != NULL && ga_grow(newargs, 1) == FAIL)
-		goto err_ret;
-	    if (newargs != NULL)
-	    {
-		c = *p;
-		*p = NUL;
-		arg = vim_strsave(arg);
-		if (arg == NULL)
-		{
-		    *p = c;
-		    goto err_ret;
-		}
-
-		// Check for duplicate argument name.
-		for (i = 0; i < newargs->ga_len; ++i)
-		    if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0)
-		    {
-			semsg(_("E853: Duplicate argument name: %s"), arg);
-			vim_free(arg);
-			goto err_ret;
-		    }
-		((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
-		newargs->ga_len++;
-
-		*p = c;
-	    }
+
 	    if (*skipwhite(p) == '=' && default_args != NULL)
 	    {
 		typval_T	rettv;
@@ -266,7 +321,7 @@ get_lambda_tv(char_u **arg, typval_T *re
     ga_init(&newlines);
 
     // First, check if this is a lambda expression. "->" must exist.
-    ret = get_function_args(&start, '-', NULL, NULL, NULL, TRUE);
+    ret = get_function_args(&start, '-', NULL, NULL, NULL, NULL, TRUE);
     if (ret == FAIL || *start != '>')
 	return NOTDONE;
 
@@ -276,7 +331,8 @@ get_lambda_tv(char_u **arg, typval_T *re
     else
 	pnewargs = NULL;
     *arg = skipwhite(*arg + 1);
-    ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, FALSE);
+    // TODO: argument types
+    ret = get_function_args(arg, '-', pnewargs, NULL, &varargs, NULL, FALSE);
     if (ret == FAIL || **arg != '>')
 	goto errret;
 
@@ -307,6 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *re
 	fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
 	if (fp == NULL)
 	    goto errret;
+	fp->uf_dfunc_idx = -1;
 	pt = ALLOC_CLEAR_ONE(partial_T);
 	if (pt == NULL)
 	    goto errret;
@@ -345,6 +402,7 @@ get_lambda_tv(char_u **arg, typval_T *re
 #endif
 	if (sandbox)
 	    flags |= FC_SANDBOX;
+	// can be called with more args than uf_args.ga_len
 	fp->uf_varargs = TRUE;
 	fp->uf_flags = flags;
 	fp->uf_calls = 0;
@@ -581,17 +639,73 @@ fname_trans_sid(char_u *name, char_u *fn
 }
 
 /*
+ * Find a function "name" in script "sid".
+ */
+    static ufunc_T *
+find_func_with_sid(char_u *name, int sid)
+{
+    hashitem_T	*hi;
+    char_u	buffer[200];
+
+    buffer[0] = K_SPECIAL;
+    buffer[1] = KS_EXTRA;
+    buffer[2] = (int)KE_SNR;
+    vim_snprintf((char *)buffer + 3, sizeof(buffer) - 3, "%ld_%s",
+							      (long)sid, name);
+    hi = hash_find(&func_hashtab, buffer);
+    if (!HASHITEM_EMPTY(hi))
+	return HI2UF(hi);
+
+    return NULL;
+}
+
+/*
  * Find a function by name, return pointer to it in ufuncs.
  * Return NULL for unknown function.
  */
-    ufunc_T *
-find_func(char_u *name)
+    static ufunc_T *
+find_func_even_dead(char_u *name, cctx_T *cctx)
 {
     hashitem_T	*hi;
+    ufunc_T	*func;
+    imported_T	*imported;
+
+    if (in_vim9script())
+    {
+	// Find script-local function before global one.
+	func = find_func_with_sid(name, current_sctx.sc_sid);
+	if (func != NULL)
+	    return func;
+
+	// Find imported funcion before global one.
+	imported = find_imported(name, cctx);
+	if (imported != NULL && imported->imp_funcname != NULL)
+	{
+	    hi = hash_find(&func_hashtab, imported->imp_funcname);
+	    if (!HASHITEM_EMPTY(hi))
+		return HI2UF(hi);
+	}
+    }
 
     hi = hash_find(&func_hashtab, name);
     if (!HASHITEM_EMPTY(hi))
 	return HI2UF(hi);
+
+    return NULL;
+}
+
+/*
+ * Find a function by name, return pointer to it in ufuncs.
+ * "cctx" is passed in a :def function to find imported functions.
+ * Return NULL for unknown or dead function.
+ */
+    ufunc_T *
+find_func(char_u *name, cctx_T *cctx)
+{
+    ufunc_T	*fp = find_func_even_dead(name, cctx);
+
+    if (fp != NULL && (fp->uf_flags & FC_DEAD) == 0)
+	return fp;
     return NULL;
 }
 
@@ -761,6 +875,127 @@ cleanup_function_call(funccall_T *fc)
 	}
     }
 }
+/*
+ * Unreference "fc": decrement the reference count and free it when it
+ * becomes zero.  "fp" is detached from "fc".
+ * When "force" is TRUE we are exiting.
+ */
+    static void
+funccal_unref(funccall_T *fc, ufunc_T *fp, int force)
+{
+    funccall_T	**pfc;
+    int		i;
+
+    if (fc == NULL)
+	return;
+
+    if (--fc->fc_refcount <= 0 && (force || (
+		fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
+		&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
+		&& fc->l_avars.dv_refcount == DO_NOT_FREE_CNT)))
+	for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller)
+	{
+	    if (fc == *pfc)
+	    {
+		*pfc = fc->caller;
+		free_funccal_contents(fc);
+		return;
+	    }
+	}
+    for (i = 0; i < fc->fc_funcs.ga_len; ++i)
+	if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp)
+	    ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+}
+
+/*
+ * Remove the function from the function hashtable.  If the function was
+ * deleted while it still has references this was already done.
+ * Return TRUE if the entry was deleted, FALSE if it wasn't found.
+ */
+    static int
+func_remove(ufunc_T *fp)
+{
+    hashitem_T	*hi;
+
+    // Return if it was already virtually deleted.
+    if (fp->uf_flags & FC_DEAD)
+	return FALSE;
+
+    hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+    if (!HASHITEM_EMPTY(hi))
+    {
+	// When there is a def-function index do not actually remove the
+	// function, so we can find the index when defining the function again.
+	if (fp->uf_dfunc_idx >= 0)
+	    fp->uf_flags |= FC_DEAD;
+	else
+	    hash_remove(&func_hashtab, hi);
+	return TRUE;
+    }
+    return FALSE;
+}
+
+    static void
+func_clear_items(ufunc_T *fp)
+{
+    ga_clear_strings(&(fp->uf_args));
+    ga_clear_strings(&(fp->uf_def_args));
+    ga_clear_strings(&(fp->uf_lines));
+    VIM_CLEAR(fp->uf_name_exp);
+    VIM_CLEAR(fp->uf_arg_types);
+    ga_clear(&fp->uf_type_list);
+#ifdef FEAT_PROFILE
+    VIM_CLEAR(fp->uf_tml_count);
+    VIM_CLEAR(fp->uf_tml_total);
+    VIM_CLEAR(fp->uf_tml_self);
+#endif
+}
+
+/*
+ * Free all things that a function contains.  Does not free the function
+ * itself, use func_free() for that.
+ * When "force" is TRUE we are exiting.
+ */
+    static void
+func_clear(ufunc_T *fp, int force)
+{
+    if (fp->uf_cleared)
+	return;
+    fp->uf_cleared = TRUE;
+
+    // clear this function
+    func_clear_items(fp);
+    funccal_unref(fp->uf_scoped, fp, force);
+    delete_def_function(fp);
+}
+
+/*
+ * Free a function and remove it from the list of functions.  Does not free
+ * what a function contains, call func_clear() first.
+ */
+    static void
+func_free(ufunc_T *fp)
+{
+    // Only remove it when not done already, otherwise we would remove a newer
+    // version of the function with the same name.
+    if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
+	func_remove(fp);
+
+    if ((fp->uf_flags & FC_DEAD) == 0)
+	vim_free(fp);
+}
+
+/*
+ * Free all things that a function contains and free the function itself.
+ * When "force" is TRUE we are exiting.
+ */
+    static void
+func_clear_free(ufunc_T *fp, int force)
+{
+    func_clear(fp, force);
+    func_free(fp);
+}
+
 
 /*
  * Call a user function.
@@ -822,6 +1057,20 @@ call_user_func(
     ga_init2(&fc->fc_funcs, sizeof(ufunc_T *), 1);
     func_ptr_ref(fp);
 
+    if (fp->uf_dfunc_idx >= 0)
+    {
+	estack_push_ufunc(ETYPE_UFUNC, fp, 1);
+
+	// Execute the compiled function.
+	call_def_function(fp, argcount, argvars, rettv);
+	--depth;
+	current_funccal = fc->caller;
+
+	estack_pop();
+	free_funccal(fc);
+	return;
+    }
+
     if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
 	islambda = TRUE;
 
@@ -1146,110 +1395,57 @@ call_user_func(
 }
 
 /*
- * Unreference "fc": decrement the reference count and free it when it
- * becomes zero.  "fp" is detached from "fc".
- * When "force" is TRUE we are exiting.
+ * Call a user function after checking the arguments.
  */
-    static void
-funccal_unref(funccall_T *fc, ufunc_T *fp, int force)
-{
-    funccall_T	**pfc;
-    int		i;
-
-    if (fc == NULL)
-	return;
-
-    if (--fc->fc_refcount <= 0 && (force || (
-		fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
-		&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
-		&& fc->l_avars.dv_refcount == DO_NOT_FREE_CNT)))
-	for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller)
-	{
-	    if (fc == *pfc)
-	    {
-		*pfc = fc->caller;
-		free_funccal_contents(fc);
-		return;
-	    }
-	}
-    for (i = 0; i < fc->fc_funcs.ga_len; ++i)
-	if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp)
-	    ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
-}
-
-/*
- * Remove the function from the function hashtable.  If the function was
- * deleted while it still has references this was already done.
- * Return TRUE if the entry was deleted, FALSE if it wasn't found.
- */
-    static int
-func_remove(ufunc_T *fp)
-{
-    hashitem_T	*hi = hash_find(&func_hashtab, UF2HIKEY(fp));
-
-    if (!HASHITEM_EMPTY(hi))
-    {
-	hash_remove(&func_hashtab, hi);
-	return TRUE;
-    }
-    return FALSE;
-}
-
-    static void
-func_clear_items(ufunc_T *fp)
+    int
+call_user_func_check(
+	ufunc_T	    *fp,
+	int	    argcount,
+	typval_T    *argvars,
+	typval_T    *rettv,
+	funcexe_T   *funcexe,
+	dict_T	    *selfdict)
 {
-    ga_clear_strings(&(fp->uf_args));
-    ga_clear_strings(&(fp->uf_def_args));
-    ga_clear_strings(&(fp->uf_lines));
-    VIM_CLEAR(fp->uf_name_exp);
-#ifdef FEAT_PROFILE
-    VIM_CLEAR(fp->uf_tml_count);
-    VIM_CLEAR(fp->uf_tml_total);
-    VIM_CLEAR(fp->uf_tml_self);
-#endif
-}
-
-/*
- * Free all things that a function contains.  Does not free the function
- * itself, use func_free() for that.
- * When "force" is TRUE we are exiting.
- */
-    static void
-func_clear(ufunc_T *fp, int force)
-{
-    if (fp->uf_cleared)
-	return;
-    fp->uf_cleared = TRUE;
-
-    // clear this function
-    func_clear_items(fp);
-    funccal_unref(fp->uf_scoped, fp, force);
-}
-
-/*
- * Free a function and remove it from the list of functions.  Does not free
- * what a function contains, call func_clear() first.
- */
-    static void
-func_free(ufunc_T *fp)
-{
-    // only remove it when not done already, otherwise we would remove a newer
-    // version of the function
-    if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
-	func_remove(fp);
-
-    vim_free(fp);
-}
-
-/*
- * Free all things that a function contains and free the function itself.
- * When "force" is TRUE we are exiting.
- */
-    static void
-func_clear_free(ufunc_T *fp, int force)
-{
-    func_clear(fp, force);
-    func_free(fp);
+    int error;
+    int regular_args = fp->uf_args.ga_len;
+
+    if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL)
+	*funcexe->doesrange = TRUE;
+    if (argcount < regular_args - fp->uf_def_args.ga_len)
+	error = FCERR_TOOFEW;
+    else if (!has_varargs(fp) && argcount > regular_args)
+	error = FCERR_TOOMANY;
+    else if ((fp->uf_flags & FC_DICT) && selfdict == NULL)
+	error = FCERR_DICT;
+    else
+    {
+	int		did_save_redo = FALSE;
+	save_redo_T	save_redo;
+
+	/*
+	 * Call the user function.
+	 * Save and restore search patterns, script variables and
+	 * redo buffer.
+	 */
+	save_search_patterns();
+	if (!ins_compl_active())
+	{
+	    saveRedobuff(&save_redo);
+	    did_save_redo = TRUE;
+	}
+	++fp->uf_calls;
+	call_user_func(fp, argcount, argvars, rettv,
+			     funcexe->firstline, funcexe->lastline,
+		      (fp->uf_flags & FC_DICT) ? selfdict : NULL);
+	if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0)
+	    // Function was unreferenced while being used, free it now.
+	    func_clear_free(fp, FALSE);
+	if (did_save_redo)
+	    restoreRedobuff(&save_redo);
+	restore_search_patterns();
+	error = FCERR_NONE;
+    }
+    return error;
 }
 
 /*
@@ -1327,9 +1523,13 @@ free_all_functions(void)
 	for (hi = func_hashtab.ht_array; todo > 0; ++hi)
 	    if (!HASHITEM_EMPTY(hi))
 	    {
+		// clear the def function index now
+		fp = HI2UF(hi);
+		fp->uf_flags &= ~FC_DEAD;
+		fp->uf_dfunc_idx = -1;
+
 		// Only free functions that are not refcounted, those are
 		// supposed to be freed when no longer referenced.
-		fp = HI2UF(hi);
 		if (func_name_refcount(fp->uf_name))
 		    ++skipped;
 		else
@@ -1371,6 +1571,8 @@ free_all_functions(void)
     }
     if (skipped == 0)
 	hash_clear(&func_hashtab);
+
+    free_def_functions();
 }
 #endif
 
@@ -1379,7 +1581,7 @@ free_all_functions(void)
  * lower case letter and doesn't contain AUTOLOAD_CHAR.
  * "len" is the length of "name", or -1 for NUL terminated.
  */
-    static int
+    int
 builtin_function(char_u *name, int len)
 {
     char_u *p;
@@ -1469,6 +1671,43 @@ call_callback(
 }
 
 /*
+ * Give an error message for the result of a function.
+ * Nothing if "error" is FCERR_NONE.
+ */
+    void
+user_func_error(int error, char_u *name)
+{
+    switch (error)
+    {
+	case FCERR_UNKNOWN:
+		emsg_funcname(e_unknownfunc, name);
+		break;
+	case FCERR_NOTMETHOD:
+		emsg_funcname(
+			N_("E276: Cannot use function as a method: %s"), name);
+		break;
+	case FCERR_DELETED:
+		emsg_funcname(N_(e_func_deleted), name);
+		break;
+	case FCERR_TOOMANY:
+		emsg_funcname((char *)e_toomanyarg, name);
+		break;
+	case FCERR_TOOFEW:
+		emsg_funcname((char *)e_toofewarg, name);
+		break;
+	case FCERR_SCRIPT:
+		emsg_funcname(
+		    N_("E120: Using <SID> not in a script context: %s"), name);
+		break;
+	case FCERR_DICT:
+		emsg_funcname(
+		      N_("E725: Calling dict function without Dictionary: %s"),
+			name);
+		break;
+    }
+}
+
+/*
  * Call a function with its resolved parameters
  *
  * Return FAIL when the function can't be called,  OK otherwise.
@@ -1561,7 +1800,7 @@ call_func(
 	    if (partial != NULL && partial->pt_func != NULL)
 		fp = partial->pt_func;
 	    else
-		fp = find_func(rfname);
+		fp = find_func(rfname, NULL);
 
 	    // Trigger FuncUndefined event, may load the function.
 	    if (fp == NULL
@@ -1570,13 +1809,13 @@ call_func(
 		    && !aborting())
 	    {
 		// executed an autocommand, search for the function again
-		fp = find_func(rfname);
+		fp = find_func(rfname, NULL);
 	    }
 	    // Try loading a package.
 	    if (fp == NULL && script_autoload(rfname, TRUE) && !aborting())
 	    {
 		// loaded a package, search for the function again
-		fp = find_func(rfname);
+		fp = find_func(rfname, NULL);
 	    }
 
 	    if (fp != NULL && (fp->uf_flags & FC_DELETED))
@@ -1598,43 +1837,8 @@ call_func(
 		    argv_base = 1;
 		}
 
-		if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL)
-		    *funcexe->doesrange = TRUE;
-		if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len)
-		    error = FCERR_TOOFEW;
-		else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
-		    error = FCERR_TOOMANY;
-		else if ((fp->uf_flags & FC_DICT) && selfdict == NULL)
-		    error = FCERR_DICT;
-		else
-		{
-		    int did_save_redo = FALSE;
-		    save_redo_T	save_redo;
-
-		    /*
-		     * Call the user function.
-		     * Save and restore search patterns, script variables and
-		     * redo buffer.
-		     */
-		    save_search_patterns();
-		    if (!ins_compl_active())
-		    {
-			saveRedobuff(&save_redo);
-			did_save_redo = TRUE;
-		    }
-		    ++fp->uf_calls;
-		    call_user_func(fp, argcount, argvars, rettv,
-					 funcexe->firstline, funcexe->lastline,
-				  (fp->uf_flags & FC_DICT) ? selfdict : NULL);
-		    if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0)
-			// Function was unreferenced while being used, free it
-			// now.
-			func_clear_free(fp, FALSE);
-		    if (did_save_redo)
-			restoreRedobuff(&save_redo);
-		    restore_search_patterns();
-		    error = FCERR_NONE;
-		}
+		error = call_user_func_check(fp, argcount, argvars, rettv,
+							    funcexe, selfdict);
 	    }
 	}
 	else if (funcexe->basetv != NULL)
@@ -1675,38 +1879,7 @@ theend:
      */
     if (!aborting())
     {
-	switch (error)
-	{
-	    case FCERR_UNKNOWN:
-		    emsg_funcname(N_("E117: Unknown function: %s"), name);
-		    break;
-	    case FCERR_NOTMETHOD:
-		    emsg_funcname(
-			       N_("E276: Cannot use function as a method: %s"),
-									 name);
-		    break;
-	    case FCERR_DELETED:
-		    emsg_funcname(N_("E933: Function was deleted: %s"), name);
-		    break;
-	    case FCERR_TOOMANY:
-		    emsg_funcname((char *)e_toomanyarg, name);
-		    break;
-	    case FCERR_TOOFEW:
-		    emsg_funcname(
-			     N_("E119: Not enough arguments for function: %s"),
-									name);
-		    break;
-	    case FCERR_SCRIPT:
-		    emsg_funcname(
-			   N_("E120: Using <SID> not in a script context: %s"),
-									name);
-		    break;
-	    case FCERR_DICT:
-		    emsg_funcname(
-		      N_("E725: Calling dict function without Dictionary: %s"),
-									name);
-		    break;
-	}
+	user_func_error(error, name);
     }
 
     // clear the copies made from the partial
@@ -1719,6 +1892,12 @@ theend:
     return ret;
 }
 
+    static char_u *
+printable_func_name(ufunc_T *fp)
+{
+    return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
+}
+
 /*
  * List the head of the function: "name(arg1, arg2)".
  */
@@ -1731,16 +1910,21 @@ list_func_head(ufunc_T *fp, int indent)
     if (indent)
 	msg_puts("   ");
     msg_puts("function ");
-    if (fp->uf_name_exp != NULL)
-	msg_puts((char *)fp->uf_name_exp);
-    else
-	msg_puts((char *)fp->uf_name);
+    msg_puts((char *)printable_func_name(fp));
     msg_putchar('(');
     for (j = 0; j < fp->uf_args.ga_len; ++j)
     {
 	if (j)
 	    msg_puts(", ");
 	msg_puts((char *)FUNCARG(fp, j));
+	if (fp->uf_arg_types != NULL)
+	{
+	    char *tofree;
+
+	    msg_puts(": ");
+	    msg_puts(type_name(fp->uf_arg_types[j], &tofree));
+	    vim_free(tofree);
+	}
 	if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len)
 	{
 	    msg_puts(" = ");
@@ -1754,6 +1938,21 @@ list_func_head(ufunc_T *fp, int indent)
 	    msg_puts(", ");
 	msg_puts("...");
     }
+    if (fp->uf_va_name != NULL)
+    {
+	if (j)
+	    msg_puts(", ");
+	msg_puts("...");
+	msg_puts((char *)fp->uf_va_name);
+	if (fp->uf_va_type)
+	{
+	    char *tofree;
+
+	    msg_puts(": ");
+	    msg_puts(type_name(fp->uf_va_type, &tofree));
+	    vim_free(tofree);
+	}
+    }
     msg_putchar(')');
     if (fp->uf_flags & FC_ABORT)
 	msg_puts(" abort");
@@ -1793,7 +1992,9 @@ trans_function_name(
     int		lead;
     char_u	sid_buf[20];
     int		len;
+    int		extra = 0;
     lval_T	lv;
+    int		vim9script;
 
     if (fdp != NULL)
 	vim_memset(fdp, 0, sizeof(funcdict_T));
@@ -1934,6 +2135,10 @@ trans_function_name(
 	len = (int)(end - lv.ll_name);
     }
 
+    // In Vim9 script a user function is script-local by default.
+    vim9script = ASCII_ISUPPER(*start)
+			     && current_sctx.sc_version == SCRIPT_VERSION_VIM9;
+
     /*
      * Copy the function name to allocated memory.
      * Accept <SID>name() inside a script, translate into <SNR>123_name().
@@ -1941,20 +2146,25 @@ trans_function_name(
      */
     if (skip)
 	lead = 0;	// do nothing
-    else if (lead > 0)
+    else if (lead > 0 || vim9script)
     {
-	lead = 3;
-	if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
+	if (!vim9script)
+	    lead = 3;
+	if (vim9script || (lv.ll_exp_name != NULL
+					     && eval_fname_sid(lv.ll_exp_name))
 						       || eval_fname_sid(*pp))
 	{
-	    // It's "s:" or "<SID>"
+	    // It's script-local, "s:" or "<SID>"
 	    if (current_sctx.sc_sid <= 0)
 	    {
 		emsg(_(e_usingsid));
 		goto theend;
 	    }
 	    sprintf((char *)sid_buf, "%ld_", (long)current_sctx.sc_sid);
-	    lead += (int)STRLEN(sid_buf);
+	    if (vim9script)
+		extra = 3 + (int)STRLEN(sid_buf);
+	    else
+		lead += (int)STRLEN(sid_buf);
 	}
     }
     else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, len))
@@ -1974,19 +2184,19 @@ trans_function_name(
 	}
     }
 
-    name = alloc(len + lead + 1);
+    name = alloc(len + lead + extra + 1);
     if (name != NULL)
     {
-	if (lead > 0)
+	if (lead > 0 || vim9script)
 	{
 	    name[0] = K_SPECIAL;
 	    name[1] = KS_EXTRA;
 	    name[2] = (int)KE_SNR;
-	    if (lead > 3)	// If it's "<SID>"
+	    if (vim9script || lead > 3)	// If it's "<SID>"
 		STRCPY(name + 3, sid_buf);
 	}
-	mch_memmove(name + lead, lv.ll_name, (size_t)len);
-	name[lead + len] = NUL;
+	mch_memmove(name + lead + extra, lv.ll_name, (size_t)len);
+	name[lead + extra + len] = NUL;
     }
     *pp = end;
 
@@ -2012,19 +2222,22 @@ ex_function(exarg_T *eap)
     char_u	*arg;
     char_u	*line_arg = NULL;
     garray_T	newargs;
+    garray_T	argtypes;
     garray_T	default_args;
     garray_T	newlines;
     int		varargs = FALSE;
     int		flags = 0;
+    char_u	*ret_type = NULL;
     ufunc_T	*fp;
     int		overwrite = FALSE;
     int		indent;
     int		nesting;
+#define MAX_FUNC_NESTING 50
+    char	nesting_def[MAX_FUNC_NESTING];
     dictitem_T	*v;
     funcdict_T	fudi;
     static int	func_nr = 0;	    // number for nameless function
     int		paren;
-    hashtab_T	*ht;
     int		todo;
     hashitem_T	*hi;
     int		do_concat = TRUE;
@@ -2048,7 +2261,8 @@ ex_function(exarg_T *eap)
 		{
 		    --todo;
 		    fp = HI2UF(hi);
-		    if (message_filtered(fp->uf_name))
+		    if ((fp->uf_flags & FC_DEAD)
+					      || message_filtered(fp->uf_name))
 			continue;
 		    if (!func_name_refcount(fp->uf_name))
 			list_func_head(fp, FALSE);
@@ -2084,8 +2298,9 @@ ex_function(exarg_T *eap)
 		    {
 			--todo;
 			fp = HI2UF(hi);
-			if (!isdigit(*fp->uf_name)
-				    && vim_regexec(&regmatch, fp->uf_name, 0))
+			if ((fp->uf_flags & FC_DEAD) == 0
+				&& !isdigit(*fp->uf_name)
+				&& vim_regexec(&regmatch, fp->uf_name, 0))
 			    list_func_head(fp, FALSE);
 		    }
 		}
@@ -2098,6 +2313,10 @@ ex_function(exarg_T *eap)
 	return;
     }
 
+    ga_init(&newargs);
+    ga_init(&argtypes);
+    ga_init(&default_args);
+
     /*
      * Get the function name.  There are these situations:
      * func	    normal function name
@@ -2155,7 +2374,7 @@ ex_function(exarg_T *eap)
 	    *p = NUL;
 	if (!eap->skip && !got_int)
 	{
-	    fp = find_func(name);
+	    fp = find_func(name, NULL);
 	    if (fp != NULL)
 	    {
 		list_func_head(fp, TRUE);
@@ -2176,7 +2395,10 @@ ex_function(exarg_T *eap)
 		if (!got_int)
 		{
 		    msg_putchar('\n');
-		    msg_puts("   endfunction");
+		    if (fp->uf_dfunc_idx >= 0)
+			msg_puts("   enddef");
+		    else
+			msg_puts("   endfunction");
 		}
 	    }
 	    else
@@ -2231,43 +2453,58 @@ ex_function(exarg_T *eap)
 	    emsg(_("E862: Cannot use g: here"));
     }
 
-    if (get_function_args(&p, ')', &newargs, &varargs,
-					    &default_args, eap->skip) == FAIL)
+    if (get_function_args(&p, ')', &newargs,
+			eap->cmdidx == CMD_def ? &argtypes : NULL,
+			 &varargs, &default_args, eap->skip) == FAIL)
 	goto errret_2;
 
-    // find extra arguments "range", "dict", "abort" and "closure"
-    for (;;)
+    if (eap->cmdidx == CMD_def)
     {
-	p = skipwhite(p);
-	if (STRNCMP(p, "range", 5) == 0)
+	// find the return type: :def Func(): type
+	if (*p == ':')
 	{
-	    flags |= FC_RANGE;
-	    p += 5;
-	}
-	else if (STRNCMP(p, "dict", 4) == 0)
-	{
-	    flags |= FC_DICT;
-	    p += 4;
+	    ret_type = skipwhite(p + 1);
+	    p = skip_type(ret_type);
+	    if (p > ret_type)
+		p = skipwhite(p);
+	    else
+		semsg(_("E1056: expected a type: %s"), ret_type);
 	}
-	else if (STRNCMP(p, "abort", 5) == 0)
-	{
-	    flags |= FC_ABORT;
-	    p += 5;
-	}
-	else if (STRNCMP(p, "closure", 7) == 0)
+    }
+    else
+	// find extra arguments "range", "dict", "abort" and "closure"
+	for (;;)
 	{
-	    flags |= FC_CLOSURE;
-	    p += 7;
-	    if (current_funccal == NULL)
+	    p = skipwhite(p);
+	    if (STRNCMP(p, "range", 5) == 0)
+	    {
+		flags |= FC_RANGE;
+		p += 5;
+	    }
+	    else if (STRNCMP(p, "dict", 4) == 0)
+	    {
+		flags |= FC_DICT;
+		p += 4;
+	    }
+	    else if (STRNCMP(p, "abort", 5) == 0)
 	    {
-		emsg_funcname(N_("E932: Closure function should not be at top level: %s"),
-			name == NULL ? (char_u *)"" : name);
-		goto erret;
+		flags |= FC_ABORT;
+		p += 5;
 	    }
+	    else if (STRNCMP(p, "closure", 7) == 0)
+	    {
+		flags |= FC_CLOSURE;
+		p += 7;
+		if (current_funccal == NULL)
+		{
+		    emsg_funcname(N_("E932: Closure function should not be at top level: %s"),
+			    name == NULL ? (char_u *)"" : name);
+		    goto erret;
+		}
+	    }
+	    else
+		break;
 	}
-	else
-	    break;
-    }
 
     // When there is a line break use what follows for the function body.
     // Makes 'exe "func Test()\n...\nendfunc"' work.
@@ -2277,7 +2514,8 @@ ex_function(exarg_T *eap)
 	emsg(_(e_trailing));
 
     /*
-     * Read the body of the function, until ":endfunction" is found.
+     * Read the body of the function, until "}", ":endfunction" or ":enddef" is
+     * found.
      */
     if (KeyTyped)
     {
@@ -2288,7 +2526,7 @@ ex_function(exarg_T *eap)
 	{
 	    if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
 		emsg(_(e_funcdict));
-	    else if (name != NULL && find_func(name) != NULL)
+	    else if (name != NULL && find_func(name, NULL) != NULL)
 		emsg_funcname(e_funcexts, name);
 	}
 
@@ -2304,6 +2542,7 @@ ex_function(exarg_T *eap)
 
     indent = 2;
     nesting = 0;
+    nesting_def[nesting] = (eap->cmdidx == CMD_def);
     for (;;)
     {
 	if (KeyTyped)
@@ -2339,7 +2578,10 @@ ex_function(exarg_T *eap)
 	    lines_left = Rows - 1;
 	if (theline == NULL)
 	{
-	    emsg(_("E126: Missing :endfunction"));
+	    if (eap->cmdidx == CMD_def)
+		emsg(_("E1057: Missing :enddef"));
+	    else
+		emsg(_("E126: Missing :endfunction"));
 	    goto erret;
 	}
 
@@ -2352,7 +2594,7 @@ ex_function(exarg_T *eap)
 
 	if (skip_until != NULL)
 	{
-	    // Don't check for ":endfunc" between
+	    // Don't check for ":endfunc"/":enddef" between
 	    // * ":append" and "."
 	    // * ":python <<EOF" and "EOF"
 	    // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
@@ -2383,8 +2625,9 @@ ex_function(exarg_T *eap)
 	    for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
 		;
 
-	    // Check for "endfunction".
-	    if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0)
+	    // Check for "endfunction" or "enddef".
+	    if (checkforcmd(&p, nesting_def[nesting]
+			     ? "enddef" : "endfunction", 4) && nesting-- == 0)
 	    {
 		char_u *nextcmd = NULL;
 
@@ -2393,8 +2636,9 @@ ex_function(exarg_T *eap)
 		else if (line_arg != NULL && *skipwhite(line_arg) != NUL)
 		    nextcmd = line_arg;
 		else if (*p != NUL && *p != '"' && p_verbose > 0)
-		    give_warning2(
-			 (char_u *)_("W22: Text found after :endfunction: %s"),
+		    give_warning2(eap->cmdidx == CMD_def
+			? (char_u *)_("W1001: Text found after :enddef: %s")
+			: (char_u *)_("W22: Text found after :endfunction: %s"),
 			 p, TRUE);
 		if (nextcmd != NULL)
 		{
@@ -2414,7 +2658,7 @@ ex_function(exarg_T *eap)
 
 	    // Increase indent inside "if", "while", "for" and "try", decrease
 	    // at "end".
-	    if (indent > 2 && STRNCMP(p, "end", 3) == 0)
+	    if (indent > 2 && (*p == '}' || STRNCMP(p, "end", 3) == 0))
 		indent -= 2;
 	    else if (STRNCMP(p, "if", 2) == 0
 		    || STRNCMP(p, "wh", 2) == 0
@@ -2423,7 +2667,8 @@ ex_function(exarg_T *eap)
 		indent += 2;
 
 	    // Check for defining a function inside this function.
-	    if (checkforcmd(&p, "function", 2))
+	    c = *p;
+	    if (checkforcmd(&p, "function", 2) || checkforcmd(&p, "def", 3))
 	    {
 		if (*p == '!')
 		    p = skipwhite(p + 1);
@@ -2431,8 +2676,14 @@ ex_function(exarg_T *eap)
 		vim_free(trans_function_name(&p, TRUE, 0, NULL, NULL));
 		if (*skipwhite(p) == '(')
 		{
-		    ++nesting;
-		    indent += 2;
+		    if (nesting == MAX_FUNC_NESTING - 1)
+			emsg(_("E1058: function nesting too deep"));
+		    else
+		    {
+			++nesting;
+			nesting_def[nesting] = (c == 'd');
+			indent += 2;
+		    }
 		}
 	    }
 
@@ -2537,6 +2788,8 @@ ex_function(exarg_T *eap)
      */
     if (fudi.fd_dict == NULL)
     {
+	hashtab_T	*ht;
+
 	v = find_var(name, &ht, FALSE);
 	if (v != NULL && v->di_tv.v_type == VAR_FUNC)
 	{
@@ -2545,12 +2798,14 @@ ex_function(exarg_T *eap)
 	    goto erret;
 	}
 
-	fp = find_func(name);
+	fp = find_func_even_dead(name, NULL);
 	if (fp != NULL)
 	{
+	    int dead = fp->uf_flags & FC_DEAD;
+
 	    // Function can be replaced with "function!" and when sourcing the
 	    // same script again, but only once.
-	    if (!eap->forceit
+	    if (!dead && !eap->forceit
 			&& (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid
 			    || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq))
 	    {
@@ -2582,6 +2837,7 @@ ex_function(exarg_T *eap)
 		fp->uf_name_exp = NULL;
 		func_clear_items(fp);
 		fp->uf_name_exp = exp_name;
+		fp->uf_flags &= ~FC_DEAD;
 #ifdef FEAT_PROFILE
 		fp->uf_profiling = FALSE;
 		fp->uf_prof_initialized = FALSE;
@@ -2651,6 +2907,7 @@ ex_function(exarg_T *eap)
 	fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
 	if (fp == NULL)
 	    goto erret;
+	fp->uf_dfunc_idx = -1;
 
 	if (fudi.fd_dict != NULL)
 	{
@@ -2696,6 +2953,61 @@ ex_function(exarg_T *eap)
     }
     fp->uf_args = newargs;
     fp->uf_def_args = default_args;
+    fp->uf_ret_type = &t_any;
+
+    if (eap->cmdidx == CMD_def)
+    {
+	// parse the argument types
+	ga_init2(&fp->uf_type_list, sizeof(type_T), 5);
+
+	if (argtypes.ga_len > 0)
+	{
+	    // When "varargs" is set the last name/type goes into uf_va_name
+	    // and uf_va_type.
+	    int len = argtypes.ga_len - (varargs ? 1 : 0);
+
+	    fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len);
+	    if (fp->uf_arg_types != NULL)
+	    {
+		int i;
+
+		for (i = 0; i < len; ++ i)
+		{
+		    p = ((char_u **)argtypes.ga_data)[i];
+		    if (p == NULL)
+			// todo: get type from default value
+			fp->uf_arg_types[i] = &t_any;
+		    else
+			fp->uf_arg_types[i] = parse_type(&p, &fp->uf_type_list);
+		}
+	    }
+	    if (varargs)
+	    {
+		// Move the last argument "...name: type" to uf_va_name and
+		// uf_va_type.
+		fp->uf_va_name = ((char_u **)fp->uf_args.ga_data)
+						      [fp->uf_args.ga_len - 1];
+		--fp->uf_args.ga_len;
+		p = ((char_u **)argtypes.ga_data)[len];
+		if (p == NULL)
+		    // todo: get type from default value
+		    fp->uf_va_type = &t_any;
+		else
+		    fp->uf_va_type = parse_type(&p, &fp->uf_type_list);
+	    }
+	    varargs = FALSE;
+	}
+
+	// parse the return type, if any
+	if (ret_type == NULL)
+	    fp->uf_ret_type = &t_void;
+	else
+	{
+	    p = ret_type;
+	    fp->uf_ret_type = parse_type(&p, &fp->uf_type_list);
+	}
+    }
+
     fp->uf_lines = newlines;
     if ((flags & FC_CLOSURE) != 0)
     {
@@ -2716,10 +3028,22 @@ ex_function(exarg_T *eap)
     fp->uf_calls = 0;
     fp->uf_script_ctx = current_sctx;
     fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+    if (is_export)
+    {
+	fp->uf_flags |= FC_EXPORT;
+	// let ex_export() know the export worked.
+	is_export = FALSE;
+    }
+
+    // ":def Func()" needs to be compiled
+    if (eap->cmdidx == CMD_def)
+	compile_def_function(fp, FALSE);
+
     goto ret_free;
 
 erret:
     ga_clear_strings(&newargs);
+    ga_clear_strings(&argtypes);
     ga_clear_strings(&default_args);
 errret_2:
     ga_clear_strings(&newlines);
@@ -2755,7 +3079,17 @@ translated_function_exists(char_u *name)
 {
     if (builtin_function(name, -1))
 	return has_internal_func(name);
-    return find_func(name) != NULL;
+    return find_func(name, NULL) != NULL;
+}
+
+/*
+ * Return TRUE when "ufunc" has old-style "..." varargs
+ * or named varargs "...name: type".
+ */
+    int
+has_varargs(ufunc_T *ufunc)
+{
+    return ufunc->uf_varargs || ufunc->uf_va_name != NULL;
 }
 
 /*
@@ -2826,9 +3160,10 @@ get_user_func_name(expand_T *xp, int idx
 	    ++hi;
 	fp = HI2UF(hi);
 
-	if ((fp->uf_flags & FC_DICT)
+	// don't show dead, dict and lambda functions
+	if ((fp->uf_flags & FC_DEAD) || (fp->uf_flags & FC_DICT)
 				|| STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
-	    return (char_u *)""; // don't show dict and lambda functions
+	    return (char_u *)"";
 
 	if (STRLEN(fp->uf_name) + 4 >= IOSIZE)
 	    return fp->uf_name;	// prevents overflow
@@ -2837,7 +3172,7 @@ get_user_func_name(expand_T *xp, int idx
 	if (xp->xp_context != EXPAND_USER_FUNC)
 	{
 	    STRCAT(IObuff, "(");
-	    if (!fp->uf_varargs && fp->uf_args.ga_len == 0)
+	    if (!has_varargs(fp) && fp->uf_args.ga_len == 0)
 		STRCAT(IObuff, ")");
 	}
 	return IObuff;
@@ -2876,7 +3211,7 @@ ex_delfunction(exarg_T *eap)
 	*p = NUL;
 
     if (!eap->skip)
-	fp = find_func(name);
+	fp = find_func(name, NULL);
     vim_free(name);
 
     if (!eap->skip)
@@ -2931,7 +3266,7 @@ func_unref(char_u *name)
 
     if (name == NULL || !func_name_refcount(name))
 	return;
-    fp = find_func(name);
+    fp = find_func(name, NULL);
     if (fp == NULL && isdigit(*name))
     {
 #ifdef EXITFREE
@@ -2974,7 +3309,7 @@ func_ref(char_u *name)
 
     if (name == NULL || !func_name_refcount(name))
 	return;
-    fp = find_func(name);
+    fp = find_func(name, NULL);
     if (fp != NULL)
 	++fp->uf_refcount;
     else if (isdigit(*name))
@@ -3119,7 +3454,7 @@ ex_call(exarg_T *eap)
 
     if (*startarg != '(')
     {
-	semsg(_(e_missingparen), eap->arg);
+	semsg(_(e_missing_paren), eap->arg);
 	goto end;
     }
 
@@ -3444,7 +3779,7 @@ make_partial(dict_T *selfdict_in, typval
 					      : rettv->vval.v_partial->pt_name;
 	// Translate "s:func" to the stored function name.
 	fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
-	fp = find_func(fname);
+	fp = find_func(fname, NULL);
 	vim_free(tofree);
     }
 
@@ -3610,7 +3945,7 @@ get_funccal(void)
     hashtab_T *
 get_funccal_local_ht()
 {
-    if (current_funccal == NULL)
+    if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
 	return NULL;
     return &get_funccal()->l_vars.dv_hashtab;
 }
@@ -3622,7 +3957,7 @@ get_funccal_local_ht()
     dictitem_T *
 get_funccal_local_var()
 {
-    if (current_funccal == NULL)
+    if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
 	return NULL;
     return &get_funccal()->l_vars_var;
 }
@@ -3634,7 +3969,7 @@ get_funccal_local_var()
     hashtab_T *
 get_funccal_args_ht()
 {
-    if (current_funccal == NULL)
+    if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
 	return NULL;
     return &get_funccal()->l_avars.dv_hashtab;
 }
@@ -3646,7 +3981,7 @@ get_funccal_args_ht()
     dictitem_T *
 get_funccal_args_var()
 {
-    if (current_funccal == NULL)
+    if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
 	return NULL;
     return &get_funccal()->l_avars_var;
 }
@@ -3657,7 +3992,7 @@ get_funccal_args_var()
     void
 list_func_vars(int *first)
 {
-    if (current_funccal != NULL)
+    if (current_funccal != NULL && current_funccal->l_vars.dv_refcount > 0)
 	list_hashtable_vars(&current_funccal->l_vars.dv_hashtab,
 							   "l:", FALSE, first);
 }
@@ -3866,7 +4201,7 @@ set_ref_in_func(char_u *name, ufunc_T *f
     if (fp_in == NULL)
     {
 	fname = fname_trans_sid(name, fname_buf, &tofree, &error);
-	fp = find_func(fname);
+	fp = find_func(fname, NULL);
     }
     if (fp != NULL)
     {
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    149,
+/**/
     148,
 /**/
     147,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2151,6 +2151,10 @@ typedef enum {
     USEPOPUP_HIDDEN	// use info popup initially hidden
 } use_popup_T;
 
+// Flags for assignment functions.
+#define LET_IS_CONST	1   // ":const"
+#define LET_NO_COMMAND	2   // "var = expr" without ":let" or ":const"
+
 #include "ex_cmds.h"	    // Ex command defines
 #include "spell.h"	    // spell checking stuff
 
new file mode 100644
--- /dev/null
+++ b/src/vim9.h
@@ -0,0 +1,252 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * vim9.h: types and globals used for Vim9 script.
+ */
+
+typedef enum {
+    ISN_EXEC,	    // execute Ex command line isn_arg.string
+    ISN_ECHO,	    // echo isn_arg.number items on top of stack
+
+    // get and set variables
+    ISN_LOAD,	    // push local variable isn_arg.number
+    ISN_LOADV,	    // push v: variable isn_arg.number
+    ISN_LOADSCRIPT, // push script-local variable isn_arg.script.
+    ISN_LOADS,	    // push s: variable isn_arg.string
+    ISN_LOADG,	    // push g: variable isn_arg.string
+    ISN_LOADOPT,    // push option isn_arg.string
+    ISN_LOADENV,    // push environment variable isn_arg.string
+    ISN_LOADREG,    // push register isn_arg.number
+
+    ISN_STORE,	    // pop into local variable isn_arg.number
+    ISN_STOREG,	    // pop into global variable isn_arg.string
+    ISN_STORESCRIPT, // pop into scirpt variable isn_arg.script
+    ISN_STOREOPT,   // pop into option isn_arg.string
+    // ISN_STOREOTHER, // pop into other script variable isn_arg.other.
+
+    ISN_STORENR,    // store number into local variable isn_arg.storenr.str_idx
+
+    // constants
+    ISN_PUSHNR,	    // push number isn_arg.number
+    ISN_PUSHBOOL,   // push bool value isn_arg.number
+    ISN_PUSHSPEC,   // push special value isn_arg.number
+    ISN_PUSHF,	    // push float isn_arg.fnumber
+    ISN_PUSHS,	    // push string isn_arg.string
+    ISN_PUSHBLOB,   // push blob isn_arg.blob
+    ISN_NEWLIST,    // push list from stack items, size is isn_arg.number
+    ISN_NEWDICT,    // push dict from stack items, size is isn_arg.number
+
+    // function call
+    ISN_BCALL,	    // call builtin function isn_arg.bfunc
+    ISN_DCALL,	    // call def function isn_arg.dfunc
+    ISN_UCALL,	    // call user function or funcref/partial isn_arg.ufunc
+    ISN_PCALL,	    // call partial, use isn_arg.pfunc
+    ISN_RETURN,	    // return, result is on top of stack
+    ISN_FUNCREF,    // push a function ref to dfunc isn_arg.number
+
+    // expression operations
+    ISN_JUMP,	    // jump if condition is matched isn_arg.jump
+
+    // loop
+    ISN_FOR,	    // get next item from a list, uses isn_arg.forloop
+
+    ISN_TRY,	    // add entry to ec_trystack, uses isn_arg.try
+    ISN_THROW,	    // pop value of stack, store in v:exception
+    ISN_PUSHEXC,    // push v:exception
+    ISN_CATCH,	    // drop v:exception
+    ISN_ENDTRY,	    // take entry off from ec_trystack
+
+    // moreexpression operations
+    ISN_ADDLIST,
+    ISN_ADDBLOB,
+
+    // operation with two arguments; isn_arg.op.op_type is exptype_T
+    ISN_OPNR,
+    ISN_OPFLOAT,
+    ISN_OPANY,
+
+    // comparative operations; isn_arg.op.op_type is exptype_T, op_ic used
+    ISN_COMPAREBOOL,
+    ISN_COMPARESPECIAL,
+    ISN_COMPARENR,
+    ISN_COMPAREFLOAT,
+    ISN_COMPARESTRING,
+    ISN_COMPAREBLOB,
+    ISN_COMPARELIST,
+    ISN_COMPAREDICT,
+    ISN_COMPAREFUNC,
+    ISN_COMPAREPARTIAL,
+    ISN_COMPAREANY,
+
+    // expression operations
+    ISN_CONCAT,
+    ISN_INDEX,	    // [expr] list index
+    ISN_MEMBER,	    // dict.member using isn_arg.string
+    ISN_2BOOL,	    // convert value to bool, invert if isn_arg.number != 0
+    ISN_2STRING,    // convert value to string at isn_arg.number on stack
+    ISN_NEGATENR,   // apply "-" to number
+
+    ISN_CHECKNR,    // check value can be used as a number
+    ISN_CHECKTYPE,  // check value type is isn_arg.type.tc_type
+
+    ISN_DROP	    // pop stack and discard value
+} isntype_T;
+
+
+// arguments to ISN_BCALL
+typedef struct {
+    int	    cbf_idx;	    // index in "global_functions"
+    int	    cbf_argcount;   // number of arguments on top of stack
+} cbfunc_T;
+
+// arguments to ISN_DCALL
+typedef struct {
+    int	    cdf_idx;	    // index in "def_functions" for ISN_DCALL
+    int	    cdf_argcount;   // number of arguments on top of stack
+} cdfunc_T;
+
+// arguments to ISN_PCALL
+typedef struct {
+    int	    cpf_top;	    // when TRUE partial is above the arguments
+    int	    cpf_argcount;   // number of arguments on top of stack
+} cpfunc_T;
+
+// arguments to ISN_UCALL and ISN_XCALL
+typedef struct {
+    char_u  *cuf_name;
+    int	    cuf_argcount;   // number of arguments on top of stack
+} cufunc_T;
+
+typedef enum {
+    JUMP_ALWAYS,
+    JUMP_IF_TRUE,		// pop and jump if true
+    JUMP_IF_FALSE,		// pop and jump if false
+    JUMP_AND_KEEP_IF_TRUE,	// jump if top of stack is true, drop if not
+    JUMP_AND_KEEP_IF_FALSE,	// jump if top of stack is false, drop if not
+} jumpwhen_T;
+
+// arguments to ISN_JUMP
+typedef struct {
+    jumpwhen_T	jump_when;
+    int		jump_where;	    // position to jump to
+} jump_T;
+
+// arguments to ISN_FOR
+typedef struct {
+    int	    for_idx;	    // loop variable index
+    int	    for_end;	    // position to jump to after done
+} forloop_T;
+
+// arguments to ISN_TRY
+typedef struct {
+    int	    try_catch;	    // position to jump to on throw
+    int	    try_finally;    // position to jump to for return
+} try_T;
+
+// arguments to ISN_ECHO
+typedef struct {
+    int	    echo_with_white;    // :echo instead of :echon
+    int	    echo_count;		// number of expressions
+} echo_T;
+
+// arguments to ISN_OPNR, ISN_OPFLOAT, etc.
+typedef struct {
+    exptype_T	op_type;
+    int		op_ic;	    // TRUE with '#', FALSE with '?', else MAYBE
+} opexpr_T;
+
+// arguments to ISN_CHECKTYPE
+typedef struct {
+    vartype_T	ct_type;
+    int		ct_off;	    // offset in stack, -1 is bottom
+} checktype_T;
+
+// arguments to ISN_STORENR
+typedef struct {
+    int		str_idx;
+    varnumber_T	str_val;
+} storenr_T;
+
+// arguments to ISN_STOREOPT
+typedef struct {
+    char_u	*so_name;
+    int		so_flags;
+} storeopt_T;
+
+// arguments to ISN_LOADS
+typedef struct {
+    char_u	*ls_name;	// variable name
+    int		ls_sid;		// script ID
+} loads_T;
+
+// arguments to ISN_LOADSCRIPT
+typedef struct {
+    int		script_sid;	// script ID
+    int		script_idx;	// index in sn_var_vals
+} script_T;
+
+/*
+ * Instruction
+ */
+typedef struct {
+    isntype_T	isn_type;
+    int		isn_lnum;
+    union {
+	char_u		    *string;
+	varnumber_T	    number;
+	blob_T		    *blob;
+#ifdef FEAT_FLOAT
+	float_T		    fnumber;
+#endif
+	jump_T		    jump;
+	forloop_T	    forloop;
+	try_T		    try;
+	cbfunc_T	    bfunc;
+	cdfunc_T	    dfunc;
+	cpfunc_T	    pfunc;
+	cufunc_T	    ufunc;
+	echo_T		    echo;
+	opexpr_T	    op;
+	checktype_T	    type;
+	storenr_T	    storenr;
+	storeopt_T	    storeopt;
+	loads_T		    loads;
+	script_T	    script;
+    } isn_arg;
+} isn_T;
+
+/*
+ * Info about a function defined with :def.  Used in "def_functions".
+ */
+struct dfunc_S {
+    ufunc_T	*df_ufunc;	    // struct containing most stuff
+    int		df_idx;		    // index in def_functions
+    int		df_deleted;	    // if TRUE function was deleted
+
+    garray_T	df_def_args_isn;    // default argument instructions
+    isn_T	*df_instr;	    // function body to be executed
+    int		df_instr_count;
+
+    int		df_varcount;	    // number of local variables
+};
+
+// Number of entries used by stack frame for a function call.
+#define STACK_FRAME_SIZE 3
+
+
+#ifdef DEFINE_VIM9_GLOBALS
+// Functions defined with :def are stored in this growarray.
+// They are never removed, so that they can be found by index.
+// Deleted functions have the df_deleted flag set.
+garray_T def_functions = {0, 0, sizeof(dfunc_T), 50, NULL};
+#else
+extern garray_T def_functions;
+#endif
+
new file mode 100644
--- /dev/null
+++ b/src/vim9compile.c
@@ -0,0 +1,4612 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * vim9compile.c: :def and dealing with instructions
+ */
+
+#define USING_FLOAT_STUFF
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+#ifdef VMS
+# include <float.h>
+#endif
+
+#define DEFINE_VIM9_GLOBALS
+#include "vim9.h"
+
+/*
+ * Chain of jump instructions where the end label needs to be set.
+ */
+typedef struct endlabel_S endlabel_T;
+struct endlabel_S {
+    endlabel_T	*el_next;	    // chain end_label locations
+    int		el_end_label;	    // instruction idx where to set end
+};
+
+/*
+ * info specific for the scope of :if / elseif / else
+ */
+typedef struct {
+    int		is_if_label;	    // instruction idx at IF or ELSEIF
+    endlabel_T	*is_end_label;	    // instructions to set end label
+} ifscope_T;
+
+/*
+ * info specific for the scope of :while
+ */
+typedef struct {
+    int		ws_top_label;	    // instruction idx at WHILE
+    endlabel_T	*ws_end_label;	    // instructions to set end
+} whilescope_T;
+
+/*
+ * info specific for the scope of :for
+ */
+typedef struct {
+    int		fs_top_label;	    // instruction idx at FOR
+    endlabel_T	*fs_end_label;	    // break instructions
+} forscope_T;
+
+/*
+ * info specific for the scope of :try
+ */
+typedef struct {
+    int		ts_try_label;	    // instruction idx at TRY
+    endlabel_T	*ts_end_label;	    // jump to :finally or :endtry
+    int		ts_catch_label;	    // instruction idx of last CATCH
+    int		ts_caught_all;	    // "catch" without argument encountered
+} tryscope_T;
+
+typedef enum {
+    NO_SCOPE,
+    IF_SCOPE,
+    WHILE_SCOPE,
+    FOR_SCOPE,
+    TRY_SCOPE,
+    BLOCK_SCOPE
+} scopetype_T;
+
+/*
+ * Info for one scope, pointed to by "ctx_scope".
+ */
+typedef struct scope_S scope_T;
+struct scope_S {
+    scope_T	*se_outer;	    // scope containing this one
+    scopetype_T se_type;
+    int		se_local_count;	    // ctx_locals.ga_len before scope
+    union {
+	ifscope_T	se_if;
+	whilescope_T	se_while;
+	forscope_T	se_for;
+	tryscope_T	se_try;
+    };
+};
+
+/*
+ * Entry for "ctx_locals".  Used for arguments and local variables.
+ */
+typedef struct {
+    char_u	*lv_name;
+    type_T	*lv_type;
+    int		lv_const;   // when TRUE cannot be assigned to
+    int		lv_arg;	    // when TRUE this is an argument
+} lvar_T;
+
+/*
+ * Context for compiling lines of Vim script.
+ * Stores info about the local variables and condition stack.
+ */
+struct cctx_S {
+    ufunc_T	*ctx_ufunc;	    // current function
+    int		ctx_lnum;	    // line number in current function
+    garray_T	ctx_instr;	    // generated instructions
+
+    garray_T	ctx_locals;	    // currently visible local variables
+    int		ctx_max_local;	    // maximum number of locals at one time
+
+    garray_T	ctx_imports;	    // imported items
+
+    scope_T	*ctx_scope;	    // current scope, NULL at toplevel
+
+    garray_T	ctx_type_stack;	    // type of each item on the stack
+    garray_T	*ctx_type_list;	    // space for adding types
+};
+
+static char e_var_notfound[] = N_("E1001: variable not found: %s");
+static char e_syntax_at[] = N_("E1002: Syntax error at %s");
+
+static int compile_expr1(char_u **arg,  cctx_T *cctx);
+static int compile_expr2(char_u **arg,  cctx_T *cctx);
+static int compile_expr3(char_u **arg,  cctx_T *cctx);
+
+/*
+ * Lookup variable "name" in the local scope and return the index.
+ */
+    static int
+lookup_local(char_u *name, size_t len, cctx_T *cctx)
+{
+    int	    idx;
+
+    if (len <= 0)
+	return -1;
+    for (idx = 0; idx < cctx->ctx_locals.ga_len; ++idx)
+    {
+	lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+
+	if (STRNCMP(name, lvar->lv_name, len) == 0
+					       && STRLEN(lvar->lv_name) == len)
+	    return idx;
+    }
+    return -1;
+}
+
+/*
+ * Lookup an argument in the current function.
+ * Returns the argument index or -1 if not found.
+ */
+    static int
+lookup_arg(char_u *name, size_t len, cctx_T *cctx)
+{
+    int	    idx;
+
+    if (len <= 0)
+	return -1;
+    for (idx = 0; idx < cctx->ctx_ufunc->uf_args.ga_len; ++idx)
+    {
+	char_u *arg = FUNCARG(cctx->ctx_ufunc, idx);
+
+	if (STRNCMP(name, arg, len) == 0 && STRLEN(arg) == len)
+	    return idx;
+    }
+    return -1;
+}
+
+/*
+ * Lookup a vararg argument in the current function.
+ * Returns TRUE if there is a match.
+ */
+    static int
+lookup_vararg(char_u *name, size_t len, cctx_T *cctx)
+{
+    char_u  *va_name = cctx->ctx_ufunc->uf_va_name;
+
+    return len > 0 && va_name != NULL
+		 && STRNCMP(name, va_name, len) == 0 && STRLEN(va_name) == len;
+}
+
+/*
+ * Lookup a variable in the current script.
+ * Returns OK or FAIL.
+ */
+    static int
+lookup_script(char_u *name, size_t len)
+{
+    int		    cc;
+    hashtab_T	    *ht = &SCRIPT_VARS(current_sctx.sc_sid);
+    dictitem_T	    *di;
+
+    cc = name[len];
+    name[len] = NUL;
+    di = find_var_in_ht(ht, 0, name, TRUE);
+    name[len] = cc;
+    return di == NULL ? FAIL: OK;
+}
+
+    static type_T *
+get_list_type(type_T *member_type, garray_T *type_list)
+{
+    type_T *type;
+
+    // recognize commonly used types
+    if (member_type->tt_type == VAR_UNKNOWN)
+	return &t_list_any;
+    if (member_type->tt_type == VAR_NUMBER)
+	return &t_list_number;
+    if (member_type->tt_type == VAR_STRING)
+	return &t_list_string;
+
+    // Not a common type, create a new entry.
+    if (ga_grow(type_list, 1) == FAIL)
+	return FAIL;
+    type = ((type_T *)type_list->ga_data) + type_list->ga_len;
+    ++type_list->ga_len;
+    type->tt_type = VAR_LIST;
+    type->tt_member = member_type;
+    return type;
+}
+
+    static type_T *
+get_dict_type(type_T *member_type, garray_T *type_list)
+{
+    type_T *type;
+
+    // recognize commonly used types
+    if (member_type->tt_type == VAR_UNKNOWN)
+	return &t_dict_any;
+    if (member_type->tt_type == VAR_NUMBER)
+	return &t_dict_number;
+    if (member_type->tt_type == VAR_STRING)
+	return &t_dict_string;
+
+    // Not a common type, create a new entry.
+    if (ga_grow(type_list, 1) == FAIL)
+	return FAIL;
+    type = ((type_T *)type_list->ga_data) + type_list->ga_len;
+    ++type_list->ga_len;
+    type->tt_type = VAR_DICT;
+    type->tt_member = member_type;
+    return type;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Following generate_ functions expect the caller to call ga_grow().
+
+/*
+ * Generate an instruction without arguments.
+ * Returns a pointer to the new instruction, NULL if failed.
+ */
+    static isn_T *
+generate_instr(cctx_T *cctx, isntype_T isn_type)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+
+    if (ga_grow(instr, 1) == FAIL)
+	return NULL;
+    isn = ((isn_T *)instr->ga_data) + instr->ga_len;
+    isn->isn_type = isn_type;
+    isn->isn_lnum = cctx->ctx_lnum + 1;
+    ++instr->ga_len;
+
+    return isn;
+}
+
+/*
+ * Generate an instruction without arguments.
+ * "drop" will be removed from the stack.
+ * Returns a pointer to the new instruction, NULL if failed.
+ */
+    static isn_T *
+generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop)
+{
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    stack->ga_len -= drop;
+    return generate_instr(cctx, isn_type);
+}
+
+/*
+ * Generate instruction "isn_type" and put "type" on the type stack.
+ */
+    static isn_T *
+generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, isn_type)) == NULL)
+	return NULL;
+
+    if (ga_grow(stack, 1) == FAIL)
+	return NULL;
+    ((type_T **)stack->ga_data)[stack->ga_len] = type;
+    ++stack->ga_len;
+
+    return isn;
+}
+
+/*
+ * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING.
+ */
+    static int
+may_generate_2STRING(int offset, cctx_T *cctx)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    type_T	**type = ((type_T **)stack->ga_data) + stack->ga_len + offset;
+
+    if ((*type)->tt_type == VAR_STRING)
+	return OK;
+    *type = &t_string;
+
+    if ((isn = generate_instr(cctx, ISN_2STRING)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = offset;
+
+    return OK;
+}
+
+    static int
+check_number_or_float(vartype_T type1, vartype_T type2, char_u *op)
+{
+    if (!((type1 == VAR_NUMBER || type1 == VAR_FLOAT || type1 == VAR_UNKNOWN)
+	    && (type2 == VAR_NUMBER || type2 == VAR_FLOAT
+						     || type2 == VAR_UNKNOWN)))
+    {
+	if (*op == '+')
+	    semsg(_("E1035: wrong argument type for +"));
+	else
+	    semsg(_("E1036: %c requires number or float arguments"), *op);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Generate an instruction with two arguments.  The instruction depends on the
+ * type of the arguments.
+ */
+    static int
+generate_two_op(cctx_T *cctx, char_u *op)
+{
+    garray_T	*stack = &cctx->ctx_type_stack;
+    type_T	*type1;
+    type_T	*type2;
+    vartype_T	vartype;
+    isn_T	*isn;
+
+    // Get the known type of the two items on the stack.  If they are matching
+    // use a type-specific instruction. Otherwise fall back to runtime type
+    // checking.
+    type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2];
+    type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+    vartype = VAR_UNKNOWN;
+    if (type1->tt_type == type2->tt_type
+	    && (type1->tt_type == VAR_NUMBER
+		|| type1->tt_type == VAR_LIST
+#ifdef FEAT_FLOAT
+		|| type1->tt_type == VAR_FLOAT
+#endif
+		|| type1->tt_type == VAR_BLOB))
+	vartype = type1->tt_type;
+
+    switch (*op)
+    {
+	case '+': if (vartype != VAR_LIST && vartype != VAR_BLOB
+			  && check_number_or_float(
+				   type1->tt_type, type2->tt_type, op) == FAIL)
+		      return FAIL;
+		  isn = generate_instr_drop(cctx,
+			    vartype == VAR_NUMBER ? ISN_OPNR
+			  : vartype == VAR_LIST ? ISN_ADDLIST
+			  : vartype == VAR_BLOB ? ISN_ADDBLOB
+#ifdef FEAT_FLOAT
+			  : vartype == VAR_FLOAT ? ISN_OPFLOAT
+#endif
+			  : ISN_OPANY, 1);
+		  if (isn != NULL)
+		      isn->isn_arg.op.op_type = EXPR_ADD;
+		  break;
+
+	case '-':
+	case '*':
+	case '/': if (check_number_or_float(type1->tt_type, type2->tt_type,
+								   op) == FAIL)
+		      return FAIL;
+		  if (vartype == VAR_NUMBER)
+		      isn = generate_instr_drop(cctx, ISN_OPNR, 1);
+#ifdef FEAT_FLOAT
+		  else if (vartype == VAR_FLOAT)
+		      isn = generate_instr_drop(cctx, ISN_OPFLOAT, 1);
+#endif
+		  else
+		      isn = generate_instr_drop(cctx, ISN_OPANY, 1);
+		  if (isn != NULL)
+		      isn->isn_arg.op.op_type = *op == '*'
+				 ? EXPR_MULT : *op == '/'? EXPR_DIV : EXPR_SUB;
+		  break;
+
+	case '%': if ((type1->tt_type != VAR_UNKNOWN
+					       && type1->tt_type != VAR_NUMBER)
+			  || (type2->tt_type != VAR_UNKNOWN
+					      && type2->tt_type != VAR_NUMBER))
+		  {
+		      emsg(_("E1035: % requires number arguments"));
+		      return FAIL;
+		  }
+		  isn = generate_instr_drop(cctx,
+			      vartype == VAR_NUMBER ? ISN_OPNR : ISN_OPANY, 1);
+		  if (isn != NULL)
+		      isn->isn_arg.op.op_type = EXPR_REM;
+		  break;
+    }
+
+    // correct type of result
+    if (vartype == VAR_UNKNOWN)
+    {
+	type_T *type = &t_any;
+
+#ifdef FEAT_FLOAT
+	// float+number and number+float results in float
+	if ((type1->tt_type == VAR_NUMBER || type1->tt_type == VAR_FLOAT)
+		&& (type2->tt_type == VAR_NUMBER || type2->tt_type == VAR_FLOAT))
+	    type = &t_float;
+#endif
+	((type_T **)stack->ga_data)[stack->ga_len - 1] = type;
+    }
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_COMPARE* instruction with a boolean result.
+ */
+    static int
+generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
+{
+    isntype_T	isntype = ISN_DROP;
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    vartype_T	type1;
+    vartype_T	type2;
+
+    // Get the known type of the two items on the stack.  If they are matching
+    // use a type-specific instruction. Otherwise fall back to runtime type
+    // checking.
+    type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type;
+    type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type;
+    if (type1 == type2)
+    {
+	switch (type1)
+	{
+	    case VAR_BOOL: isntype = ISN_COMPAREBOOL; break;
+	    case VAR_SPECIAL: isntype = ISN_COMPARESPECIAL; break;
+	    case VAR_NUMBER: isntype = ISN_COMPARENR; break;
+	    case VAR_FLOAT: isntype = ISN_COMPAREFLOAT; break;
+	    case VAR_STRING: isntype = ISN_COMPARESTRING; break;
+	    case VAR_BLOB: isntype = ISN_COMPAREBLOB; break;
+	    case VAR_LIST: isntype = ISN_COMPARELIST; break;
+	    case VAR_DICT: isntype = ISN_COMPAREDICT; break;
+	    case VAR_FUNC: isntype = ISN_COMPAREFUNC; break;
+	    case VAR_PARTIAL: isntype = ISN_COMPAREPARTIAL; break;
+	    default: isntype = ISN_COMPAREANY; break;
+	}
+    }
+    else if (type1 == VAR_UNKNOWN || type2 == VAR_UNKNOWN
+	    || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT)
+	      && (type2 == VAR_NUMBER || type2 ==VAR_FLOAT)))
+	isntype = ISN_COMPAREANY;
+
+    if ((exptype == EXPR_IS || exptype == EXPR_ISNOT)
+	    && (isntype == ISN_COMPAREBOOL
+	    || isntype == ISN_COMPARESPECIAL
+	    || isntype == ISN_COMPARENR
+	    || isntype == ISN_COMPAREFLOAT))
+    {
+	semsg(_("E1037: Cannot use \"%s\" with %s"),
+		exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1));
+	return FAIL;
+    }
+    if (isntype == ISN_DROP
+	    || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL
+		    && (type1 == VAR_BOOL || type1 == VAR_SPECIAL
+		       || type2 == VAR_BOOL || type2 == VAR_SPECIAL)))
+	    || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL
+				 && exptype != EXPR_IS && exptype != EXPR_ISNOT
+		    && (type1 == VAR_BLOB || type2 == VAR_BLOB
+			|| type1 == VAR_LIST || type2 == VAR_LIST))))
+    {
+	semsg(_("E1037: Cannot compare %s with %s"),
+		vartype_name(type1), vartype_name(type2));
+	return FAIL;
+    }
+
+    if ((isn = generate_instr(cctx, isntype)) == NULL)
+	return FAIL;
+    isn->isn_arg.op.op_type = exptype;
+    isn->isn_arg.op.op_ic = ic;
+
+    // takes two arguments, puts one bool back
+    if (stack->ga_len >= 2)
+    {
+	--stack->ga_len;
+	((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool;
+    }
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_2BOOL instruction.
+ */
+    static int
+generate_2BOOL(cctx_T *cctx, int invert)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = invert;
+
+    // type becomes bool
+    ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool;
+
+    return OK;
+}
+
+    static int
+generate_TYPECHECK(cctx_T *cctx, type_T *vartype, int offset)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL)
+	return FAIL;
+    isn->isn_arg.type.ct_type = vartype->tt_type;  // TODO: whole type
+    isn->isn_arg.type.ct_off = offset;
+
+    // type becomes vartype
+    ((type_T **)stack->ga_data)[stack->ga_len - 1] = vartype;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_PUSHNR instruction.
+ */
+    static int
+generate_PUSHNR(cctx_T *cctx, varnumber_T number)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = number;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_PUSHBOOL instruction.
+ */
+    static int
+generate_PUSHBOOL(cctx_T *cctx, varnumber_T number)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHBOOL, &t_bool)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = number;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_PUSHSPEC instruction.
+ */
+    static int
+generate_PUSHSPEC(cctx_T *cctx, varnumber_T number)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = number;
+
+    return OK;
+}
+
+#ifdef FEAT_FLOAT
+/*
+ * Generate an ISN_PUSHF instruction.
+ */
+    static int
+generate_PUSHF(cctx_T *cctx, float_T fnumber)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHF, &t_float)) == NULL)
+	return FAIL;
+    isn->isn_arg.fnumber = fnumber;
+
+    return OK;
+}
+#endif
+
+/*
+ * Generate an ISN_PUSHS instruction.
+ * Consumes "str".
+ */
+    static int
+generate_PUSHS(cctx_T *cctx, char_u *str)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL)
+	return FAIL;
+    isn->isn_arg.string = str;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_PUSHBLOB instruction.
+ * Consumes "blob".
+ */
+    static int
+generate_PUSHBLOB(cctx_T *cctx, blob_T *blob)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL)
+	return FAIL;
+    isn->isn_arg.blob = blob;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_STORE instruction.
+ */
+    static int
+generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL)
+	return FAIL;
+    if (name != NULL)
+	isn->isn_arg.string = vim_strsave(name);
+    else
+	isn->isn_arg.number = idx;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE)
+ */
+    static int
+generate_STORENR(cctx_T *cctx, int idx, varnumber_T value)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL)
+	return FAIL;
+    isn->isn_arg.storenr.str_idx = idx;
+    isn->isn_arg.storenr.str_val = value;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_STOREOPT instruction
+ */
+    static int
+generate_STOREOPT(cctx_T *cctx, char_u *name, int opt_flags)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr(cctx, ISN_STOREOPT)) == NULL)
+	return FAIL;
+    isn->isn_arg.storeopt.so_name = vim_strsave(name);
+    isn->isn_arg.storeopt.so_flags = opt_flags;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_LOAD or similar instruction.
+ */
+    static int
+generate_LOAD(
+	cctx_T	    *cctx,
+	isntype_T   isn_type,
+	int	    idx,
+	char_u	    *name,
+	type_T	    *type)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, isn_type, type)) == NULL)
+	return FAIL;
+    if (name != NULL)
+	isn->isn_arg.string = vim_strsave(name);
+    else
+	isn->isn_arg.number = idx;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_LOADS instruction.
+ */
+    static int
+generate_LOADS(
+	cctx_T	    *cctx,
+	char_u	    *name,
+	int	    sid)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_type(cctx, ISN_LOADS, &t_any)) == NULL)
+	return FAIL;
+    isn->isn_arg.loads.ls_name = vim_strsave(name);
+    isn->isn_arg.loads.ls_sid = sid;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction.
+ */
+    static int
+generate_SCRIPT(
+	cctx_T	    *cctx,
+	isntype_T   isn_type,
+	int	    sid,
+	int	    idx,
+	type_T	    *type)
+{
+    isn_T	*isn;
+
+    if (isn_type == ISN_LOADSCRIPT)
+	isn = generate_instr_type(cctx, isn_type, type);
+    else
+	isn = generate_instr_drop(cctx, isn_type, 1);
+    if (isn == NULL)
+	return FAIL;
+    isn->isn_arg.script.script_sid = sid;
+    isn->isn_arg.script.script_idx = idx;
+    return OK;
+}
+
+/*
+ * Generate an ISN_NEWLIST instruction.
+ */
+    static int
+generate_NEWLIST(cctx_T *cctx, int count)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    garray_T	*type_list = cctx->ctx_type_list;
+    type_T	*type;
+    type_T	*member;
+
+    if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = count;
+
+    // drop the value types
+    stack->ga_len -= count;
+
+    // use the first value type for the list member type
+    if (count > 0)
+	member = ((type_T **)stack->ga_data)[stack->ga_len];
+    else
+	member = &t_any;
+    type = get_list_type(member, type_list);
+
+    // add the list type to the type stack
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    ((type_T **)stack->ga_data)[stack->ga_len] = type;
+    ++stack->ga_len;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_NEWDICT instruction.
+ */
+    static int
+generate_NEWDICT(cctx_T *cctx, int count)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    garray_T	*type_list = cctx->ctx_type_list;
+    type_T	*type;
+    type_T	*member;
+
+    if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = count;
+
+    // drop the key and value types
+    stack->ga_len -= 2 * count;
+
+    // use the first value type for the list member type
+    if (count > 0)
+	member = ((type_T **)stack->ga_data)[stack->ga_len + 1];
+    else
+	member = &t_any;
+    type = get_dict_type(member, type_list);
+
+    // add the dict type to the type stack
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    ((type_T **)stack->ga_data)[stack->ga_len] = type;
+    ++stack->ga_len;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_FUNCREF instruction.
+ */
+    static int
+generate_FUNCREF(cctx_T *cctx, int dfunc_idx)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL)
+	return FAIL;
+    isn->isn_arg.number = dfunc_idx;
+
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    ((type_T **)stack->ga_data)[stack->ga_len] = &t_partial_any;
+    // TODO: argument and return types
+    ++stack->ga_len;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_JUMP instruction.
+ */
+    static int
+generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_JUMP)) == NULL)
+	return FAIL;
+    isn->isn_arg.jump.jump_when = when;
+    isn->isn_arg.jump.jump_where = where;
+
+    if (when != JUMP_ALWAYS && stack->ga_len > 0)
+	--stack->ga_len;
+
+    return OK;
+}
+
+    static int
+generate_FOR(cctx_T *cctx, int loop_idx)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_FOR)) == NULL)
+	return FAIL;
+    isn->isn_arg.forloop.for_idx = loop_idx;
+
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    // type doesn't matter, will be stored next
+    ((type_T **)stack->ga_data)[stack->ga_len] = &t_any;
+    ++stack->ga_len;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_BCALL instruction.
+ * Return FAIL if the number of arguments is wrong.
+ */
+    static int
+generate_BCALL(cctx_T *cctx, int func_idx, int argcount)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if (check_internal_func(func_idx, argcount) == FAIL)
+	return FAIL;
+
+    if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL)
+	return FAIL;
+    isn->isn_arg.bfunc.cbf_idx = func_idx;
+    isn->isn_arg.bfunc.cbf_argcount = argcount;
+
+    stack->ga_len -= argcount; // drop the arguments
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    ((type_T **)stack->ga_data)[stack->ga_len] =
+				    internal_func_ret_type(func_idx, argcount);
+    ++stack->ga_len;	    // add return value
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_DCALL or ISN_UCALL instruction.
+ * Return FAIL if the number of arguments is wrong.
+ */
+    static int
+generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int argcount)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    int		regular_args = ufunc->uf_args.ga_len;
+
+    if (argcount > regular_args && !has_varargs(ufunc))
+    {
+	semsg(_(e_toomanyarg), ufunc->uf_name);
+	return FAIL;
+    }
+    if (argcount < regular_args - ufunc->uf_def_args.ga_len)
+    {
+	semsg(_(e_toofewarg), ufunc->uf_name);
+	return FAIL;
+    }
+
+    // Turn varargs into a list.
+    if (ufunc->uf_va_name != NULL)
+    {
+	int count = argcount - regular_args;
+
+	// TODO: add default values for optional arguments?
+	generate_NEWLIST(cctx, count < 0 ? 0 : count);
+	argcount = regular_args + 1;
+    }
+
+    if ((isn = generate_instr(cctx,
+		    ufunc->uf_dfunc_idx >= 0 ? ISN_DCALL : ISN_UCALL)) == NULL)
+	return FAIL;
+    if (ufunc->uf_dfunc_idx >= 0)
+    {
+	isn->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx;
+	isn->isn_arg.dfunc.cdf_argcount = argcount;
+    }
+    else
+    {
+	// A user function may be deleted and redefined later, can't use the
+	// ufunc pointer, need to look it up again at runtime.
+	isn->isn_arg.ufunc.cuf_name = vim_strsave(ufunc->uf_name);
+	isn->isn_arg.ufunc.cuf_argcount = argcount;
+    }
+
+    stack->ga_len -= argcount; // drop the arguments
+    if (ga_grow(stack, 1) == FAIL)
+	return FAIL;
+    // add return value
+    ((type_T **)stack->ga_data)[stack->ga_len] = ufunc->uf_ret_type;
+    ++stack->ga_len;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_UCALL instruction when the function isn't defined yet.
+ */
+    static int
+generate_UCALL(cctx_T *cctx, char_u *name, int argcount)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_UCALL)) == NULL)
+	return FAIL;
+    isn->isn_arg.ufunc.cuf_name = vim_strsave(name);
+    isn->isn_arg.ufunc.cuf_argcount = argcount;
+
+    stack->ga_len -= argcount; // drop the arguments
+
+    // drop the funcref/partial, get back the return value
+    ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_any;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_PCALL instruction.
+ */
+    static int
+generate_PCALL(cctx_T *cctx, int argcount, int at_top)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+
+    if ((isn = generate_instr(cctx, ISN_PCALL)) == NULL)
+	return FAIL;
+    isn->isn_arg.pfunc.cpf_top = at_top;
+    isn->isn_arg.pfunc.cpf_argcount = argcount;
+
+    stack->ga_len -= argcount; // drop the arguments
+
+    // drop the funcref/partial, get back the return value
+    ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_any;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_MEMBER instruction.
+ */
+    static int
+generate_MEMBER(cctx_T *cctx, char_u *name, size_t len)
+{
+    isn_T	*isn;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    type_T	*type;
+
+    if ((isn = generate_instr(cctx, ISN_MEMBER)) == NULL)
+	return FAIL;
+    isn->isn_arg.string = vim_strnsave(name, (int)len);
+
+    // change dict type to dict member type
+    type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+    ((type_T **)stack->ga_data)[stack->ga_len - 1] = type->tt_member;
+
+    return OK;
+}
+
+/*
+ * Generate an ISN_ECHO instruction.
+ */
+    static int
+generate_ECHO(cctx_T *cctx, int with_white, int count)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr_drop(cctx, ISN_ECHO, count)) == NULL)
+	return FAIL;
+    isn->isn_arg.echo.echo_with_white = with_white;
+    isn->isn_arg.echo.echo_count = count;
+
+    return OK;
+}
+
+    static int
+generate_EXEC(cctx_T *cctx, char_u *line)
+{
+    isn_T	*isn;
+
+    if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL)
+	return FAIL;
+    isn->isn_arg.string = vim_strsave(line);
+    return OK;
+}
+
+static char e_white_both[] =
+			N_("E1004: white space required before and after '%s'");
+
+/*
+ * Reserve space for a local variable.
+ * Return the index or -1 if it failed.
+ */
+    static int
+reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type)
+{
+    int	    idx;
+    lvar_T  *lvar;
+
+    if (lookup_arg(name, len, cctx) >= 0 || lookup_vararg(name, len, cctx))
+    {
+	emsg_namelen(_("E1006: %s is used as an argument"), name, (int)len);
+	return -1;
+    }
+
+    if (ga_grow(&cctx->ctx_locals, 1) == FAIL)
+	return -1;
+    idx = cctx->ctx_locals.ga_len;
+    if (cctx->ctx_max_local < idx + 1)
+	cctx->ctx_max_local = idx + 1;
+    ++cctx->ctx_locals.ga_len;
+
+    lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+    lvar->lv_name = vim_strnsave(name, (int)(len == 0 ? STRLEN(name) : len));
+    lvar->lv_const = isConst;
+    lvar->lv_type = type;
+
+    return idx;
+}
+
+/*
+ * Skip over a type definition and return a pointer to just after it.
+ */
+    char_u *
+skip_type(char_u *start)
+{
+    char_u *p = start;
+
+    while (ASCII_ISALNUM(*p) || *p == '_')
+	++p;
+
+    // Skip over "<type>"; this is permissive about white space.
+    if (*skipwhite(p) == '<')
+    {
+	p = skipwhite(p);
+	p = skip_type(skipwhite(p + 1));
+	p = skipwhite(p);
+	if (*p == '>')
+	    ++p;
+    }
+    return p;
+}
+
+/*
+ * Parse the member type: "<type>" and return "type" with the member set.
+ * Use "type_list" if a new type needs to be added.
+ * Returns NULL in case of failure.
+ */
+    static type_T *
+parse_type_member(char_u **arg, type_T *type, garray_T *type_list)
+{
+    type_T  *member_type;
+
+    if (**arg != '<')
+    {
+	if (*skipwhite(*arg) == '<')
+	    emsg(_("E1007: No white space allowed before <"));
+	else
+	    emsg(_("E1008: Missing <type>"));
+	return NULL;
+    }
+    *arg = skipwhite(*arg + 1);
+
+    member_type = parse_type(arg, type_list);
+    if (member_type == NULL)
+	return NULL;
+
+    *arg = skipwhite(*arg);
+    if (**arg != '>')
+    {
+	emsg(_("E1009: Missing > after type"));
+	return NULL;
+    }
+    ++*arg;
+
+    if (type->tt_type == VAR_LIST)
+	return get_list_type(member_type, type_list);
+    return get_dict_type(member_type, type_list);
+}
+
+/*
+ * Parse a type at "arg" and advance over it.
+ * Return NULL for failure.
+ */
+    type_T *
+parse_type(char_u **arg, garray_T *type_list)
+{
+    char_u  *p = *arg;
+    size_t  len;
+
+    // skip over the first word
+    while (ASCII_ISALNUM(*p) || *p == '_')
+	++p;
+    len = p - *arg;
+
+    switch (**arg)
+    {
+	case 'a':
+	    if (len == 3 && STRNCMP(*arg, "any", len) == 0)
+	    {
+		*arg += len;
+		return &t_any;
+	    }
+	    break;
+	case 'b':
+	    if (len == 4 && STRNCMP(*arg, "bool", len) == 0)
+	    {
+		*arg += len;
+		return &t_bool;
+	    }
+	    if (len == 4 && STRNCMP(*arg, "blob", len) == 0)
+	    {
+		*arg += len;
+		return &t_blob;
+	    }
+	    break;
+	case 'c':
+	    if (len == 7 && STRNCMP(*arg, "channel", len) == 0)
+	    {
+		*arg += len;
+		return &t_channel;
+	    }
+	    break;
+	case 'd':
+	    if (len == 4 && STRNCMP(*arg, "dict", len) == 0)
+	    {
+		*arg += len;
+		return parse_type_member(arg, &t_dict_any, type_list);
+	    }
+	    break;
+	case 'f':
+	    if (len == 5 && STRNCMP(*arg, "float", len) == 0)
+	    {
+		*arg += len;
+		return &t_float;
+	    }
+	    if (len == 4 && STRNCMP(*arg, "func", len) == 0)
+	    {
+		*arg += len;
+		// TODO: arguments and return type
+		return &t_func_any;
+	    }
+	    break;
+	case 'j':
+	    if (len == 3 && STRNCMP(*arg, "job", len) == 0)
+	    {
+		*arg += len;
+		return &t_job;
+	    }
+	    break;
+	case 'l':
+	    if (len == 4 && STRNCMP(*arg, "list", len) == 0)
+	    {
+		*arg += len;
+		return parse_type_member(arg, &t_list_any, type_list);
+	    }
+	    break;
+	case 'n':
+	    if (len == 6 && STRNCMP(*arg, "number", len) == 0)
+	    {
+		*arg += len;
+		return &t_number;
+	    }
+	    break;
+	case 'p':
+	    if (len == 4 && STRNCMP(*arg, "partial", len) == 0)
+	    {
+		*arg += len;
+		// TODO: arguments and return type
+		return &t_partial_any;
+	    }
+	    break;
+	case 's':
+	    if (len == 6 && STRNCMP(*arg, "string", len) == 0)
+	    {
+		*arg += len;
+		return &t_string;
+	    }
+	    break;
+	case 'v':
+	    if (len == 4 && STRNCMP(*arg, "void", len) == 0)
+	    {
+		*arg += len;
+		return &t_void;
+	    }
+	    break;
+    }
+
+    semsg(_("E1010: Type not recognized: %s"), *arg);
+    return &t_any;
+}
+
+/*
+ * Check if "type1" and "type2" are exactly the same.
+ */
+    static int
+equal_type(type_T *type1, type_T *type2)
+{
+    if (type1->tt_type != type2->tt_type)
+	return FALSE;
+    switch (type1->tt_type)
+    {
+	case VAR_VOID:
+	case VAR_UNKNOWN:
+	case VAR_SPECIAL:
+	case VAR_BOOL:
+	case VAR_NUMBER:
+	case VAR_FLOAT:
+	case VAR_STRING:
+	case VAR_BLOB:
+	case VAR_JOB:
+	case VAR_CHANNEL:
+	    return TRUE;  // not composite is always OK
+	case VAR_LIST:
+	case VAR_DICT:
+	    return equal_type(type1->tt_member, type2->tt_member);
+	case VAR_FUNC:
+	case VAR_PARTIAL:
+	    // TODO; check argument types.
+	    return equal_type(type1->tt_member, type2->tt_member)
+		&& type1->tt_argcount == type2->tt_argcount;
+    }
+    return TRUE;
+}
+
+/*
+ * Find the common type of "type1" and "type2" and put it in "dest".
+ * "type2" and "dest" may be the same.
+ */
+    static void
+common_type(type_T *type1, type_T *type2, type_T *dest)
+{
+    if (equal_type(type1, type2))
+    {
+	if (dest != type2)
+	    *dest = *type2;
+	return;
+    }
+
+    if (type1->tt_type == type2->tt_type)
+    {
+	dest->tt_type = type1->tt_type;
+	if (type1->tt_type == VAR_LIST || type2->tt_type == VAR_DICT)
+	{
+	    common_type(type1->tt_member, type2->tt_member, dest->tt_member);
+	    return;
+	}
+	// TODO: VAR_FUNC and VAR_PARTIAL
+    }
+
+    dest->tt_type = VAR_UNKNOWN;  // "any"
+}
+
+    char *
+vartype_name(vartype_T type)
+{
+    switch (type)
+    {
+	case VAR_VOID: return "void";
+	case VAR_UNKNOWN: return "any";
+	case VAR_SPECIAL: return "special";
+	case VAR_BOOL: return "bool";
+	case VAR_NUMBER: return "number";
+	case VAR_FLOAT: return "float";
+	case VAR_STRING: return "string";
+	case VAR_BLOB: return "blob";
+	case VAR_JOB: return "job";
+	case VAR_CHANNEL: return "channel";
+	case VAR_LIST: return "list";
+	case VAR_DICT: return "dict";
+	case VAR_FUNC: return "function";
+	case VAR_PARTIAL: return "partial";
+    }
+    return "???";
+}
+
+/*
+ * Return the name of a type.
+ * The result may be in allocated memory, in which case "tofree" is set.
+ */
+    char *
+type_name(type_T *type, char **tofree)
+{
+    char *name = vartype_name(type->tt_type);
+
+    *tofree = NULL;
+    if (type->tt_type == VAR_LIST || type->tt_type == VAR_DICT)
+    {
+	char *member_free;
+	char *member_name = type_name(type->tt_member, &member_free);
+	size_t len;
+
+	len = STRLEN(name) + STRLEN(member_name) + 3;
+	*tofree = alloc(len);
+	if (*tofree != NULL)
+	{
+	    vim_snprintf(*tofree, len, "%s<%s>", name, member_name);
+	    vim_free(member_free);
+	    return *tofree;
+	}
+    }
+    // TODO: function and partial argument types
+
+    return name;
+}
+
+/*
+ * Find "name" in script-local items of script "sid".
+ * Returns the index in "sn_var_vals" if found.
+ * If found but not in "sn_var_vals" returns -1.
+ * If not found returns -2.
+ */
+    int
+get_script_item_idx(int sid, char_u *name, int check_writable)
+{
+    hashtab_T	    *ht;
+    dictitem_T	    *di;
+    scriptitem_T    *si = &SCRIPT_ITEM(sid);
+    int		    idx;
+
+    // First look the name up in the hashtable.
+    if (sid <= 0 || sid > script_items.ga_len)
+	return -1;
+    ht = &SCRIPT_VARS(sid);
+    di = find_var_in_ht(ht, 0, name, TRUE);
+    if (di == NULL)
+	return -2;
+
+    // Now find the svar_T index in sn_var_vals.
+    for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx)
+    {
+	svar_T    *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
+
+	if (sv->sv_tv == &di->di_tv)
+	{
+	    if (check_writable && sv->sv_const)
+		semsg(_(e_readonlyvar), name);
+	    return idx;
+	}
+    }
+    return -1;
+}
+
+/*
+ * Find "name" in imported items of the current script/
+ */
+    imported_T *
+find_imported(char_u *name, cctx_T *cctx)
+{
+    scriptitem_T    *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+    int		    idx;
+
+    if (cctx != NULL)
+	for (idx = 0; idx < cctx->ctx_imports.ga_len; ++idx)
+	{
+	    imported_T *import = ((imported_T *)cctx->ctx_imports.ga_data)
+									 + idx;
+
+	    if (STRCMP(name, import->imp_name) == 0)
+		return import;
+	}
+
+    for (idx = 0; idx < si->sn_imports.ga_len; ++idx)
+    {
+	imported_T *import = ((imported_T *)si->sn_imports.ga_data) + idx;
+
+	if (STRCMP(name, import->imp_name) == 0)
+	    return import;
+    }
+    return NULL;
+}
+
+/*
+ * Generate an instruction to load script-local variable "name".
+ */
+    static int
+compile_load_scriptvar(cctx_T *cctx, char_u *name)
+{
+    scriptitem_T    *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+    int		    idx = get_script_item_idx(current_sctx.sc_sid, name, FALSE);
+    imported_T	    *import;
+
+    if (idx == -1)
+    {
+	// variable exists but is not in sn_var_vals: old style script.
+	return generate_LOADS(cctx, name, current_sctx.sc_sid);
+    }
+    if (idx >= 0)
+    {
+	svar_T		*sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
+
+	generate_SCRIPT(cctx, ISN_LOADSCRIPT,
+					current_sctx.sc_sid, idx, sv->sv_type);
+	return OK;
+    }
+
+    import = find_imported(name, cctx);
+    if (import != NULL)
+    {
+	// TODO: check this is a variable, not a function
+	generate_SCRIPT(cctx, ISN_LOADSCRIPT,
+		import->imp_sid,
+		import->imp_var_vals_idx,
+		import->imp_type);
+	return OK;
+    }
+
+    semsg(_("E1050: Item not found: %s"), name);
+    return FAIL;
+}
+
+/*
+ * Compile a variable name into a load instruction.
+ * "end" points to just after the name.
+ * When "error" is FALSE do not give an error when not found.
+ */
+    static int
+compile_load(char_u **arg, char_u *end, cctx_T *cctx, int error)
+{
+    type_T	*type;
+    char_u	*name;
+    int		res = FAIL;
+
+    if (*(*arg + 1) == ':')
+    {
+	// load namespaced variable
+	name = vim_strnsave(*arg + 2, end - (*arg + 2));
+	if (name == NULL)
+	    return FAIL;
+
+	if (**arg == 'v')
+	{
+	    // load v:var
+	    int vidx = find_vim_var(name);
+
+	    if (vidx < 0)
+	    {
+		if (error)
+		    semsg(_(e_var_notfound), name);
+		goto theend;
+	    }
+
+	    // TODO: get actual type
+	    res = generate_LOAD(cctx, ISN_LOADV, vidx, NULL, &t_any);
+	}
+	else if (**arg == 'g')
+	{
+	    // Global variables can be defined later, thus we don't check if it
+	    // exists, give error at runtime.
+	    res = generate_LOAD(cctx, ISN_LOADG, 0, name, &t_any);
+	}
+	else if (**arg == 's')
+	{
+	    res = compile_load_scriptvar(cctx, name);
+	}
+	else
+	{
+	    semsg("Namespace not supported yet: %s", **arg);
+	    goto theend;
+	}
+    }
+    else
+    {
+	size_t	    len = end - *arg;
+	int	    idx;
+	int	    gen_load = FALSE;
+
+	name = vim_strnsave(*arg, end - *arg);
+	if (name == NULL)
+	    return FAIL;
+
+	idx = lookup_arg(*arg, len, cctx);
+	if (idx >= 0)
+	{
+	    if (cctx->ctx_ufunc->uf_arg_types != NULL)
+		type = cctx->ctx_ufunc->uf_arg_types[idx];
+	    else
+		type = &t_any;
+
+	    // Arguments are located above the frame pointer.
+	    idx -= cctx->ctx_ufunc->uf_args.ga_len + STACK_FRAME_SIZE;
+	    if (cctx->ctx_ufunc->uf_va_name != NULL)
+		--idx;
+	    gen_load = TRUE;
+	}
+	else if (lookup_vararg(*arg, len, cctx))
+	{
+	    // varargs is always the last argument
+	    idx = -STACK_FRAME_SIZE - 1;
+	    type = cctx->ctx_ufunc->uf_va_type;
+	    gen_load = TRUE;
+	}
+	else
+	{
+	    idx = lookup_local(*arg, len, cctx);
+	    if (idx >= 0)
+	    {
+		type = (((lvar_T *)cctx->ctx_locals.ga_data) + idx)->lv_type;
+		gen_load = TRUE;
+	    }
+	    else
+	    {
+		if ((len == 4 && STRNCMP("true", *arg, 4) == 0)
+			|| (len == 5 && STRNCMP("false", *arg, 5) == 0))
+		    res = generate_PUSHBOOL(cctx, **arg == 't'
+						     ? VVAL_TRUE : VVAL_FALSE);
+		else
+		   res = compile_load_scriptvar(cctx, name);
+	    }
+	}
+	if (gen_load)
+	    res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type);
+    }
+
+    *arg = end;
+
+theend:
+    if (res == FAIL && error)
+	semsg(_(e_var_notfound), name);
+    vim_free(name);
+    return res;
+}
+
+/*
+ * Compile the argument expressions.
+ * "arg" points to just after the "(" and is advanced to after the ")"
+ */
+    static int
+compile_arguments(char_u **arg, cctx_T *cctx, int *argcount)
+{
+    char_u *p = *arg;
+
+    while (*p != NUL && *p != ')')
+    {
+	if (compile_expr1(&p, cctx) == FAIL)
+	    return FAIL;
+	++*argcount;
+	if (*p == ',')
+	    p = skipwhite(p + 1);
+    }
+    if (*p != ')')
+    {
+	emsg(_(e_missing_close));
+	return FAIL;
+    }
+    *arg = p + 1;
+    return OK;
+}
+
+/*
+ * Compile a function call:  name(arg1, arg2)
+ * "arg" points to "name", "arg + varlen" to the "(".
+ * "argcount_init" is 1 for "value->method()"
+ * Instructions:
+ *	EVAL arg1
+ *	EVAL arg2
+ *	BCALL / DCALL / UCALL
+ */
+    static int
+compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init)
+{
+    char_u	*name = *arg;
+    char_u	*p = *arg + varlen + 1;
+    int		argcount = argcount_init;
+    char_u	namebuf[100];
+    ufunc_T	*ufunc;
+
+    if (varlen >= sizeof(namebuf))
+    {
+	semsg(_("E1011: name too long: %s"), name);
+	return FAIL;
+    }
+    vim_strncpy(namebuf, name, varlen);
+
+    *arg = skipwhite(*arg + varlen + 1);
+    if (compile_arguments(arg, cctx, &argcount) == FAIL)
+	return FAIL;
+
+    if (ASCII_ISLOWER(*name))
+    {
+	int	    idx;
+
+	// builtin function
+	idx = find_internal_func(namebuf);
+	if (idx >= 0)
+	    return generate_BCALL(cctx, idx, argcount);
+	semsg(_(e_unknownfunc), namebuf);
+    }
+
+    // User defined function or variable must start with upper case.
+    if (!ASCII_ISUPPER(*name))
+    {
+	semsg(_("E1012: Invalid function name: %s"), namebuf);
+	return FAIL;
+    }
+
+    // If we can find the function by name generate the right call.
+    ufunc = find_func(namebuf, cctx);
+    if (ufunc != NULL)
+	return generate_CALL(cctx, ufunc, argcount);
+
+    // If the name is a variable, load it and use PCALL.
+    p = namebuf;
+    if (compile_load(&p, namebuf + varlen, cctx, FALSE) == OK)
+	return generate_PCALL(cctx, argcount, FALSE);
+
+    // The function may be defined only later.  Need to figure out at runtime.
+    return generate_UCALL(cctx, namebuf, argcount);
+}
+
+// like NAMESPACE_CHAR but with 'a' and 'l'.
+#define VIM9_NAMESPACE_CHAR	(char_u *)"bgstvw"
+
+/*
+ * Find the end of a variable or function name.  Unlike find_name_end() this
+ * does not recognize magic braces.
+ * Return a pointer to just after the name.  Equal to "arg" if there is no
+ * valid name.
+ */
+    char_u *
+to_name_end(char_u *arg)
+{
+    char_u	*p;
+
+    // Quick check for valid starting character.
+    if (!eval_isnamec1(*arg))
+	return arg;
+
+    for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p))
+	// Include a namespace such as "s:var" and "v:var".  But "n:" is not
+	// and can be used in slice "[n:]".
+	if (*p == ':' && (p != arg + 1
+			     || vim_strchr(VIM9_NAMESPACE_CHAR, *arg) == NULL))
+	    break;
+    return p;
+}
+
+/*
+ * Like to_name_end() but also skip over a list or dict constant.
+ */
+    char_u *
+to_name_const_end(char_u *arg)
+{
+    char_u	*p = to_name_end(arg);
+    typval_T	rettv;
+
+    if (p == arg && *arg == '[')
+    {
+
+	// Can be "[1, 2, 3]->Func()".
+	if (get_list_tv(&p, &rettv, FALSE, FALSE) == FAIL)
+	    p = arg;
+    }
+    else if (p == arg && *arg == '#' && arg[1] == '{')
+    {
+	++p;
+	if (eval_dict(&p, &rettv, FALSE, TRUE) == FAIL)
+	    p = arg;
+    }
+    else if (p == arg && *arg == '{')
+    {
+	int	    ret = get_lambda_tv(&p, &rettv, FALSE);
+
+	if (ret == NOTDONE)
+	    ret = eval_dict(&p, &rettv, FALSE, FALSE);
+	if (ret != OK)
+	    p = arg;
+    }
+
+    return p;
+}
+
+    static void
+type_mismatch(type_T *expected, type_T *actual)
+{
+    char *tofree1, *tofree2;
+
+    semsg(_("E1013: type mismatch, expected %s but got %s"),
+		   type_name(expected, &tofree1), type_name(actual, &tofree2));
+    vim_free(tofree1);
+    vim_free(tofree2);
+}
+
+/*
+ * Check if the expected and actual types match.
+ */
+    static int
+check_type(type_T *expected, type_T *actual, int give_msg)
+{
+    if (expected->tt_type != VAR_UNKNOWN)
+    {
+	if (expected->tt_type != actual->tt_type)
+	{
+	    if (give_msg)
+		type_mismatch(expected, actual);
+	    return FAIL;
+	}
+	if (expected->tt_type == VAR_DICT || expected->tt_type == VAR_LIST)
+	{
+	    int ret = check_type(expected->tt_member, actual->tt_member,
+									FALSE);
+	    if (ret == FAIL && give_msg)
+		type_mismatch(expected, actual);
+	    return ret;
+	}
+    }
+    return OK;
+}
+
+/*
+ * Check that
+ * - "actual" is "expected" type or
+ * - "actual" is a type that can be "expected" type: add a runtime check; or
+ * - return FAIL.
+ */
+    static int
+need_type(type_T *actual, type_T *expected, int offset, cctx_T *cctx)
+{
+    if (equal_type(actual, expected) || expected->tt_type == VAR_UNKNOWN)
+	return OK;
+    if (actual->tt_type != VAR_UNKNOWN)
+    {
+	type_mismatch(expected, actual);
+	return FAIL;
+    }
+    generate_TYPECHECK(cctx, expected, offset);
+    return OK;
+}
+
+/*
+ * parse a list: [expr, expr]
+ * "*arg" points to the '['.
+ */
+    static int
+compile_list(char_u **arg, cctx_T *cctx)
+{
+    char_u	*p = skipwhite(*arg + 1);
+    int		count = 0;
+
+    while (*p != ']')
+    {
+	if (*p == NUL)
+	    return FAIL;
+	if (compile_expr1(&p, cctx) == FAIL)
+	    break;
+	++count;
+	if (*p == ',')
+	    ++p;
+	p = skipwhite(p);
+    }
+    *arg = p + 1;
+
+    generate_NEWLIST(cctx, count);
+    return OK;
+}
+
+/*
+ * parse a lambda: {arg, arg -> expr}
+ * "*arg" points to the '{'.
+ */
+    static int
+compile_lambda(char_u **arg, cctx_T *cctx)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    typval_T	rettv;
+    ufunc_T	*ufunc;
+
+    // Get the funcref in "rettv".
+    if (get_lambda_tv(arg, &rettv, TRUE) == FAIL)
+	return FAIL;
+    ufunc = rettv.vval.v_partial->pt_func;
+
+    // The function will have one line: "return {expr}".
+    // Compile it into instructions.
+    compile_def_function(ufunc, TRUE);
+
+    if (ufunc->uf_dfunc_idx >= 0)
+    {
+	if (ga_grow(instr, 1) == FAIL)
+	    return FAIL;
+	generate_FUNCREF(cctx, ufunc->uf_dfunc_idx);
+	return OK;
+    }
+    return FAIL;
+}
+
+/*
+ * Compile a lamda call: expr->{lambda}(args)
+ * "arg" points to the "{".
+ */
+    static int
+compile_lambda_call(char_u **arg, cctx_T *cctx)
+{
+    ufunc_T	*ufunc;
+    typval_T	rettv;
+    int		argcount = 1;
+    int		ret = FAIL;
+
+    // Get the funcref in "rettv".
+    if (get_lambda_tv(arg, &rettv, TRUE) == FAIL)
+	return FAIL;
+
+    if (**arg != '(')
+    {
+	if (*skipwhite(*arg) == '(')
+	    semsg(_(e_nowhitespace));
+	else
+	    semsg(_(e_missing_paren), "lambda");
+	clear_tv(&rettv);
+	return FAIL;
+    }
+
+    // The function will have one line: "return {expr}".
+    // Compile it into instructions.
+    ufunc = rettv.vval.v_partial->pt_func;
+    ++ufunc->uf_refcount;
+    compile_def_function(ufunc, TRUE);
+
+    // compile the arguments
+    *arg = skipwhite(*arg + 1);
+    if (compile_arguments(arg, cctx, &argcount) == OK)
+	// call the compiled function
+	ret = generate_CALL(cctx, ufunc, argcount);
+
+    clear_tv(&rettv);
+    return ret;
+}
+
+/*
+ * parse a dict: {'key': val} or #{key: val}
+ * "*arg" points to the '{'.
+ */
+    static int
+compile_dict(char_u **arg, cctx_T *cctx, int literal)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    int		count = 0;
+    dict_T	*d = dict_alloc();
+    dictitem_T	*item;
+
+    if (d == NULL)
+	return FAIL;
+    *arg = skipwhite(*arg + 1);
+    while (**arg != '}' && **arg != NUL)
+    {
+	char_u *key = NULL;
+
+	if (literal)
+	{
+	    char_u *p = to_name_end(*arg);
+
+	    if (p == *arg)
+	    {
+		semsg(_("E1014: Invalid key: %s"), *arg);
+		return FAIL;
+	    }
+	    key = vim_strnsave(*arg, p - *arg);
+	    if (generate_PUSHS(cctx, key) == FAIL)
+		return FAIL;
+	    *arg = p;
+	}
+	else
+	{
+	    isn_T		*isn;
+
+	    if (compile_expr1(arg, cctx) == FAIL)
+		return FAIL;
+	    // TODO: check type is string
+	    isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
+	    if (isn->isn_type == ISN_PUSHS)
+		key = isn->isn_arg.string;
+	}
+
+	// Check for duplicate keys, if using string keys.
+	if (key != NULL)
+	{
+	    item = dict_find(d, key, -1);
+	    if (item != NULL)
+	    {
+		semsg(_(e_duplicate_key), key);
+		goto failret;
+	    }
+	    item = dictitem_alloc(key);
+	    if (item != NULL)
+	    {
+		item->di_tv.v_type = VAR_UNKNOWN;
+		item->di_tv.v_lock = 0;
+		if (dict_add(d, item) == FAIL)
+		    dictitem_free(item);
+	    }
+	}
+
+	*arg = skipwhite(*arg);
+	if (**arg != ':')
+	{
+	    semsg(_(e_missing_dict_colon), *arg);
+	    return FAIL;
+	}
+
+	*arg = skipwhite(*arg + 1);
+	if (compile_expr1(arg, cctx) == FAIL)
+	    return FAIL;
+	++count;
+
+	if (**arg == '}')
+	    break;
+	if (**arg != ',')
+	{
+	    semsg(_(e_missing_dict_comma), *arg);
+	    goto failret;
+	}
+	*arg = skipwhite(*arg + 1);
+    }
+
+    if (**arg != '}')
+    {
+	semsg(_(e_missing_dict_end), *arg);
+	goto failret;
+    }
+    *arg = *arg + 1;
+
+    dict_unref(d);
+    return generate_NEWDICT(cctx, count);
+
+failret:
+    dict_unref(d);
+    return FAIL;
+}
+
+/*
+ * Compile "&option".
+ */
+    static int
+compile_get_option(char_u **arg, cctx_T *cctx)
+{
+    typval_T	rettv;
+    char_u	*start = *arg;
+    int		ret;
+
+    // parse the option and get the current value to get the type.
+    rettv.v_type = VAR_UNKNOWN;
+    ret = get_option_tv(arg, &rettv, TRUE);
+    if (ret == OK)
+    {
+	// include the '&' in the name, get_option_tv() expects it.
+	char_u *name = vim_strnsave(start, *arg - start);
+	type_T	*type = rettv.v_type == VAR_NUMBER ? &t_number : &t_string;
+
+	ret = generate_LOAD(cctx, ISN_LOADOPT, 0, name, type);
+	vim_free(name);
+    }
+    clear_tv(&rettv);
+
+    return ret;
+}
+
+/*
+ * Compile "$VAR".
+ */
+    static int
+compile_get_env(char_u **arg, cctx_T *cctx)
+{
+    char_u	*start = *arg;
+    int		len;
+    int		ret;
+    char_u	*name;
+
+    start = *arg;
+    ++*arg;
+    len = get_env_len(arg);
+    if (len == 0)
+    {
+	semsg(_(e_syntax_at), start - 1);
+	return FAIL;
+    }
+
+    // include the '$' in the name, get_env_tv() expects it.
+    name = vim_strnsave(start, len + 1);
+    ret = generate_LOAD(cctx, ISN_LOADENV, 0, name, &t_string);
+    vim_free(name);
+    return ret;
+}
+
+/*
+ * Compile "@r".
+ */
+    static int
+compile_get_register(char_u **arg, cctx_T *cctx)
+{
+    int		ret;
+
+    ++*arg;
+    if (**arg == NUL)
+    {
+	semsg(_(e_syntax_at), *arg - 1);
+	return FAIL;
+    }
+    if (!valid_yank_reg(**arg, TRUE))
+    {
+	emsg_invreg(**arg);
+	return FAIL;
+    }
+    ret = generate_LOAD(cctx, ISN_LOADREG, **arg, NULL, &t_string);
+    ++*arg;
+    return ret;
+}
+
+/*
+ * Apply leading '!', '-' and '+' to constant "rettv".
+ */
+    static int
+apply_leader(typval_T *rettv, char_u *start, char_u *end)
+{
+    char_u *p = end;
+
+    // this works from end to start
+    while (p > start)
+    {
+	--p;
+	if (*p == '-' || *p == '+')
+	{
+	    // only '-' has an effect, for '+' we only check the type
+#ifdef FEAT_FLOAT
+	    if (rettv->v_type == VAR_FLOAT)
+	    {
+		if (*p == '-')
+		    rettv->vval.v_float = -rettv->vval.v_float;
+	    }
+	    else
+#endif
+	    {
+		varnumber_T	val;
+		int		error = FALSE;
+
+		// tv_get_number_chk() accepts a string, but we don't want that
+		// here
+		if (check_not_string(rettv) == FAIL)
+		    return FAIL;
+		val = tv_get_number_chk(rettv, &error);
+		clear_tv(rettv);
+		if (error)
+		    return FAIL;
+		if (*p == '-')
+		    val = -val;
+		rettv->v_type = VAR_NUMBER;
+		rettv->vval.v_number = val;
+	    }
+	}
+	else
+	{
+	    int v = tv2bool(rettv);
+
+	    // '!' is permissive in the type.
+	    clear_tv(rettv);
+	    rettv->v_type = VAR_BOOL;
+	    rettv->vval.v_number = v ? VVAL_FALSE : VVAL_TRUE;
+	}
+    }
+    return OK;
+}
+
+/*
+ * Recognize v: variables that are constants and set "rettv".
+ */
+    static void
+get_vim_constant(char_u **arg, typval_T *rettv)
+{
+    if (STRNCMP(*arg, "v:true", 6) == 0)
+    {
+	rettv->v_type = VAR_BOOL;
+	rettv->vval.v_number = VVAL_TRUE;
+	*arg += 6;
+    }
+    else if (STRNCMP(*arg, "v:false", 7) == 0)
+    {
+	rettv->v_type = VAR_BOOL;
+	rettv->vval.v_number = VVAL_FALSE;
+	*arg += 7;
+    }
+    else if (STRNCMP(*arg, "v:null", 6) == 0)
+    {
+	rettv->v_type = VAR_SPECIAL;
+	rettv->vval.v_number = VVAL_NULL;
+	*arg += 6;
+    }
+    else if (STRNCMP(*arg, "v:none", 6) == 0)
+    {
+	rettv->v_type = VAR_SPECIAL;
+	rettv->vval.v_number = VVAL_NONE;
+	*arg += 6;
+    }
+}
+
+/*
+ * Compile code to apply '-', '+' and '!'.
+ */
+    static int
+compile_leader(cctx_T *cctx, char_u *start, char_u *end)
+{
+    char_u	*p = end;
+
+    // this works from end to start
+    while (p > start)
+    {
+	--p;
+	if (*p == '-' || *p == '+')
+	{
+	    int	    negate = *p == '-';
+	    isn_T   *isn;
+
+	    // TODO: check type
+	    while (p > start && (p[-1] == '-' || p[-1] == '+'))
+	    {
+		--p;
+		if (*p == '-')
+		    negate = !negate;
+	    }
+	    // only '-' has an effect, for '+' we only check the type
+	    if (negate)
+		isn = generate_instr(cctx, ISN_NEGATENR);
+	    else
+		isn = generate_instr(cctx, ISN_CHECKNR);
+	    if (isn == NULL)
+		return FAIL;
+	}
+	else
+	{
+	    int  invert = TRUE;
+
+	    while (p > start && p[-1] == '!')
+	    {
+		--p;
+		invert = !invert;
+	    }
+	    if (generate_2BOOL(cctx, invert) == FAIL)
+		return FAIL;
+	}
+    }
+    return OK;
+}
+
+/*
+ * Compile whatever comes after "name" or "name()".
+ */
+    static int
+compile_subscript(
+	char_u **arg,
+	cctx_T *cctx,
+	char_u **start_leader,
+	char_u *end_leader)
+{
+    for (;;)
+    {
+	if (**arg == '(')
+	{
+	    int	    argcount = 0;
+
+	    // funcref(arg)
+	    *arg = skipwhite(*arg + 1);
+	    if (compile_arguments(arg, cctx, &argcount) == FAIL)
+		return FAIL;
+	    if (generate_PCALL(cctx, argcount, TRUE) == FAIL)
+		return FAIL;
+	}
+	else if (**arg == '-' && (*arg)[1] == '>')
+	{
+	    char_u *p;
+
+	    // something->method()
+	    // Apply the '!', '-' and '+' first:
+	    //   -1.0->func() works like (-1.0)->func()
+	    if (compile_leader(cctx, *start_leader, end_leader) == FAIL)
+		return FAIL;
+	    *start_leader = end_leader;   // don't apply again later
+
+	    *arg = skipwhite(*arg + 2);
+	    if (**arg == '{')
+	    {
+		// lambda call:  list->{lambda}
+		if (compile_lambda_call(arg, cctx) == FAIL)
+		    return FAIL;
+	    }
+	    else
+	    {
+		// method call:  list->method()
+		for (p = *arg; eval_isnamec1(*p); ++p)
+		    ;
+		if (*p != '(')
+		{
+		    semsg(_(e_missing_paren), arg);
+		    return FAIL;
+		}
+		// TODO: base value may not be the first argument
+		if (compile_call(arg, p - *arg, cctx, 1) == FAIL)
+		    return FAIL;
+	    }
+	}
+	else if (**arg == '[')
+	{
+	    // list index: list[123]
+	    // TODO: more arguments
+	    // TODO: dict member  dict['name']
+	    *arg = skipwhite(*arg + 1);
+	    if (compile_expr1(arg, cctx) == FAIL)
+		return FAIL;
+
+	    if (**arg != ']')
+	    {
+		emsg(_(e_missbrac));
+		return FAIL;
+	    }
+	    *arg = skipwhite(*arg + 1);
+
+	    if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL)
+		return FAIL;
+	}
+	else if (**arg == '.' && (*arg)[1] != '.')
+	{
+	    char_u *p;
+
+	    ++*arg;
+	    p = *arg;
+	    // dictionary member: dict.name
+	    if (eval_isnamec1(*p))
+		while (eval_isnamec(*p))
+		    MB_PTR_ADV(p);
+	    if (p == *arg)
+	    {
+		semsg(_(e_syntax_at), *arg);
+		return FAIL;
+	    }
+	    // TODO: check type is dict
+	    if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL)
+		return FAIL;
+	    *arg = p;
+	}
+	else
+	    break;
+    }
+
+    // TODO - see handle_subscript():
+    // Turn "dict.Func" into a partial for "Func" bound to "dict".
+    // Don't do this when "Func" is already a partial that was bound
+    // explicitly (pt_auto is FALSE).
+
+    return OK;
+}
+
+/*
+ * Compile an expression at "*p" and add instructions to "instr".
+ * "p" is advanced until after the expression, skipping white space.
+ *
+ * This is the equivalent of eval1(), eval2(), etc.
+ */
+
+/*
+ *  number		number constant
+ *  0zFFFFFFFF		Blob constant
+ *  "string"		string constant
+ *  'string'		literal string constant
+ *  &option-name	option value
+ *  @r			register contents
+ *  identifier		variable value
+ *  function()		function call
+ *  $VAR		environment variable
+ *  (expression)	nested expression
+ *  [expr, expr]	List
+ *  {key: val, key: val}   Dictionary
+ *  #{key: val, key: val}  Dictionary with literal keys
+ *
+ *  Also handle:
+ *  ! in front		logical NOT
+ *  - in front		unary minus
+ *  + in front		unary plus (ignored)
+ *  trailing (arg)	funcref/partial call
+ *  trailing []		subscript in String or List
+ *  trailing .name	entry in Dictionary
+ *  trailing ->name()	method call
+ */
+    static int
+compile_expr7(char_u **arg, cctx_T *cctx)
+{
+    typval_T	rettv;
+    char_u	*start_leader, *end_leader;
+    int		ret = OK;
+
+    /*
+     * Skip '!', '-' and '+' characters.  They are handled later.
+     */
+    start_leader = *arg;
+    while (**arg == '!' || **arg == '-' || **arg == '+')
+	*arg = skipwhite(*arg + 1);
+    end_leader = *arg;
+
+    rettv.v_type = VAR_UNKNOWN;
+    switch (**arg)
+    {
+	/*
+	 * Number constant.
+	 */
+	case '0':	// also for blob starting with 0z
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	case '.':   if (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL)
+			return FAIL;
+		    break;
+
+	/*
+	 * String constant: "string".
+	 */
+	case '"':   if (get_string_tv(arg, &rettv, TRUE) == FAIL)
+			return FAIL;
+		    break;
+
+	/*
+	 * Literal string constant: 'str''ing'.
+	 */
+	case '\'':  if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL)
+			return FAIL;
+		    break;
+
+	/*
+	 * Constant Vim variable.
+	 */
+	case 'v':   get_vim_constant(arg, &rettv);
+		    ret = NOTDONE;
+		    break;
+
+	/*
+	 * List: [expr, expr]
+	 */
+	case '[':   ret = compile_list(arg, cctx);
+		    break;
+
+	/*
+	 * Dictionary: #{key: val, key: val}
+	 */
+	case '#':   if ((*arg)[1] == '{')
+		    {
+			++*arg;
+			ret = compile_dict(arg, cctx, TRUE);
+		    }
+		    else
+			ret = NOTDONE;
+		    break;
+
+	/*
+	 * Lambda: {arg, arg -> expr}
+	 * Dictionary: {'key': val, 'key': val}
+	 */
+	case '{':   {
+			char_u *start = skipwhite(*arg + 1);
+
+			// Find out what comes after the arguments.
+			ret = get_function_args(&start, '-', NULL,
+						       NULL, NULL, NULL, TRUE);
+			if (ret != FAIL && *start == '>')
+			    ret = compile_lambda(arg, cctx);
+			else
+			    ret = compile_dict(arg, cctx, FALSE);
+		    }
+		    break;
+
+	/*
+	 * Option value: &name
+	 */
+	case '&':	ret = compile_get_option(arg, cctx);
+			break;
+
+	/*
+	 * Environment variable: $VAR.
+	 */
+	case '$':	ret = compile_get_env(arg, cctx);
+			break;
+
+	/*
+	 * Register contents: @r.
+	 */
+	case '@':	ret = compile_get_register(arg, cctx);
+			break;
+	/*
+	 * nested expression: (expression).
+	 */
+	case '(':   *arg = skipwhite(*arg + 1);
+		    ret = compile_expr1(arg, cctx);	// recursive!
+		    *arg = skipwhite(*arg);
+		    if (**arg == ')')
+			++*arg;
+		    else if (ret == OK)
+		    {
+			emsg(_(e_missing_close));
+			ret = FAIL;
+		    }
+		    break;
+
+	default:    ret = NOTDONE;
+		    break;
+    }
+    if (ret == FAIL)
+	return FAIL;
+
+    if (rettv.v_type != VAR_UNKNOWN)
+    {
+	// apply the '!', '-' and '+' before the constant
+	if (apply_leader(&rettv, start_leader, end_leader) == FAIL)
+	{
+	    clear_tv(&rettv);
+	    return FAIL;
+	}
+	start_leader = end_leader;   // don't apply again below
+
+	// push constant
+	switch (rettv.v_type)
+	{
+	    case VAR_BOOL:
+		generate_PUSHBOOL(cctx, rettv.vval.v_number);
+		break;
+	    case VAR_SPECIAL:
+		generate_PUSHSPEC(cctx, rettv.vval.v_number);
+		break;
+	    case VAR_NUMBER:
+		generate_PUSHNR(cctx, rettv.vval.v_number);
+		break;
+#ifdef FEAT_FLOAT
+	    case VAR_FLOAT:
+		generate_PUSHF(cctx, rettv.vval.v_float);
+		break;
+#endif
+	    case VAR_BLOB:
+		generate_PUSHBLOB(cctx, rettv.vval.v_blob);
+		rettv.vval.v_blob = NULL;
+		break;
+	    case VAR_STRING:
+		generate_PUSHS(cctx, rettv.vval.v_string);
+		rettv.vval.v_string = NULL;
+		break;
+	    default:
+		iemsg("constant type missing");
+		return FAIL;
+	}
+    }
+    else if (ret == NOTDONE)
+    {
+	char_u	    *p;
+	int	    r;
+
+	if (!eval_isnamec1(**arg))
+	{
+	    semsg(_("E1015: Name expected: %s"), *arg);
+	    return FAIL;
+	}
+
+	// "name" or "name()"
+	p = to_name_end(*arg);
+	if (*p == '(')
+	    r = compile_call(arg, p - *arg, cctx, 0);
+	else
+	    r = compile_load(arg, p, cctx, TRUE);
+	if (r == FAIL)
+	    return FAIL;
+    }
+
+    if (compile_subscript(arg, cctx, &start_leader, end_leader) == FAIL)
+	return FAIL;
+
+    // Now deal with prefixed '-', '+' and '!', if not done already.
+    return compile_leader(cctx, start_leader, end_leader);
+}
+
+/*
+ *	*	number multiplication
+ *	/	number division
+ *	%	number modulo
+ */
+    static int
+compile_expr6(char_u **arg, cctx_T *cctx)
+{
+    char_u	*op;
+
+    // get the first variable
+    if (compile_expr7(arg, cctx) == FAIL)
+	return FAIL;
+
+    /*
+     * Repeat computing, until no "*", "/" or "%" is following.
+     */
+    for (;;)
+    {
+	op = skipwhite(*arg);
+	if (*op != '*' && *op != '/' && *op != '%')
+	    break;
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[1]))
+	{
+	    char_u buf[3];
+
+	    vim_strncpy(buf, op, 1);
+	    semsg(_(e_white_both), buf);
+	}
+	*arg = skipwhite(op + 1);
+
+	// get the second variable
+	if (compile_expr7(arg, cctx) == FAIL)
+	    return FAIL;
+
+	generate_two_op(cctx, op);
+    }
+
+    return OK;
+}
+
+/*
+ *      +	number addition
+ *      -	number subtraction
+ *      ..	string concatenation
+ */
+    static int
+compile_expr5(char_u **arg, cctx_T *cctx)
+{
+    char_u	*op;
+    int		oplen;
+
+    // get the first variable
+    if (compile_expr6(arg, cctx) == FAIL)
+	return FAIL;
+
+    /*
+     * Repeat computing, until no "+", "-" or ".." is following.
+     */
+    for (;;)
+    {
+	op = skipwhite(*arg);
+	if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.')))
+	    break;
+	oplen = (*op == '.' ? 2 : 1);
+
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[oplen]))
+	{
+	    char_u buf[3];
+
+	    vim_strncpy(buf, op, oplen);
+	    semsg(_(e_white_both), buf);
+	}
+
+	*arg = skipwhite(op + oplen);
+
+	// get the second variable
+	if (compile_expr6(arg, cctx) == FAIL)
+	    return FAIL;
+
+	if (*op == '.')
+	{
+	    if (may_generate_2STRING(-2, cctx) == FAIL
+		    || may_generate_2STRING(-1, cctx) == FAIL)
+		return FAIL;
+	    generate_instr_drop(cctx, ISN_CONCAT, 1);
+	}
+	else
+	    generate_two_op(cctx, op);
+    }
+
+    return OK;
+}
+
+/*
+ * expr5a == expr5b
+ * expr5a =~ expr5b
+ * expr5a != expr5b
+ * expr5a !~ expr5b
+ * expr5a > expr5b
+ * expr5a >= expr5b
+ * expr5a < expr5b
+ * expr5a <= expr5b
+ * expr5a is expr5b
+ * expr5a isnot expr5b
+ *
+ * Produces instructions:
+ *	EVAL expr5a		Push result of "expr5a"
+ *	EVAL expr5b		Push result of "expr5b"
+ *	COMPARE			one of the compare instructions
+ */
+    static int
+compile_expr4(char_u **arg, cctx_T *cctx)
+{
+    exptype_T	type = EXPR_UNKNOWN;
+    char_u	*p;
+    int		len = 2;
+    int		i;
+    int		type_is = FALSE;
+
+    // get the first variable
+    if (compile_expr5(arg, cctx) == FAIL)
+	return FAIL;
+
+    p = skipwhite(*arg);
+    switch (p[0])
+    {
+	case '=':   if (p[1] == '=')
+			type = EXPR_EQUAL;
+		    else if (p[1] == '~')
+			type = EXPR_MATCH;
+		    break;
+	case '!':   if (p[1] == '=')
+			type = EXPR_NEQUAL;
+		    else if (p[1] == '~')
+			type = EXPR_NOMATCH;
+		    break;
+	case '>':   if (p[1] != '=')
+		    {
+			type = EXPR_GREATER;
+			len = 1;
+		    }
+		    else
+			type = EXPR_GEQUAL;
+		    break;
+	case '<':   if (p[1] != '=')
+		    {
+			type = EXPR_SMALLER;
+			len = 1;
+		    }
+		    else
+			type = EXPR_SEQUAL;
+		    break;
+	case 'i':   if (p[1] == 's')
+		    {
+			// "is" and "isnot"; but not a prefix of a name
+			if (p[2] == 'n' && p[3] == 'o' && p[4] == 't')
+			    len = 5;
+			i = p[len];
+			if (!isalnum(i) && i != '_')
+			{
+			    type = len == 2 ? EXPR_IS : EXPR_ISNOT;
+			    type_is = TRUE;
+			}
+		    }
+		    break;
+    }
+
+    /*
+     * If there is a comparative operator, use it.
+     */
+    if (type != EXPR_UNKNOWN)
+    {
+	int ic = FALSE;  // Default: do not ignore case
+
+	if (type_is && (p[len] == '?' || p[len] == '#'))
+	{
+	    semsg(_(e_invexpr2), *arg);
+	    return FAIL;
+	}
+	// extra question mark appended: ignore case
+	if (p[len] == '?')
+	{
+	    ic = TRUE;
+	    ++len;
+	}
+	// extra '#' appended: match case (ignored)
+	else if (p[len] == '#')
+	    ++len;
+	// nothing appended: match case
+
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[len]))
+	{
+	    char_u buf[7];
+
+	    vim_strncpy(buf, p, len);
+	    semsg(_(e_white_both), buf);
+	}
+
+	// get the second variable
+	*arg = skipwhite(p + len);
+	if (compile_expr5(arg, cctx) == FAIL)
+	    return FAIL;
+
+	generate_COMPARE(cctx, type, ic);
+    }
+
+    return OK;
+}
+
+/*
+ * Compile || or &&.
+ */
+    static int
+compile_and_or(char_u **arg, cctx_T *cctx, char *op)
+{
+    char_u	*p = skipwhite(*arg);
+    int		opchar = *op;
+
+    if (p[0] == opchar && p[1] == opchar)
+    {
+	garray_T	*instr = &cctx->ctx_instr;
+	garray_T	end_ga;
+
+	/*
+	 * Repeat until there is no following "||" or "&&"
+	 */
+	ga_init2(&end_ga, sizeof(int), 10);
+	while (p[0] == opchar && p[1] == opchar)
+	{
+	    if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2]))
+		semsg(_(e_white_both), op);
+
+	    if (ga_grow(&end_ga, 1) == FAIL)
+	    {
+		ga_clear(&end_ga);
+		return FAIL;
+	    }
+	    *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len;
+	    ++end_ga.ga_len;
+	    generate_JUMP(cctx, opchar == '|'
+			 ?  JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0);
+
+	    // eval the next expression
+	    *arg = skipwhite(p + 2);
+	    if ((opchar == '|' ? compile_expr3(arg, cctx)
+					   : compile_expr4(arg, cctx)) == FAIL)
+	    {
+		ga_clear(&end_ga);
+		return FAIL;
+	    }
+	    p = skipwhite(*arg);
+	}
+
+	// Fill in the end label in all jumps.
+	while (end_ga.ga_len > 0)
+	{
+	    isn_T	*isn;
+
+	    --end_ga.ga_len;
+	    isn = ((isn_T *)instr->ga_data)
+				  + *(((int *)end_ga.ga_data) + end_ga.ga_len);
+	    isn->isn_arg.jump.jump_where = instr->ga_len;
+	}
+	ga_clear(&end_ga);
+    }
+
+    return OK;
+}
+
+/*
+ * expr4a && expr4a && expr4a	    logical AND
+ *
+ * Produces instructions:
+ *	EVAL expr4a		Push result of "expr4a"
+ *	JUMP_AND_KEEP_IF_FALSE end
+ *	EVAL expr4b		Push result of "expr4b"
+ *	JUMP_AND_KEEP_IF_FALSE end
+ *	EVAL expr4c		Push result of "expr4c"
+ * end:
+ */
+    static int
+compile_expr3(char_u **arg, cctx_T *cctx)
+{
+    // get the first variable
+    if (compile_expr4(arg, cctx) == FAIL)
+	return FAIL;
+
+    // || and && work almost the same
+    return compile_and_or(arg, cctx, "&&");
+}
+
+/*
+ * expr3a || expr3b || expr3c	    logical OR
+ *
+ * Produces instructions:
+ *	EVAL expr3a		Push result of "expr3a"
+ *	JUMP_AND_KEEP_IF_TRUE end
+ *	EVAL expr3b		Push result of "expr3b"
+ *	JUMP_AND_KEEP_IF_TRUE end
+ *	EVAL expr3c		Push result of "expr3c"
+ * end:
+ */
+    static int
+compile_expr2(char_u **arg, cctx_T *cctx)
+{
+    // eval the first expression
+    if (compile_expr3(arg, cctx) == FAIL)
+	return FAIL;
+
+    // || and && work almost the same
+    return compile_and_or(arg, cctx, "||");
+}
+
+/*
+ * Toplevel expression: expr2 ? expr1a : expr1b
+ *
+ * Produces instructions:
+ *	EVAL expr2		Push result of "expr"
+ *      JUMP_IF_FALSE alt	jump if false
+ *      EVAL expr1a
+ *      JUMP_ALWAYS end
+ * alt:	EVAL expr1b
+ * end:
+ */
+    static int
+compile_expr1(char_u **arg,  cctx_T *cctx)
+{
+    char_u	*p;
+
+    // evaluate the first expression
+    if (compile_expr2(arg, cctx) == FAIL)
+	return FAIL;
+
+    p = skipwhite(*arg);
+    if (*p == '?')
+    {
+	garray_T	*instr = &cctx->ctx_instr;
+	garray_T	*stack = &cctx->ctx_type_stack;
+	int		alt_idx = instr->ga_len;
+	int		end_idx;
+	isn_T		*isn;
+	type_T		*type1;
+	type_T		*type2;
+
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+	    semsg(_(e_white_both), "?");
+
+	generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+
+	// evaluate the second expression; any type is accepted
+	*arg = skipwhite(p + 1);
+	compile_expr1(arg, cctx);
+
+	// remember the type and drop it
+	--stack->ga_len;
+	type1 = ((type_T **)stack->ga_data)[stack->ga_len];
+
+	end_idx = instr->ga_len;
+	generate_JUMP(cctx, JUMP_ALWAYS, 0);
+
+	// jump here from JUMP_IF_FALSE
+	isn = ((isn_T *)instr->ga_data) + alt_idx;
+	isn->isn_arg.jump.jump_where = instr->ga_len;
+
+	// Check for the ":".
+	p = skipwhite(*arg);
+	if (*p != ':')
+	{
+	    emsg(_(e_missing_colon));
+	    return FAIL;
+	}
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+	    semsg(_(e_white_both), ":");
+
+	// evaluate the third expression
+	*arg = skipwhite(p + 1);
+	compile_expr1(arg, cctx);
+
+	// If the types differ, the result has a more generic type.
+	type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+	common_type(type1, type2, type2);
+
+	// jump here from JUMP_ALWAYS
+	isn = ((isn_T *)instr->ga_data) + end_idx;
+	isn->isn_arg.jump.jump_where = instr->ga_len;
+    }
+    return OK;
+}
+
+/*
+ * compile "return [expr]"
+ */
+    static char_u *
+compile_return(char_u *arg, int set_return_type, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    type_T	*stack_type;
+
+    if (*p != NUL && *p != '|' && *p != '\n')
+    {
+	// compile return argument into instructions
+	if (compile_expr1(&p, cctx) == FAIL)
+	    return NULL;
+
+	stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+	if (set_return_type)
+	    cctx->ctx_ufunc->uf_ret_type = stack_type;
+	else if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, cctx)
+								       == FAIL)
+	    return NULL;
+    }
+    else
+    {
+	if (set_return_type)
+	    cctx->ctx_ufunc->uf_ret_type = &t_void;
+	else if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID)
+	{
+	    emsg(_("E1003: Missing return value"));
+	    return NULL;
+	}
+
+	// No argument, return zero.
+	generate_PUSHNR(cctx, 0);
+    }
+
+    if (generate_instr(cctx, ISN_RETURN) == NULL)
+	return NULL;
+
+    // "return val | endif" is possible
+    return skipwhite(p);
+}
+
+/*
+ * Return the length of an assignment operator, or zero if there isn't one.
+ */
+    int
+assignment_len(char_u *p, int *heredoc)
+{
+    if (*p == '=')
+    {
+	if (p[1] == '<' && p[2] == '<')
+	{
+	    *heredoc = TRUE;
+	    return 3;
+	}
+	return 1;
+    }
+    if (vim_strchr((char_u *)"+-*/%", *p) != NULL && p[1] == '=')
+	return 2;
+    if (STRNCMP(p, "..=", 3) == 0)
+	return 3;
+    return 0;
+}
+
+// words that cannot be used as a variable
+static char *reserved[] = {
+    "true",
+    "false",
+    NULL
+};
+
+/*
+ * Get a line for "=<<".
+ * Return a pointer to the line in allocated memory.
+ * Return NULL for end-of-file or some error.
+ */
+    static char_u *
+heredoc_getline(
+	int c UNUSED,
+	void *cookie,
+	int indent UNUSED,
+	int do_concat UNUSED)
+{
+    cctx_T  *cctx = (cctx_T *)cookie;
+
+    if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
+	NULL;
+    ++cctx->ctx_lnum;
+    return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
+							     [cctx->ctx_lnum]);
+}
+
+/*
+ * compile "let var [= expr]", "const var = expr" and "var = expr"
+ * "arg" points to "var".
+ */
+    static char_u *
+compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
+{
+    char_u	*p;
+    char_u	*ret = NULL;
+    int		var_count = 0;
+    int		semicolon = 0;
+    size_t	varlen;
+    garray_T	*instr = &cctx->ctx_instr;
+    int		idx = -1;
+    char_u	*op;
+    int		option = FALSE;
+    int		opt_type;
+    int		opt_flags = 0;
+    int		global = FALSE;
+    int		script = FALSE;
+    int		oplen = 0;
+    int		heredoc = FALSE;
+    type_T	*type;
+    lvar_T	*lvar;
+    char_u	*name;
+    char_u	*sp;
+    int		has_type = FALSE;
+    int		is_decl = cmdidx == CMD_let || cmdidx == CMD_const;
+    int		instr_count = -1;
+
+    p = skip_var_list(arg, FALSE, &var_count, &semicolon);
+    if (p == NULL)
+	return NULL;
+    if (var_count > 0)
+    {
+	// TODO: let [var, var] = list
+	emsg("Cannot handle a list yet");
+	return NULL;
+    }
+
+    varlen = p - arg;
+    name = vim_strnsave(arg, (int)varlen);
+    if (name == NULL)
+	return NULL;
+
+    if (*arg == '&')
+    {
+	int	    cc;
+	long	    numval;
+	char_u	    *stringval = NULL;
+
+	option = TRUE;
+	if (cmdidx == CMD_const)
+	{
+	    emsg(_(e_const_option));
+	    return NULL;
+	}
+	if (is_decl)
+	{
+	    semsg(_("E1052: Cannot declare an option: %s"), arg);
+	    goto theend;
+	}
+	p = arg;
+	p = find_option_end(&p, &opt_flags);
+	if (p == NULL)
+	{
+	    emsg(_(e_letunexp));
+	    return NULL;
+	}
+	cc = *p;
+	*p = NUL;
+	opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags);
+	*p = cc;
+	if (opt_type == -3)
+	{
+	    semsg(_(e_unknown_option), *arg);
+	    return NULL;
+	}
+	if (opt_type == -2 || opt_type == 0)
+	    type = &t_string;
+	else
+	    type = &t_number;	// both number and boolean option
+    }
+    else if (STRNCMP(arg, "g:", 2) == 0)
+    {
+	global = TRUE;
+	if (is_decl)
+	{
+	    semsg(_("E1016: Cannot declare a global variable: %s"), name);
+	    goto theend;
+	}
+    }
+    else
+    {
+	for (idx = 0; reserved[idx] != NULL; ++idx)
+	    if (STRCMP(reserved[idx], name) == 0)
+	    {
+		semsg(_("E1034: Cannot use reserved name %s"), name);
+		goto theend;
+	    }
+
+	idx = lookup_local(arg, varlen, cctx);
+	if (idx >= 0)
+	{
+	    if (is_decl)
+	    {
+		semsg(_("E1017: Variable already declared: %s"), name);
+		goto theend;
+	    }
+	    else
+	    {
+		lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+		if (lvar->lv_const)
+		{
+		    semsg(_("E1018: Cannot assign to a constant: %s"), name);
+		    goto theend;
+		}
+	    }
+	}
+	else if (lookup_script(arg, varlen) == OK)
+	{
+	    script = TRUE;
+	    if (is_decl)
+	    {
+		semsg(_("E1054: Variable already declared in the script: %s"),
+									 name);
+		goto theend;
+	    }
+	}
+    }
+
+    if (!option)
+    {
+	if (is_decl && *p == ':')
+	{
+	    // parse optional type: "let var: type = expr"
+	    p = skipwhite(p + 1);
+	    type = parse_type(&p, cctx->ctx_type_list);
+	    if (type == NULL)
+		goto theend;
+	    has_type = TRUE;
+	}
+	else if (idx < 0)
+	{
+	    // global and new local default to "any" type
+	    type = &t_any;
+	}
+	else
+	{
+	    lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+	    type = lvar->lv_type;
+	}
+    }
+
+    sp = p;
+    p = skipwhite(p);
+    op = p;
+    oplen = assignment_len(p, &heredoc);
+    if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen])))
+    {
+	char_u  buf[4];
+
+	vim_strncpy(buf, op, oplen);
+	semsg(_(e_white_both), buf);
+    }
+
+    if (oplen == 3 && !heredoc && !global && type->tt_type != VAR_STRING
+					       && type->tt_type != VAR_UNKNOWN)
+    {
+	emsg("E1019: Can only concatenate to string");
+	goto theend;
+    }
+
+    // +=, /=, etc. require an existing variable
+    if (idx < 0 && !global && !option)
+    {
+	if (oplen > 1 && !heredoc)
+	{
+	    semsg(_("E1020: cannot use an operator on a new variable: %s"),
+									 name);
+	    goto theend;
+	}
+
+	// new local variable
+	idx = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type);
+	if (idx < 0)
+	    goto theend;
+    }
+
+    if (heredoc)
+    {
+	list_T	   *l;
+	listitem_T *li;
+
+	// [let] varname =<< [trim] {end}
+	eap->getline = heredoc_getline;
+	eap->cookie = cctx;
+	l = heredoc_get(eap, op + 3);
+
+	// Push each line and the create the list.
+	for (li = l->lv_first; li != NULL; li = li->li_next)
+	{
+	    generate_PUSHS(cctx, li->li_tv.vval.v_string);
+	    li->li_tv.vval.v_string = NULL;
+	}
+	generate_NEWLIST(cctx, l->lv_len);
+	type = &t_list_string;
+	list_free(l);
+	p += STRLEN(p);
+    }
+    else if (oplen > 0)
+    {
+	// for "+=", "*=", "..=" etc. first load the current value
+	if (*op != '=')
+	{
+	    if (option)
+		generate_LOAD(cctx, ISN_LOADOPT, 0, name + 1, type);
+	    else if (global)
+		generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type);
+	    else
+		generate_LOAD(cctx, ISN_LOAD, idx, NULL, type);
+	}
+
+	// compile the expression
+	instr_count = instr->ga_len;
+	p = skipwhite(p + oplen);
+	if (compile_expr1(&p, cctx) == FAIL)
+	    goto theend;
+
+	if (idx >= 0 && (is_decl || !has_type))
+	{
+	    garray_T	*stack = &cctx->ctx_type_stack;
+	    type_T	*stacktype =
+				((type_T **)stack->ga_data)[stack->ga_len - 1];
+
+	    lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+	    if (!has_type)
+	    {
+		if (stacktype->tt_type == VAR_VOID)
+		{
+		    emsg(_("E1031: Cannot use void value"));
+		    goto theend;
+		}
+		else
+		    lvar->lv_type = stacktype;
+	    }
+	    else
+		if (check_type(lvar->lv_type, stacktype, TRUE) == FAIL)
+		    goto theend;
+	}
+    }
+    else if (cmdidx == CMD_const)
+    {
+	emsg(_("E1021: const requires a value"));
+	goto theend;
+    }
+    else if (!has_type || option)
+    {
+	emsg(_("E1022: type or initialization required"));
+	goto theend;
+    }
+    else
+    {
+	// variables are always initialized
+	// TODO: support more types
+	if (ga_grow(instr, 1) == FAIL)
+	    goto theend;
+	if (type->tt_type == VAR_STRING)
+	    generate_PUSHS(cctx, vim_strsave((char_u *)""));
+	else
+	    generate_PUSHNR(cctx, 0);
+    }
+
+    if (oplen > 0 && *op != '=')
+    {
+	type_T	    *expected = &t_number;
+	garray_T    *stack = &cctx->ctx_type_stack;
+	type_T	    *stacktype;
+
+	// TODO: if type is known use float or any operation
+
+	if (*op == '.')
+	    expected = &t_string;
+	stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+	if (need_type(stacktype, expected, -1, cctx) == FAIL)
+	    goto theend;
+
+	if (*op == '.')
+	    generate_instr_drop(cctx, ISN_CONCAT, 1);
+	else
+	{
+	    isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1);
+
+	    if (isn == NULL)
+		goto theend;
+	    switch (*op)
+	    {
+		case '+': isn->isn_arg.op.op_type = EXPR_ADD; break;
+		case '-': isn->isn_arg.op.op_type = EXPR_SUB; break;
+		case '*': isn->isn_arg.op.op_type = EXPR_MULT; break;
+		case '/': isn->isn_arg.op.op_type = EXPR_DIV; break;
+		case '%': isn->isn_arg.op.op_type = EXPR_REM; break;
+	    }
+	}
+    }
+
+    if (option)
+	generate_STOREOPT(cctx, name + 1, opt_flags);
+    else if (global)
+	generate_STORE(cctx, ISN_STOREG, 0, name + 2);
+    else if (script)
+    {
+	idx = get_script_item_idx(current_sctx.sc_sid, name, TRUE);
+	// TODO: specific type
+	generate_SCRIPT(cctx, ISN_STORESCRIPT,
+					     current_sctx.sc_sid, idx, &t_any);
+    }
+    else
+    {
+	isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
+
+	// optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE into
+	// ISN_STORENR
+	if (instr->ga_len == instr_count + 1 && isn->isn_type == ISN_PUSHNR)
+	{
+	    varnumber_T val = isn->isn_arg.number;
+	    garray_T	*stack = &cctx->ctx_type_stack;
+
+	    isn->isn_type = ISN_STORENR;
+	    isn->isn_arg.storenr.str_idx = idx;
+	    isn->isn_arg.storenr.str_val = val;
+	    if (stack->ga_len > 0)
+		--stack->ga_len;
+	}
+	else
+	    generate_STORE(cctx, ISN_STORE, idx, NULL);
+    }
+    ret = p;
+
+theend:
+    vim_free(name);
+    return ret;
+}
+
+/*
+ * Compile an :import command.
+ */
+    static char_u *
+compile_import(char_u *arg, cctx_T *cctx)
+{
+    return handle_import(arg, &cctx->ctx_imports, 0);
+}
+
+/*
+ * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
+ */
+    static int
+compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    endlabel_T  *endlabel = ALLOC_CLEAR_ONE(endlabel_T);
+
+    if (endlabel == NULL)
+	return FAIL;
+    endlabel->el_next = *el;
+    *el = endlabel;
+    endlabel->el_end_label = instr->ga_len;
+
+    generate_JUMP(cctx, when, 0);
+    return OK;
+}
+
+    static void
+compile_fill_jump_to_end(endlabel_T **el, cctx_T *cctx)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+
+    while (*el != NULL)
+    {
+	endlabel_T  *cur = (*el);
+	isn_T	    *isn;
+
+	isn = ((isn_T *)instr->ga_data) + cur->el_end_label;
+	isn->isn_arg.jump.jump_where = instr->ga_len;
+	*el = cur->el_next;
+	vim_free(cur);
+    }
+}
+
+/*
+ * Create a new scope and set up the generic items.
+ */
+    static scope_T *
+new_scope(cctx_T *cctx, scopetype_T type)
+{
+    scope_T *scope = ALLOC_CLEAR_ONE(scope_T);
+
+    if (scope == NULL)
+	return NULL;
+    scope->se_outer = cctx->ctx_scope;
+    cctx->ctx_scope = scope;
+    scope->se_type = type;
+    scope->se_local_count = cctx->ctx_locals.ga_len;
+    return scope;
+}
+
+/*
+ * compile "if expr"
+ *
+ * "if expr" Produces instructions:
+ *	EVAL expr		Push result of "expr"
+ *	JUMP_IF_FALSE end
+ *	... body ...
+ * end:
+ *
+ * "if expr | else" Produces instructions:
+ *	EVAL expr		Push result of "expr"
+ *	JUMP_IF_FALSE else
+ *	... body ...
+ *	JUMP_ALWAYS end
+ * else:
+ *	... body ...
+ * end:
+ *
+ * "if expr1 | elseif expr2 | else" Produces instructions:
+ *	EVAL expr		Push result of "expr"
+ *	JUMP_IF_FALSE elseif
+ *	... body ...
+ *	JUMP_ALWAYS end
+ * elseif:
+ *	EVAL expr		Push result of "expr"
+ *	JUMP_IF_FALSE else
+ *	... body ...
+ *	JUMP_ALWAYS end
+ * else:
+ *	... body ...
+ * end:
+ */
+    static char_u *
+compile_if(char_u *arg, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    garray_T	*instr = &cctx->ctx_instr;
+    scope_T	*scope;
+
+    // compile "expr"
+    if (compile_expr1(&p, cctx) == FAIL)
+	return NULL;
+
+    scope = new_scope(cctx, IF_SCOPE);
+    if (scope == NULL)
+	return NULL;
+
+    // "where" is set when ":elseif", "else" or ":endif" is found
+    scope->se_if.is_if_label = instr->ga_len;
+    generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+
+    return p;
+}
+
+    static char_u *
+compile_elseif(char_u *arg, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+    scope_T	*scope = cctx->ctx_scope;
+
+    if (scope == NULL || scope->se_type != IF_SCOPE)
+    {
+	emsg(_(e_elseif_without_if));
+	return NULL;
+    }
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+
+    // jump from previous block to the end
+    if (compile_jump_to_end(&scope->se_if.is_end_label,
+						    JUMP_ALWAYS, cctx) == FAIL)
+	return NULL;
+
+    // previous "if" or "elseif" jumps here
+    isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label;
+    isn->isn_arg.jump.jump_where = instr->ga_len;
+
+    // compile "expr"
+    if (compile_expr1(&p, cctx) == FAIL)
+	return NULL;
+
+    // "where" is set when ":elseif", "else" or ":endif" is found
+    scope->se_if.is_if_label = instr->ga_len;
+    generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+
+    return p;
+}
+
+    static char_u *
+compile_else(char_u *arg, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+    scope_T	*scope = cctx->ctx_scope;
+
+    if (scope == NULL || scope->se_type != IF_SCOPE)
+    {
+	emsg(_(e_else_without_if));
+	return NULL;
+    }
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+
+    // jump from previous block to the end
+    if (compile_jump_to_end(&scope->se_if.is_end_label,
+						    JUMP_ALWAYS, cctx) == FAIL)
+	return NULL;
+
+    // previous "if" or "elseif" jumps here
+    isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label;
+    isn->isn_arg.jump.jump_where = instr->ga_len;
+
+    return p;
+}
+
+    static char_u *
+compile_endif(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    ifscope_T	*ifscope;
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+
+    if (scope == NULL || scope->se_type != IF_SCOPE)
+    {
+	emsg(_(e_endif_without_if));
+	return NULL;
+    }
+    ifscope = &scope->se_if;
+    cctx->ctx_scope = scope->se_outer;
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+
+    // previous "if" or "elseif" jumps here
+    isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label;
+    isn->isn_arg.jump.jump_where = instr->ga_len;
+
+    // Fill in the "end" label in jumps at the end of the blocks.
+    compile_fill_jump_to_end(&ifscope->is_end_label, cctx);
+
+    vim_free(scope);
+    return arg;
+}
+
+/*
+ * compile "for var in expr"
+ *
+ * Produces instructions:
+ *       PUSHNR -1
+ *       STORE loop-idx		Set index to -1
+ *       EVAL expr		Push result of "expr"
+ * top:  FOR loop-idx, end	Increment index, use list on bottom of stack
+ *				- if beyond end, jump to "end"
+ *				- otherwise get item from list and push it
+ *       STORE var		Store item in "var"
+ *       ... body ...
+ *       JUMP top		Jump back to repeat
+ * end:	 DROP			Drop the result of "expr"
+ *
+ */
+    static char_u *
+compile_for(char_u *arg, cctx_T *cctx)
+{
+    char_u	*p;
+    size_t	varlen;
+    garray_T	*instr = &cctx->ctx_instr;
+    garray_T	*stack = &cctx->ctx_type_stack;
+    scope_T	*scope;
+    int		loop_idx;	// index of loop iteration variable
+    int		var_idx;	// index of "var"
+    type_T	*vartype;
+
+    // TODO: list of variables: "for [key, value] in dict"
+    // parse "var"
+    for (p = arg; eval_isnamec1(*p); ++p)
+	;
+    varlen = p - arg;
+    var_idx = lookup_local(arg, varlen, cctx);
+    if (var_idx >= 0)
+    {
+	semsg(_("E1023: variable already defined: %s"), arg);
+	return NULL;
+    }
+
+    // consume "in"
+    p = skipwhite(p);
+    if (STRNCMP(p, "in", 2) != 0 || !VIM_ISWHITE(p[2]))
+    {
+	emsg(_(e_missing_in));
+	return NULL;
+    }
+    p = skipwhite(p + 2);
+
+
+    scope = new_scope(cctx, FOR_SCOPE);
+    if (scope == NULL)
+	return NULL;
+
+    // Reserve a variable to store the loop iteration counter.
+    loop_idx = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
+    if (loop_idx < 0)
+	return NULL;
+
+    // Reserve a variable to store "var"
+    var_idx = reserve_local(cctx, arg, varlen, FALSE, &t_any);
+    if (var_idx < 0)
+	return NULL;
+
+    generate_STORENR(cctx, loop_idx, -1);
+
+    // compile "expr", it remains on the stack until "endfor"
+    arg = p;
+    if (compile_expr1(&arg, cctx) == FAIL)
+	return NULL;
+
+    // now we know the type of "var"
+    vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+    if (vartype->tt_type != VAR_LIST)
+    {
+	emsg(_("E1024: need a List to iterate over"));
+	return NULL;
+    }
+    if (vartype->tt_member->tt_type != VAR_UNKNOWN)
+    {
+	lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + var_idx;
+
+	lvar->lv_type = vartype->tt_member;
+    }
+
+    // "for_end" is set when ":endfor" is found
+    scope->se_for.fs_top_label = instr->ga_len;
+
+    generate_FOR(cctx, loop_idx);
+    generate_STORE(cctx, ISN_STORE, var_idx, NULL);
+
+    return arg;
+}
+
+/*
+ * compile "endfor"
+ */
+    static char_u *
+compile_endfor(char_u *arg, cctx_T *cctx)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    scope_T	*scope = cctx->ctx_scope;
+    forscope_T	*forscope;
+    isn_T	*isn;
+
+    if (scope == NULL || scope->se_type != FOR_SCOPE)
+    {
+	emsg(_(e_for));
+	return NULL;
+    }
+    forscope = &scope->se_for;
+    cctx->ctx_scope = scope->se_outer;
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+
+    // At end of ":for" scope jump back to the FOR instruction.
+    generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label);
+
+    // Fill in the "end" label in the FOR statement so it can jump here
+    isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label;
+    isn->isn_arg.forloop.for_end = instr->ga_len;
+
+    // Fill in the "end" label any BREAK statements
+    compile_fill_jump_to_end(&forscope->fs_end_label, cctx);
+
+    // Below the ":for" scope drop the "expr" list from the stack.
+    if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
+	return NULL;
+
+    vim_free(scope);
+
+    return arg;
+}
+
+/*
+ * compile "while expr"
+ *
+ * Produces instructions:
+ * top:  EVAL expr		Push result of "expr"
+ *       JUMP_IF_FALSE end	jump if false
+ *       ... body ...
+ *       JUMP top		Jump back to repeat
+ * end:
+ *
+ */
+    static char_u *
+compile_while(char_u *arg, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    garray_T	*instr = &cctx->ctx_instr;
+    scope_T	*scope;
+
+    scope = new_scope(cctx, WHILE_SCOPE);
+    if (scope == NULL)
+	return NULL;
+
+    scope->se_while.ws_top_label = instr->ga_len;
+
+    // compile "expr"
+    if (compile_expr1(&p, cctx) == FAIL)
+	return NULL;
+
+    // "while_end" is set when ":endwhile" is found
+    if (compile_jump_to_end(&scope->se_while.ws_end_label,
+						  JUMP_IF_FALSE, cctx) == FAIL)
+	return FAIL;
+
+    return p;
+}
+
+/*
+ * compile "endwhile"
+ */
+    static char_u *
+compile_endwhile(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+
+    if (scope == NULL || scope->se_type != WHILE_SCOPE)
+    {
+	emsg(_(e_while));
+	return NULL;
+    }
+    cctx->ctx_scope = scope->se_outer;
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+
+    // At end of ":for" scope jump back to the FOR instruction.
+    generate_JUMP(cctx, JUMP_ALWAYS, scope->se_while.ws_top_label);
+
+    // Fill in the "end" label in the WHILE statement so it can jump here.
+    // And in any jumps for ":break"
+    compile_fill_jump_to_end(&scope->se_while.ws_end_label, cctx);
+
+    vim_free(scope);
+
+    return arg;
+}
+
+/*
+ * compile "continue"
+ */
+    static char_u *
+compile_continue(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+
+    for (;;)
+    {
+	if (scope == NULL)
+	{
+	    emsg(_(e_continue));
+	    return NULL;
+	}
+	if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
+	    break;
+	scope = scope->se_outer;
+    }
+
+    // Jump back to the FOR or WHILE instruction.
+    generate_JUMP(cctx, JUMP_ALWAYS,
+	    scope->se_type == FOR_SCOPE ? scope->se_for.fs_top_label
+					       : scope->se_while.ws_top_label);
+    return arg;
+}
+
+/*
+ * compile "break"
+ */
+    static char_u *
+compile_break(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    endlabel_T	**el;
+
+    for (;;)
+    {
+	if (scope == NULL)
+	{
+	    emsg(_(e_break));
+	    return NULL;
+	}
+	if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
+	    break;
+	scope = scope->se_outer;
+    }
+
+    // Jump to the end of the FOR or WHILE loop.
+    if (scope->se_type == FOR_SCOPE)
+	el = &scope->se_for.fs_end_label;
+    else
+	el = &scope->se_while.ws_end_label;
+    if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL)
+	return FAIL;
+
+    return arg;
+}
+
+/*
+ * compile "{" start of block
+ */
+    static char_u *
+compile_block(char_u *arg, cctx_T *cctx)
+{
+    if (new_scope(cctx, BLOCK_SCOPE) == NULL)
+	return NULL;
+    return skipwhite(arg + 1);
+}
+
+/*
+ * compile end of block: drop one scope
+ */
+    static void
+compile_endblock(cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+
+    cctx->ctx_scope = scope->se_outer;
+    cctx->ctx_locals.ga_len = scope->se_local_count;
+    vim_free(scope);
+}
+
+/*
+ * compile "try"
+ * Creates a new scope for the try-endtry, pointing to the first catch and
+ * finally.
+ * Creates another scope for the "try" block itself.
+ * TRY instruction sets up exception handling at runtime.
+ *
+ *	"try"
+ *	    TRY -> catch1, -> finally  push trystack entry
+ *	    ... try block
+ *	"throw {exception}"
+ *	    EVAL {exception}
+ *	    THROW		create exception
+ *	    ... try block
+ *	" catch {expr}"
+ *	    JUMP -> finally
+ * catch1:  PUSH exeception
+ *	    EVAL {expr}
+ *	    MATCH
+ *	    JUMP nomatch -> catch2
+ *	    CATCH   remove exception
+ *	    ... catch block
+ *	" catch"
+ *	    JUMP -> finally
+ * catch2:  CATCH   remove exception
+ *	    ... catch block
+ *	" finally"
+ * finally:
+ *	    ... finally block
+ *	" endtry"
+ *	    ENDTRY  pop trystack entry, may rethrow
+ */
+    static char_u *
+compile_try(char_u *arg, cctx_T *cctx)
+{
+    garray_T	*instr = &cctx->ctx_instr;
+    scope_T	*try_scope;
+    scope_T	*scope;
+
+    // scope that holds the jumps that go to catch/finally/endtry
+    try_scope = new_scope(cctx, TRY_SCOPE);
+    if (try_scope == NULL)
+	return NULL;
+
+    // "catch" is set when the first ":catch" is found.
+    // "finally" is set when ":finally" or ":endtry" is found
+    try_scope->se_try.ts_try_label = instr->ga_len;
+    if (generate_instr(cctx, ISN_TRY) == NULL)
+	return NULL;
+
+    // scope for the try block itself
+    scope = new_scope(cctx, BLOCK_SCOPE);
+    if (scope == NULL)
+	return NULL;
+
+    return arg;
+}
+
+/*
+ * compile "catch {expr}"
+ */
+    static char_u *
+compile_catch(char_u *arg, cctx_T *cctx UNUSED)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    garray_T	*instr = &cctx->ctx_instr;
+    char_u	*p;
+    isn_T	*isn;
+
+    // end block scope from :try or :catch
+    if (scope != NULL && scope->se_type == BLOCK_SCOPE)
+	compile_endblock(cctx);
+    scope = cctx->ctx_scope;
+
+    // Error if not in a :try scope
+    if (scope == NULL || scope->se_type != TRY_SCOPE)
+    {
+	emsg(_(e_catch));
+	return NULL;
+    }
+
+    if (scope->se_try.ts_caught_all)
+    {
+	emsg(_("E1033: catch unreachable after catch-all"));
+	return NULL;
+    }
+
+    // Jump from end of previous block to :finally or :endtry
+    if (compile_jump_to_end(&scope->se_try.ts_end_label,
+						    JUMP_ALWAYS, cctx) == FAIL)
+	return NULL;
+
+    // End :try or :catch scope: set value in ISN_TRY instruction
+    isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label;
+    if (isn->isn_arg.try.try_catch == 0)
+	isn->isn_arg.try.try_catch = instr->ga_len;
+    if (scope->se_try.ts_catch_label != 0)
+    {
+	// Previous catch without match jumps here
+	isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label;
+	isn->isn_arg.jump.jump_where = instr->ga_len;
+    }
+
+    p = skipwhite(arg);
+    if (ends_excmd(*p))
+    {
+	scope->se_try.ts_caught_all = TRUE;
+	scope->se_try.ts_catch_label = 0;
+    }
+    else
+    {
+	// Push v:exception, push {expr} and MATCH
+	generate_instr_type(cctx, ISN_PUSHEXC, &t_string);
+
+	if (compile_expr1(&p, cctx) == FAIL)
+	    return NULL;
+
+	// TODO: check for strings?
+	if (generate_COMPARE(cctx, EXPR_MATCH, FALSE) == FAIL)
+	    return NULL;
+
+	scope->se_try.ts_catch_label = instr->ga_len;
+	if (generate_JUMP(cctx, JUMP_IF_FALSE, 0) == FAIL)
+	    return NULL;
+    }
+
+    if (generate_instr(cctx, ISN_CATCH) == NULL)
+	return NULL;
+
+    if (new_scope(cctx, BLOCK_SCOPE) == NULL)
+	return NULL;
+    return p;
+}
+
+    static char_u *
+compile_finally(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+
+    // end block scope from :try or :catch
+    if (scope != NULL && scope->se_type == BLOCK_SCOPE)
+	compile_endblock(cctx);
+    scope = cctx->ctx_scope;
+
+    // Error if not in a :try scope
+    if (scope == NULL || scope->se_type != TRY_SCOPE)
+    {
+	emsg(_(e_finally));
+	return NULL;
+    }
+
+    // End :catch or :finally scope: set value in ISN_TRY instruction
+    isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label;
+    if (isn->isn_arg.try.try_finally != 0)
+    {
+	emsg(_(e_finally_dup));
+	return NULL;
+    }
+
+    // Fill in the "end" label in jumps at the end of the blocks.
+    compile_fill_jump_to_end(&scope->se_try.ts_end_label, cctx);
+
+    if (scope->se_try.ts_catch_label != 0)
+    {
+	// Previous catch without match jumps here
+	isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label;
+	isn->isn_arg.jump.jump_where = instr->ga_len;
+    }
+
+    isn->isn_arg.try.try_finally = instr->ga_len;
+    // TODO: set index in ts_finally_label jumps
+
+    return arg;
+}
+
+    static char_u *
+compile_endtry(char_u *arg, cctx_T *cctx)
+{
+    scope_T	*scope = cctx->ctx_scope;
+    garray_T	*instr = &cctx->ctx_instr;
+    isn_T	*isn;
+
+    // end block scope from :catch or :finally
+    if (scope != NULL && scope->se_type == BLOCK_SCOPE)
+	compile_endblock(cctx);
+    scope = cctx->ctx_scope;
+
+    // Error if not in a :try scope
+    if (scope == NULL || scope->se_type != TRY_SCOPE)
+    {
+	if (scope == NULL)
+	    emsg(_(e_no_endtry));
+	else if (scope->se_type == WHILE_SCOPE)
+	    emsg(_(e_endwhile));
+	if (scope->se_type == FOR_SCOPE)
+	    emsg(_(e_endfor));
+	else
+	    emsg(_(e_endif));
+	return NULL;
+    }
+
+    isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label;
+    if (isn->isn_arg.try.try_catch == 0 && isn->isn_arg.try.try_finally == 0)
+    {
+	emsg(_("E1032: missing :catch or :finally"));
+	return NULL;
+    }
+
+    // Fill in the "end" label in jumps at the end of the blocks, if not done
+    // by ":finally".
+    compile_fill_jump_to_end(&scope->se_try.ts_end_label, cctx);
+
+    // End :catch or :finally scope: set value in ISN_TRY instruction
+    if (isn->isn_arg.try.try_finally == 0)
+	isn->isn_arg.try.try_finally = instr->ga_len;
+    compile_endblock(cctx);
+
+    if (generate_instr(cctx, ISN_ENDTRY) == NULL)
+	return NULL;
+    return arg;
+}
+
+/*
+ * compile "throw {expr}"
+ */
+    static char_u *
+compile_throw(char_u *arg, cctx_T *cctx UNUSED)
+{
+    char_u *p = skipwhite(arg);
+
+    if (ends_excmd(*p))
+    {
+	emsg(_(e_argreq));
+	return NULL;
+    }
+    if (compile_expr1(&p, cctx) == FAIL)
+	return NULL;
+    if (may_generate_2STRING(-1, cctx) == FAIL)
+	return NULL;
+    if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL)
+	return NULL;
+
+    return p;
+}
+
+/*
+ * compile "echo expr"
+ */
+    static char_u *
+compile_echo(char_u *arg, int with_white, cctx_T *cctx)
+{
+    char_u	*p = arg;
+    int		count = 0;
+
+    // for ()
+    {
+	if (compile_expr1(&p, cctx) == FAIL)
+	    return NULL;
+	++count;
+    }
+
+    generate_ECHO(cctx, with_white, count);
+
+    return p;
+}
+
+/*
+ * After ex_function() has collected all the function lines: parse and compile
+ * the lines into instructions.
+ * Adds the function to "def_functions".
+ * When "set_return_type" is set then set ufunc->uf_ret_type to the type of the
+ * return statement (used for lambda).
+ */
+    void
+compile_def_function(ufunc_T *ufunc, int set_return_type)
+{
+    dfunc_T	*dfunc;
+    char_u	*line = NULL;
+    char_u	*p;
+    exarg_T	ea;
+    char	*errormsg = NULL;	// error message
+    int		had_return = FALSE;
+    cctx_T	cctx;
+    garray_T	*instr;
+    int		called_emsg_before = called_emsg;
+    int		ret = FAIL;
+    sctx_T	save_current_sctx = current_sctx;
+
+    if (ufunc->uf_dfunc_idx >= 0)
+    {
+	// redefining a function that was compiled before
+	dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
+	dfunc->df_deleted = FALSE;
+    }
+    else
+    {
+	// Add the function to "def_functions".
+	if (ga_grow(&def_functions, 1) == FAIL)
+	    return;
+	dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len;
+	vim_memset(dfunc, 0, sizeof(dfunc_T));
+	dfunc->df_idx = def_functions.ga_len;
+	ufunc->uf_dfunc_idx = dfunc->df_idx;
+	dfunc->df_ufunc = ufunc;
+	++def_functions.ga_len;
+    }
+
+    vim_memset(&cctx, 0, sizeof(cctx));
+    cctx.ctx_ufunc = ufunc;
+    cctx.ctx_lnum = -1;
+    ga_init2(&cctx.ctx_locals, sizeof(lvar_T), 10);
+    ga_init2(&cctx.ctx_type_stack, sizeof(type_T *), 50);
+    ga_init2(&cctx.ctx_imports, sizeof(imported_T), 10);
+    cctx.ctx_type_list = &ufunc->uf_type_list;
+    ga_init2(&cctx.ctx_instr, sizeof(isn_T), 50);
+    instr = &cctx.ctx_instr;
+
+    // Most modern script version.
+    current_sctx.sc_version = SCRIPT_VERSION_VIM9;
+
+    for (;;)
+    {
+	if (line != NULL && *line == '|')
+	    // the line continues after a '|'
+	    ++line;
+	else if (line != NULL && *line != NUL)
+	{
+	    semsg(_("E488: Trailing characters: %s"), line);
+	    goto erret;
+	}
+	else
+	{
+	    do
+	    {
+		++cctx.ctx_lnum;
+		if (cctx.ctx_lnum == ufunc->uf_lines.ga_len)
+		    break;
+		line = ((char_u **)ufunc->uf_lines.ga_data)[cctx.ctx_lnum];
+	    } while (line == NULL);
+	    if (cctx.ctx_lnum == ufunc->uf_lines.ga_len)
+		break;
+	    SOURCING_LNUM = ufunc->uf_script_ctx.sc_lnum + cctx.ctx_lnum + 1;
+	}
+
+	had_return = FALSE;
+	vim_memset(&ea, 0, sizeof(ea));
+	ea.cmdlinep = &line;
+	ea.cmd = skipwhite(line);
+
+	// "}" ends a block scope
+	if (*ea.cmd == '}')
+	{
+	    scopetype_T stype = cctx.ctx_scope == NULL
+					 ? NO_SCOPE : cctx.ctx_scope->se_type;
+
+	    if (stype == BLOCK_SCOPE)
+	    {
+		compile_endblock(&cctx);
+		line = ea.cmd;
+	    }
+	    else
+	    {
+		emsg("E1025: using } outside of a block scope");
+		goto erret;
+	    }
+	    if (line != NULL)
+		line = skipwhite(ea.cmd + 1);
+	    continue;
+	}
+
+	// "{" starts a block scope
+	if (*ea.cmd == '{')
+	{
+	    line = compile_block(ea.cmd, &cctx);
+	    continue;
+	}
+
+	/*
+	 * COMMAND MODIFIERS
+	 */
+	if (parse_command_modifiers(&ea, &errormsg, FALSE) == FAIL)
+	{
+	    if (errormsg != NULL)
+		goto erret;
+	    // empty line or comment
+	    line = (char_u *)"";
+	    continue;
+	}
+
+	// Skip ":call" to get to the function name.
+	if (checkforcmd(&ea.cmd, "call", 3))
+	    ea.cmd = skipwhite(ea.cmd);
+
+	// Assuming the command starts with a variable or function name, find
+	// what follows.  Also "&opt = value".
+	p = (*ea.cmd == '&') ? ea.cmd + 1 : ea.cmd;
+	p = to_name_end(p);
+	if (p > ea.cmd && *p != NUL)
+	{
+	    int oplen;
+	    int heredoc;
+
+	    // "funcname(" is always a function call.
+	    // "varname[]" is an expression.
+	    // "g:varname" is an expression.
+	    // "varname->expr" is an expression.
+	    if (*p == '('
+		    || *p == '['
+		    || ((p - ea.cmd) > 2 && ea.cmd[1] == ':')
+		    || (*p == '-' && p[1] == '>'))
+	    {
+		// TODO
+	    }
+
+	    oplen = assignment_len(skipwhite(p), &heredoc);
+	    if (oplen > 0)
+	    {
+		// Recognize an assignment if we recognize the variable name:
+		// "g:var = expr"
+		// "var = expr"  where "var" is a local var name.
+		// "&opt = expr"
+		if (*ea.cmd == '&'
+			|| ((p - ea.cmd) > 2 && ea.cmd[1] == ':')
+			|| lookup_local(ea.cmd, p - ea.cmd, &cctx) >= 0
+			|| lookup_script(ea.cmd, p - ea.cmd) == OK)
+		{
+		    line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx);
+		    if (line == NULL)
+			goto erret;
+		    continue;
+		}
+	    }
+	}
+
+	/*
+	 * COMMAND after range
+	 */
+	ea.cmd = skip_range(ea.cmd, NULL);
+	p = find_ex_command(&ea, NULL, lookup_local, &cctx);
+
+	if (p == ea.cmd && ea.cmdidx != CMD_SIZE)
+	{
+	    // Expression or function call.
+	    if (ea.cmdidx == CMD_eval)
+	    {
+		p = ea.cmd;
+		if (compile_expr1(&p, &cctx) == FAIL)
+		    goto erret;
+
+		// drop the return value
+		generate_instr_drop(&cctx, ISN_DROP, 1);
+		line = p;
+		continue;
+	    }
+	    if (ea.cmdidx == CMD_let)
+	    {
+		line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx);
+		if (line == NULL)
+		    goto erret;
+		continue;
+	    }
+	    iemsg("Command from find_ex_command() not handled");
+	    goto erret;
+	}
+
+	p = skipwhite(p);
+
+	switch (ea.cmdidx)
+	{
+	    case CMD_def:
+	    case CMD_function:
+		    // TODO: Nested function
+		    emsg("Nested function not implemented yet");
+		    goto erret;
+
+	    case CMD_return:
+		    line = compile_return(p, set_return_type, &cctx);
+		    had_return = TRUE;
+		    break;
+
+	    case CMD_let:
+	    case CMD_const:
+		    line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
+		    break;
+
+	    case CMD_import:
+		    line = compile_import(p, &cctx);
+		    break;
+
+	    case CMD_if:
+		    line = compile_if(p, &cctx);
+		    break;
+	    case CMD_elseif:
+		    line = compile_elseif(p, &cctx);
+		    break;
+	    case CMD_else:
+		    line = compile_else(p, &cctx);
+		    break;
+	    case CMD_endif:
+		    line = compile_endif(p, &cctx);
+		    break;
+
+	    case CMD_while:
+		    line = compile_while(p, &cctx);
+		    break;
+	    case CMD_endwhile:
+		    line = compile_endwhile(p, &cctx);
+		    break;
+
+	    case CMD_for:
+		    line = compile_for(p, &cctx);
+		    break;
+	    case CMD_endfor:
+		    line = compile_endfor(p, &cctx);
+		    break;
+	    case CMD_continue:
+		    line = compile_continue(p, &cctx);
+		    break;
+	    case CMD_break:
+		    line = compile_break(p, &cctx);
+		    break;
+
+	    case CMD_try:
+		    line = compile_try(p, &cctx);
+		    break;
+	    case CMD_catch:
+		    line = compile_catch(p, &cctx);
+		    break;
+	    case CMD_finally:
+		    line = compile_finally(p, &cctx);
+		    break;
+	    case CMD_endtry:
+		    line = compile_endtry(p, &cctx);
+		    break;
+	    case CMD_throw:
+		    line = compile_throw(p, &cctx);
+		    break;
+
+	    case CMD_echo:
+		    line = compile_echo(p, TRUE, &cctx);
+		    break;
+	    case CMD_echon:
+		    line = compile_echo(p, FALSE, &cctx);
+		    break;
+
+	    default:
+		    // Not recognized, execute with do_cmdline_cmd().
+		    generate_EXEC(&cctx, line);
+		    line = (char_u *)"";
+		    break;
+	}
+	if (line == NULL)
+	    goto erret;
+
+	if (cctx.ctx_type_stack.ga_len < 0)
+	{
+	    iemsg("Type stack underflow");
+	    goto erret;
+	}
+    }
+
+    if (cctx.ctx_scope != NULL)
+    {
+	if (cctx.ctx_scope->se_type == IF_SCOPE)
+	    emsg(_(e_endif));
+	else if (cctx.ctx_scope->se_type == WHILE_SCOPE)
+	    emsg(_(e_endwhile));
+	else if (cctx.ctx_scope->se_type == FOR_SCOPE)
+	    emsg(_(e_endfor));
+	else
+	    emsg(_("E1026: Missing }"));
+	goto erret;
+    }
+
+    if (!had_return)
+    {
+	if (ufunc->uf_ret_type->tt_type != VAR_VOID)
+	{
+	    emsg(_("E1027: Missing return statement"));
+	    goto erret;
+	}
+
+	// Return zero if there is no return at the end.
+	generate_PUSHNR(&cctx, 0);
+	generate_instr(&cctx, ISN_RETURN);
+    }
+
+    dfunc->df_instr = instr->ga_data;
+    dfunc->df_instr_count = instr->ga_len;
+    dfunc->df_varcount = cctx.ctx_max_local;
+
+    ret = OK;
+
+erret:
+    if (ret == FAIL)
+    {
+	ga_clear(instr);
+	ufunc->uf_dfunc_idx = -1;
+	--def_functions.ga_len;
+	if (errormsg != NULL)
+	    emsg(errormsg);
+	else if (called_emsg == called_emsg_before)
+	    emsg("E1028: compile_def_function failed");
+
+	// don't execute this function body
+	ufunc->uf_lines.ga_len = 0;
+    }
+
+    current_sctx = save_current_sctx;
+    ga_clear(&cctx.ctx_type_stack);
+    ga_clear(&cctx.ctx_locals);
+}
+
+/*
+ * Delete an instruction, free what it contains.
+ */
+    static void
+delete_instr(isn_T *isn)
+{
+    switch (isn->isn_type)
+    {
+	case ISN_EXEC:
+	case ISN_LOADENV:
+	case ISN_LOADG:
+	case ISN_LOADOPT:
+	case ISN_MEMBER:
+	case ISN_PUSHEXC:
+	case ISN_PUSHS:
+	case ISN_STOREG:
+	    vim_free(isn->isn_arg.string);
+	    break;
+
+	case ISN_LOADS:
+	    vim_free(isn->isn_arg.loads.ls_name);
+	    break;
+
+	case ISN_STOREOPT:
+	    vim_free(isn->isn_arg.storeopt.so_name);
+	    break;
+
+	case ISN_PUSHBLOB:   // push blob isn_arg.blob
+	    blob_unref(isn->isn_arg.blob);
+	    break;
+
+	case ISN_UCALL:
+	    vim_free(isn->isn_arg.ufunc.cuf_name);
+	    break;
+
+	case ISN_2BOOL:
+	case ISN_2STRING:
+	case ISN_ADDBLOB:
+	case ISN_ADDLIST:
+	case ISN_BCALL:
+	case ISN_CATCH:
+	case ISN_CHECKNR:
+	case ISN_CHECKTYPE:
+	case ISN_COMPAREANY:
+	case ISN_COMPAREBLOB:
+	case ISN_COMPAREBOOL:
+	case ISN_COMPAREDICT:
+	case ISN_COMPAREFLOAT:
+	case ISN_COMPAREFUNC:
+	case ISN_COMPARELIST:
+	case ISN_COMPARENR:
+	case ISN_COMPAREPARTIAL:
+	case ISN_COMPARESPECIAL:
+	case ISN_COMPARESTRING:
+	case ISN_CONCAT:
+	case ISN_DCALL:
+	case ISN_DROP:
+	case ISN_ECHO:
+	case ISN_ENDTRY:
+	case ISN_FOR:
+	case ISN_FUNCREF:
+	case ISN_INDEX:
+	case ISN_JUMP:
+	case ISN_LOAD:
+	case ISN_LOADSCRIPT:
+	case ISN_LOADREG:
+	case ISN_LOADV:
+	case ISN_NEGATENR:
+	case ISN_NEWDICT:
+	case ISN_NEWLIST:
+	case ISN_OPNR:
+	case ISN_OPFLOAT:
+	case ISN_OPANY:
+	case ISN_PCALL:
+	case ISN_PUSHF:
+	case ISN_PUSHNR:
+	case ISN_PUSHBOOL:
+	case ISN_PUSHSPEC:
+	case ISN_RETURN:
+	case ISN_STORE:
+	case ISN_STORENR:
+	case ISN_STORESCRIPT:
+	case ISN_THROW:
+	case ISN_TRY:
+	    // nothing allocated
+	    break;
+    }
+}
+
+/*
+ * When a user function is deleted, delete any associated def function.
+ */
+    void
+delete_def_function(ufunc_T *ufunc)
+{
+    int idx;
+
+    if (ufunc->uf_dfunc_idx >= 0)
+    {
+	dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+							 + ufunc->uf_dfunc_idx;
+	ga_clear(&dfunc->df_def_args_isn);
+
+	for (idx = 0; idx < dfunc->df_instr_count; ++idx)
+	    delete_instr(dfunc->df_instr + idx);
+	VIM_CLEAR(dfunc->df_instr);
+
+	dfunc->df_deleted = TRUE;
+    }
+}
+
+#if defined(EXITFREE) || defined(PROTO)
+    void
+free_def_functions(void)
+{
+    vim_free(def_functions.ga_data);
+}
+#endif
+
+
+#endif // FEAT_EVAL
new file mode 100644
--- /dev/null
+++ b/src/vim9execute.c
@@ -0,0 +1,1934 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * vim9execute.c: execute Vim9 script instructions
+ */
+
+#define USING_FLOAT_STUFF
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+#ifdef VMS
+# include <float.h>
+#endif
+
+#include "vim9.h"
+
+// Structure put on ec_trystack when ISN_TRY is encountered.
+typedef struct {
+    int	    tcd_frame;		// ec_frame when ISN_TRY was encountered
+    int	    tcd_catch_idx;	// instruction of the first catch
+    int	    tcd_finally_idx;	// instruction of the finally block
+    int	    tcd_caught;		// catch block entered
+    int	    tcd_return;		// when TRUE return from end of :finally
+} trycmd_T;
+
+
+// A stack is used to store:
+// - arguments passed to a :def function
+// - info about the calling function, to use when returning
+// - local variables
+// - temporary values
+//
+// In detail (FP == Frame Pointer):
+//	  arg1		first argument from caller (if present)
+//	  arg2		second argument from caller (if present)
+//	  extra_arg1	any missing optional argument default value
+// FP ->  cur_func	calling function
+//        current	previous instruction pointer
+//        frame_ptr	previous Frame Pointer
+//        var1		space for local variable
+//        var2		space for local variable
+//        ....		fixed space for max. number of local variables
+//        temp		temporary values
+//        ....		flexible space for temporary values (can grow big)
+
+/*
+ * Execution context.
+ */
+typedef struct {
+    garray_T	ec_stack;	// stack of typval_T values
+    int		ec_frame;	// index in ec_stack: context of ec_dfunc_idx
+
+    garray_T	ec_trystack;	// stack of trycmd_T values
+    int		ec_in_catch;	// when TRUE in catch or finally block
+
+    int		ec_dfunc_idx;	// current function index
+    isn_T	*ec_instr;	// array with instructions
+    int		ec_iidx;	// index in ec_instr: instruction to execute
+} ectx_T;
+
+// Get pointer to item relative to the bottom of the stack, -1 is the last one.
+#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
+
+/*
+ * Return the number of arguments, including any vararg.
+ */
+    static int
+ufunc_argcount(ufunc_T *ufunc)
+{
+    return ufunc->uf_args.ga_len + (ufunc->uf_va_name != NULL ? 1 : 0);
+}
+
+/*
+ * Call compiled function "cdf_idx" from compiled code.
+ *
+ * Stack has:
+ * - current arguments (already there)
+ * - omitted optional argument (default values) added here
+ * - stack frame:
+ *	- pointer to calling function
+ *	- Index of next instruction in calling function
+ *	- previous frame pointer
+ * - reserved space for local variables
+ */
+    static int
+call_dfunc(int cdf_idx, int argcount, ectx_T *ectx)
+{
+    dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + cdf_idx;
+    ufunc_T *ufunc = dfunc->df_ufunc;
+    int	    optcount = ufunc_argcount(ufunc) - argcount;
+    int	    idx;
+
+    if (dfunc->df_deleted)
+    {
+	emsg_funcname(e_func_deleted, ufunc->uf_name);
+	return FAIL;
+    }
+
+    if (ga_grow(&ectx->ec_stack, optcount + 3 + dfunc->df_varcount) == FAIL)
+	return FAIL;
+
+// TODO: Put omitted argument default values on the stack.
+    if (optcount > 0)
+    {
+	emsg("optional arguments not implemented yet");
+	return FAIL;
+    }
+    if (optcount < 0)
+    {
+	emsg("argument count wrong?");
+	return FAIL;
+    }
+//    for (idx = argcount - dfunc->df_minarg;
+//				     idx < dfunc->df_maxarg; ++idx)
+//    {
+//	copy_tv(&dfunc->df_defarg[idx], STACK_TV_BOT(0));
+//	++ectx->ec_stack.ga_len;
+//    }
+
+    // Store current execution state in stack frame for ISN_RETURN.
+    // TODO: If the actual number of arguments doesn't match what the called
+    // function expects things go bad.
+    STACK_TV_BOT(0)->vval.v_number = ectx->ec_dfunc_idx;
+    STACK_TV_BOT(1)->vval.v_number = ectx->ec_iidx;
+    STACK_TV_BOT(2)->vval.v_number = ectx->ec_frame;
+    ectx->ec_frame = ectx->ec_stack.ga_len;
+
+    // Initialize local variables
+    for (idx = 0; idx < dfunc->df_varcount; ++idx)
+	STACK_TV_BOT(STACK_FRAME_SIZE + idx)->v_type = VAR_UNKNOWN;
+    ectx->ec_stack.ga_len += STACK_FRAME_SIZE + dfunc->df_varcount;
+
+    // Set execution state to the start of the called function.
+    ectx->ec_dfunc_idx = cdf_idx;
+    ectx->ec_instr = dfunc->df_instr;
+    estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1);
+    ectx->ec_iidx = 0;
+
+    return OK;
+}
+
+// Get pointer to item in the stack.
+#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
+
+/*
+ * Return from the current function.
+ */
+    static void
+func_return(ectx_T *ectx)
+{
+    int		ret_idx = ectx->ec_stack.ga_len - 1;
+    int		idx;
+    dfunc_T	*dfunc;
+
+    // execution context goes one level up
+    estack_pop();
+
+    // Clear the local variables and temporary values, but not
+    // the return value.
+    for (idx = ectx->ec_frame + STACK_FRAME_SIZE;
+					 idx < ectx->ec_stack.ga_len - 1; ++idx)
+	clear_tv(STACK_TV(idx));
+    dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
+    ectx->ec_stack.ga_len = ectx->ec_frame
+					 - ufunc_argcount(dfunc->df_ufunc) + 1;
+    ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame)->vval.v_number;
+    ectx->ec_iidx = STACK_TV(ectx->ec_frame + 1)->vval.v_number;
+    ectx->ec_frame = STACK_TV(ectx->ec_frame + 2)->vval.v_number;
+    *STACK_TV_BOT(-1) = *STACK_TV(ret_idx);
+    dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
+    ectx->ec_instr = dfunc->df_instr;
+}
+
+#undef STACK_TV
+
+/*
+ * Prepare arguments and rettv for calling a builtin or user function.
+ */
+    static int
+call_prepare(int argcount, typval_T *argvars, ectx_T *ectx)
+{
+    int		idx;
+    typval_T	*tv;
+
+    // Move arguments from bottom of the stack to argvars[] and add terminator.
+    for (idx = 0; idx < argcount; ++idx)
+	argvars[idx] = *STACK_TV_BOT(idx - argcount);
+    argvars[argcount].v_type = VAR_UNKNOWN;
+
+    // Result replaces the arguments on the stack.
+    if (argcount > 0)
+	ectx->ec_stack.ga_len -= argcount - 1;
+    else if (ga_grow(&ectx->ec_stack, 1) == FAIL)
+	return FAIL;
+    else
+	++ectx->ec_stack.ga_len;
+
+    // Default return value is zero.
+    tv = STACK_TV_BOT(-1);
+    tv->v_type = VAR_NUMBER;
+    tv->vval.v_number = 0;
+
+    return OK;
+}
+
+/*
+ * Call a builtin function by index.
+ */
+    static int
+call_bfunc(int func_idx, int argcount, ectx_T *ectx)
+{
+    typval_T	argvars[MAX_FUNC_ARGS];
+    int		idx;
+
+    if (call_prepare(argcount, argvars, ectx) == FAIL)
+	return FAIL;
+
+    // Call the builtin function.
+    call_internal_func_by_idx(func_idx, argvars, STACK_TV_BOT(-1));
+
+    // Clear the arguments.
+    for (idx = 0; idx < argcount; ++idx)
+	clear_tv(&argvars[idx]);
+    return OK;
+}
+
+/*
+ * Execute a user defined function.
+ */
+    static int
+call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx)
+{
+    typval_T	argvars[MAX_FUNC_ARGS];
+    funcexe_T   funcexe;
+    int		error;
+    int		idx;
+
+    if (ufunc->uf_dfunc_idx >= 0)
+	// The function has been compiled, can call it quickly.
+	return call_dfunc(ufunc->uf_dfunc_idx, argcount, ectx);
+
+    if (call_prepare(argcount, argvars, ectx) == FAIL)
+	return FAIL;
+    vim_memset(&funcexe, 0, sizeof(funcexe));
+    funcexe.evaluate = TRUE;
+
+    // Call the user function.  Result goes in last position on the stack.
+    // TODO: add selfdict if there is one
+    error = call_user_func_check(ufunc, argcount, argvars,
+					     STACK_TV_BOT(-1), &funcexe, NULL);
+
+    // Clear the arguments.
+    for (idx = 0; idx < argcount; ++idx)
+	clear_tv(&argvars[idx]);
+
+    if (error != FCERR_NONE)
+    {
+	user_func_error(error, ufunc->uf_name);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Execute a function by "name".
+ * This can be a builtin function or a user function.
+ * Returns FAIL if not found without an error message.
+ */
+    static int
+call_by_name(char_u *name, int argcount, ectx_T *ectx)
+{
+    ufunc_T *ufunc;
+
+    if (builtin_function(name, -1))
+    {
+	int func_idx = find_internal_func(name);
+
+	if (func_idx < 0)
+	    return FAIL;
+	if (check_internal_func(func_idx, argcount) == FAIL)
+	    return FAIL;
+	return call_bfunc(func_idx, argcount, ectx);
+    }
+
+    ufunc = find_func(name, NULL);
+    if (ufunc != NULL)
+	return call_ufunc(ufunc, argcount, ectx);
+
+    return FAIL;
+}
+
+    static int
+call_partial(typval_T *tv, int argcount, ectx_T *ectx)
+{
+    char_u	*name;
+    int		called_emsg_before = called_emsg;
+
+    if (tv->v_type == VAR_PARTIAL)
+    {
+	partial_T *pt = tv->vval.v_partial;
+
+	if (pt->pt_func != NULL)
+	    return call_ufunc(pt->pt_func, argcount, ectx);
+	name = pt->pt_name;
+    }
+    else
+	name = tv->vval.v_string;
+    if (call_by_name(name, argcount, ectx) == FAIL)
+    {
+	if (called_emsg == called_emsg_before)
+	    semsg(_(e_unknownfunc), name);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Execute a function by "name".
+ * This can be a builtin function, user function or a funcref.
+ */
+    static int
+call_eval_func(char_u *name, int argcount, ectx_T *ectx)
+{
+    int		called_emsg_before = called_emsg;
+
+    if (call_by_name(name, argcount, ectx) == FAIL
+					  && called_emsg == called_emsg_before)
+    {
+	// "name" may be a variable that is a funcref or partial
+	//    if find variable
+	//      call_partial()
+	//    else
+	//      semsg(_(e_unknownfunc), name);
+	emsg("call_eval_func(partial) not implemented yet");
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Call a "def" function from old Vim script.
+ * Return OK or FAIL.
+ */
+    int
+call_def_function(
+    ufunc_T	*ufunc,
+    int		argc,		// nr of arguments
+    typval_T	*argv,		// arguments
+    typval_T	*rettv)		// return value
+{
+    ectx_T	ectx;		// execution context
+    int		initial_frame_ptr;
+    typval_T	*tv;
+    int		idx;
+    int		ret = FAIL;
+    dfunc_T	*dfunc;
+
+// Get pointer to item in the stack.
+#define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
+
+// Get pointer to item at the bottom of the stack, -1 is the bottom.
+#undef STACK_TV_BOT
+#define STACK_TV_BOT(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_stack.ga_len + idx)
+
+// Get pointer to local variable on the stack.
+#define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame + STACK_FRAME_SIZE + idx)
+
+    vim_memset(&ectx, 0, sizeof(ectx));
+    ga_init2(&ectx.ec_stack, sizeof(typval_T), 500);
+    if (ga_grow(&ectx.ec_stack, 20) == FAIL)
+	goto failed;
+    ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx;
+
+    ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10);
+
+    // Put arguments on the stack.
+    for (idx = 0; idx < argc; ++idx)
+    {
+	copy_tv(&argv[idx], STACK_TV_BOT(0));
+	++ectx.ec_stack.ga_len;
+    }
+
+    // Frame pointer points to just after arguments.
+    ectx.ec_frame = ectx.ec_stack.ga_len;
+    initial_frame_ptr = ectx.ec_frame;
+
+    // dummy frame entries
+    for (idx = 0; idx < STACK_FRAME_SIZE; ++idx)
+    {
+	STACK_TV(ectx.ec_stack.ga_len)->v_type = VAR_UNKNOWN;
+	++ectx.ec_stack.ga_len;
+    }
+
+    // Reserve space for local variables.
+    dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
+    for (idx = 0; idx < dfunc->df_varcount; ++idx)
+	STACK_TV_VAR(idx)->v_type = VAR_UNKNOWN;
+    ectx.ec_stack.ga_len += dfunc->df_varcount;
+
+    ectx.ec_instr = dfunc->df_instr;
+    ectx.ec_iidx = 0;
+    for (;;)
+    {
+	isn_T	    *iptr;
+	trycmd_T    *trycmd = NULL;
+
+	if (did_throw && !ectx.ec_in_catch)
+	{
+	    garray_T	*trystack = &ectx.ec_trystack;
+
+	    // An exception jumps to the first catch, finally, or returns from
+	    // the current function.
+	    if (trystack->ga_len > 0)
+		trycmd = ((trycmd_T *)trystack->ga_data) + trystack->ga_len - 1;
+	    if (trycmd != NULL && trycmd->tcd_frame == ectx.ec_frame)
+	    {
+		// jump to ":catch" or ":finally"
+		ectx.ec_in_catch = TRUE;
+		ectx.ec_iidx = trycmd->tcd_catch_idx;
+	    }
+	    else
+	    {
+		// not inside try or need to return from current functions.
+		if (ectx.ec_frame == initial_frame_ptr)
+		{
+		    // At the toplevel we are done.  Push a dummy return value.
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    tv = STACK_TV_BOT(0);
+		    tv->v_type = VAR_NUMBER;
+		    tv->vval.v_number = 0;
+		    ++ectx.ec_stack.ga_len;
+		    goto done;
+		}
+
+		func_return(&ectx);
+	    }
+	    continue;
+	}
+
+	iptr = &ectx.ec_instr[ectx.ec_iidx++];
+	switch (iptr->isn_type)
+	{
+	    // execute Ex command line
+	    case ISN_EXEC:
+		do_cmdline_cmd(iptr->isn_arg.string);
+		break;
+
+	    // execute :echo {string} ...
+	    case ISN_ECHO:
+		{
+		    int count = iptr->isn_arg.echo.echo_count;
+		    int	atstart = TRUE;
+		    int needclr = TRUE;
+
+		    for (idx = 0; idx < count; ++idx)
+		    {
+			tv = STACK_TV_BOT(idx - count);
+			echo_one(tv, iptr->isn_arg.echo.echo_with_white,
+							   &atstart, &needclr);
+			clear_tv(tv);
+		    }
+		    ectx.ec_stack.ga_len -= count;
+		}
+		break;
+
+	    // load local variable or argument
+	    case ISN_LOAD:
+		if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+		    goto failed;
+		copy_tv(STACK_TV_VAR(iptr->isn_arg.number), STACK_TV_BOT(0));
+		++ectx.ec_stack.ga_len;
+		break;
+
+	    // load v: variable
+	    case ISN_LOADV:
+		if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+		    goto failed;
+		copy_tv(get_vim_var_tv(iptr->isn_arg.number), STACK_TV_BOT(0));
+		++ectx.ec_stack.ga_len;
+		break;
+
+	    // load s: variable in vim9script
+	    case ISN_LOADSCRIPT:
+		{
+		    scriptitem_T *si =
+				 &SCRIPT_ITEM(iptr->isn_arg.script.script_sid);
+		    svar_T	 *sv;
+
+		    sv = ((svar_T *)si->sn_var_vals.ga_data)
+					     + iptr->isn_arg.script.script_idx;
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    copy_tv(sv->sv_tv, STACK_TV_BOT(0));
+		    ++ectx.ec_stack.ga_len;
+		}
+		break;
+
+	    // load s: variable in old script
+	    case ISN_LOADS:
+		{
+		    hashtab_T	*ht = &SCRIPT_VARS(iptr->isn_arg.loads.ls_sid);
+		    char_u	*name = iptr->isn_arg.loads.ls_name;
+		    dictitem_T	*di = find_var_in_ht(ht, 0, name, TRUE);
+		    if (di == NULL)
+		    {
+			semsg(_("E121: Undefined variable: s:%s"), name);
+			goto failed;
+		    }
+		    else
+		    {
+			if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			    goto failed;
+			copy_tv(&di->di_tv, STACK_TV_BOT(0));
+			++ectx.ec_stack.ga_len;
+		    }
+		}
+		break;
+
+	    // load g: variable
+	    case ISN_LOADG:
+		{
+		    dictitem_T *di;
+
+		    di = find_var_in_ht(get_globvar_ht(), 0,
+						   iptr->isn_arg.string, TRUE);
+		    if (di == NULL)
+		    {
+			semsg(_("E121: Undefined variable: g:%s"),
+							 iptr->isn_arg.string);
+			goto failed;
+		    }
+		    else
+		    {
+			if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			    goto failed;
+			copy_tv(&di->di_tv, STACK_TV_BOT(0));
+			++ectx.ec_stack.ga_len;
+		    }
+		}
+		break;
+
+	    // load &option
+	    case ISN_LOADOPT:
+		{
+		    typval_T	optval;
+		    char_u	*name = iptr->isn_arg.string;
+
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    get_option_tv(&name, &optval, TRUE);
+		    *STACK_TV_BOT(0) = optval;
+		    ++ectx.ec_stack.ga_len;
+		}
+		break;
+
+	    // load $ENV
+	    case ISN_LOADENV:
+		{
+		    typval_T	optval;
+		    char_u	*name = iptr->isn_arg.string;
+
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    get_env_tv(&name, &optval, TRUE);
+		    *STACK_TV_BOT(0) = optval;
+		    ++ectx.ec_stack.ga_len;
+		}
+		break;
+
+	    // load @register
+	    case ISN_LOADREG:
+		if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+		    goto failed;
+		tv = STACK_TV_BOT(0);
+		tv->v_type = VAR_STRING;
+		tv->vval.v_string = get_reg_contents(
+					  iptr->isn_arg.number, GREG_EXPR_SRC);
+		++ectx.ec_stack.ga_len;
+		break;
+
+	    // store local variable
+	    case ISN_STORE:
+		--ectx.ec_stack.ga_len;
+		tv = STACK_TV_VAR(iptr->isn_arg.number);
+		clear_tv(tv);
+		*tv = *STACK_TV_BOT(0);
+		break;
+
+	    // store script-local variable
+	    case ISN_STORESCRIPT:
+		{
+		    scriptitem_T *si = &SCRIPT_ITEM(
+					      iptr->isn_arg.script.script_sid);
+		    svar_T	 *sv = ((svar_T *)si->sn_var_vals.ga_data)
+					     + iptr->isn_arg.script.script_idx;
+
+		    --ectx.ec_stack.ga_len;
+		    clear_tv(sv->sv_tv);
+		    *sv->sv_tv = *STACK_TV_BOT(0);
+		}
+		break;
+
+	    // store option
+	    case ISN_STOREOPT:
+		{
+		    long	n = 0;
+		    char_u	*s = NULL;
+		    char	*msg;
+
+		    --ectx.ec_stack.ga_len;
+		    tv = STACK_TV_BOT(0);
+		    if (tv->v_type == VAR_STRING)
+			s = tv->vval.v_string;
+		    else if (tv->v_type == VAR_NUMBER)
+			n = tv->vval.v_number;
+		    else
+		    {
+			emsg(_("E1051: Expected string or number"));
+			goto failed;
+		    }
+		    msg = set_option_value(iptr->isn_arg.storeopt.so_name,
+					n, s, iptr->isn_arg.storeopt.so_flags);
+		    if (msg != NULL)
+		    {
+			emsg(_(msg));
+			goto failed;
+		    }
+		    clear_tv(tv);
+		}
+		break;
+
+	    // store g: variable
+	    case ISN_STOREG:
+		{
+		    dictitem_T *di;
+
+		    --ectx.ec_stack.ga_len;
+		    di = find_var_in_ht(get_globvar_ht(), 0,
+						   iptr->isn_arg.string, TRUE);
+		    if (di == NULL)
+		    {
+			funccal_entry_T entry;
+
+			save_funccal(&entry);
+			set_var_const(iptr->isn_arg.string, NULL,
+						    STACK_TV_BOT(0), FALSE, 0);
+			restore_funccal();
+		    }
+		    else
+		    {
+			clear_tv(&di->di_tv);
+			di->di_tv = *STACK_TV_BOT(0);
+		    }
+		}
+		break;
+
+	    // store number in local variable
+	    case ISN_STORENR:
+		tv = STACK_TV_VAR(iptr->isn_arg.storenr.str_idx);
+		clear_tv(tv);
+		tv->v_type = VAR_NUMBER;
+		tv->vval.v_number = iptr->isn_arg.storenr.str_val;
+		break;
+
+	    // push constant
+	    case ISN_PUSHNR:
+	    case ISN_PUSHBOOL:
+	    case ISN_PUSHSPEC:
+	    case ISN_PUSHF:
+	    case ISN_PUSHS:
+	    case ISN_PUSHBLOB:
+		if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+		    goto failed;
+		tv = STACK_TV_BOT(0);
+		++ectx.ec_stack.ga_len;
+		switch (iptr->isn_type)
+		{
+		    case ISN_PUSHNR:
+			tv->v_type = VAR_NUMBER;
+			tv->vval.v_number = iptr->isn_arg.number;
+			break;
+		    case ISN_PUSHBOOL:
+			tv->v_type = VAR_BOOL;
+			tv->vval.v_number = iptr->isn_arg.number;
+			break;
+		    case ISN_PUSHSPEC:
+			tv->v_type = VAR_SPECIAL;
+			tv->vval.v_number = iptr->isn_arg.number;
+			break;
+#ifdef FEAT_FLOAT
+		    case ISN_PUSHF:
+			tv->v_type = VAR_FLOAT;
+			tv->vval.v_float = iptr->isn_arg.fnumber;
+			break;
+#endif
+		    case ISN_PUSHBLOB:
+			blob_copy(iptr->isn_arg.blob, tv);
+			break;
+		    default:
+			tv->v_type = VAR_STRING;
+			tv->vval.v_string = vim_strsave(iptr->isn_arg.string);
+		}
+		break;
+
+	    // create a list from items on the stack; uses a single allocation
+	    // for the list header and the items
+	    case ISN_NEWLIST:
+		{
+		    int	    count = iptr->isn_arg.number;
+		    list_T  *list = list_alloc_with_items(count);
+
+		    if (list == NULL)
+			goto failed;
+		    for (idx = 0; idx < count; ++idx)
+			list_set_item(list, idx, STACK_TV_BOT(idx - count));
+
+		    if (count > 0)
+			ectx.ec_stack.ga_len -= count - 1;
+		    else if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    else
+			++ectx.ec_stack.ga_len;
+		    tv = STACK_TV_BOT(-1);
+		    tv->v_type = VAR_LIST;
+		    tv->vval.v_list = list;
+		    ++list->lv_refcount;
+		}
+		break;
+
+	    // create a dict from items on the stack
+	    case ISN_NEWDICT:
+		{
+		    int	    count = iptr->isn_arg.number;
+		    dict_T  *dict = dict_alloc();
+		    dictitem_T *item;
+
+		    if (dict == NULL)
+			goto failed;
+		    for (idx = 0; idx < count; ++idx)
+		    {
+			// check key type is VAR_STRING
+			tv = STACK_TV_BOT(2 * (idx - count));
+			item = dictitem_alloc(tv->vval.v_string);
+			clear_tv(tv);
+			if (item == NULL)
+			    goto failed;
+			item->di_tv = *STACK_TV_BOT(2 * (idx - count) + 1);
+			item->di_tv.v_lock = 0;
+			if (dict_add(dict, item) == FAIL)
+			    goto failed;
+		    }
+
+		    if (count > 0)
+			ectx.ec_stack.ga_len -= 2 * count - 1;
+		    else if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    else
+			++ectx.ec_stack.ga_len;
+		    tv = STACK_TV_BOT(-1);
+		    tv->v_type = VAR_DICT;
+		    tv->vval.v_dict = dict;
+		    ++dict->dv_refcount;
+		}
+		break;
+
+	    // call a :def function
+	    case ISN_DCALL:
+		if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx,
+			      iptr->isn_arg.dfunc.cdf_argcount,
+			      &ectx) == FAIL)
+		    goto failed;
+		break;
+
+	    // call a builtin function
+	    case ISN_BCALL:
+		SOURCING_LNUM = iptr->isn_lnum;
+		if (call_bfunc(iptr->isn_arg.bfunc.cbf_idx,
+			      iptr->isn_arg.bfunc.cbf_argcount,
+			      &ectx) == FAIL)
+		    goto failed;
+		break;
+
+	    // call a funcref or partial
+	    case ISN_PCALL:
+		{
+		    cpfunc_T	*pfunc = &iptr->isn_arg.pfunc;
+		    int		r;
+		    typval_T	partial;
+
+		    SOURCING_LNUM = iptr->isn_lnum;
+		    if (pfunc->cpf_top)
+		    {
+			// funcref is above the arguments
+			tv = STACK_TV_BOT(-pfunc->cpf_argcount - 1);
+		    }
+		    else
+		    {
+			// Get the funcref from the stack.
+			--ectx.ec_stack.ga_len;
+			partial = *STACK_TV_BOT(0);
+			tv = &partial;
+		    }
+		    r = call_partial(tv, pfunc->cpf_argcount, &ectx);
+		    if (tv == &partial)
+			clear_tv(&partial);
+		    if (r == FAIL)
+			goto failed;
+
+		    if (pfunc->cpf_top)
+		    {
+			// Get the funcref from the stack, overwrite with the
+			// return value.
+			clear_tv(tv);
+			--ectx.ec_stack.ga_len;
+			*STACK_TV_BOT(-1) = *STACK_TV_BOT(0);
+		    }
+		}
+		break;
+
+	    // call a user defined function or funcref/partial
+	    case ISN_UCALL:
+		{
+		    cufunc_T	*cufunc = &iptr->isn_arg.ufunc;
+
+		    SOURCING_LNUM = iptr->isn_lnum;
+		    if (call_eval_func(cufunc->cuf_name,
+					  cufunc->cuf_argcount, &ectx) == FAIL)
+			goto failed;
+		}
+		break;
+
+	    // return from a :def function call
+	    case ISN_RETURN:
+		{
+		    if (trycmd != NULL && trycmd->tcd_frame == ectx.ec_frame
+			    && trycmd->tcd_finally_idx != 0)
+		    {
+			// jump to ":finally"
+			ectx.ec_iidx = trycmd->tcd_finally_idx;
+			trycmd->tcd_return = TRUE;
+		    }
+		    else
+		    {
+			// Restore previous function. If the frame pointer
+			// is zero then there is none and we are done.
+			if (ectx.ec_frame == initial_frame_ptr)
+			    goto done;
+
+			func_return(&ectx);
+		    }
+		}
+		break;
+
+	    // push a function reference to a compiled function
+	    case ISN_FUNCREF:
+		{
+		    partial_T   *pt = NULL;
+
+		    pt = ALLOC_CLEAR_ONE(partial_T);
+		    if (pt == NULL)
+			goto failed;
+		    dfunc = ((dfunc_T *)def_functions.ga_data)
+							+ iptr->isn_arg.number;
+		    pt->pt_func = dfunc->df_ufunc;
+		    pt->pt_refcount = 1;
+		    ++dfunc->df_ufunc->uf_refcount;
+
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    tv = STACK_TV_BOT(0);
+		    ++ectx.ec_stack.ga_len;
+		    tv->vval.v_partial = pt;
+		    tv->v_type = VAR_PARTIAL;
+		}
+		break;
+
+	    // jump if a condition is met
+	    case ISN_JUMP:
+		{
+		    jumpwhen_T	when = iptr->isn_arg.jump.jump_when;
+		    int		jump = TRUE;
+
+		    if (when != JUMP_ALWAYS)
+		    {
+			tv = STACK_TV_BOT(-1);
+			jump = tv2bool(tv);
+			if (when == JUMP_IF_FALSE
+					     || when == JUMP_AND_KEEP_IF_FALSE)
+			    jump = !jump;
+			if (when == JUMP_IF_FALSE || when == JUMP_IF_TRUE
+								      || !jump)
+			{
+			    // drop the value from the stack
+			    clear_tv(tv);
+			    --ectx.ec_stack.ga_len;
+			}
+		    }
+		    if (jump)
+			ectx.ec_iidx = iptr->isn_arg.jump.jump_where;
+		}
+		break;
+
+	    // top of a for loop
+	    case ISN_FOR:
+		{
+		    list_T	*list = STACK_TV_BOT(-1)->vval.v_list;
+		    typval_T	*idxtv =
+				   STACK_TV_VAR(iptr->isn_arg.forloop.for_idx);
+
+		    // push the next item from the list
+		    if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+			goto failed;
+		    if (++idxtv->vval.v_number >= list->lv_len)
+			// past the end of the list, jump to "endfor"
+			ectx.ec_iidx = iptr->isn_arg.forloop.for_end;
+		    else if (list->lv_first == &range_list_item)
+		    {
+			// non-materialized range() list
+			tv = STACK_TV_BOT(0);
+			tv->v_type = VAR_NUMBER;
+			tv->vval.v_number = list_find_nr(
+					     list, idxtv->vval.v_number, NULL);
+			++ectx.ec_stack.ga_len;
+		    }
+		    else
+		    {
+			listitem_T *li = list_find(list, idxtv->vval.v_number);
+
+			if (li == NULL)
+			    goto failed;
+			copy_tv(&li->li_tv, STACK_TV_BOT(0));
+			++ectx.ec_stack.ga_len;
+		    }
+		}
+		break;
+
+	    // start of ":try" block
+	    case ISN_TRY:
+		{
+		    if (ga_grow(&ectx.ec_trystack, 1) == FAIL)
+			goto failed;
+		    trycmd = ((trycmd_T *)ectx.ec_trystack.ga_data)
+						     + ectx.ec_trystack.ga_len;
+		    ++ectx.ec_trystack.ga_len;
+		    ++trylevel;
+		    trycmd->tcd_frame = ectx.ec_frame;
+		    trycmd->tcd_catch_idx = iptr->isn_arg.try.try_catch;
+		    trycmd->tcd_finally_idx = iptr->isn_arg.try.try_finally;
+		}
+		break;
+
+	    case ISN_PUSHEXC:
+		if (current_exception == NULL)
+		{
+		    iemsg("Evaluating catch while current_exception is NULL");
+		    goto failed;
+		}
+		if (ga_grow(&ectx.ec_stack, 1) == FAIL)
+		    goto failed;
+		tv = STACK_TV_BOT(0);
+		++ectx.ec_stack.ga_len;
+		tv->v_type = VAR_STRING;
+		tv->vval.v_string = vim_strsave(
+					   (char_u *)current_exception->value);
+		break;
+
+	    case ISN_CATCH:
+		{
+		    garray_T	*trystack = &ectx.ec_trystack;
+
+		    if (trystack->ga_len > 0)
+		    {
+			trycmd = ((trycmd_T *)trystack->ga_data)
+							+ trystack->ga_len - 1;
+			trycmd->tcd_caught = TRUE;
+		    }
+		    did_emsg = got_int = did_throw = FALSE;
+		    catch_exception(current_exception);
+		}
+		break;
+
+	    // end of ":try" block
+	    case ISN_ENDTRY:
+		{
+		    garray_T	*trystack = &ectx.ec_trystack;
+
+		    if (trystack->ga_len > 0)
+		    {
+			--trystack->ga_len;
+			--trylevel;
+			trycmd = ((trycmd_T *)trystack->ga_data)
+							    + trystack->ga_len;
+			if (trycmd->tcd_caught)
+			{
+			    // discard the exception
+			    if (caught_stack == current_exception)
+				caught_stack = caught_stack->caught;
+			    discard_current_exception();
+			}
+
+			if (trycmd->tcd_return)
+			{
+			    // Restore previous function. If the frame pointer
+			    // is zero then there is none and we are done.
+			    if (ectx.ec_frame == initial_frame_ptr)
+				goto done;
+
+			    func_return(&ectx);
+			}
+		    }
+		}
+		break;
+
+	    case ISN_THROW:
+		--ectx.ec_stack.ga_len;
+		tv = STACK_TV_BOT(0);
+		if (throw_exception(tv->vval.v_string, ET_USER, NULL) == FAIL)
+		{
+		    vim_free(tv->vval.v_string);
+		    goto failed;
+		}
+		did_throw = TRUE;
+		break;
+
+	    // compare with special values
+	    case ISN_COMPAREBOOL:
+	    case ISN_COMPARESPECIAL:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    varnumber_T arg1 = tv1->vval.v_number;
+		    varnumber_T arg2 = tv2->vval.v_number;
+		    int		res;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_EQUAL: res = arg1 == arg2; break;
+			case EXPR_NEQUAL: res = arg1 != arg2; break;
+			default: res = 0; break;
+		    }
+
+		    --ectx.ec_stack.ga_len;
+		    tv1->v_type = VAR_BOOL;
+		    tv1->vval.v_number = res ? VVAL_TRUE : VVAL_FALSE;
+		}
+		break;
+
+	    // Operation with two number arguments
+	    case ISN_OPNR:
+	    case ISN_COMPARENR:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    varnumber_T arg1 = tv1->vval.v_number;
+		    varnumber_T arg2 = tv2->vval.v_number;
+		    varnumber_T res;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_MULT: res = arg1 * arg2; break;
+			case EXPR_DIV: res = arg1 / arg2; break;
+			case EXPR_REM: res = arg1 % arg2; break;
+			case EXPR_SUB: res = arg1 - arg2; break;
+			case EXPR_ADD: res = arg1 + arg2; break;
+
+			case EXPR_EQUAL: res = arg1 == arg2; break;
+			case EXPR_NEQUAL: res = arg1 != arg2; break;
+			case EXPR_GREATER: res = arg1 > arg2; break;
+			case EXPR_GEQUAL: res = arg1 >= arg2; break;
+			case EXPR_SMALLER: res = arg1 < arg2; break;
+			case EXPR_SEQUAL: res = arg1 <= arg2; break;
+			default: res = 0; break;
+		    }
+
+		    --ectx.ec_stack.ga_len;
+		    if (iptr->isn_type == ISN_COMPARENR)
+		    {
+			tv1->v_type = VAR_BOOL;
+			tv1->vval.v_number = res ? VVAL_TRUE : VVAL_FALSE;
+		    }
+		    else
+			tv1->vval.v_number = res;
+		}
+		break;
+
+	    // Computation with two float arguments
+	    case ISN_OPFLOAT:
+	    case ISN_COMPAREFLOAT:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    float_T	arg1 = tv1->vval.v_float;
+		    float_T	arg2 = tv2->vval.v_float;
+		    float_T	res = 0;
+		    int		cmp = FALSE;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_MULT: res = arg1 * arg2; break;
+			case EXPR_DIV: res = arg1 / arg2; break;
+			case EXPR_SUB: res = arg1 - arg2; break;
+			case EXPR_ADD: res = arg1 + arg2; break;
+
+			case EXPR_EQUAL: cmp = arg1 == arg2; break;
+			case EXPR_NEQUAL: cmp = arg1 != arg2; break;
+			case EXPR_GREATER: cmp = arg1 > arg2; break;
+			case EXPR_GEQUAL: cmp = arg1 >= arg2; break;
+			case EXPR_SMALLER: cmp = arg1 < arg2; break;
+			case EXPR_SEQUAL: cmp = arg1 <= arg2; break;
+			default: cmp = 0; break;
+		    }
+		    --ectx.ec_stack.ga_len;
+		    if (iptr->isn_type == ISN_COMPAREFLOAT)
+		    {
+			tv1->v_type = VAR_BOOL;
+			tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE;
+		    }
+		    else
+			tv1->vval.v_float = res;
+		}
+		break;
+
+	    case ISN_COMPARELIST:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    list_T	*arg1 = tv1->vval.v_list;
+		    list_T	*arg2 = tv2->vval.v_list;
+		    int		cmp = FALSE;
+		    int		ic = iptr->isn_arg.op.op_ic;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_EQUAL: cmp =
+				      list_equal(arg1, arg2, ic, FALSE); break;
+			case EXPR_NEQUAL: cmp =
+				     !list_equal(arg1, arg2, ic, FALSE); break;
+			case EXPR_IS: cmp = arg1 == arg2; break;
+			case EXPR_ISNOT: cmp = arg1 != arg2; break;
+			default: cmp = 0; break;
+		    }
+		    --ectx.ec_stack.ga_len;
+		    clear_tv(tv1);
+		    clear_tv(tv2);
+		    tv1->v_type = VAR_BOOL;
+		    tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE;
+		}
+		break;
+
+	    case ISN_COMPAREBLOB:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    blob_T	*arg1 = tv1->vval.v_blob;
+		    blob_T	*arg2 = tv2->vval.v_blob;
+		    int		cmp = FALSE;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_EQUAL: cmp = blob_equal(arg1, arg2); break;
+			case EXPR_NEQUAL: cmp = !blob_equal(arg1, arg2); break;
+			case EXPR_IS: cmp = arg1 == arg2; break;
+			case EXPR_ISNOT: cmp = arg1 != arg2; break;
+			default: cmp = 0; break;
+		    }
+		    --ectx.ec_stack.ga_len;
+		    clear_tv(tv1);
+		    clear_tv(tv2);
+		    tv1->v_type = VAR_BOOL;
+		    tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE;
+		}
+		break;
+
+		// TODO: handle separately
+	    case ISN_COMPARESTRING:
+	    case ISN_COMPAREDICT:
+	    case ISN_COMPAREFUNC:
+	    case ISN_COMPAREPARTIAL:
+	    case ISN_COMPAREANY:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    exptype_T	exptype = iptr->isn_arg.op.op_type;
+		    int		ic = iptr->isn_arg.op.op_ic;
+
+		    typval_compare(tv1, tv2, exptype, ic);
+		    clear_tv(tv2);
+		    tv1->v_type = VAR_BOOL;
+		    tv1->vval.v_number = tv1->vval.v_number
+						      ? VVAL_TRUE : VVAL_FALSE;
+		    --ectx.ec_stack.ga_len;
+		}
+		break;
+
+	    case ISN_ADDLIST:
+	    case ISN_ADDBLOB:
+		{
+		    typval_T *tv1 = STACK_TV_BOT(-2);
+		    typval_T *tv2 = STACK_TV_BOT(-1);
+
+		    if (iptr->isn_type == ISN_ADDLIST)
+			eval_addlist(tv1, tv2);
+		    else
+			eval_addblob(tv1, tv2);
+		    clear_tv(tv2);
+		    --ectx.ec_stack.ga_len;
+		}
+		break;
+
+	    // Computation with two arguments of unknown type
+	    case ISN_OPANY:
+		{
+		    typval_T	*tv1 = STACK_TV_BOT(-2);
+		    typval_T	*tv2 = STACK_TV_BOT(-1);
+		    varnumber_T	n1, n2;
+#ifdef FEAT_FLOAT
+		    float_T	f1 = 0, f2 = 0;
+#endif
+		    int		error = FALSE;
+
+		    if (iptr->isn_arg.op.op_type == EXPR_ADD)
+		    {
+			if (tv1->v_type == VAR_LIST && tv2->v_type == VAR_LIST)
+			{
+			    eval_addlist(tv1, tv2);
+			    clear_tv(tv2);
+			    --ectx.ec_stack.ga_len;
+			    break;
+			}
+			else if (tv1->v_type == VAR_BLOB
+						    && tv2->v_type == VAR_BLOB)
+			{
+			    eval_addblob(tv1, tv2);
+			    clear_tv(tv2);
+			    --ectx.ec_stack.ga_len;
+			    break;
+			}
+		    }
+#ifdef FEAT_FLOAT
+		    if (tv1->v_type == VAR_FLOAT)
+		    {
+			f1 = tv1->vval.v_float;
+			n1 = 0;
+		    }
+		    else
+#endif
+		    {
+			n1 = tv_get_number_chk(tv1, &error);
+			if (error)
+			    goto failed;
+#ifdef FEAT_FLOAT
+			if (tv2->v_type == VAR_FLOAT)
+			    f1 = n1;
+#endif
+		    }
+#ifdef FEAT_FLOAT
+		    if (tv2->v_type == VAR_FLOAT)
+		    {
+			f2 = tv2->vval.v_float;
+			n2 = 0;
+		    }
+		    else
+#endif
+		    {
+			n2 = tv_get_number_chk(tv2, &error);
+			if (error)
+			    goto failed;
+#ifdef FEAT_FLOAT
+			if (tv1->v_type == VAR_FLOAT)
+			    f2 = n2;
+#endif
+		    }
+#ifdef FEAT_FLOAT
+		    // if there is a float on either side the result is a float
+		    if (tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT)
+		    {
+			switch (iptr->isn_arg.op.op_type)
+			{
+			    case EXPR_MULT: f1 = f1 * f2; break;
+			    case EXPR_DIV:  f1 = f1 / f2; break;
+			    case EXPR_SUB:  f1 = f1 - f2; break;
+			    case EXPR_ADD:  f1 = f1 + f2; break;
+			    default: emsg(_(e_modulus)); goto failed;
+			}
+			clear_tv(tv1);
+			clear_tv(tv2);
+			tv1->v_type = VAR_FLOAT;
+			tv1->vval.v_float = f1;
+			--ectx.ec_stack.ga_len;
+		    }
+		    else
+#endif
+		    {
+			switch (iptr->isn_arg.op.op_type)
+			{
+			    case EXPR_MULT: n1 = n1 * n2; break;
+			    case EXPR_DIV:  n1 = num_divide(n1, n2); break;
+			    case EXPR_SUB:  n1 = n1 - n2; break;
+			    case EXPR_ADD:  n1 = n1 + n2; break;
+			    default:	    n1 = num_modulus(n1, n2); break;
+			}
+			clear_tv(tv1);
+			clear_tv(tv2);
+			tv1->v_type = VAR_NUMBER;
+			tv1->vval.v_number = n1;
+			--ectx.ec_stack.ga_len;
+		    }
+		}
+		break;
+
+	    case ISN_CONCAT:
+		{
+		    char_u *str1 = STACK_TV_BOT(-2)->vval.v_string;
+		    char_u *str2 = STACK_TV_BOT(-1)->vval.v_string;
+		    char_u *res;
+
+		    res = concat_str(str1, str2);
+		    clear_tv(STACK_TV_BOT(-2));
+		    clear_tv(STACK_TV_BOT(-1));
+		    --ectx.ec_stack.ga_len;
+		    STACK_TV_BOT(-1)->vval.v_string = res;
+		}
+		break;
+
+	    case ISN_INDEX:
+		{
+		    list_T	*list;
+		    varnumber_T	n;
+		    listitem_T	*li;
+
+		    // list index: list is at stack-2, index at stack-1
+		    tv = STACK_TV_BOT(-2);
+		    if (tv->v_type != VAR_LIST)
+		    {
+			emsg(_(e_listreq));
+			goto failed;
+		    }
+		    list = tv->vval.v_list;
+
+		    tv = STACK_TV_BOT(-1);
+		    if (tv->v_type != VAR_NUMBER)
+		    {
+			emsg(_(e_number_exp));
+			goto failed;
+		    }
+		    n = tv->vval.v_number;
+		    clear_tv(tv);
+		    if ((li = list_find(list, n)) == NULL)
+		    {
+			semsg(_(e_listidx), n);
+			goto failed;
+		    }
+		    --ectx.ec_stack.ga_len;
+		    clear_tv(STACK_TV_BOT(-1));
+		    copy_tv(&li->li_tv, STACK_TV_BOT(-1));
+		}
+		break;
+
+	    // dict member with string key
+	    case ISN_MEMBER:
+		{
+		    dict_T	*dict;
+		    dictitem_T	*di;
+
+		    tv = STACK_TV_BOT(-1);
+		    if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
+		    {
+			emsg(_(e_dictreq));
+			goto failed;
+		    }
+		    dict = tv->vval.v_dict;
+
+		    if ((di = dict_find(dict, iptr->isn_arg.string, -1))
+								       == NULL)
+		    {
+			semsg(_(e_dictkey), iptr->isn_arg.string);
+			goto failed;
+		    }
+		    clear_tv(tv);
+		    copy_tv(&di->di_tv, tv);
+		}
+		break;
+
+	    case ISN_NEGATENR:
+		tv = STACK_TV_BOT(-1);
+		tv->vval.v_number = -tv->vval.v_number;
+		break;
+
+	    case ISN_CHECKNR:
+		{
+		    int		error = FALSE;
+
+		    tv = STACK_TV_BOT(-1);
+		    if (check_not_string(tv) == FAIL)
+		    {
+			--ectx.ec_stack.ga_len;
+			goto failed;
+		    }
+		    (void)tv_get_number_chk(tv, &error);
+		    if (error)
+			goto failed;
+		}
+		break;
+
+	    case ISN_CHECKTYPE:
+		{
+		    checktype_T *ct = &iptr->isn_arg.type;
+
+		    tv = STACK_TV_BOT(ct->ct_off);
+		    if (tv->v_type != ct->ct_type)
+		    {
+			semsg(_("E1029: Expected %s but got %s"),
+				    vartype_name(ct->ct_type),
+				    vartype_name(tv->v_type));
+			goto failed;
+		    }
+		}
+		break;
+
+	    case ISN_2BOOL:
+		{
+		    int n;
+
+		    tv = STACK_TV_BOT(-1);
+		    n = tv2bool(tv);
+		    if (iptr->isn_arg.number)  // invert
+			n = !n;
+		    clear_tv(tv);
+		    tv->v_type = VAR_BOOL;
+		    tv->vval.v_number = n ? VVAL_TRUE : VVAL_FALSE;
+		}
+		break;
+
+	    case ISN_2STRING:
+		{
+		    char_u *str;
+
+		    tv = STACK_TV_BOT(iptr->isn_arg.number);
+		    if (tv->v_type != VAR_STRING)
+		    {
+			str = typval_tostring(tv);
+			clear_tv(tv);
+			tv->v_type = VAR_STRING;
+			tv->vval.v_string = str;
+		    }
+		}
+		break;
+
+	    case ISN_DROP:
+		--ectx.ec_stack.ga_len;
+		clear_tv(STACK_TV_BOT(0));
+		break;
+	}
+    }
+
+done:
+    // function finished, get result from the stack.
+    tv = STACK_TV_BOT(-1);
+    *rettv = *tv;
+    tv->v_type = VAR_UNKNOWN;
+    ret = OK;
+
+failed:
+    for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx)
+	clear_tv(STACK_TV(idx));
+    vim_free(ectx.ec_stack.ga_data);
+    return ret;
+}
+
+#define DISASSEMBLE 1
+
+/*
+ * ":dissassemble".
+ */
+    void
+ex_disassemble(exarg_T *eap)
+{
+#ifdef DISASSEMBLE
+    ufunc_T	*ufunc = find_func(eap->arg, NULL);
+    dfunc_T	*dfunc;
+    isn_T	*instr;
+    int		current;
+    int		line_idx = 0;
+    int		prev_current = 0;
+
+    if (ufunc == NULL)
+    {
+	semsg("Cannot find function %s", eap->arg);
+	return;
+    }
+    if (ufunc->uf_dfunc_idx < 0)
+    {
+	semsg("Function %s is not compiled", eap->arg);
+	return;
+    }
+    if (ufunc->uf_name_exp != NULL)
+	msg((char *)ufunc->uf_name_exp);
+    else
+	msg((char *)ufunc->uf_name);
+
+    dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
+    instr = dfunc->df_instr;
+    for (current = 0; current < dfunc->df_instr_count; ++current)
+    {
+	isn_T	    *iptr = &instr[current];
+
+	while (line_idx < iptr->isn_lnum && line_idx < ufunc->uf_lines.ga_len)
+	{
+	    if (current > prev_current)
+	    {
+		msg_puts("\n\n");
+		prev_current = current;
+	    }
+	    msg(((char **)ufunc->uf_lines.ga_data)[line_idx++]);
+	}
+
+	switch (iptr->isn_type)
+	{
+	    case ISN_EXEC:
+		smsg("%4d EXEC %s", current, iptr->isn_arg.string);
+		break;
+	    case ISN_ECHO:
+		{
+		    echo_T *echo = &iptr->isn_arg.echo;
+
+		    smsg("%4d %s %d", current,
+			    echo->echo_with_white ? "ECHO" : "ECHON",
+			    echo->echo_count);
+		}
+		break;
+	    case ISN_LOAD:
+		if (iptr->isn_arg.number < 0)
+		    smsg("%4d LOAD arg[%lld]", current,
+				      iptr->isn_arg.number + STACK_FRAME_SIZE);
+		else
+		    smsg("%4d LOAD $%lld", current, iptr->isn_arg.number);
+		break;
+	    case ISN_LOADV:
+		smsg("%4d LOADV v:%s", current,
+				       get_vim_var_name(iptr->isn_arg.number));
+		break;
+	    case ISN_LOADSCRIPT:
+		{
+		    scriptitem_T *si =
+				 &SCRIPT_ITEM(iptr->isn_arg.script.script_sid);
+		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+					     + iptr->isn_arg.script.script_idx;
+
+		    smsg("%4d LOADSCRIPT %s from %s", current,
+						     sv->sv_name, si->sn_name);
+		}
+		break;
+	    case ISN_LOADS:
+		{
+		    scriptitem_T *si = &SCRIPT_ITEM(iptr->isn_arg.loads.ls_sid);
+
+		    smsg("%4d LOADS s:%s from %s", current,
+					    iptr->isn_arg.string, si->sn_name);
+		}
+		break;
+	    case ISN_LOADG:
+		smsg("%4d LOADG g:%s", current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADOPT:
+		smsg("%4d LOADOPT %s", current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADENV:
+		smsg("%4d LOADENV %s", current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADREG:
+		smsg("%4d LOADREG @%c", current, iptr->isn_arg.number);
+		break;
+
+	    case ISN_STORE:
+		smsg("%4d STORE $%lld", current, iptr->isn_arg.number);
+		break;
+	    case ISN_STOREG:
+		smsg("%4d STOREG g:%s", current, iptr->isn_arg.string);
+		break;
+	    case ISN_STORESCRIPT:
+		{
+		    scriptitem_T *si =
+				 &SCRIPT_ITEM(iptr->isn_arg.script.script_sid);
+		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+					     + iptr->isn_arg.script.script_idx;
+
+		    smsg("%4d STORESCRIPT %s in %s", current,
+						     sv->sv_name, si->sn_name);
+		}
+		break;
+	    case ISN_STOREOPT:
+		smsg("%4d STOREOPT &%s", current,
+					       iptr->isn_arg.storeopt.so_name);
+		break;
+
+	    case ISN_STORENR:
+		smsg("%4d STORE %lld in $%d", current,
+				iptr->isn_arg.storenr.str_val,
+				iptr->isn_arg.storenr.str_idx);
+		break;
+
+	    // constants
+	    case ISN_PUSHNR:
+		smsg("%4d PUSHNR %lld", current, iptr->isn_arg.number);
+		break;
+	    case ISN_PUSHBOOL:
+	    case ISN_PUSHSPEC:
+		smsg("%4d PUSH %s", current,
+				   get_var_special_name(iptr->isn_arg.number));
+		break;
+	    case ISN_PUSHF:
+		smsg("%4d PUSHF %g", current, iptr->isn_arg.fnumber);
+		break;
+	    case ISN_PUSHS:
+		smsg("%4d PUSHS \"%s\"", current, iptr->isn_arg.string);
+		break;
+	    case ISN_PUSHBLOB:
+		{
+		    char_u	*r;
+		    char_u	numbuf[NUMBUFLEN];
+		    char_u	*tofree;
+
+		    r = blob2string(iptr->isn_arg.blob, &tofree, numbuf);
+		    smsg("%4d PUSHBLOB \"%s\"", current, r);
+		    vim_free(tofree);
+		}
+		break;
+	    case ISN_PUSHEXC:
+		smsg("%4d PUSH v:exception", current);
+		break;
+	    case ISN_NEWLIST:
+		smsg("%4d NEWLIST size %lld", current, iptr->isn_arg.number);
+		break;
+	    case ISN_NEWDICT:
+		smsg("%4d NEWDICT size %lld", current, iptr->isn_arg.number);
+		break;
+
+	    // function call
+	    case ISN_BCALL:
+		{
+		    cbfunc_T	*cbfunc = &iptr->isn_arg.bfunc;
+
+		    smsg("%4d BCALL %s(argc %d)", current,
+			    internal_func_name(cbfunc->cbf_idx),
+			    cbfunc->cbf_argcount);
+		}
+		break;
+	    case ISN_DCALL:
+		{
+		    cdfunc_T	*cdfunc = &iptr->isn_arg.dfunc;
+		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
+							     + cdfunc->cdf_idx;
+
+		    smsg("%4d DCALL %s(argc %d)", current,
+			    df->df_ufunc->uf_name_exp != NULL
+				? df->df_ufunc->uf_name_exp
+				: df->df_ufunc->uf_name, cdfunc->cdf_argcount);
+		}
+		break;
+	    case ISN_UCALL:
+		{
+		    cufunc_T	*cufunc = &iptr->isn_arg.ufunc;
+
+		    smsg("%4d UCALL %s(argc %d)", current,
+				       cufunc->cuf_name, cufunc->cuf_argcount);
+		}
+		break;
+	    case ISN_PCALL:
+		{
+		    cpfunc_T	*cpfunc = &iptr->isn_arg.pfunc;
+
+		    smsg("%4d PCALL%s (argc %d)", current,
+			   cpfunc->cpf_top ? " top" : "", cpfunc->cpf_argcount);
+		}
+		break;
+	    case ISN_RETURN:
+		smsg("%4d RETURN", current);
+		break;
+	    case ISN_FUNCREF:
+		{
+		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
+							+ iptr->isn_arg.number;
+
+		    smsg("%4d FUNCREF %s", current, df->df_ufunc->uf_name);
+		}
+		break;
+
+	    case ISN_JUMP:
+		{
+		    char *when = "?";
+
+		    switch (iptr->isn_arg.jump.jump_when)
+		    {
+			case JUMP_ALWAYS:
+			    when = "JUMP";
+			    break;
+			case JUMP_IF_TRUE:
+			    when = "JUMP_IF_TRUE";
+			    break;
+			case JUMP_AND_KEEP_IF_TRUE:
+			    when = "JUMP_AND_KEEP_IF_TRUE";
+			    break;
+			case JUMP_IF_FALSE:
+			    when = "JUMP_IF_FALSE";
+			    break;
+			case JUMP_AND_KEEP_IF_FALSE:
+			    when = "JUMP_AND_KEEP_IF_FALSE";
+			    break;
+		    }
+		    smsg("%4d %s -> %lld", current, when,
+						iptr->isn_arg.jump.jump_where);
+		}
+		break;
+
+	    case ISN_FOR:
+		{
+		    forloop_T *forloop = &iptr->isn_arg.forloop;
+
+		    smsg("%4d FOR $%d -> %d", current,
+					   forloop->for_idx, forloop->for_end);
+		}
+		break;
+
+	    case ISN_TRY:
+		{
+		    try_T *try = &iptr->isn_arg.try;
+
+		    smsg("%4d TRY catch -> %d, finally -> %d", current,
+					     try->try_catch, try->try_finally);
+		}
+		break;
+	    case ISN_CATCH:
+		// TODO
+		smsg("%4d CATCH", current);
+		break;
+	    case ISN_ENDTRY:
+		smsg("%4d ENDTRY", current);
+		break;
+	    case ISN_THROW:
+		smsg("%4d THROW", current);
+		break;
+
+	    // expression operations on number
+	    case ISN_OPNR:
+	    case ISN_OPFLOAT:
+	    case ISN_OPANY:
+		{
+		    char *what;
+		    char *ins;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_MULT: what = "*"; break;
+			case EXPR_DIV: what = "/"; break;
+			case EXPR_REM: what = "%"; break;
+			case EXPR_SUB: what = "-"; break;
+			case EXPR_ADD: what = "+"; break;
+			default:       what = "???"; break;
+		    }
+		    switch (iptr->isn_type)
+		    {
+			case ISN_OPNR: ins = "OPNR"; break;
+			case ISN_OPFLOAT: ins = "OPFLOAT"; break;
+			case ISN_OPANY: ins = "OPANY"; break;
+			default: ins = "???"; break;
+		    }
+		    smsg("%4d %s %s", current, ins, what);
+		}
+		break;
+
+	    case ISN_COMPAREBOOL:
+	    case ISN_COMPARESPECIAL:
+	    case ISN_COMPARENR:
+	    case ISN_COMPAREFLOAT:
+	    case ISN_COMPARESTRING:
+	    case ISN_COMPAREBLOB:
+	    case ISN_COMPARELIST:
+	    case ISN_COMPAREDICT:
+	    case ISN_COMPAREFUNC:
+	    case ISN_COMPAREPARTIAL:
+	    case ISN_COMPAREANY:
+		   {
+		       char *p;
+		       char buf[10];
+		       char *type;
+
+		       switch (iptr->isn_arg.op.op_type)
+		       {
+			   case EXPR_EQUAL:	 p = "=="; break;
+			   case EXPR_NEQUAL:    p = "!="; break;
+			   case EXPR_GREATER:   p = ">"; break;
+			   case EXPR_GEQUAL:    p = ">="; break;
+			   case EXPR_SMALLER:   p = "<"; break;
+			   case EXPR_SEQUAL:    p = "<="; break;
+			   case EXPR_MATCH:	 p = "=~"; break;
+			   case EXPR_IS:	 p = "is"; break;
+			   case EXPR_ISNOT:	 p = "isnot"; break;
+			   case EXPR_NOMATCH:	 p = "!~"; break;
+			   default:  p = "???"; break;
+		       }
+		       STRCPY(buf, p);
+		       if (iptr->isn_arg.op.op_ic == TRUE)
+			   strcat(buf, "?");
+		       switch(iptr->isn_type)
+		       {
+			   case ISN_COMPAREBOOL: type = "COMPAREBOOL"; break;
+			   case ISN_COMPARESPECIAL:
+						 type = "COMPARESPECIAL"; break;
+			   case ISN_COMPARENR: type = "COMPARENR"; break;
+			   case ISN_COMPAREFLOAT: type = "COMPAREFLOAT"; break;
+			   case ISN_COMPARESTRING:
+						  type = "COMPARESTRING"; break;
+			   case ISN_COMPAREBLOB: type = "COMPAREBLOB"; break;
+			   case ISN_COMPARELIST: type = "COMPARELIST"; break;
+			   case ISN_COMPAREDICT: type = "COMPAREDICT"; break;
+			   case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break;
+			   case ISN_COMPAREPARTIAL:
+						 type = "COMPAREPARTIAL"; break;
+			   case ISN_COMPAREANY: type = "COMPAREANY"; break;
+			   default: type = "???"; break;
+		       }
+
+		       smsg("%4d %s %s", current, type, buf);
+		   }
+		   break;
+
+	    case ISN_ADDLIST: smsg("%4d ADDLIST", current); break;
+	    case ISN_ADDBLOB: smsg("%4d ADDBLOB", current); break;
+
+	    // expression operations
+	    case ISN_CONCAT: smsg("%4d CONCAT", current); break;
+	    case ISN_INDEX: smsg("%4d INDEX", current); break;
+	    case ISN_MEMBER: smsg("%4d MEMBER %s", current,
+						  iptr->isn_arg.string); break;
+	    case ISN_NEGATENR: smsg("%4d NEGATENR", current); break;
+
+	    case ISN_CHECKNR: smsg("%4d CHECKNR", current); break;
+	    case ISN_CHECKTYPE: smsg("%4d CHECKTYPE %s stack[%d]", current,
+				      vartype_name(iptr->isn_arg.type.ct_type),
+				      iptr->isn_arg.type.ct_off);
+				break;
+	    case ISN_2BOOL: if (iptr->isn_arg.number)
+				smsg("%4d INVERT (!val)", current);
+			    else
+				smsg("%4d 2BOOL (!!val)", current);
+			    break;
+	    case ISN_2STRING: smsg("%4d 2STRING stack[%d]", current,
+							 iptr->isn_arg.number);
+				break;
+
+	    case ISN_DROP: smsg("%4d DROP", current); break;
+	}
+    }
+#endif
+}
+
+/*
+ * Return TRUE when "tv" is not falsey: non-zero, non-empty string, non-empty
+ * list, etc.  Mostly like what JavaScript does, except that empty list and
+ * empty dictionary are FALSE.
+ */
+    int
+tv2bool(typval_T *tv)
+{
+    switch (tv->v_type)
+    {
+	case VAR_NUMBER:
+	    return tv->vval.v_number != 0;
+	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
+	    return tv->vval.v_float != 0.0;
+#else
+	    break;
+#endif
+	case VAR_PARTIAL:
+	    return tv->vval.v_partial != NULL;
+	case VAR_FUNC:
+	case VAR_STRING:
+	    return tv->vval.v_string != NULL && *tv->vval.v_string != NUL;
+	case VAR_LIST:
+	    return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0;
+	case VAR_DICT:
+	    return tv->vval.v_dict != NULL
+				    && tv->vval.v_dict->dv_hashtab.ht_used > 0;
+	case VAR_BOOL:
+	case VAR_SPECIAL:
+	    return tv->vval.v_number == VVAL_TRUE ? TRUE : FALSE;
+	case VAR_JOB:
+#ifdef FEAT_JOB_CHANNEL
+	    return tv->vval.v_job != NULL;
+#else
+	    break;
+#endif
+	case VAR_CHANNEL:
+#ifdef FEAT_JOB_CHANNEL
+	    return tv->vval.v_channel != NULL;
+#else
+	    break;
+#endif
+	case VAR_BLOB:
+	    return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0;
+	case VAR_UNKNOWN:
+	case VAR_VOID:
+	    break;
+    }
+    return FALSE;
+}
+
+/*
+ * If "tv" is a string give an error and return FAIL.
+ */
+    int
+check_not_string(typval_T *tv)
+{
+    if (tv->v_type == VAR_STRING)
+    {
+	emsg(_("E1030: Using a String as a Number"));
+	clear_tv(tv);
+	return FAIL;
+    }
+    return OK;
+}
+
+
+#endif // FEAT_EVAL
new file mode 100644
--- /dev/null
+++ b/src/vim9script.c
@@ -0,0 +1,405 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * vim9script.c: :vim9script, :import, :export and friends
+ */
+
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+#include "vim9.h"
+
+static char e_needs_vim9[] = N_("E1042: import/export can only be used in vim9script");
+
+    int
+in_vim9script(void)
+{
+    // TODO: go up the stack?
+    return current_sctx.sc_version == SCRIPT_VERSION_VIM9;
+}
+
+/*
+ * ":vim9script".
+ */
+    void
+ex_vim9script(exarg_T *eap)
+{
+    scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+
+    if (!getline_equal(eap->getline, eap->cookie, getsourceline))
+    {
+	emsg(_("E1038: vim9script can only be used in a script"));
+	return;
+    }
+    if (si->sn_had_command)
+    {
+	emsg(_("E1039: vim9script must be the first command in a script"));
+	return;
+    }
+    current_sctx.sc_version = SCRIPT_VERSION_VIM9;
+    si->sn_version = SCRIPT_VERSION_VIM9;
+    si->sn_had_command = TRUE;
+
+    if (STRCMP(p_cpo, CPO_VIM) != 0)
+    {
+	si->sn_save_cpo = p_cpo;
+	p_cpo = vim_strsave((char_u *)CPO_VIM);
+    }
+}
+
+/*
+ * ":export let Name: type"
+ * ":export const Name: type"
+ * ":export def Name(..."
+ * ":export class Name ..."
+ *
+ * ":export {Name, ...}"
+ */
+    void
+ex_export(exarg_T *eap UNUSED)
+{
+    if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
+    {
+	emsg(_(e_needs_vim9));
+	return;
+    }
+
+    eap->cmd = eap->arg;
+    (void)find_ex_command(eap, NULL, lookup_scriptvar, NULL);
+    switch (eap->cmdidx)
+    {
+	case CMD_let:
+	case CMD_const:
+	case CMD_def:
+	// case CMD_class:
+	    is_export = TRUE;
+	    do_cmdline(eap->cmd, eap->getline, eap->cookie,
+						DOCMD_VERBOSE + DOCMD_NOWAIT);
+
+	    // The command will reset "is_export" when exporting an item.
+	    if (is_export)
+	    {
+		emsg(_("E1044: export with invalid argument"));
+		is_export = FALSE;
+	    }
+	    break;
+	default:
+	    emsg(_("E1043: Invalid command after :export"));
+	    break;
+    }
+}
+
+/*
+ * Add a new imported item entry to the current script.
+ */
+    static imported_T *
+new_imported(garray_T *gap)
+{
+    if (ga_grow(gap, 1) == OK)
+	return ((imported_T *)gap->ga_data + gap->ga_len++);
+    return NULL;
+}
+
+/*
+ * Free all imported items in script "sid".
+ */
+    void
+free_imports(int sid)
+{
+    scriptitem_T    *si = &SCRIPT_ITEM(sid);
+    int		    idx;
+
+    for (idx = 0; idx < si->sn_imports.ga_len; ++idx)
+    {
+	imported_T *imp = ((imported_T *)si->sn_imports.ga_data + idx);
+
+	vim_free(imp->imp_name);
+    }
+    ga_clear(&si->sn_imports);
+}
+
+/*
+ * ":import Item from 'filename'"
+ * ":import Item as Alias from 'filename'"
+ * ":import {Item} from 'filename'".
+ * ":import {Item as Alias} from 'filename'"
+ * ":import {Item, Item} from 'filename'"
+ * ":import {Item, Item as Alias} from 'filename'"
+ *
+ * ":import * as Name from 'filename'"
+ */
+    void
+ex_import(exarg_T *eap)
+{
+    if (current_sctx.sc_version != SCRIPT_VERSION_VIM9)
+	emsg(_(e_needs_vim9));
+    else
+    {
+	char_u *cmd_end = handle_import(eap->arg, NULL, current_sctx.sc_sid);
+
+	if (cmd_end != NULL)
+	    eap->nextcmd = check_nextcmd(cmd_end);
+    }
+}
+
+/*
+ * Handle an ":import" command and add the resulting imported_T to "gap", when
+ * not NULL, or script "import_sid" sn_imports.
+ * Returns a pointer to after the command or NULL in case of failure
+ */
+    char_u *
+handle_import(char_u *arg_start, garray_T *gap, int import_sid)
+{
+    char_u	*arg = arg_start;
+    char_u	*cmd_end;
+    char_u	*as_ptr = NULL;
+    char_u	*from_ptr;
+    int		as_len = 0;
+    int		ret = FAIL;
+    typval_T	tv;
+    int		sid = -1;
+    int		res;
+
+    if (*arg == '{')
+    {
+	// skip over {item} list
+	while (*arg != NUL && *arg != '}')
+	    ++arg;
+	if (*arg == '}')
+	    arg = skipwhite(arg + 1);
+    }
+    else
+    {
+	if (*arg == '*')
+	    arg = skipwhite(arg + 1);
+	else
+	{
+	    while (eval_isnamec1(*arg))
+		++arg;
+	    arg = skipwhite(arg);
+	}
+	if (STRNCMP("as", arg, 2) == 0 && VIM_ISWHITE(arg[2]))
+	{
+	    // skip over "as Name "
+	    arg = skipwhite(arg + 2);
+	    as_ptr = arg;
+	    while (eval_isnamec1(*arg))
+		++arg;
+	    as_len = (int)(arg - as_ptr);
+	    arg = skipwhite(arg);
+	}
+	else if (*arg_start == '*')
+	{
+	    emsg(_("E1045: Missing \"as\" after *"));
+	    return NULL;
+	}
+    }
+    if (STRNCMP("from", arg, 4) != 0 || !VIM_ISWHITE(arg[4]))
+    {
+	emsg(_("E1045: Missing \"from\""));
+	return NULL;
+    }
+    from_ptr = arg;
+    arg = skipwhite(arg + 4);
+    tv.v_type = VAR_UNKNOWN;
+    // TODO: should we accept any expression?
+    if (*arg == '\'')
+	ret = get_lit_string_tv(&arg, &tv, TRUE);
+    else if (*arg == '"')
+	ret = get_string_tv(&arg, &tv, TRUE);
+    if (ret == FAIL || tv.vval.v_string == NULL || *tv.vval.v_string == NUL)
+    {
+	emsg(_("E1045: Invalid string after \"from\""));
+	return NULL;
+    }
+    cmd_end = arg;
+
+    // find script tv.vval.v_string
+    if (*tv.vval.v_string == '.')
+    {
+	size_t		len;
+	scriptitem_T	*si = &SCRIPT_ITEM(current_sctx.sc_sid);
+	char_u		*tail = gettail(si->sn_name);
+	char_u		*from_name;
+
+	// Relative to current script: "./name.vim", "../../name.vim".
+	len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2;
+	from_name = alloc((int)len);
+	if (from_name == NULL)
+	{
+	    clear_tv(&tv);
+	    return NULL;
+	}
+	vim_strncpy(from_name, si->sn_name, tail - si->sn_name);
+	add_pathsep(from_name);
+	STRCAT(from_name, tv.vval.v_string);
+	simplify_filename(from_name);
+
+	res = do_source(from_name, FALSE, DOSO_NONE, &sid);
+	vim_free(from_name);
+    }
+    else if (mch_isFullName(tv.vval.v_string))
+    {
+	// Absolute path: "/tmp/name.vim"
+	res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid);
+    }
+    else
+    {
+	size_t	    len = 7 + STRLEN(tv.vval.v_string) + 1;
+	char_u	    *from_name;
+
+	// Find file in "import" subdirs in 'runtimepath'.
+	from_name = alloc((int)len);
+	if (from_name == NULL)
+	{
+	    clear_tv(&tv);
+	    return NULL;
+	}
+	vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string);
+	res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid);
+	vim_free(from_name);
+    }
+
+    if (res == FAIL || sid <= 0)
+    {
+	semsg(_("E1053: Could not import \"%s\""), tv.vval.v_string);
+	clear_tv(&tv);
+	return NULL;
+    }
+    clear_tv(&tv);
+
+    if (*arg_start == '*')
+    {
+	imported_T *imported = new_imported(gap != NULL ? gap
+					: &SCRIPT_ITEM(import_sid).sn_imports);
+
+	if (imported == NULL)
+	    return NULL;
+	imported->imp_name = vim_strnsave(as_ptr, as_len);
+	imported->imp_sid = sid;
+	imported->imp_all = TRUE;
+    }
+    else
+    {
+	scriptitem_T *script = &SCRIPT_ITEM(sid);
+
+	arg = arg_start;
+	if (*arg == '{')
+	    arg = skipwhite(arg + 1);
+	for (;;)
+	{
+	    char_u	*name = arg;
+	    int		name_len;
+	    int		cc;
+	    int		idx;
+	    svar_T	*sv;
+	    imported_T	*imported;
+	    ufunc_T	*ufunc;
+
+	    // isolate one name
+	    while (eval_isnamec1(*arg))
+		++arg;
+	    name_len = (int)(arg - name);
+
+	    // find name in "script"
+	    // TODO: also find script-local user function
+	    cc = *arg;
+	    *arg = NUL;
+	    idx = get_script_item_idx(sid, name, FALSE);
+	    if (idx >= 0)
+	    {
+		sv = ((svar_T *)script->sn_var_vals.ga_data) + idx;
+		if (!sv->sv_export)
+		{
+		    semsg(_("E1049: Item not exported in script: %s"), name);
+		    *arg = cc;
+		    return NULL;
+		}
+	    }
+	    else
+	    {
+		char_u	buffer[200];
+		char_u	*funcname;
+
+		// it could be a user function.
+		if (STRLEN(name) < sizeof(buffer) - 10)
+		    funcname = buffer;
+		else
+		{
+		    funcname = alloc(STRLEN(name) + 10);
+		    if (funcname == NULL)
+		    {
+			*arg = cc;
+			return NULL;
+		    }
+		}
+		funcname[0] = K_SPECIAL;
+		funcname[1] = KS_EXTRA;
+		funcname[2] = (int)KE_SNR;
+		sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name);
+		ufunc = find_func(funcname, NULL);
+		if (funcname != buffer)
+		    vim_free(funcname);
+
+		if (ufunc == NULL)
+		{
+		    semsg(_("E1048: Item not found in script: %s"), name);
+		    *arg = cc;
+		    return NULL;
+		}
+	    }
+
+	    imported = new_imported(gap != NULL ? gap
+					: &SCRIPT_ITEM(import_sid).sn_imports);
+	    if (imported == NULL)
+		return NULL;
+
+	    *arg = cc;
+	    arg = skipwhite(arg);
+
+	    // TODO: check for "as" following
+	    // imported->imp_name = vim_strnsave(as_ptr, as_len);
+	    imported->imp_name = vim_strnsave(name, name_len);
+	    imported->imp_sid = sid;
+	    if (idx >= 0)
+	    {
+		imported->imp_type = sv->sv_type;
+		imported->imp_var_vals_idx = idx;
+	    }
+	    else
+		imported->imp_funcname = ufunc->uf_name;
+
+	    arg = skipwhite(arg);
+	    if (*arg_start != '{')
+		break;
+	    if (*arg == '}')
+	    {
+		arg = skipwhite(arg + 1);
+		break;
+	    }
+
+	    if (*arg != ',')
+	    {
+		emsg(_("E1046: Missing comma in import"));
+		return NULL;
+	    }
+	    arg = skipwhite(arg + 1);
+	}
+	if (arg != from_ptr)
+	{
+	    emsg(_("E1047: syntax error in import"));
+	    return NULL;
+	}
+    }
+    return cmd_end;
+}
+
+#endif // FEAT_EVAL
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -1327,6 +1327,7 @@ write_viminfo_varlist(FILE *fp)
 		    case VAR_SPECIAL: s = "XPL"; break;
 
 		    case VAR_UNKNOWN:
+		    case VAR_VOID:
 		    case VAR_FUNC:
 		    case VAR_PARTIAL:
 		    case VAR_JOB: