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

patch 8.2.1685: Vim9: cannot declare a constant value

Problem:    Vim9: cannot declare a constant value.
Solution:   Introduce ":const!".
This commit is contained in:
Bram Moolenaar 2020-09-14 21:39:44 +02:00
parent efd5d8a967
commit 0b4c66c67a
12 changed files with 204 additions and 38 deletions

View File

@ -1,4 +1,4 @@
*vim9.txt* For Vim version 8.2. Last change: 2020 Sep 07 *vim9.txt* For Vim version 8.2. Last change: 2020 Sep 13
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -192,6 +192,9 @@ To intentionally avoid a variable being available later, a block can be used:
} }
echo temp # Error! 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 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 declaration. Global, window, tab, buffer and Vim variables can only be used
without `:let`, because they are not really declared, they can also be deleted without `:let`, because they are not really declared, they can also be deleted
@ -210,6 +213,40 @@ at the script level. >
Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be
used to repeat a `:substitute` command. used to repeat a `:substitute` command.
*vim9-const*
In legacy Vim script "const list = []" would make the variable "list"
immutable and also the value. Thus you cannot add items to the list. This
differs from what many languages do. Vim9 script does it like TypeScript: only
"list" is immutable, the value can be changed.
One can use `:const!` to make both the variable and the value immutable. Use
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* *E1092*
Declaring more than one variable at a time, using the unpack notation, is Declaring more than one variable at a time, using the unpack notation, is
@ -408,7 +445,7 @@ for using a list or job. This is very much like JavaScript, but there are a
few exceptions. few exceptions.
type TRUE when ~ type TRUE when ~
bool v:true bool v:true or 1
number non-zero number non-zero
float non-zero float non-zero
string non-empty string non-empty
@ -946,26 +983,41 @@ declarations: >
Expression evaluation was already close to what JavaScript and other languages 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 are doing. Some details are unexpected and can be fixed. For example how the
|| and && operators work. Legacy Vim script: > || and && operators work. Legacy Vim script: >
let result = 44 let value = 44
... ...
return result || 0 # returns 1 let result = value || 0 # result == 1
Vim9 script works like JavaScript/TypeScript, keep the value: > Vim9 script works like JavaScript/TypeScript, keep the value: >
let result = 44 let value = 44
... ...
return result || 0 # returns 44 let result = value || 0 # result == 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.
There is no intention to completely match TypeScript syntax and semantics. We 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 just want to take those parts that we can use for Vim and we expect Vim users
are happy with. TypeScript is a complex language with its own advantages and will be happy with. TypeScript is a complex language with its own advantages
disadvantages. People used to other languages (Java, Python, etc.) will also and disadvantages. To get an idea of the disadvantages read the book:
find things in TypeScript that they do not like or do not understand. We'll "JavaScript: The Good Parts". Or find the article "TypeScript: the good
try to avoid those things. 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 ~ Import and Export ~

View File

@ -258,4 +258,12 @@ EXTERN char e_assert_fails_fifth_argument[]
INIT(= N_("E1116: assert_fails() fifth argument must be a string")); INIT(= N_("E1116: assert_fails() fifth argument must be a string"));
EXTERN char e_cannot_use_bang_with_nested_def[] EXTERN char e_cannot_use_bang_with_nested_def[]
INIT(= N_("E1117: Cannot use ! 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 #endif

View File

@ -1200,7 +1200,7 @@ set_var_lval(
char_u *endp, char_u *endp,
typval_T *rettv, typval_T *rettv,
int copy, int copy,
int flags, // LET_IS_CONST and/or LET_NO_COMMAND int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
char_u *op) char_u *op)
{ {
int cc; int cc;

View File

@ -173,7 +173,6 @@ 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 flags, 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 int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie); 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 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 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(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); 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)
// detect Vim9 assignment without ":let" or ":const" // detect Vim9 assignment without ":let" or ":const"
if (eap->arg == eap->cmd) if (eap->arg == eap->cmd)
flags |= LET_NO_COMMAND; flags |= LET_NO_COMMAND;
if (eap->forceit)
flags |= LET_FORCEIT;
argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE); argend = skip_var_list(arg, TRUE, &var_count, &semicolon, FALSE);
if (argend == NULL) if (argend == NULL)
@ -859,7 +860,7 @@ ex_let_vars(
int copy, // copy values from "tv", don't move int copy, // copy values from "tv", don't move
int semicolon, // from skip_var_list() int semicolon, // from skip_var_list()
int var_count, // from skip_var_list() int var_count, // from skip_var_list()
int flags, // LET_IS_CONST and/or LET_NO_COMMAND int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
char_u *op) char_u *op)
{ {
char_u *arg = arg_start; char_u *arg = arg_start;
@ -1214,7 +1215,7 @@ ex_let_one(
char_u *arg, // points to variable name char_u *arg, // points to variable name
typval_T *tv, // value to assign to variable typval_T *tv, // value to assign to variable
int copy, // copy value from "tv" int copy, // copy value from "tv"
int flags, // LET_IS_CONST and/or LET_NO_COMMAND int flags, // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
char_u *endchars, // valid chars after variable name or NULL char_u *endchars, // valid chars after variable name or NULL
char_u *op) // "+", "-", "." or NULL char_u *op) // "+", "-", "." or NULL
{ {
@ -1741,7 +1742,7 @@ do_lock_var(
* When "check_refcount" is TRUE do not lock a list or dict with a reference * When "check_refcount" is TRUE do not lock a list or dict with a reference
* count larger than 1. * count larger than 1.
*/ */
static void void
item_lock(typval_T *tv, int deep, int lock, int check_refcount) item_lock(typval_T *tv, int deep, int lock, int check_refcount)
{ {
static int recurse = 0; static int recurse = 0;
@ -2937,7 +2938,7 @@ set_var_const(
type_T *type, type_T *type,
typval_T *tv_arg, typval_T *tv_arg,
int copy, // make copy of value in "tv" int copy, // make copy of value in "tv"
int flags) // LET_IS_CONST and/or LET_NO_COMMAND int flags) // LET_IS_CONST, LET_FORCEIT, LET_NO_COMMAND
{ {
typval_T *tv = tv_arg; typval_T *tv = tv_arg;
typval_T bool_tv; typval_T bool_tv;
@ -3124,8 +3125,8 @@ set_var_const(
init_tv(tv); init_tv(tv);
} }
// ":const var = val" locks the value, but not in Vim9 script // ":const var = val" locks the value; in Vim9 script only with ":const!"
if ((flags & LET_IS_CONST) && !vim9script) if ((flags & LET_IS_CONST) && (!vim9script || (flags & LET_FORCEIT)))
// Like :lockvar! name: lock the value and what it contains, but only // Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal // if the reference count is up to one. That locks only literal
// values. // values.

View File

@ -398,7 +398,7 @@ EXCMD(CMD_confirm, "confirm", ex_wrongmodifier,
EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK, EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),
EXCMD(CMD_const, "const", ex_let, EXCMD(CMD_const, "const", ex_let,
EX_EXTRA|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK, EX_EXTRA|EX_BANG|EX_NOTRLCOM|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),
EXCMD(CMD_copen, "copen", ex_copen, EXCMD(CMD_copen, "copen", ex_copen,
EX_RANGE|EX_COUNT|EX_TRLBAR, EX_RANGE|EX_COUNT|EX_TRLBAR,

View File

@ -23,6 +23,7 @@ void ex_unlet(exarg_T *eap);
void ex_lockvar(exarg_T *eap); 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); 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); 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); void del_menutrans_vars(void);
char_u *get_user_var_name(expand_T *xp, int idx); char_u *get_user_var_name(expand_T *xp, int idx);
char *get_var_special_name(int nr); char *get_var_special_name(int nr);
@ -65,7 +66,7 @@ void unref_var_dict(dict_T *dict);
void vars_clear(hashtab_T *ht); void vars_clear(hashtab_T *ht);
void vars_clear_ext(hashtab_T *ht, int free_val); void vars_clear_ext(hashtab_T *ht, int free_val);
void set_var(char_u *name, typval_T *tv, int copy); 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); 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_ro(int flags, char_u *name, int use_gettext);
int var_check_fixed(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); int var_wrong_func_name(char_u *name, int new_var);

View File

@ -828,10 +828,50 @@ def Test_const()
let lines =<< trim END let lines =<< trim END
const list = [1, 2, 3] const list = [1, 2, 3]
list[0] = 4 list[0] = 4
list->assert_equal([4, 2, 3])
const! other = [5, 6, 7]
other->assert_equal([5, 6, 7])
END END
CheckDefAndScriptSuccess(lines) CheckDefAndScriptSuccess(lines)
enddef 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() def Test_range_no_colon()
CheckDefFailure(['%s/a/b/'], 'E1050:') CheckDefFailure(['%s/a/b/'], 'E1050:')
CheckDefFailure(['+ s/a/b/'], 'E1050:') CheckDefFailure(['+ s/a/b/'], 'E1050:')

View File

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

View File

@ -2136,7 +2136,8 @@ typedef enum {
// Flags for assignment functions. // Flags for assignment functions.
#define LET_IS_CONST 1 // ":const" #define LET_IS_CONST 1 // ":const"
#define LET_NO_COMMAND 2 // "var = expr" without ":let" or ":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 "ex_cmds.h" // Ex command defines
#include "spell.h" // spell checking stuff #include "spell.h" // spell checking stuff

View File

@ -58,6 +58,8 @@ typedef enum {
ISN_UNLET, // unlet variable isn_arg.unlet.ul_name ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name
ISN_LOCKCONST, // lock constant value
// constants // constants
ISN_PUSHNR, // push number isn_arg.number ISN_PUSHNR, // push number isn_arg.number
ISN_PUSHBOOL, // push bool value isn_arg.number ISN_PUSHBOOL, // push bool value isn_arg.number

View File

@ -1108,6 +1108,20 @@ generate_UNLET(cctx_T *cctx, isntype_T isn_type, char_u *name, int forceit)
return OK; return OK;
} }
/*
* 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. * Generate an ISN_LOADS instruction.
*/ */
@ -4342,7 +4356,7 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
ufunc_T *ufunc; ufunc_T *ufunc;
int r; int r;
if (*name_start == '!') if (eap->forceit)
{ {
emsg(_(e_cannot_use_bang_with_nested_def)); emsg(_(e_cannot_use_bang_with_nested_def));
return NULL; return NULL;
@ -5232,6 +5246,11 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
} }
else 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) switch (dest)
{ {
case dest_option: case dest_option:
@ -6362,13 +6381,8 @@ compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx)
char_u *line = arg; char_u *line = arg;
linenr_T lnum; linenr_T lnum;
char *errormsg; char *errormsg;
int above = FALSE; int above = eap->forceit;
if (*arg == '!')
{
above = TRUE;
line = skipwhite(arg + 1);
}
eap->regname = *line; eap->regname = *line;
if (eap->regname == '=') if (eap->regname == '=')
@ -6411,7 +6425,7 @@ compile_exec(char_u *line, exarg_T *eap, cctx_T *cctx)
if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE) if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE)
{ {
long argt = excmd_get_argt(eap->cmdidx); long argt = eap->argt;
int usefilter = FALSE; int usefilter = FALSE;
has_expr = argt & (EX_XFILE | EX_EXPAND); has_expr = argt & (EX_XFILE | EX_EXPAND);
@ -6870,8 +6884,6 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
} }
} }
p = skipwhite(p);
if (cctx.ctx_had_return if (cctx.ctx_had_return
&& ea.cmdidx != CMD_elseif && ea.cmdidx != CMD_elseif
&& ea.cmdidx != CMD_else && ea.cmdidx != CMD_else
@ -6886,6 +6898,18 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
goto erret; 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) switch (ea.cmdidx)
{ {
case CMD_def: case CMD_def:
@ -7309,6 +7333,7 @@ delete_instr(isn_T *isn)
case ISN_LOADTDICT: case ISN_LOADTDICT:
case ISN_LOADV: case ISN_LOADV:
case ISN_LOADWDICT: case ISN_LOADWDICT:
case ISN_LOCKCONST:
case ISN_MEMBER: case ISN_MEMBER:
case ISN_NEGATENR: case ISN_NEGATENR:
case ISN_NEWDICT: case ISN_NEWDICT:

View File

@ -677,6 +677,21 @@ call_partial(typval_T *tv, int argcount_arg, ectx_T *ectx)
return OK; return OK;
} }
/*
* 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". * Store "tv" in variable "name".
* This is for s: and g: variables. * This is for s: and g: variables.
@ -1455,12 +1470,12 @@ call_def_function(
typval_T *tv_list = STACK_TV_BOT(-1); typval_T *tv_list = STACK_TV_BOT(-1);
list_T *list = tv_list->vval.v_list; list_T *list = tv_list->vval.v_list;
SOURCING_LNUM = iptr->isn_lnum;
if (lidx < 0 && list->lv_len + lidx >= 0) if (lidx < 0 && list->lv_len + lidx >= 0)
// negative index is relative to the end // negative index is relative to the end
lidx = list->lv_len + lidx; lidx = list->lv_len + lidx;
if (lidx < 0 || lidx > list->lv_len) if (lidx < 0 || lidx > list->lv_len)
{ {
SOURCING_LNUM = iptr->isn_lnum;
semsg(_(e_listidx), lidx); semsg(_(e_listidx), lidx);
goto on_error; goto on_error;
} }
@ -1469,12 +1484,18 @@ call_def_function(
{ {
listitem_T *li = list_find(list, lidx); 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 // overwrite existing list item
clear_tv(&li->li_tv); clear_tv(&li->li_tv);
li->li_tv = *tv; li->li_tv = *tv;
} }
else else
{ {
if (error_if_locked(list->lv_lock,
e_cannot_change_list))
goto failed;
// append to list, only fails when out of memory // append to list, only fails when out of memory
if (list_append_tv(list, tv) == FAIL) if (list_append_tv(list, tv) == FAIL)
goto failed; goto failed;
@ -1495,9 +1516,9 @@ call_def_function(
dict_T *dict = tv_dict->vval.v_dict; dict_T *dict = tv_dict->vval.v_dict;
dictitem_T *di; dictitem_T *di;
SOURCING_LNUM = iptr->isn_lnum;
if (dict == NULL) if (dict == NULL)
{ {
SOURCING_LNUM = iptr->isn_lnum;
emsg(_(e_dictionary_not_set)); emsg(_(e_dictionary_not_set));
goto on_error; goto on_error;
} }
@ -1507,12 +1528,18 @@ call_def_function(
di = dict_find(dict, key, -1); di = dict_find(dict, key, -1);
if (di != NULL) if (di != NULL)
{ {
if (error_if_locked(di->di_tv.v_lock,
e_cannot_change_dict_item))
goto failed;
// overwrite existing value // overwrite existing value
clear_tv(&di->di_tv); clear_tv(&di->di_tv);
di->di_tv = *tv; di->di_tv = *tv;
} }
else else
{ {
if (error_if_locked(dict->dv_lock,
e_cannot_change_dict))
goto failed;
// add to dict, only fails when out of memory // add to dict, only fails when out of memory
if (dict_add_tv(dict, (char *)key, tv) == FAIL) if (dict_add_tv(dict, (char *)key, tv) == FAIL)
goto failed; goto failed;
@ -1603,6 +1630,10 @@ call_def_function(
vim_unsetenv(iptr->isn_arg.unlet.ul_name); vim_unsetenv(iptr->isn_arg.unlet.ul_name);
break; 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 // create a list from items on the stack; uses a single allocation
// for the list header and the items // for the list header and the items
case ISN_NEWLIST: case ISN_NEWLIST:
@ -3025,6 +3056,9 @@ ex_disassemble(exarg_T *eap)
iptr->isn_arg.unlet.ul_forceit ? "!" : "", iptr->isn_arg.unlet.ul_forceit ? "!" : "",
iptr->isn_arg.unlet.ul_name); iptr->isn_arg.unlet.ul_name);
break; break;
case ISN_LOCKCONST:
smsg("%4d LOCKCONST", current);
break;
case ISN_NEWLIST: case ISN_NEWLIST:
smsg("%4d NEWLIST size %lld", current, smsg("%4d NEWLIST size %lld", current,
(long long)(iptr->isn_arg.number)); (long long)(iptr->isn_arg.number));