Skip to content

Commit 0b4c66c

Browse files
committed
patch 8.2.1685: Vim9: cannot declare a constant value
Problem: Vim9: cannot declare a constant value. Solution: Introduce ":const!".
1 parent efd5d8a commit 0b4c66c

12 files changed

Lines changed: 204 additions & 38 deletions

File tree

runtime/doc/vim9.txt

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*vim9.txt* For Vim version 8.2. Last change: 2020 Sep 07
1+
*vim9.txt* For Vim version 8.2. Last change: 2020 Sep 13
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -192,6 +192,9 @@ To intentionally avoid a variable being available later, a block can be used:
192192
}
193193
echo temp # Error!
194194
195+
Declaring a variable with a type but without an initializer will initialize to
196+
zero, false or empty.
197+
195198
An existing variable cannot be assigned to with `:let`, since that implies a
196199
declaration. Global, window, tab, buffer and Vim variables can only be used
197200
without `:let`, because they are not really declared, they can also be deleted
@@ -210,6 +213,40 @@ at the script level. >
210213
211214
Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be
212215
used to repeat a `:substitute` command.
216+
*vim9-const*
217+
In legacy Vim script "const list = []" would make the variable "list"
218+
immutable and also the value. Thus you cannot add items to the list. This
219+
differs from what many languages do. Vim9 script does it like TypeScript: only
220+
"list" is immutable, the value can be changed.
221+
222+
One can use `:const!` to make both the variable and the value immutable. Use
223+
this for composite structures that you want to make sure will not be modified.
224+
225+
How this works: >
226+
vim9script
227+
const list = [1, 2]
228+
list = [3, 4] # Error!
229+
list[0] = 2 # OK
230+
231+
const! LIST = [1, 2]
232+
LIST = [3, 4] # Error!
233+
LIST[0] = 2 # Error!
234+
It is common to write constants as ALL_CAPS, but you don't have to.
235+
236+
The constant only applies to the value itself, not what it refers to. >
237+
cont females = ["Mary"]
238+
const! NAMES = [["John", "Peter"], females]
239+
NAMES[0] = ["Jack"] # Error!
240+
NAMES[0][0] = ["Jack"] # Error!
241+
NAMES[1] = ["Emma"] # Error!
242+
Names[1][0] = "Emma" # OK, now females[0] == "Emma"
243+
244+
Rationale: TypeScript has no way to make the value immutable. One can use
245+
immutable types, but that quickly gets complicated for nested values. And
246+
with a type cast the value can be made mutable again, which means there is no
247+
guarantee the value won't change. Vim supports immutable values, in legacy
248+
script this was done with `:lockvar`. But that is an extra statement and also
249+
applies to nested values. Therefore the solution to use `:const!`.
213250

214251
*E1092*
215252
Declaring more than one variable at a time, using the unpack notation, is
@@ -408,7 +445,7 @@ for using a list or job. This is very much like JavaScript, but there are a
408445
few exceptions.
409446

410447
type TRUE when ~
411-
bool v:true
448+
bool v:true or 1
412449
number non-zero
413450
float non-zero
414451
string non-empty
@@ -946,26 +983,41 @@ declarations: >
946983
Expression evaluation was already close to what JavaScript and other languages
947984
are doing. Some details are unexpected and can be fixed. For example how the
948985
|| and && operators work. Legacy Vim script: >
949-
let result = 44
986+
let value = 44
950987
...
951-
return result || 0 # returns 1
988+
let result = value || 0 # result == 1
952989
953990
Vim9 script works like JavaScript/TypeScript, keep the value: >
954-
let result = 44
991+
let value = 44
955992
...
956-
return result || 0 # returns 44
957-
958-
On the other hand, overloading "+" to use both for addition and string
959-
concatenation goes against legacy Vim script and often leads to mistakes.
960-
For that reason we will keep using ".." for string concatenation. Lua also
961-
uses ".." this way.
993+
let result = value || 0 # result == 44
962994
963995
There is no intention to completely match TypeScript syntax and semantics. We
964996
just want to take those parts that we can use for Vim and we expect Vim users
965-
are happy with. TypeScript is a complex language with its own advantages and
966-
disadvantages. People used to other languages (Java, Python, etc.) will also
967-
find things in TypeScript that they do not like or do not understand. We'll
968-
try to avoid those things.
997+
will be happy with. TypeScript is a complex language with its own advantages
998+
and disadvantages. To get an idea of the disadvantages read the book:
999+
"JavaScript: The Good Parts". Or find the article "TypeScript: the good
1000+
parts" and read the "Things to avoid" section.
1001+
1002+
People used to other languages (Java, Python, etc.) will also find things in
1003+
TypeScript that they do not like or do not understand. We'll try to avoid
1004+
those things.
1005+
1006+
Specific items from TypeScript we avoid:
1007+
- Overloading "+", using it both for addition and string concatenation. This
1008+
goes against legacy Vim script and often leads to mistakes. For that reason
1009+
we will keep using ".." for string concatenation. Lua also uses ".." this
1010+
way. And it allows for conversion to string for more values.
1011+
- TypeScript can use an expression like "99 || 'yes'" in a condition, but
1012+
cannot assign the value to a boolean. That is inconsistent and can be
1013+
annoying. Vim recognizes an expression with && or || and allows using the
1014+
result as a bool.
1015+
- TypeScript considers an empty string as Falsy, but an empty list or dict as
1016+
Truthy. That is inconsistent. In Vim an empty list and dict are also
1017+
Falsy.
1018+
- TypeScript has various "Readonly" types, which have limited usefulness,
1019+
since a type cast can remove the immutable nature. Vim locks the value,
1020+
which is more flexible, but is only checked at runtime.
9691021

9701022

9711023
Import and Export ~

src/errors.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,12 @@ EXTERN char e_assert_fails_fifth_argument[]
258258
INIT(= N_("E1116: assert_fails() fifth argument must be a string"));
259259
EXTERN char e_cannot_use_bang_with_nested_def[]
260260
INIT(= N_("E1117: Cannot use ! with nested :def"));
261+
EXTERN char e_cannot_change_list[]
262+
INIT(= N_("E1118: Cannot change list"));
263+
EXTERN char e_cannot_change_list_item[]
264+
INIT(= N_("E1119: Cannot change list item"));
265+
EXTERN char e_cannot_change_dict[]
266+
INIT(= N_("E1120: Cannot change dict"));
267+
EXTERN char e_cannot_change_dict_item[]
268+
INIT(= N_("E1121: Cannot change dict item"));
261269
#endif

src/eval.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ set_var_lval(
12001200
char_u *endp,
12011201
typval_T *rettv,
12021202
int copy,
1203-
int flags, // LET_IS_CONST and/or LET_NO_COMMAND
1203+
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
12041204
char_u *op)
12051205
{
12061206
int cc;

src/evalvars.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first);
173173
static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op);
174174
static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
175175
static int do_lock_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
176-
static void item_lock(typval_T *tv, int deep, int lock, int check_refcount);
177176
static void delete_var(hashtab_T *ht, hashitem_T *hi);
178177
static void list_one_var(dictitem_T *v, char *prefix, int *first);
179178
static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first);
@@ -709,6 +708,8 @@ ex_let(exarg_T *eap)
709708
// detect Vim9 assignment without ":let" or ":const"
710709
if (eap->arg == eap->cmd)
711710
flags |= LET_NO_COMMAND;
711+
if (eap->forceit)
712+
flags |= LET_FORCEIT;
712713

713714
argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
714715
if (argend == NULL)
@@ -859,7 +860,7 @@ ex_let_vars(
859860
int copy, // copy values from "tv", don't move
860861
int semicolon, // from skip_var_list()
861862
int var_count, // from skip_var_list()
862-
int flags, // LET_IS_CONST and/or LET_NO_COMMAND
863+
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
863864
char_u *op)
864865
{
865866
char_u *arg = arg_start;
@@ -1214,7 +1215,7 @@ ex_let_one(
12141215
char_u *arg, // points to variable name
12151216
typval_T *tv, // value to assign to variable
12161217
int copy, // copy value from "tv"
1217-
int flags, // LET_IS_CONST and/or LET_NO_COMMAND
1218+
int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
12181219
char_u *endchars, // valid chars after variable name or NULL
12191220
char_u *op) // "+", "-", "." or NULL
12201221
{
@@ -1741,7 +1742,7 @@ do_lock_var(
17411742
* When "check_refcount" is TRUE do not lock a list or dict with a reference
17421743
* count larger than 1.
17431744
*/
1744-
static void
1745+
void
17451746
item_lock(typval_T *tv, int deep, int lock, int check_refcount)
17461747
{
17471748
static int recurse = 0;
@@ -2937,7 +2938,7 @@ set_var_const(
29372938
type_T *type,
29382939
typval_T *tv_arg,
29392940
int copy, // make copy of value in "tv"
2940-
int flags) // LET_IS_CONST and/or LET_NO_COMMAND
2941+
int flags) // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
29412942
{
29422943
typval_T *tv = tv_arg;
29432944
typval_T bool_tv;
@@ -3124,8 +3125,8 @@ set_var_const(
31243125
init_tv(tv);
31253126
}
31263127

3127-
// ":const var = val" locks the value, but not in Vim9 script
3128-
if ((flags & LET_IS_CONST) && !vim9script)
3128+
// ":const var = val" locks the value; in Vim9 script only with ":const!"
3129+
if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT)))
31293130
// Like :lockvar! name: lock the value and what it contains, but only
31303131
// if the reference count is up to one. That locks only literal
31313132
// values.

src/ex_cmds.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ EXCMD(CMD_confirm, "confirm", ex_wrongmodifier,
398398
EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK,
399399
ADDR_NONE),
400400
EXCMD(CMD_const, "const", ex_let,
401-
EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
401+
EX_EXTRA|EX_BANG|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
402402
ADDR_NONE),
403403
EXCMD(CMD_copen, "copen", ex_copen,
404404
EX_RANGE|EX_COUNT|EX_TRLBAR,

src/proto/evalvars.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ void ex_unlet(exarg_T *eap);
2323
void ex_lockvar(exarg_T *eap);
2424
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);
2525
int do_unlet(char_u *name, int forceit);
26+
void item_lock(typval_T *tv, int deep, int lock, int check_refcount);
2627
void del_menutrans_vars(void);
2728
char_u *get_user_var_name(expand_T *xp, int idx);
2829
char *get_var_special_name(int nr);
@@ -65,7 +66,7 @@ void unref_var_dict(dict_T *dict);
6566
void vars_clear(hashtab_T *ht);
6667
void vars_clear_ext(hashtab_T *ht, int free_val);
6768
void set_var(char_u *name, typval_T *tv, int copy);
68-
void set_var_const(char_u *name, type_T *type, typval_T *tv, int copy, int flags);
69+
void set_var_const(char_u *name, type_T *type, typval_T *tv_arg, int copy, int flags);
6970
int var_check_ro(int flags, char_u *name, int use_gettext);
7071
int var_check_fixed(int flags, char_u *name, int use_gettext);
7172
int var_wrong_func_name(char_u *name, int new_var);

src/testdir/test_vim9_script.vim

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,10 +828,50 @@ def Test_const()
828828
let lines =<< trim END
829829
const list = [1, 2, 3]
830830
list[0] = 4
831+
list->assert_equal([4, 2, 3])
832+
const! other = [5, 6, 7]
833+
other->assert_equal([5, 6, 7])
831834
END
832835
CheckDefAndScriptSuccess(lines)
833836
enddef
834837

838+
def Test_const_bang()
839+
let lines =<< trim END
840+
const! var = 234
841+
var = 99
842+
END
843+
CheckDefExecFailure(lines, 'E1018:', 2)
844+
CheckScriptFailure(['vim9script'] + lines, 'E46:', 3)
845+
846+
lines =<< trim END
847+
const! ll = [2, 3, 4]
848+
ll[0] = 99
849+
END
850+
CheckDefExecFailure(lines, 'E1119:', 2)
851+
CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
852+
853+
lines =<< trim END
854+
const! ll = [2, 3, 4]
855+
ll[3] = 99
856+
END
857+
CheckDefExecFailure(lines, 'E1118:', 2)
858+
CheckScriptFailure(['vim9script'] + lines, 'E684:', 3)
859+
860+
lines =<< trim END
861+
const! dd = #{one: 1, two: 2}
862+
dd["one"] = 99
863+
END
864+
CheckDefExecFailure(lines, 'E1121:', 2)
865+
CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
866+
867+
lines =<< trim END
868+
const! dd = #{one: 1, two: 2}
869+
dd["three"] = 99
870+
END
871+
CheckDefExecFailure(lines, 'E1120:')
872+
CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
873+
enddef
874+
835875
def Test_range_no_colon()
836876
CheckDefFailure(['%s/a/b/'], 'E1050:')
837877
CheckDefFailure(['+ s/a/b/'], 'E1050:')

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
1685,
753755
/**/
754756
1684,
755757
/**/

src/vim.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2136,7 +2136,8 @@ typedef enum {
21362136

21372137
// Flags for assignment functions.
21382138
#define LET_IS_CONST 1 // ":const"
2139-
#define LET_NO_COMMAND 2 // "var = expr" without ":let" or ":const"
2139+
#define LET_FORCEIT 2 // ":const!" (LET_IS_CONST is also set)
2140+
#define LET_NO_COMMAND 4 // "var = expr" without ":let" or ":const"
21402141

21412142
#include "ex_cmds.h" // Ex command defines
21422143
#include "spell.h" // spell checking stuff

src/vim9.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ typedef enum {
5858
ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
5959
ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name
6060

61+
ISN_LOCKCONST, // lock constant value
62+
6163
// constants
6264
ISN_PUSHNR, // push number isn_arg.number
6365
ISN_PUSHBOOL, // push bool value isn_arg.number

0 commit comments

Comments
 (0)