mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
vim-patch:9.0.1631: passing wrong variable type to option gives multiple errors (#24026)
Problem: Passing a wrong variable type to an option gives multiple errors.
Solution: Bail out early on failure. (closes vim/vim#12504)
4c7cb372c1
This commit is contained in:
parent
cf6cffda89
commit
78d77c03de
@ -769,17 +769,28 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hidden;
|
|
||||||
bool error;
|
|
||||||
const char c1 = *p;
|
const char c1 = *p;
|
||||||
*p = NUL;
|
*p = NUL;
|
||||||
|
|
||||||
OptVal curval = get_option_value(arg, NULL, scope, &hidden);
|
uint32_t opt_p_flags;
|
||||||
OptVal newval = tv_to_optval(tv, arg, scope, &error);
|
bool hidden;
|
||||||
|
OptVal curval = get_option_value(arg, &opt_p_flags, scope, &hidden);
|
||||||
|
OptVal newval = NIL_OPTVAL;
|
||||||
|
if (curval.type == kOptValTypeNil && arg[0] != 't' && arg[1] != '_') {
|
||||||
|
semsg(_(e_unknown_option2), arg);
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
if (op != NULL && *op != '='
|
||||||
|
&& ((curval.type != kOptValTypeString && *op == '.')
|
||||||
|
|| (curval.type == kOptValTypeString && *op != '.'))) {
|
||||||
|
semsg(_(e_letwrong), op);
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore errors for num types
|
bool error;
|
||||||
if (newval.type != kOptValTypeNumber && newval.type != kOptValTypeBoolean && error) {
|
newval = tv_to_optval(tv, arg, opt_p_flags, &error);
|
||||||
goto end;
|
if (error) {
|
||||||
|
goto theend;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't assume current and new values are of the same type in order to future-proof the code for
|
// Don't assume current and new values are of the same type in order to future-proof the code for
|
||||||
@ -789,61 +800,47 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
|
|||||||
const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
|
const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
|
||||||
|
|
||||||
if (op != NULL && *op != '=') {
|
if (op != NULL && *op != '=') {
|
||||||
if (!hidden && ((is_num && *op == '.') || (is_string && *op != '.'))) {
|
if (!hidden && is_num) { // number or bool
|
||||||
semsg(_(e_letwrong), op);
|
Integer cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
|
||||||
goto end;
|
Integer new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
|
||||||
} else {
|
|
||||||
// number or bool
|
|
||||||
if (!hidden && is_num) {
|
|
||||||
Integer cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
|
|
||||||
Integer new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
|
|
||||||
|
|
||||||
switch (*op) {
|
switch (*op) {
|
||||||
case '+':
|
case '+':
|
||||||
new_n = cur_n + new_n; break;
|
new_n = cur_n + new_n; break;
|
||||||
case '-':
|
case '-':
|
||||||
new_n = cur_n - new_n; break;
|
new_n = cur_n - new_n; break;
|
||||||
case '*':
|
case '*':
|
||||||
new_n = cur_n * new_n; break;
|
new_n = cur_n * new_n; break;
|
||||||
case '/':
|
case '/':
|
||||||
new_n = num_divide(cur_n, new_n); break;
|
new_n = num_divide(cur_n, new_n); break;
|
||||||
case '%':
|
case '%':
|
||||||
new_n = num_modulus(cur_n, new_n); break;
|
new_n = num_modulus(cur_n, new_n); break;
|
||||||
}
|
|
||||||
|
|
||||||
// clamp boolean values
|
|
||||||
if (newval.type == kOptValTypeBoolean && (new_n > 1 || new_n < -1)) {
|
|
||||||
new_n = (new_n > 1) ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
newval = kOptValTypeNumber ? NUMBER_OPTVAL(new_n) : BOOLEAN_OPTVAL((TriState)new_n);
|
|
||||||
} else if (!hidden && is_string && curval.data.string.data != NULL
|
|
||||||
&& newval.data.string.data != NULL) {
|
|
||||||
// string
|
|
||||||
OptVal newval_old = newval;
|
|
||||||
newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
|
|
||||||
optval_free(newval_old);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clamp boolean values
|
||||||
|
if (newval.type == kOptValTypeBoolean && (new_n > 1 || new_n < -1)) {
|
||||||
|
new_n = (new_n > 1) ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newval = kOptValTypeNumber ? NUMBER_OPTVAL(new_n) : BOOLEAN_OPTVAL((TriState)new_n);
|
||||||
|
} else if (!hidden && is_string
|
||||||
|
&& curval.data.string.data != NULL && newval.data.string.data != NULL) { // string
|
||||||
|
OptVal newval_old = newval;
|
||||||
|
newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
|
||||||
|
optval_free(newval_old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If new value is a string and is NULL, show an error if it's not a hidden option.
|
const char *err = set_option_value(arg, newval, scope);
|
||||||
// For hidden options, just pass the value to `set_option_value` and let it fail silently.
|
arg_end = p;
|
||||||
if (hidden || newval.type != kOptValTypeString || newval.data.string.data != NULL) {
|
if (err != NULL) {
|
||||||
const char *err = set_option_value(arg, newval, scope);
|
emsg(_(err));
|
||||||
arg_end = p;
|
|
||||||
if (err != NULL) {
|
|
||||||
emsg(_(err));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emsg(_(e_stringreq));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
end:
|
theend:
|
||||||
*p = c1;
|
*p = c1;
|
||||||
optval_free(curval);
|
optval_free(curval);
|
||||||
optval_free(newval);
|
optval_free(newval);
|
||||||
|
|
||||||
return arg_end;
|
return arg_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1813,25 +1810,18 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
|
|||||||
///
|
///
|
||||||
/// @param[in] tv typval to convert.
|
/// @param[in] tv typval to convert.
|
||||||
/// @param[in] option Option name.
|
/// @param[in] option Option name.
|
||||||
/// @param[in] scope Option scope.
|
/// @param[in] flags Option flags.
|
||||||
/// @param[out] error Whether an error occured.
|
/// @param[out] error Whether an error occured.
|
||||||
///
|
///
|
||||||
/// @return Typval converted to OptVal. Must be freed by caller.
|
/// @return Typval converted to OptVal. Must be freed by caller.
|
||||||
/// Returns NIL_OPTVAL for invalid option name.
|
/// Returns NIL_OPTVAL for invalid option name.
|
||||||
static OptVal tv_to_optval(typval_T *tv, const char *option, int scope, bool *error)
|
static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, bool *error)
|
||||||
{
|
{
|
||||||
OptVal value = NIL_OPTVAL;
|
OptVal value = NIL_OPTVAL;
|
||||||
char nbuf[NUMBUFLEN];
|
char nbuf[NUMBUFLEN];
|
||||||
uint32_t flags;
|
|
||||||
bool err = false;
|
bool err = false;
|
||||||
|
|
||||||
OptVal curval = get_option_value(option, &flags, scope, NULL);
|
if ((flags & P_FUNC) && tv_is_func(*tv)) {
|
||||||
|
|
||||||
// TODO(famiu): Delegate all of these type-checks to set_option_value()
|
|
||||||
if (curval.type == kOptValTypeNil) {
|
|
||||||
// Invalid option name,
|
|
||||||
value = NIL_OPTVAL;
|
|
||||||
} else if ((flags & P_FUNC) && tv_is_func(*tv)) {
|
|
||||||
// If the option can be set to a function reference or a lambda
|
// If the option can be set to a function reference or a lambda
|
||||||
// and the passed value is a function reference, then convert it to
|
// and the passed value is a function reference, then convert it to
|
||||||
// the name (string) of the function reference.
|
// the name (string) of the function reference.
|
||||||
@ -1839,29 +1829,31 @@ static OptVal tv_to_optval(typval_T *tv, const char *option, int scope, bool *er
|
|||||||
err = strval == NULL;
|
err = strval == NULL;
|
||||||
value = CSTR_AS_OPTVAL(strval);
|
value = CSTR_AS_OPTVAL(strval);
|
||||||
} else if (flags & (P_NUM | P_BOOL)) {
|
} else if (flags & (P_NUM | P_BOOL)) {
|
||||||
varnumber_T n = tv_get_number_chk(tv, &err);
|
varnumber_T n = (flags & P_NUM) ? tv_get_number_chk(tv, &err)
|
||||||
// This could be either "0" or a string that's not a number. So we need to check if it's
|
: tv_get_bool_chk(tv, &err);
|
||||||
// actually a number.
|
// This could be either "0" or a string that's not a number.
|
||||||
|
// So we need to check if it's actually a number.
|
||||||
if (!err && tv->v_type == VAR_STRING && n == 0) {
|
if (!err && tv->v_type == VAR_STRING && n == 0) {
|
||||||
unsigned idx;
|
unsigned idx;
|
||||||
for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
|
for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
|
||||||
if (tv->vval.v_string[idx] != NUL || idx == 0) {
|
if (tv->vval.v_string[idx] != NUL || idx == 0) {
|
||||||
// There's another character after zeros or the string is empty.
|
// There's another character after zeros or the string is empty.
|
||||||
// In both cases, we are trying to set a num option using a string.
|
// In both cases, we are trying to set a num option using a string.
|
||||||
|
err = true;
|
||||||
semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
|
semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = (flags & P_NUM) ? NUMBER_OPTVAL(n)
|
value = (flags & P_NUM) ? NUMBER_OPTVAL(n)
|
||||||
: BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone));
|
: BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone));
|
||||||
} else if (flags & P_STRING || is_tty_option(option)) {
|
} else if ((flags & P_STRING) || is_tty_option(option)) {
|
||||||
// Avoid setting string option to a boolean.
|
// Avoid setting string option to a boolean or a special value.
|
||||||
if (tv->v_type == VAR_BOOL) {
|
if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
|
||||||
err = true;
|
|
||||||
emsg(_(e_stringreq));
|
|
||||||
} else {
|
|
||||||
const char *strval = tv_get_string_buf_chk(tv, nbuf);
|
const char *strval = tv_get_string_buf_chk(tv, nbuf);
|
||||||
err = strval == NULL;
|
err = strval == NULL;
|
||||||
value = CSTR_TO_OPTVAL(strval);
|
value = CSTR_TO_OPTVAL(strval);
|
||||||
|
} else if (flags & P_STRING) {
|
||||||
|
err = true;
|
||||||
|
emsg(_(e_stringreq));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
abort(); // This should never happen.
|
abort(); // This should never happen.
|
||||||
@ -1870,19 +1862,23 @@ static OptVal tv_to_optval(typval_T *tv, const char *option, int scope, bool *er
|
|||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
*error = err;
|
*error = err;
|
||||||
}
|
}
|
||||||
optval_free(curval);
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set option "varname" to the value of "varp" for the current buffer/window.
|
/// Set option "varname" to the value of "varp" for the current buffer/window.
|
||||||
static void set_option_from_tv(const char *varname, typval_T *varp)
|
static void set_option_from_tv(const char *varname, typval_T *varp)
|
||||||
{
|
{
|
||||||
bool error = false;
|
int opt_idx = findoption(varname);
|
||||||
OptVal value = tv_to_optval(varp, varname, OPT_LOCAL, &error);
|
if (opt_idx < 0) {
|
||||||
|
|
||||||
if (!error && value.type == kOptValTypeNil) {
|
|
||||||
semsg(_(e_unknown_option2), varname);
|
semsg(_(e_unknown_option2), varname);
|
||||||
} else if (!error) {
|
return;
|
||||||
|
}
|
||||||
|
uint32_t opt_p_flags = get_option(opt_idx)->flags;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
OptVal value = tv_to_optval(varp, varname, opt_p_flags, &error);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
set_option_value_give_err(varname, value, OPT_LOCAL);
|
set_option_value_give_err(varname, value, OPT_LOCAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3787,14 +3787,6 @@ bool is_option_allocated(const char *name)
|
|||||||
return idx >= 0 && (options[idx].flags & P_ALLOCED);
|
return idx >= 0 && (options[idx].flags & P_ALLOCED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if "name" is a string option.
|
|
||||||
/// Returns false if option "name" does not exist.
|
|
||||||
bool is_string_option(const char *name)
|
|
||||||
{
|
|
||||||
int idx = findoption(name);
|
|
||||||
return idx >= 0 && (options[idx].flags & P_STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
|
// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
|
||||||
// When "has_lt" is true there is a '<' before "*arg_arg".
|
// When "has_lt" is true there is a '<' before "*arg_arg".
|
||||||
// Returns 0 when the key is not recognized.
|
// Returns 0 when the key is not recognized.
|
||||||
|
@ -242,7 +242,7 @@ func Test_let_no_type_checking()
|
|||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_let_termcap()
|
func Test_let_termcap()
|
||||||
throw 'skipped: Nvim does not support termcap option'
|
throw 'Skipped: Nvim does not support termcap options'
|
||||||
" Terminal code
|
" Terminal code
|
||||||
let old_t_te = &t_te
|
let old_t_te = &t_te
|
||||||
let &t_te = "\<Esc>[yes;"
|
let &t_te = "\<Esc>[yes;"
|
||||||
@ -257,25 +257,45 @@ func Test_let_termcap()
|
|||||||
let &t_k1 = old_t_k1
|
let &t_k1 = old_t_k1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call assert_fails('let x = &t_xx', 'E113')
|
call assert_fails('let x = &t_xx', 'E113:')
|
||||||
let &t_xx = "yes"
|
let &t_xx = "yes"
|
||||||
call assert_equal("yes", &t_xx)
|
call assert_equal("yes", &t_xx)
|
||||||
let &t_xx = ""
|
let &t_xx = ""
|
||||||
call assert_fails('let x = &t_xx', 'E113')
|
call assert_fails('let x = &t_xx', 'E113:')
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_let_option_error()
|
func Test_let_option_error()
|
||||||
let _w = &tw
|
let _w = &tw
|
||||||
let &tw = 80
|
let &tw = 80
|
||||||
call assert_fails('let &tw .= 1', 'E734')
|
call assert_fails('let &tw .= 1', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &tw .= []', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &tw = []', ['E745:', 'E745:'])
|
||||||
|
call assert_fails('let &tw += []', ['E745:', 'E745:'])
|
||||||
call assert_equal(80, &tw)
|
call assert_equal(80, &tw)
|
||||||
let &tw = _w
|
let &tw = _w
|
||||||
|
|
||||||
|
let _w = &autoread
|
||||||
|
let &autoread = 1
|
||||||
|
call assert_fails('let &autoread .= 1', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &autoread .= []', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &autoread = []', ['E745:', 'E745:'])
|
||||||
|
call assert_fails('let &autoread += []', ['E745:', 'E745:'])
|
||||||
|
call assert_equal(1, &autoread)
|
||||||
|
let &autoread = _w
|
||||||
|
|
||||||
let _w = &fillchars
|
let _w = &fillchars
|
||||||
let &fillchars = "vert:|"
|
let &fillchars = "vert:|"
|
||||||
call assert_fails('let &fillchars += "diff:-"', 'E734')
|
call assert_fails('let &fillchars += "diff:-"', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &fillchars += []', ['E734:', 'E734:'])
|
||||||
|
call assert_fails('let &fillchars = []', ['E730:', 'E730:'])
|
||||||
|
call assert_fails('let &fillchars .= []', ['E730:', 'E730:'])
|
||||||
call assert_equal("vert:|", &fillchars)
|
call assert_equal("vert:|", &fillchars)
|
||||||
let &fillchars = _w
|
let &fillchars = _w
|
||||||
|
|
||||||
|
call assert_fails('let &nosuchoption = 1', ['E355:', 'E355:'])
|
||||||
|
call assert_fails('let &nosuchoption = ""', ['E355:', 'E355:'])
|
||||||
|
call assert_fails('let &nosuchoption = []', ['E355:', 'E355:'])
|
||||||
|
call assert_fails('let &t_xx = []', ['E730:', 'E730:'])
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
" Errors with the :let statement
|
" Errors with the :let statement
|
||||||
|
@ -380,7 +380,7 @@ func Test_set_completion()
|
|||||||
call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
|
call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_set_errors()
|
func Test_set_option_errors()
|
||||||
call assert_fails('set scroll=-1', 'E49:')
|
call assert_fails('set scroll=-1', 'E49:')
|
||||||
call assert_fails('set backupcopy=', 'E474:')
|
call assert_fails('set backupcopy=', 'E474:')
|
||||||
call assert_fails('set regexpengine=3', 'E474:')
|
call assert_fails('set regexpengine=3', 'E474:')
|
||||||
@ -482,7 +482,7 @@ func Test_set_errors()
|
|||||||
if has('python') || has('python3')
|
if has('python') || has('python3')
|
||||||
call assert_fails('set pyxversion=6', 'E474:')
|
call assert_fails('set pyxversion=6', 'E474:')
|
||||||
endif
|
endif
|
||||||
call assert_fails("let &tabstop='ab'", 'E521:')
|
call assert_fails("let &tabstop='ab'", ['E521:', 'E521:'])
|
||||||
call assert_fails('set spellcapcheck=%\\(', 'E54:')
|
call assert_fails('set spellcapcheck=%\\(', 'E54:')
|
||||||
call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
|
call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
|
||||||
call assert_fails('set foldmarker={{{,', 'E474:')
|
call assert_fails('set foldmarker={{{,', 'E474:')
|
||||||
@ -506,6 +506,12 @@ func Test_set_errors()
|
|||||||
" call assert_fails('set t_#-&', 'E522:')
|
" call assert_fails('set t_#-&', 'E522:')
|
||||||
call assert_fails('let &formatoptions = "?"', 'E539:')
|
call assert_fails('let &formatoptions = "?"', 'E539:')
|
||||||
call assert_fails('call setbufvar("", "&formatoptions", "?")', 'E539:')
|
call assert_fails('call setbufvar("", "&formatoptions", "?")', 'E539:')
|
||||||
|
call assert_fails('call setwinvar(0, "&scrolloff", [])', ['E745:', 'E745:'])
|
||||||
|
call assert_fails('call setwinvar(0, "&list", [])', ['E745:', 'E745:'])
|
||||||
|
call assert_fails('call setwinvar(0, "&listchars", [])', ['E730:', 'E730:'])
|
||||||
|
call assert_fails('call setwinvar(0, "&nosuchoption", 0)', ['E355:', 'E355:'])
|
||||||
|
call assert_fails('call setwinvar(0, "&nosuchoption", "")', ['E355:', 'E355:'])
|
||||||
|
call assert_fails('call setwinvar(0, "&nosuchoption", [])', ['E355:', 'E355:'])
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func CheckWasSet(name)
|
func CheckWasSet(name)
|
||||||
|
@ -6997,10 +6997,7 @@ func Test_compound_assignment_operators()
|
|||||||
call assert_equal(6, &scrolljump)
|
call assert_equal(6, &scrolljump)
|
||||||
let &scrolljump %= 5
|
let &scrolljump %= 5
|
||||||
call assert_equal(1, &scrolljump)
|
call assert_equal(1, &scrolljump)
|
||||||
" A different error is shown due to a change in implementation of option
|
call assert_fails('let &scrolljump .= "j"', ['E734:', 'E734:'])
|
||||||
" values.
|
|
||||||
" call assert_fails('let &scrolljump .= "j"', 'E734:')
|
|
||||||
call assert_fails('let &scrolljump .= "j"', 'E521:')
|
|
||||||
set scrolljump&vim
|
set scrolljump&vim
|
||||||
|
|
||||||
let &foldlevelstart = 2
|
let &foldlevelstart = 2
|
||||||
|
Loading…
Reference in New Issue
Block a user