To: vim_dev@googlegroups.com Subject: Patch 8.2.1685 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1685 Problem: Vim9: cannot declare a constant value. Solution: Introduce ":const!". Files: runtime/doc/vim9.txt, src/ex_cmds.h, src/vim9compile.c, src/vim9.h, src/vim9execute.c, src/evalvars.c, src/proto/evalvars.pro, src/errors.h, src/vim.h, src/eval.c, src/testdir/test_vim9_script.vim *** ../vim-8.2.1684/runtime/doc/vim9.txt 2020-08-12 21:34:43.266489468 +0200 --- runtime/doc/vim9.txt 2020-09-13 14:14:26.093731771 +0200 *************** *** 49,54 **** --- 49,55 ---- The Vim9 script syntax and semantics are used in: - a function defined with the `:def` command - a script file where the first command is `vim9script` + - an autocommand defined in the context of these When using `:function` in a Vim9 script file the legacy syntax is used. However, this can be confusing and is therefore discouraged. *************** *** 70,79 **** let count = 0 # number of occurrences The reason is that a double quote can also be the start of a string. In many ! places, especially halfway an expression with a line break, it's hard to tell ! what the meaning is, since both a string and a comment can be followed by ! arbitrary text. To avoid confusion only # comments are recognized. This is ! the same as in shell scripts and Python programs. In Vi # is a command to list text with numbers. In Vim9 script you can use `:number` for that. > --- 71,80 ---- let count = 0 # number of occurrences The reason is that a double quote can also be the start of a string. In many ! places, especially halfway through an expression with a line break, it's hard ! to tell what the meaning is, since both a string and a comment can be followed ! by arbitrary text. To avoid confusion only # comments are recognized. This ! is the same as in shell scripts and Python programs. In Vi # is a command to list text with numbers. In Vim9 script you can use `:number` for that. > *************** *** 92,100 **** Many errors are already found when compiling, before the function is executed. The syntax is strict, to enforce code that is easy to read and understand. ! Compilation is done when the function is first called, or when the ! `:defcompile` command is encountered in the script where the function was ! defined. `:disassemble` also compiles the function. `:def` has no options like `:function` does: "range", "abort", "dict" or "closure". A `:def` function always aborts on an error, does not get a range --- 93,105 ---- Many errors are already found when compiling, before the function is executed. The syntax is strict, to enforce code that is easy to read and understand. ! Compilation is done when: ! - the function is first called ! - when the `:defcompile` command is encountered in the script where the ! function was defined ! - `:disassemble` is used for the function. ! - a function that is compiled calls the function or uses it as a function ! reference `:def` has no options like `:function` does: "range", "abort", "dict" or "closure". A `:def` function always aborts on an error, does not get a range *************** *** 104,115 **** be used, type checking will then be done at runtime, like with legacy functions. ! Arguments are accessed by name, without "a:". There is no "a:" dictionary or ! "a:000" list. Just like any other language. Variable arguments are defined as the last argument, with a name and have a ! list type, similar to Typescript. For example, a list of numbers: > ! def MyFunc(...itemlist: list) for item in itemlist ... --- 109,120 ---- be used, type checking will then be done at runtime, like with legacy functions. ! Arguments are accessed by name, without "a:", just like any other language. ! There is no "a:" dictionary or "a:000" list. Variable arguments are defined as the last argument, with a name and have a ! list type, similar to TypeScript. For example, a list of numbers: > ! def MyFunc(...itemlist: list) for item in itemlist ... *************** *** 123,130 **** autoload script the "name#" prefix is sufficient. > def ThisFunction() # script-local def s:ThisFunction() # script-local ! def g:ThatFunction() # global ! def ThatFunction() # global if no local ThatFunction() def scriptname#function() # autoload When using `:function` or `:def` to specify a new function inside a function, --- 128,135 ---- autoload script the "name#" prefix is sufficient. > def ThisFunction() # script-local def s:ThisFunction() # script-local ! def g:ThatFunction() # global ! def ThatFunction() # global if no local ThatFunction() def scriptname#function() # autoload When using `:function` or `:def` to specify a new function inside a function, *************** *** 142,148 **** found in the script, either defined there or imported. Global functions and variables could be defined anywhere (good luck finding out where!). ! Global functions can be still be defined and deleted at nearly any time. In Vim9 script script-local functions are defined once when the script is sourced and cannot be deleted or replaced. --- 147,153 ---- found in the script, either defined there or imported. Global functions and variables could be defined anywhere (good luck finding out where!). ! Global functions can still be defined and deleted at nearly any time. In Vim9 script script-local functions are defined once when the script is sourced and cannot be deleted or replaced. *************** *** 168,174 **** else let inner = 0 endif ! echo inner " Error! The declaration must be done earlier: > let inner: number --- 173,179 ---- else let inner = 0 endif ! echo inner # Error! The declaration must be done earlier: > let inner: number *************** *** 185,191 **** let temp = 'temp' ... } ! echo temp " Error! An existing variable cannot be assigned to with `:let`, since that implies a declaration. Global, window, tab, buffer and Vim variables can only be used --- 190,199 ---- let temp = 'temp' ... } ! echo temp # Error! ! ! Declaring a variable with a type but without an initializer will initialize to ! zero, false or empty. An existing variable cannot be assigned to with `:let`, since that implies a declaration. Global, window, tab, buffer and Vim variables can only be used *************** *** 205,210 **** --- 213,252 ---- Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be used to repeat a `:substitute` command. + *vim9-const* + In legacy Vim script "const list = []" would make the variable "list" + immutable and also the value. Thus you cannot add items to the list. This + differs from what many languages do. Vim9 script does it like TypeScript: only + "list" is immutable, the value can be changed. + + One can use `:const!` to make both the variable and the value immutable. Use + this for composite structures that you want to make sure will not be modified. + + How this works: > + vim9script + const list = [1, 2] + list = [3, 4] # Error! + list[0] = 2 # OK + + const! LIST = [1, 2] + LIST = [3, 4] # Error! + LIST[0] = 2 # Error! + It is common to write constants as ALL_CAPS, but you don't have to. + + The constant only applies to the value itself, not what it refers to. > + cont females = ["Mary"] + const! NAMES = [["John", "Peter"], females] + NAMES[0] = ["Jack"] # Error! + NAMES[0][0] = ["Jack"] # Error! + NAMES[1] = ["Emma"] # Error! + Names[1][0] = "Emma" # OK, now females[0] == "Emma" + + Rationale: TypeScript has no way to make the value immutable. One can use + immutable types, but that quickly gets complicated for nested values. And + with a type cast the value can be made mutable again, which means there is no + guarantee the value won't change. Vim supports immutable values, in legacy + script this was done with `:lockvar`. But that is an extra statement and also + applies to nested values. Therefore the solution to use `:const!`. *E1092* Declaring more than one variable at a time, using the unpack notation, is *************** *** 217,223 **** Omitting :call and :eval ~ Functions can be called without `:call`: > ! writefile(lines, 'file') Using `:call` is still possible, but this is discouraged. A method call without `eval` is possible, so long as the start is an --- 259,265 ---- Omitting :call and :eval ~ Functions can be called without `:call`: > ! writefile(lines, 'file') Using `:call` is still possible, but this is discouraged. A method call without `eval` is possible, so long as the start is an *************** *** 232,238 **** 'foobar'->Process() ('foobar')->Process() ! In rare case there is ambiguity between a function name and an Ex command, prepend ":" to make clear you want to use the Ex command. For example, there is both the `:substitute` command and the `substitute()` function. When the line starts with `substitute(` this will use the function. Prepend a colon to --- 274,280 ---- 'foobar'->Process() ('foobar')->Process() ! In the rare case there is ambiguity between a function name and an Ex command, prepend ":" to make clear you want to use the Ex command. For example, there is both the `:substitute` command and the `substitute()` function. When the line starts with `substitute(` this will use the function. Prepend a colon to *************** *** 240,247 **** :substitute(pattern (replacement ( Note that while variables need to be defined before they can be used, ! functions can be called before being defined. This is required to be able ! have cyclic dependencies between functions. It is slightly less efficient, since the function has to be looked up by name. And a typo in the function name will only be found when the function is called. --- 282,289 ---- :substitute(pattern (replacement ( Note that while variables need to be defined before they can be used, ! functions can be called before being defined. This is required to allow ! for cyclic dependencies between functions. It is slightly less efficient, since the function has to be looked up by name. And a typo in the function name will only be found when the function is called. *************** *** 324,349 **** current function. - No line break is allowed in the LHS of an assignment. Specifically when unpacking a list |:let-unpack|. This is OK: > ! [var1, var2] = Func() < This does not work: > ! [var1, var2] = Func() - No line break is allowed in between arguments of an `:echo`, `:execute` and similar commands. This is OK: > ! echo [1, 2] [3, 4] < This does not work: > ! echo [1, 2] [3, 4] - No line break is allowed in the arguments of a lambda, between the "{" and "->". This is OK: > ! filter(list, {k, v -> v > 0}) < This does not work: > ! filter(list, {k, v -> v > 0}) --- 366,391 ---- current function. - No line break is allowed in the LHS of an assignment. Specifically when unpacking a list |:let-unpack|. This is OK: > ! [var1, var2] = Func() < This does not work: > ! [var1, var2] = Func() - No line break is allowed in between arguments of an `:echo`, `:execute` and similar commands. This is OK: > ! echo [1, 2] [3, 4] < This does not work: > ! echo [1, 2] [3, 4] - No line break is allowed in the arguments of a lambda, between the "{" and "->". This is OK: > ! filter(list, {k, v -> v > 0}) < This does not work: > ! filter(list, {k, v -> v > 0}) *************** *** 367,377 **** 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 must also be put before the # that starts a comment after a command: > let var = 234# Error! --- 409,419 ---- 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 must also be put before the # that starts a comment after a command: > let var = 234# Error! *************** *** 381,400 **** 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 ! 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. --- 423,442 ---- 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 ! call Func( ! \ arg # OK \ ) Conditions and expressions ~ ! Conditions and expressions are mostly working like they do in JavaScript. A difference is made where JavaScript does not work like most people expect. Specifically, an empty list is falsey. *************** *** 403,409 **** few exceptions. type TRUE when ~ ! bool v:true number non-zero float non-zero string non-empty --- 445,451 ---- few exceptions. type TRUE when ~ ! bool v:true or 1 number non-zero float non-zero string non-empty *************** *** 429,441 **** When using `..` for string concatenation arguments of simple types are always converted to string. > 'hello ' .. 123 == 'hello 123' ! 'hello ' .. v:true == 'hello true' Simple types are string, float, special and bool. For other types |string()| can be used. ! In Vim9 script one can use "true" for v:true and "false" for v:false. What to watch out for ~ *vim9-gotchas* --- 471,489 ---- When using `..` for string concatenation arguments of simple types are always converted to string. > 'hello ' .. 123 == 'hello 123' ! 'hello ' .. v:true == 'hello v:true' Simple types are string, float, special and bool. For other types |string()| can be used. ! *false* *true* In Vim9 script one can use "true" for v:true and "false" for v:false. + Indexing a string with [idx] or [idx, idx] uses character indexes instead of + byte indexes. Example: > + echo 'bár'[1] + In legacy script this results in the character 0xc3 (an illegal byte), in Vim9 + script this results in the string 'á'. + What to watch out for ~ *vim9-gotchas* *************** *** 444,459 **** be made. Here is a summary of what might be unexpected. Ex command ranges need to be prefixed with a colon. > ! -> " legacy Vim: shifts the previous line to the right ! ->func() " Vim9: method call in continuation line ! :-> " Vim9: shifts the previous line to the right ! %s/a/b " legacy Vim: substitute on all lines x = alongname ! % another " Vim9: line continuation without a backslash ! :%s/a/b " Vim9: substitute on all lines ! 'text'->func() " Vim9: method call ! :'t " legacy Vim: jump to mark m Some Ex commands can be confused with assignments in Vim9 script: > g:name = value # assignment --- 492,507 ---- be made. Here is a summary of what might be unexpected. Ex command ranges need to be prefixed with a colon. > ! -> # legacy Vim: shifts the previous line to the right ! ->func() # Vim9: method call in continuation line ! :-> # Vim9: shifts the previous line to the right ! %s/a/b # legacy Vim: substitute on all lines x = alongname ! % another # Vim9: line continuation without a backslash ! :%s/a/b # Vim9: substitute on all lines ! 'text'->func() # Vim9: method call ! :'t # legacy Vim: jump to mark m Some Ex commands can be confused with assignments in Vim9 script: > g:name = value # assignment *************** *** 473,479 **** if !has('feature') return endif ! use-feature " May give compilation error enddef For a workaround, split it in two functions: > func Maybe() --- 521,527 ---- if !has('feature') return endif ! use-feature # May give compilation error enddef For a workaround, split it in two functions: > func Maybe() *************** *** 486,491 **** --- 534,551 ---- use-feature enddef endif + Or put the unsupported code inside an `if` with a constant expression that + evaluates to false: > + def Maybe() + if has('feature') + use-feature + endif + enddef + Note that for unrecognized commands there is no check for "|" and a following + command. This will give an error for missing `endif`: > + def Maybe() + if has('feature') | use-feature | endif + enddef ============================================================================== *************** *** 494,500 **** 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`. --- 554,560 ---- 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`. *************** *** 533,539 **** variables can be accessed without the "s:" prefix. They must be defined before the function is compiled. If the script the function is defined in is legacy script, then script-local variables must be accessed with the "s:" ! prefix. *:defc* *:defcompile* :defc[ompile] Compile functions defined in the current script that --- 593,599 ---- variables can be accessed without the "s:" prefix. They must be defined before the function is compiled. If the script the function is defined in is legacy script, then script-local variables must be accessed with the "s:" ! prefix and they do not need to exist (they can be deleted any time). *:defc* *:defcompile* :defc[ompile] Compile functions defined in the current script that *************** *** 676,683 **** 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 The type of a list and dictionary comes from the common type of the values. If the values all have the same type, that type is used for the list or --- 736,743 ---- 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 The type of a list and dictionary comes from the common type of the values. If the values all have the same type, that type is used for the list or *************** *** 686,691 **** --- 746,767 ---- ['a', 'b', 'c'] list [1, 'x', 3] list + + Stricter type checking *type-checking* + + In legacy Vim script, where a number was expected, a string would be + automatically converted to a number. This was convenient for an actual number + such as "123", but leads to unexpected problems (but no error message) if the + string doesn't start with a number. Quite often this leads to hard-to-find + bugs. + + In Vim9 script this has been made stricter. In most places it works just as + before, if the value used matches the expected type. There will sometimes be + an error, thus breaking backwards compatibility. For example: + - Using a number other than 0 or 1 where a boolean is expected. *E1023* + - Using a string value when setting a number options. + - Using a number where a string is expected. *E1024* + ============================================================================== 5. Namespace, Import and Export *************** *** 697,702 **** --- 773,781 ---- the script is local, unless exported. Those exported items, and only those items, can then be imported in another script. + You can cheat by using the global namespace explicitly. We will assume here + that you don't do that. + Namespace ~ *:vim9script* *:vim9* *************** *** 759,764 **** --- 838,846 ---- to choose the name "That", but it is highly recommended to use the name of the script file to avoid confusion. + `:import` can also be used in legacy Vim script. The imported items still + become script-local, even when the "s:" prefix is not given. + 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 *************** *** 789,795 **** < 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" --- 871,877 ---- < This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen. ! 2. In the autoload 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" *************** *** 804,810 **** items and any private items. > vim9script let localVar = 'local' ! export def FilterFunc(arg: string): string ... < This goes in .../import/someother.vim. --- 886,892 ---- items and any private items. > vim9script let localVar = 'local' ! export def FilterFunc(arg: string): string ... < This goes in .../import/someother.vim. *************** *** 824,830 **** The :def command ~ ! Plugin writers have asked for a much faster Vim script. Investigation have shown that keeping the existing semantics of function calls make this close to impossible, because of the overhead involved with calling a function, setting up the local function scope and executing lines. There are many details that --- 906,912 ---- The :def command ~ ! Plugin writers have asked for a much faster Vim script. Investigations have shown that keeping the existing semantics of function calls make this close to impossible, because of the overhead involved with calling a function, setting up the local function scope and executing lines. There are many details that *************** *** 854,860 **** 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". --- 936,942 ---- 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 were used in Vim before, with some additions such as "void" and "bool". *************** *** 892,899 **** 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' --- 974,981 ---- 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' *************** *** 901,934 **** 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/Typescript, 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 --- 983,1039 ---- Expression evaluation was already close to what JavaScript and other languages are doing. Some details are unexpected and can be fixed. For example how the || and && operators work. Legacy Vim script: > ! let value = 44 ... ! let result = value || 0 # result == 1 ! Vim9 script works like JavaScript/TypeScript, keep the value: > ! let value = 44 ... ! let result = value || 0 # result == 44 ! There is no intention to completely match TypeScript syntax and semantics. We ! just want to take those parts that we can use for Vim and we expect Vim users ! will be happy with. TypeScript is a complex language with its own advantages ! and disadvantages. To get an idea of the disadvantages read the book: ! "JavaScript: The Good Parts". Or find the article "TypeScript: the good ! parts" and read the "Things to avoid" section. ! ! People used to other languages (Java, Python, etc.) will also find things in ! TypeScript that they do not like or do not understand. We'll try to avoid ! those things. ! ! Specific items from TypeScript we avoid: ! - Overloading "+", using it both for addition and string concatenation. This ! goes against legacy Vim script and often leads to mistakes. For that reason ! we will keep using ".." for string concatenation. Lua also uses ".." this ! way. And it allows for conversion to string for more values. ! - TypeScript can use an expression like "99 || 'yes'" in a condition, but ! cannot assign the value to a boolean. That is inconsistent and can be ! annoying. Vim recognizes an expression with && or || and allows using the ! result as a bool. ! - TypeScript considers an empty string as Falsy, but an empty list or dict as ! Truthy. That is inconsistent. In Vim an empty list and dict are also ! Falsy. ! - TypeScript has various "Readonly" types, which have limited usefulness, ! since a type cast can remove the immutable nature. Vim locks the value, ! which is more flexible, but is only checked at runtime. Import and Export ~ A problem of legacy Vim script is that by default all functions and variables are global. It is possible to make them script-local, but then they are not ! available in other scripts. This defies the concept of a package that only ! exports selected items and keeps the rest local. ! In Vim9 script a mechanism very similar to the JavaScript import and export mechanism is supported. It is a variant to the existing `:source` command that works like one would expect: - Instead of making everything global by default, everything is script-local, unless exported. ! - When importing a script the symbols that are imported are explicitly listed, ! avoiding name conflicts and failures if functionality is added later. - The mechanism allows for writing a big, long script with a very clear API: the exported function(s) and class(es). - By using relative paths loading can be much faster for an import inside of a *************** *** 940,966 **** When sourcing a Vim9 script from a legacy script, only the items defined globally can be used, not the exported items. Alternatives considered: - All the exported items become available as script-local items. This makes ! it uncontrollable what items get defined. - Use the exported items and make them global. Disadvantage is that it's then not possible to avoid name clashes in the global namespace. - Completely disallow sourcing a Vim9 script, require using `:import`. That makes it difficult to use scripts for testing, or sourcing them from the command line to try them out. 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 --- 1045,1072 ---- When sourcing a Vim9 script from a legacy script, only the items defined globally can be used, not the exported items. Alternatives considered: - All the exported items become available as script-local items. This makes ! it uncontrollable what items get defined and likely soon leads to trouble. - Use the exported items and make them global. Disadvantage is that it's then not possible to avoid name clashes in the global namespace. - Completely disallow sourcing a Vim9 script, require using `:import`. That makes it difficult to use scripts for testing, or sourcing them from the command line to try them out. + Note that you can also use `:import` in legacy Vim script, see above. Classes ~ Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But ! these interfaces 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 ! support for classes 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 *************** *** 968,974 **** 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. --- 1074,1080 ---- the use of dictionaries. The support of classes in Vim9 script is a "minimal common functionality" of ! class support in most languages. It works much like Java, which is the most popular programming language. *** ../vim-8.2.1684/src/ex_cmds.h 2020-08-20 15:02:38.536534973 +0200 --- src/ex_cmds.h 2020-09-14 19:28:10.623454833 +0200 *************** *** 398,404 **** EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_const, "const", ex_let, ! EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_copen, "copen", ex_copen, EX_RANGE|EX_COUNT|EX_TRLBAR, --- 398,404 ---- EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_const, "const", ex_let, ! EX_EXTRA|EX_BANG|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_copen, "copen", ex_copen, EX_RANGE|EX_COUNT|EX_TRLBAR, *** ../vim-8.2.1684/src/vim9compile.c 2020-09-14 18:15:05.513520702 +0200 --- src/vim9compile.c 2020-09-14 21:32:01.450118547 +0200 *************** *** 1109,1114 **** --- 1109,1128 ---- } /* + * Generate an ISN_LOCKCONST instruction. + */ + static int + generate_LOCKCONST(cctx_T *cctx) + { + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_LOCKCONST)) == NULL) + return FAIL; + return OK; + } + + /* * Generate an ISN_LOADS instruction. */ static int *************** *** 4342,4348 **** ufunc_T *ufunc; int r; ! if (*name_start == '!') { emsg(_(e_cannot_use_bang_with_nested_def)); return NULL; --- 4356,4362 ---- ufunc_T *ufunc; int r; ! if (eap->forceit) { emsg(_(e_cannot_use_bang_with_nested_def)); return NULL; *************** *** 5232,5237 **** --- 5246,5256 ---- } else { + if (is_decl && eap->forceit && cmdidx == CMD_const + && (dest == dest_script || dest == dest_local)) + // ":const! var": lock the value, but not referenced variables + generate_LOCKCONST(cctx); + switch (dest) { case dest_option: *************** *** 6362,6374 **** char_u *line = arg; linenr_T lnum; char *errormsg; ! int above = FALSE; - if (*arg == '!') - { - above = TRUE; - line = skipwhite(arg + 1); - } eap->regname = *line; if (eap->regname == '=') --- 6381,6388 ---- char_u *line = arg; linenr_T lnum; char *errormsg; ! int above = eap->forceit; eap->regname = *line; if (eap->regname == '=') *************** *** 6411,6417 **** if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE) { ! long argt = excmd_get_argt(eap->cmdidx); int usefilter = FALSE; has_expr = argt & (EX_XFILE | EX_EXPAND); --- 6425,6431 ---- if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE) { ! long argt = eap->argt; int usefilter = FALSE; has_expr = argt & (EX_XFILE | EX_EXPAND); *************** *** 6870,6877 **** } } - p = skipwhite(p); - if (cctx.ctx_had_return && ea.cmdidx != CMD_elseif && ea.cmdidx != CMD_else --- 6884,6889 ---- *************** *** 6886,6891 **** --- 6898,6915 ---- goto erret; } + p = skipwhite(p); + if (ea.cmdidx != CMD_SIZE + && ea.cmdidx != CMD_write && ea.cmdidx != CMD_read) + { + ea.argt = excmd_get_argt(ea.cmdidx); + if ((ea.argt & EX_BANG) && *p == '!') + { + ea.forceit = TRUE; + p = skipwhite(p + 1); + } + } + switch (ea.cmdidx) { case CMD_def: *************** *** 7309,7314 **** --- 7333,7339 ---- case ISN_LOADTDICT: case ISN_LOADV: case ISN_LOADWDICT: + case ISN_LOCKCONST: case ISN_MEMBER: case ISN_NEGATENR: case ISN_NEWDICT: *** ../vim-8.2.1684/src/vim9.h 2020-09-08 22:45:31.109504973 +0200 --- src/vim9.h 2020-09-14 19:52:16.758998049 +0200 *************** *** 58,63 **** --- 58,65 ---- ISN_UNLET, // unlet variable isn_arg.unlet.ul_name ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name + ISN_LOCKCONST, // lock constant value + // constants ISN_PUSHNR, // push number isn_arg.number ISN_PUSHBOOL, // push bool value isn_arg.number *** ../vim-8.2.1684/src/vim9execute.c 2020-09-14 16:50:01.751887481 +0200 --- src/vim9execute.c 2020-09-14 20:52:41.008589098 +0200 *************** *** 678,683 **** --- 678,698 ---- } /* + * Check if "lock" is VAR_LOCKED or VAR_FIXED. If so give an error and return + * TRUE. + */ + static int + error_if_locked(int lock, char *error) + { + if (lock & (VAR_LOCKED | VAR_FIXED)) + { + emsg(_(error)); + return TRUE; + } + return FALSE; + } + + /* * Store "tv" in variable "name". * This is for s: and g: variables. */ *************** *** 1455,1466 **** typval_T *tv_list = STACK_TV_BOT(-1); list_T *list = tv_list->vval.v_list; if (lidx < 0 && list->lv_len + lidx >= 0) // negative index is relative to the end lidx = list->lv_len + lidx; if (lidx < 0 || lidx > list->lv_len) { - SOURCING_LNUM = iptr->isn_lnum; semsg(_(e_listidx), lidx); goto on_error; } --- 1470,1481 ---- typval_T *tv_list = STACK_TV_BOT(-1); list_T *list = tv_list->vval.v_list; + SOURCING_LNUM = iptr->isn_lnum; if (lidx < 0 && list->lv_len + lidx >= 0) // negative index is relative to the end lidx = list->lv_len + lidx; if (lidx < 0 || lidx > list->lv_len) { semsg(_(e_listidx), lidx); goto on_error; } *************** *** 1469,1480 **** --- 1484,1501 ---- { listitem_T *li = list_find(list, lidx); + if (error_if_locked(li->li_tv.v_lock, + e_cannot_change_list_item)) + goto failed; // overwrite existing list item clear_tv(&li->li_tv); li->li_tv = *tv; } else { + if (error_if_locked(list->lv_lock, + e_cannot_change_list)) + goto failed; // append to list, only fails when out of memory if (list_append_tv(list, tv) == FAIL) goto failed; *************** *** 1495,1503 **** dict_T *dict = tv_dict->vval.v_dict; dictitem_T *di; if (dict == NULL) { - SOURCING_LNUM = iptr->isn_lnum; emsg(_(e_dictionary_not_set)); goto on_error; } --- 1516,1524 ---- dict_T *dict = tv_dict->vval.v_dict; dictitem_T *di; + SOURCING_LNUM = iptr->isn_lnum; if (dict == NULL) { emsg(_(e_dictionary_not_set)); goto on_error; } *************** *** 1507,1518 **** --- 1528,1545 ---- di = dict_find(dict, key, -1); if (di != NULL) { + if (error_if_locked(di->di_tv.v_lock, + e_cannot_change_dict_item)) + goto failed; // overwrite existing value clear_tv(&di->di_tv); di->di_tv = *tv; } else { + if (error_if_locked(dict->dv_lock, + e_cannot_change_dict)) + goto failed; // add to dict, only fails when out of memory if (dict_add_tv(dict, (char *)key, tv) == FAIL) goto failed; *************** *** 1603,1608 **** --- 1630,1639 ---- vim_unsetenv(iptr->isn_arg.unlet.ul_name); break; + case ISN_LOCKCONST: + item_lock(STACK_TV_BOT(-1), 100, TRUE, TRUE); + break; + // create a list from items on the stack; uses a single allocation // for the list header and the items case ISN_NEWLIST: *************** *** 3025,3030 **** --- 3056,3064 ---- iptr->isn_arg.unlet.ul_forceit ? "!" : "", iptr->isn_arg.unlet.ul_name); break; + case ISN_LOCKCONST: + smsg("%4d LOCKCONST", current); + break; case ISN_NEWLIST: smsg("%4d NEWLIST size %lld", current, (long long)(iptr->isn_arg.number)); *** ../vim-8.2.1684/src/evalvars.c 2020-09-14 18:15:05.513520702 +0200 --- src/evalvars.c 2020-09-14 21:05:23.090578606 +0200 *************** *** 173,179 **** static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op); static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie); static int do_lock_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie); - static void item_lock(typval_T *tv, int deep, int lock, int check_refcount); static void delete_var(hashtab_T *ht, hashitem_T *hi); static void list_one_var(dictitem_T *v, char *prefix, int *first); static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first); --- 173,178 ---- *************** *** 709,714 **** --- 708,715 ---- // detect Vim9 assignment without ":let" or ":const" if (eap->arg == eap->cmd) flags |= LET_NO_COMMAND; + if (eap->forceit) + flags |= LET_FORCEIT; argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE); if (argend == NULL) *************** *** 859,865 **** int copy, // copy values from "tv", don't move int semicolon, // from skip_var_list() int var_count, // from skip_var_list() ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *op) { char_u *arg = arg_start; --- 860,866 ---- int copy, // copy values from "tv", don't move int semicolon, // from skip_var_list() int var_count, // from skip_var_list() ! int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND char_u *op) { char_u *arg = arg_start; *************** *** 1214,1220 **** char_u *arg, // points to variable name typval_T *tv, // value to assign to variable int copy, // copy value from "tv" ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *endchars, // valid chars after variable name or NULL char_u *op) // "+", "-", "." or NULL { --- 1215,1221 ---- char_u *arg, // points to variable name typval_T *tv, // value to assign to variable int copy, // copy value from "tv" ! int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND char_u *endchars, // valid chars after variable name or NULL char_u *op) // "+", "-", "." or NULL { *************** *** 1741,1747 **** * When "check_refcount" is TRUE do not lock a list or dict with a reference * count larger than 1. */ ! static void item_lock(typval_T *tv, int deep, int lock, int check_refcount) { static int recurse = 0; --- 1742,1748 ---- * When "check_refcount" is TRUE do not lock a list or dict with a reference * count larger than 1. */ ! void item_lock(typval_T *tv, int deep, int lock, int check_refcount) { static int recurse = 0; *************** *** 2937,2943 **** type_T *type, typval_T *tv_arg, int copy, // make copy of value in "tv" ! int flags) // LET_IS_CONST and/or LET_NO_COMMAND { typval_T *tv = tv_arg; typval_T bool_tv; --- 2938,2944 ---- type_T *type, typval_T *tv_arg, int copy, // make copy of value in "tv" ! int flags) // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND { typval_T *tv = tv_arg; typval_T bool_tv; *************** *** 3124,3131 **** init_tv(tv); } ! // ":const var = val" locks the value, but not in Vim9 script ! if ((flags & LET_IS_CONST) && !vim9script) // Like :lockvar! name: lock the value and what it contains, but only // if the reference count is up to one. That locks only literal // values. --- 3125,3132 ---- init_tv(tv); } ! // ":const var = val" locks the value; in Vim9 script only with ":const!" ! if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT))) // Like :lockvar! name: lock the value and what it contains, but only // if the reference count is up to one. That locks only literal // values. *** ../vim-8.2.1684/src/proto/evalvars.pro 2020-08-08 15:45:58.233358630 +0200 --- src/proto/evalvars.pro 2020-09-14 19:57:26.670034975 +0200 *************** *** 23,28 **** --- 23,29 ---- void ex_lockvar(exarg_T *eap); void ex_unletlock(exarg_T *eap, char_u *argstart, int deep, int glv_flags, int (*callback)(lval_T *, char_u *, exarg_T *, int, void *), void *cookie); int do_unlet(char_u *name, int forceit); + void item_lock(typval_T *tv, int deep, int lock, int check_refcount); void del_menutrans_vars(void); char_u *get_user_var_name(expand_T *xp, int idx); char *get_var_special_name(int nr); *************** *** 65,71 **** 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, 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_wrong_func_name(char_u *name, int new_var); --- 66,72 ---- 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, type_T *type, typval_T *tv_arg, 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_wrong_func_name(char_u *name, int new_var); *** ../vim-8.2.1684/src/errors.h 2020-09-10 22:27:57.805094402 +0200 --- src/errors.h 2020-09-14 20:42:44.193887133 +0200 *************** *** 258,261 **** --- 258,269 ---- INIT(= N_("E1116: assert_fails() fifth argument must be a string")); EXTERN char e_cannot_use_bang_with_nested_def[] INIT(= N_("E1117: Cannot use ! with nested :def")); + EXTERN char e_cannot_change_list[] + INIT(= N_("E1118: Cannot change list")); + EXTERN char e_cannot_change_list_item[] + INIT(= N_("E1119: Cannot change list item")); + EXTERN char e_cannot_change_dict[] + INIT(= N_("E1120: Cannot change dict")); + EXTERN char e_cannot_change_dict_item[] + INIT(= N_("E1121: Cannot change dict item")); #endif *** ../vim-8.2.1684/src/vim.h 2020-09-10 19:25:01.608194714 +0200 --- src/vim.h 2020-09-14 21:03:08.234946279 +0200 *************** *** 2136,2142 **** // 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 --- 2136,2143 ---- // Flags for assignment functions. #define LET_IS_CONST 1 // ":const" ! #define LET_FORCEIT 2 // ":const!" (LET_IS_CONST is also set) ! #define LET_NO_COMMAND 4 // "var = expr" without ":let" or ":const" #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff *** ../vim-8.2.1684/src/eval.c 2020-09-12 22:09:55.891607873 +0200 --- src/eval.c 2020-09-14 21:03:36.662869036 +0200 *************** *** 1200,1206 **** char_u *endp, typval_T *rettv, int copy, ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *op) { int cc; --- 1200,1206 ---- char_u *endp, typval_T *rettv, int copy, ! int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND char_u *op) { int cc; *** ../vim-8.2.1684/src/testdir/test_vim9_script.vim 2020-09-14 18:15:05.513520702 +0200 --- src/testdir/test_vim9_script.vim 2020-09-14 21:22:00.255806340 +0200 *************** *** 828,837 **** --- 828,877 ---- let lines =<< trim END const list = [1, 2, 3] list[0] = 4 + list->assert_equal([4, 2, 3]) + const! other = [5, 6, 7] + other->assert_equal([5, 6, 7]) END CheckDefAndScriptSuccess(lines) enddef + def Test_const_bang() + let lines =<< trim END + const! var = 234 + var = 99 + END + CheckDefExecFailure(lines, 'E1018:', 2) + CheckScriptFailure(['vim9script'] + lines, 'E46:', 3) + + lines =<< trim END + const! ll = [2, 3, 4] + ll[0] = 99 + END + CheckDefExecFailure(lines, 'E1119:', 2) + CheckScriptFailure(['vim9script'] + lines, 'E741:', 3) + + lines =<< trim END + const! ll = [2, 3, 4] + ll[3] = 99 + END + CheckDefExecFailure(lines, 'E1118:', 2) + CheckScriptFailure(['vim9script'] + lines, 'E684:', 3) + + lines =<< trim END + const! dd = #{one: 1, two: 2} + dd["one"] = 99 + END + CheckDefExecFailure(lines, 'E1121:', 2) + CheckScriptFailure(['vim9script'] + lines, 'E741:', 3) + + lines =<< trim END + const! dd = #{one: 1, two: 2} + dd["three"] = 99 + END + CheckDefExecFailure(lines, 'E1120:') + CheckScriptFailure(['vim9script'] + lines, 'E741:', 3) + enddef + def Test_range_no_colon() CheckDefFailure(['%s/a/b/'], 'E1050:') CheckDefFailure(['+ s/a/b/'], 'E1050:') *** ../vim-8.2.1684/src/version.c 2020-09-14 19:11:41.698381689 +0200 --- src/version.c 2020-09-14 21:23:02.539631708 +0200 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 1685, /**/ -- Living on Earth includes an annual free trip around the Sun. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///