vim-patch:8.1.1539: not easy to define a variable and lock it

Problem:    Not easy to define a variable and lock it.
Solution:   Add ":const".
9937a05543
This commit is contained in:
rhysd 2019-06-23 19:25:19 +09:00
parent de2e51439e
commit 1c90c19f62
4 changed files with 335 additions and 14 deletions

View File

@ -9629,8 +9629,31 @@ This does NOT work: >
No error message is given for a non-existing No error message is given for a non-existing
variable, also without !. variable, also without !.
If the system does not support deleting an environment If the system does not support deleting an environment
variable, it is made emtpy. variable, it is made empty.
*:cons* *:const* *E996*
:cons[t] {var-name} = {expr1}
:cons[t] [{name1}, {name2}, ...] = {expr1}
:cons[t] [{name1}, {name2}, ...] .= {expr1}
:cons[t] [{name}, ..., ; {lastname}] = {expr1}
:cons[t] {var-name} =<< [trim] {marker}
text...
text...
{marker}
Similar to |:let|, but additionally lock the variable
after setting the value. This is the same as locking
the variable with |:lockvar| just after |:let|, thus: >
:const x = 1
< is equivalent to: >
:let x = 1
:lockvar 1 x
< This is useful if you want to make sure the variable
is not modified.
*E995*
|:const| does not allow to for changing a variable. >
:let x = 1
:const x = 2 " Error!
<
:lockv[ar][!] [depth] {name} ... *:lockvar* *:lockv* :lockv[ar][!] [depth] {name} ... *:lockvar* *:lockv*
Lock the internal variable {name}. Locking means that Lock the internal variable {name}. Locking means that
it can no longer be changed (until it is unlocked). it can no longer be changed (until it is unlocked).

View File

@ -176,6 +176,7 @@ static char *e_funcref = N_("E718: Funcref required");
static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_nofunc = N_("E130: Unknown function: %s");
static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
static const char *e_readonlyvar = N_( static const char *e_readonlyvar = N_(
"E46: Cannot change read-only variable \"%.*s\""); "E46: Cannot change read-only variable \"%.*s\"");
@ -777,9 +778,9 @@ var_redir_start(
tv.v_type = VAR_STRING; tv.v_type = VAR_STRING;
tv.vval.v_string = (char_u *)""; tv.vval.v_string = (char_u *)"";
if (append) if (append)
set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); set_var_lval(redir_lval, redir_endp, &tv, TRUE, false, (char_u *)".");
else else
set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); set_var_lval(redir_lval, redir_endp, &tv, TRUE, false, (char_u *)"=");
clear_lval(redir_lval); clear_lval(redir_lval);
err = did_emsg; err = did_emsg;
did_emsg |= save_emsg; did_emsg |= save_emsg;
@ -837,7 +838,7 @@ void var_redir_stop(void)
redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval,
false, false, 0, FNE_CHECK_START); false, false, 0, FNE_CHECK_START);
if (redir_endp != NULL && redir_lval->ll_name != NULL) { if (redir_endp != NULL && redir_lval->ll_name != NULL) {
set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)".");
} }
clear_lval(redir_lval); clear_lval(redir_lval);
} }
@ -1436,6 +1437,16 @@ int eval_foldexpr(char_u *arg, int *cp)
return (int)retval; return (int)retval;
} }
/*
* :cons[t] var = expr1 define constant
* :cons[t] [name1, name2, ...] = expr1 define constnats unpacking list
* :cons[t] [name, ..., ; lastname] = expr1 define constnats unpacking list
*/
void ex_const(exarg_T *eap)
{
ex_let_const(eap, true);
}
/* /*
* ":let" list all variable values * ":let" list all variable values
* ":let var1 var2" list variable values * ":let var1 var2" list variable values
@ -1448,8 +1459,14 @@ int eval_foldexpr(char_u *arg, int *cp)
* ":let var .= expr" assignment command. * ":let var .= expr" assignment command.
* ":let var ..= expr" assignment command. * ":let var ..= expr" assignment command.
* ":let [var1, var2] = expr" unpack list. * ":let [var1, var2] = expr" unpack list.
* ":cons[t] [name, ..., ; lastname] = expr1" unpack list.
*/ */
void ex_let(exarg_T *eap) void ex_let(exarg_T *eap)
{
ex_let_const(eap, false);
}
static void ex_let_const(exarg_T *eap, const bool is_const)
{ {
char_u *arg = eap->arg; char_u *arg = eap->arg;
char_u *expr = NULL; char_u *expr = NULL;
@ -1512,7 +1529,7 @@ void ex_let(exarg_T *eap)
} }
emsg_skip--; emsg_skip--;
} else if (i != FAIL) { } else if (i != FAIL) {
(void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
tv_clear(&rettv); tv_clear(&rettv);
} }
} }
@ -1533,6 +1550,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 is_const, /* lock variables for :const */
char_u *nextchars char_u *nextchars
) )
{ {
@ -1543,7 +1561,7 @@ ex_let_vars(
/* /*
* ":let var = expr" or ":for var in list" * ":let var = expr" or ":for var in list"
*/ */
if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL)
return FAIL; return FAIL;
return OK; return OK;
} }
@ -1572,7 +1590,7 @@ ex_let_vars(
size_t rest_len = tv_list_len(l); size_t rest_len = tv_list_len(l);
while (*arg != ']') { while (*arg != ']') {
arg = skipwhite(arg + 1); arg = skipwhite(arg + 1);
arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, (const char_u *)",;]",
nextchars); nextchars);
if (arg == NULL) { if (arg == NULL) {
return FAIL; return FAIL;
@ -1595,8 +1613,7 @@ ex_let_vars(
ltv.vval.v_list = rest_list; ltv.vval.v_list = rest_list;
tv_list_ref(rest_list); tv_list_ref(rest_list);
arg = ex_let_one(skipwhite(arg + 1), &ltv, false, arg = ex_let_one(skipwhite(arg + 1), &ltv, false, is_const, (char_u *)"]", nextchars);
(char_u *)"]", nextchars);
tv_clear(&ltv); tv_clear(&ltv);
if (arg == NULL) { if (arg == NULL) {
return FAIL; return FAIL;
@ -1851,7 +1868,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
/// @return a pointer to the char just after the var name or NULL in case of /// @return a pointer to the char just after the var name or NULL in case of
/// error. /// error.
static char_u *ex_let_one(char_u *arg, typval_T *const tv, static char_u *ex_let_one(char_u *arg, typval_T *const tv,
const bool copy, const char_u *const endchars, const bool copy, const bool is_const, const char_u *const endchars,
const char_u *const op) const char_u *const op)
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{ {
@ -1864,6 +1881,11 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
* ":let $VAR = expr": Set environment variable. * ":let $VAR = expr": Set environment variable.
*/ */
if (*arg == '$') { if (*arg == '$') {
if (is_const)
{
EMSG(_("E996: Cannot lock an environment variable"));
return NULL;
}
// Find the end of the name. // Find the end of the name.
arg++; arg++;
char *name = (char *)arg; char *name = (char *)arg;
@ -1909,6 +1931,11 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
// ":let &l:option = expr": Set local option value. // ":let &l:option = expr": Set local option value.
// ":let &g:option = expr": Set global option value. // ":let &g:option = expr": Set global option value.
} else if (*arg == '&') { } else if (*arg == '&') {
if (is_const)
{
EMSG(_("E996: Cannot lock an option"));
return NULL;
}
// Find the end of the name. // Find the end of the name.
char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); char *const p = (char *)find_option_end((const char **)&arg, &opt_flags);
if (p == NULL if (p == NULL
@ -1959,6 +1986,11 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
} }
// ":let @r = expr": Set register contents. // ":let @r = expr": Set register contents.
} else if (*arg == '@') { } else if (*arg == '@') {
if (is_const)
{
EMSG(_("E996: Cannot lock a register"));
return NULL;
}
arg++; arg++;
if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) {
emsgf(_(e_letwrong), op); emsgf(_(e_letwrong), op);
@ -1998,7 +2030,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) {
EMSG(_(e_letunexp)); EMSG(_(e_letunexp));
} else { } else {
set_var_lval(&lv, p, tv, copy, op); set_var_lval(&lv, p, tv, copy, is_const, op);
arg_end = p; arg_end = p;
} }
} }
@ -2363,7 +2395,7 @@ static void clear_lval(lval_T *lp)
* "%" for "%=", "." for ".=" or "=" for "=". * "%" for "%=", "." for ".=" or "=" for "=".
*/ */
static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
int copy, const char_u *op) int copy, const bool is_const, const char_u *op)
{ {
int cc; int cc;
listitem_T *ri; listitem_T *ri;
@ -2375,6 +2407,13 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (op != NULL && *op != '=') { if (op != NULL && *op != '=') {
typval_T tv; typval_T tv;
if (is_const)
{
EMSG(_(e_cannot_mod));
*endp = cc;
return;
}
// handle +=, -=, *=, /=, %= and .= // handle +=, -=, *=, /=, %= and .=
di = NULL; di = NULL;
if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
@ -2390,7 +2429,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
tv_clear(&tv); tv_clear(&tv);
} }
} else { } else {
set_var(lp->ll_name, lp->ll_name_len, rettv, copy); set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const);
} }
*endp = cc; *endp = cc;
} else if (tv_check_lock(lp->ll_newkey == NULL } else if (tv_check_lock(lp->ll_newkey == NULL
@ -2401,6 +2440,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
listitem_T *ll_li = lp->ll_li; listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1; int ll_n1 = lp->ll_n1;
if (is_const)
{
EMSG(_("E996: Cannot lock a range"));
return;
}
// Check whether any of the list items is locked // Check whether any of the list items is locked
for (ri = tv_list_first(rettv->vval.v_list); for (ri = tv_list_first(rettv->vval.v_list);
ri != NULL && ll_li != NULL; ) { ri != NULL && ll_li != NULL; ) {
@ -2456,6 +2501,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
dict_T *dict = lp->ll_dict; dict_T *dict = lp->ll_dict;
bool watched = tv_dict_is_watched(dict); bool watched = tv_dict_is_watched(dict);
if (is_const)
{
EMSG(_("E996: Cannot lock a list or dict"));
return;
}
// Assign to a List or Dictionary item. // Assign to a List or Dictionary item.
if (lp->ll_newkey != NULL) { if (lp->ll_newkey != NULL) {
if (op != NULL && *op != '=') { if (op != NULL && *op != '=') {
@ -2579,7 +2630,7 @@ bool next_for_item(void *fi_void, char_u *arg)
} else { } else {
fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item);
return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true,
fi->fi_semicolon, fi->fi_varcount, NULL) == OK); fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK);
} }
} }
@ -20043,6 +20094,23 @@ static void list_one_var_a(const char *prefix, const char *name,
static void set_var(const char *name, const size_t name_len, typval_T *const tv, static void set_var(const char *name, const size_t name_len, typval_T *const tv,
const bool copy) const bool copy)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{
set_var_const(name, name_len, tv, copy, false);
}
/// Set variable to the given value
///
/// If the variable already exists, the value is updated. Otherwise the variable
/// is created.
///
/// @param[in] name Variable name to set.
/// @param[in] name_len Length of the variable name.
/// @param tv Variable value.
/// @param[in] copy True if value in tv is to be copied.
/// @param[in] is_const True if value in tv is to be locked.
static void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
const bool copy, const bool is_const)
FUNC_ATTR_NONNULL_ALL
{ {
dictitem_T *v; dictitem_T *v;
hashtab_T *ht; hashtab_T *ht;
@ -20069,6 +20137,12 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
typval_T oldtv = TV_INITIAL_VALUE; typval_T oldtv = TV_INITIAL_VALUE;
if (v != NULL) { if (v != NULL) {
if (is_const)
{
EMSG(_(e_cannot_mod));
return;
}
// existing variable, need to clear the value // existing variable, need to clear the value
if (var_check_ro(v->di_flags, name, name_len) if (var_check_ro(v->di_flags, name, name_len)
|| tv_check_lock(v->di_tv.v_lock, name, name_len)) { || tv_check_lock(v->di_tv.v_lock, name, name_len)) {
@ -20135,6 +20209,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
return; return;
} }
v->di_flags = DI_FLAGS_ALLOC; v->di_flags = DI_FLAGS_ALLOC;
if (is_const) {
v->di_flags |= DI_FLAGS_LOCK;
}
} }
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
@ -20153,6 +20230,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
tv_clear(&oldtv); tv_clear(&oldtv);
} }
} }
if (is_const) {
v->di_tv.v_lock |= VAR_LOCKED;
}
} }
/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX)

View File

@ -600,6 +600,12 @@ return {
addr_type=ADDR_LINES, addr_type=ADDR_LINES,
func='ex_wrongmodifier', func='ex_wrongmodifier',
}, },
{
command='const',
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
func='ex_const',
},
{ {
command='copen', command='copen',
flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR), flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR),

View File

@ -0,0 +1,211 @@
" Test for :const
func s:noop()
endfunc
func Test_define_var_with_lock()
const i = 1
const f = 1.1
const s = 'vim'
const F = funcref('s:noop')
const l = [1, 2, 3]
const d = {'foo': 10}
if has('channel')
const j = test_null_job()
const c = test_null_channel()
endif
const b = v:true
const n = v:null
call assert_fails('let i = 1', 'E741:')
call assert_fails('let f = 1.1', 'E741:')
call assert_fails('let s = "vim"', 'E741:')
call assert_fails('let F = funcref("s:noop")', 'E741:')
call assert_fails('let l = [1, 2, 3]', 'E741:')
call assert_fails('let d = {"foo": 10}', 'E741:')
if has('channel')
call assert_fails('let j = test_null_job()', 'E741:')
call assert_fails('let c = test_null_channel()', 'E741:')
endif
call assert_fails('let b = v:true', 'E741:')
call assert_fails('let n = v:null', 'E741:')
" Unlet
unlet i
unlet f
unlet s
unlet F
unlet l
unlet d
unlet j
unlet c
unlet b
unlet n
endfunc
func Test_define_l_var_with_lock()
" With l: prefix
const l:i = 1
const l:f = 1.1
const l:s = 'vim'
const l:F = funcref('s:noop')
const l:l = [1, 2, 3]
const l:d = {'foo': 10}
if has('channel')
const l:j = test_null_job()
const l:c = test_null_channel()
endif
const l:b = v:true
const l:n = v:null
call assert_fails('let l:i = 1', 'E741:')
call assert_fails('let l:f = 1.1', 'E741:')
call assert_fails('let l:s = "vim"', 'E741:')
call assert_fails('let l:F = funcref("s:noop")', 'E741:')
call assert_fails('let l:l = [1, 2, 3]', 'E741:')
call assert_fails('let l:d = {"foo": 10}', 'E741:')
if has('channel')
call assert_fails('let l:j = test_null_job()', 'E741:')
call assert_fails('let l:c = test_null_channel()', 'E741:')
endif
call assert_fails('let l:b = v:true', 'E741:')
call assert_fails('let l:n = v:null', 'E741:')
" Unlet
unlet l:i
unlet l:f
unlet l:s
unlet l:F
unlet l:l
unlet l:d
if has('channel')
unlet l:j
unlet l:c
endif
unlet l:b
unlet l:n
endfunc
func Test_define_script_var_with_lock()
const s:x = 0
call assert_fails('let s:x = 1', 'E741:')
unlet s:x
endfunc
func Test_descructuring_with_lock()
const [a, b, c] = [1, 1.1, 'vim']
call assert_fails('let a = 1', 'E741:')
call assert_fails('let b = 1.1', 'E741:')
call assert_fails('let c = "vim"', 'E741:')
const [d; e] = [1, 1.1, 'vim']
call assert_fails('let d = 1', 'E741:')
call assert_fails('let e = [2.2, "a"]', 'E741:')
endfunc
func Test_cannot_modify_existing_variable()
let i = 1
let f = 1.1
let s = 'vim'
let F = funcref('s:noop')
let l = [1, 2, 3]
let d = {'foo': 10}
if has('channel')
let j = test_null_job()
let c = test_null_channel()
endif
let b = v:true
let n = v:null
call assert_fails('const i = 1', 'E995:')
call assert_fails('const f = 1.1', 'E995:')
call assert_fails('const s = "vim"', 'E995:')
call assert_fails('const F = funcref("s:noop")', 'E995:')
call assert_fails('const l = [1, 2, 3]', 'E995:')
call assert_fails('const d = {"foo": 10}', 'E995:')
if has('channel')
call assert_fails('const j = test_null_job()', 'E995:')
call assert_fails('const c = test_null_channel()', 'E995:')
endif
call assert_fails('const b = v:true', 'E995:')
call assert_fails('const n = v:null', 'E995:')
call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:')
const i2 = 1
const f2 = 1.1
const s2 = 'vim'
const F2 = funcref('s:noop')
const l2 = [1, 2, 3]
const d2 = {'foo': 10}
if has('channel')
const j2 = test_null_job()
const c2 = test_null_channel()
endif
const b2 = v:true
const n2 = v:null
call assert_fails('const i2 = 1', 'E995:')
call assert_fails('const f2 = 1.1', 'E995:')
call assert_fails('const s2 = "vim"', 'E995:')
call assert_fails('const F2 = funcref("s:noop")', 'E995:')
call assert_fails('const l2 = [1, 2, 3]', 'E995:')
call assert_fails('const d2 = {"foo": 10}', 'E995:')
if has('channel')
call assert_fails('const j2 = test_null_job()', 'E995:')
call assert_fails('const c2 = test_null_channel()', 'E995:')
endif
call assert_fails('const b2 = v:true', 'E995:')
call assert_fails('const n2 = v:null', 'E995:')
call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
endfunc
func Test_const_with_index_access()
let l = [1, 2, 3]
call assert_fails('const l[0] = 4', 'E996:')
call assert_fails('const l[0:1] = [1, 2]', 'E996:')
let d = {'aaa': 0}
call assert_fails("const d['aaa'] = 4", 'E996:')
call assert_fails("const d.aaa = 4", 'E996:')
endfunc
func Test_const_with_compound_assign()
let i = 0
call assert_fails('const i += 4', 'E995:')
call assert_fails('const i -= 4', 'E995:')
call assert_fails('const i *= 4', 'E995:')
call assert_fails('const i /= 4', 'E995:')
call assert_fails('const i %= 4', 'E995:')
let s = 'a'
call assert_fails('const s .= "b"', 'E995:')
let [a, b, c] = [1, 2, 3]
call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:')
let [d; e] = [1, 2, 3]
call assert_fails('const [d; e] += [4, 5, 6]', 'E995:')
endfunc
func Test_const_with_special_variables()
call assert_fails('const $FOO = "hello"', 'E996:')
call assert_fails('const @a = "hello"', 'E996:')
call assert_fails('const &filetype = "vim"', 'E996:')
call assert_fails('const &l:filetype = "vim"', 'E996:')
call assert_fails('const &g:encoding = "utf-8"', 'E996:')
endfunc
func Test_lock_depth_is_1()
const l = [1, 2, 3]
const d = {'foo': 10}
" Modify list
call add(l, 4)
let l[0] = 42
" Modify dict
let d['bar'] = 'hello'
let d.foo = 44
endfunc