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

patch 8.2.1798: Vim9: trinary operator condition is too permissive

Problem:    Vim9: trinary operator condition is too permissive.
Solution:   Use tv_get_bool_chk().
This commit is contained in:
Bram Moolenaar 2020-10-04 16:06:05 +02:00
parent 6abd3dc257
commit 1310660557
10 changed files with 215 additions and 36 deletions

View File

@ -164,15 +164,20 @@ the "name#" prefix is sufficient. >
When using `:function` or `:def` to specify a nested function inside a `:def` When using `:function` or `:def` to specify a nested function inside a `:def`
function, this nested function is local to the code block it is defined in. function, this nested function is local to the code block it is defined in.
In a `:def` function IT is not possible to define a script-local function. it In a `:def` function it is not possible to define a script-local function. it
is possible to define a global function by using the "g:" prefix. is possible to define a global function by using the "g:" prefix.
When referring to a function and no "s:" or "g:" prefix is used, Vim will When referring to a function and no "s:" or "g:" prefix is used, Vim will
prefer using a local function (in the function scope, script scope or search for the function:
imported) before looking for a global function. However, it is recommended to - in the function scope
always use "g:" to refer to a local function for clarity. In all cases the - in the script scope, possibly imported
function must be defined before used. That is when it is first called or when - in the list of global functions
`:defcompile` causes the call to be compiled. However, it is recommended to always use "g:" to refer to a global function
for clarity.
In all cases the function must be defined before used. That is when it is
called, when `:defcompile` causes the it to be compiled, or when code that
calls it is being compiled (to figure out the return type).
The result is that functions and variables without a namespace can usually be The result is that functions and variables without a namespace can usually be
found in the script, either defined there or imported. Global functions and found in the script, either defined there or imported. Global functions and
@ -467,11 +472,21 @@ White space is not allowed:
Conditions and expressions ~ Conditions and expressions ~
Conditions and expressions are mostly working like they do in JavaScript. A Conditions and expressions are mostly working like they do in other languages.
difference is made where JavaScript does not work like most people expect. Some values are different from legacy Vim script:
Specifically, an empty list is falsy. value legacy Vim script Vim9 script ~
0 falsy falsy
1 truthy truthy
99 truthy Error!
"0" falsy Error!
"99" truthy Error!
"text" falsy Error!
type TRUE when ~ For the "??" operator and when using "!" then there is no error, every value
is either falsy or truthy. This is mostly like JavaScript, except that an
empty list and dict is falsy:
type truthy when ~
bool v:true or 1 bool v:true or 1
number non-zero number non-zero
float non-zero float non-zero
@ -498,13 +513,13 @@ one: >
[] || 99 Error! [] || 99 Error!
When using "!" for inverting, there is no error for using any type and the When using "!" for inverting, there is no error for using any type and the
result is a boolean: > result is a boolean. "!!" can be used to turn any value into boolean: >
!'yes' == false !'yes' == false
var myList = [1, 2, 3] !![] == false
!!myList == true !![1, 2, 3] == true
When using "`.."` for string concatenation arguments of simple types are When using "`.."` for string concatenation arguments of simple types are
always converted to string. > always converted to string: >
'hello ' .. 123 == 'hello 123' 'hello ' .. 123 == 'hello 123'
'hello ' .. v:true == 'hello v:true' 'hello ' .. v:true == 'hello v:true'

View File

@ -191,7 +191,7 @@ eval_to_bool(
if (!skip) if (!skip)
{ {
if (in_vim9script()) if (in_vim9script())
retval = tv2bool(&tv); retval = tv_get_bool_chk(&tv, error);
else else
retval = (tv_get_number_chk(&tv, error) != 0); retval = (tv_get_number_chk(&tv, error) != 0);
clear_tv(&tv); clear_tv(&tv);
@ -2143,6 +2143,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
evalarg_T local_evalarg; evalarg_T local_evalarg;
int orig_flags; int orig_flags;
int evaluate; int evaluate;
int vim9script = in_vim9script();
if (evalarg == NULL) if (evalarg == NULL)
{ {
@ -2156,7 +2157,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
*arg = eval_next_line(evalarg_used); *arg = eval_next_line(evalarg_used);
else else
{ {
if (evaluate && in_vim9script() && !VIM_ISWHITE(p[-1])) if (evaluate && vim9script && !VIM_ISWHITE(p[-1]))
{ {
error_white_both(p, 1); error_white_both(p, 1);
clear_tv(rettv); clear_tv(rettv);
@ -2170,8 +2171,10 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
{ {
int error = FALSE; int error = FALSE;
if (in_vim9script() || op_falsy) if (op_falsy)
result = tv2bool(rettv); result = tv2bool(rettv);
else if (vim9script)
result = tv_get_bool_chk(rettv, &error);
else if (tv_get_number_chk(rettv, &error) != 0) else if (tv_get_number_chk(rettv, &error) != 0)
result = TRUE; result = TRUE;
if (error || !op_falsy || !result) if (error || !op_falsy || !result)
@ -2185,7 +2188,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
*/ */
if (op_falsy) if (op_falsy)
++*arg; ++*arg;
if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[1])) if (evaluate && vim9script && !IS_WHITE_OR_NUL((*arg)[1]))
{ {
error_white_both(p, 1); error_white_both(p, 1);
clear_tv(rettv); clear_tv(rettv);
@ -2220,7 +2223,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
*arg = eval_next_line(evalarg_used); *arg = eval_next_line(evalarg_used);
else else
{ {
if (evaluate && in_vim9script() && !VIM_ISWHITE(p[-1])) if (evaluate && vim9script && !VIM_ISWHITE(p[-1]))
{ {
error_white_both(p, 1); error_white_both(p, 1);
clear_tv(rettv); clear_tv(rettv);
@ -2233,7 +2236,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
/* /*
* Get the third variable. Recursive! * Get the third variable. Recursive!
*/ */
if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[1])) if (evaluate && vim9script && !IS_WHITE_OR_NUL((*arg)[1]))
{ {
error_white_both(p, 1); error_white_both(p, 1);
clear_tv(rettv); clear_tv(rettv);

View File

@ -42,6 +42,16 @@ func Test_version()
call assert_false(has('patch-9.9.1')) call assert_false(has('patch-9.9.1'))
endfunc endfunc
func Test_op_trinary()
call assert_equal('yes', 1 ? 'yes' : 'no')
call assert_equal('no', 0 ? 'yes' : 'no')
call assert_equal('no', 'x' ? 'yes' : 'no')
call assert_equal('yes', '1x' ? 'yes' : 'no')
call assert_fails('echo [1] ? "yes" : "no"', 'E745:')
call assert_fails('echo {} ? "yes" : "no"', 'E728:')
endfunc
func Test_op_falsy() func Test_op_falsy()
call assert_equal(v:true, v:true ?? 456) call assert_equal(v:true, v:true ?? 456)
call assert_equal(123, 123 ?? 456) call assert_equal(123, 123 ?? 456)

View File

@ -68,6 +68,74 @@ def Test_echo_linebreak()
CheckScriptSuccess(lines) CheckScriptSuccess(lines)
enddef enddef
def Test_condition_types()
var lines =<< trim END
if 'text'
endif
END
CheckDefAndScriptFailure(lines, 'E1030:', 1)
lines =<< trim END
if [1]
endif
END
CheckDefFailure(lines, 'E1012:', 1)
CheckScriptFailure(['vim9script'] + lines, 'E745:', 2)
lines =<< trim END
g:cond = 'text'
if g:cond
endif
END
CheckDefExecAndScriptFailure(lines, 'E1030:', 2)
lines =<< trim END
g:cond = 0
if g:cond
elseif 'text'
endif
END
CheckDefFailure(lines, 'E1012:', 3)
CheckScriptFailure(['vim9script'] + lines, 'E1030:', 4)
lines =<< trim END
if g:cond
elseif [1]
endif
END
CheckDefFailure(lines, 'E1012:', 2)
CheckScriptFailure(['vim9script'] + lines, 'E745:', 3)
lines =<< trim END
g:cond = 'text'
if 0
elseif g:cond
endif
END
CheckDefExecAndScriptFailure(lines, 'E1030:', 3)
lines =<< trim END
while 'text'
endwhile
END
CheckDefFailure(lines, 'E1012:', 1)
CheckScriptFailure(['vim9script'] + lines, 'E1030:', 2)
lines =<< trim END
while [1]
endwhile
END
CheckDefFailure(lines, 'E1012:', 1)
CheckScriptFailure(['vim9script'] + lines, 'E745:', 2)
lines =<< trim END
g:cond = 'text'
while g:cond
endwhile
END
CheckDefExecAndScriptFailure(lines, 'E1030:', 2)
enddef
def Test_if_linebreak() def Test_if_linebreak()
var lines =<< trim END var lines =<< trim END
vim9script vim9script

View File

@ -18,27 +18,27 @@ def Test_expr1_trinary()
'one' : 'one' :
'two') 'two')
if has('float') if has('float')
assert_equal('one', 0.1 ? 'one' : 'two') assert_equal('one', !!0.1 ? 'one' : 'two')
endif endif
assert_equal('one', 'x' ? 'one' : 'two') assert_equal('one', !!'x' ? 'one' : 'two')
assert_equal('one', 'x' assert_equal('one', !!'x'
? 'one' ? 'one'
: 'two') : 'two')
assert_equal('one', 0z1234 ? 'one' : 'two') assert_equal('one', !!0z1234 ? 'one' : 'two')
assert_equal('one', [0] ? 'one' : 'two') assert_equal('one', !![0] ? 'one' : 'two')
assert_equal('one', #{x: 0} ? 'one' : 'two') assert_equal('one', !!#{x: 0} ? 'one' : 'two')
var name = 1 var name = 1
assert_equal('one', name ? 'one' : 'two') assert_equal('one', name ? 'one' : 'two')
assert_equal('two', false ? 'one' : 'two') assert_equal('two', false ? 'one' : 'two')
assert_equal('two', 0 ? 'one' : 'two') assert_equal('two', 0 ? 'one' : 'two')
if has('float') if has('float')
assert_equal('two', 0.0 ? 'one' : 'two') assert_equal('two', !!0.0 ? 'one' : 'two')
endif endif
assert_equal('two', '' ? 'one' : 'two') assert_equal('two', !!'' ? 'one' : 'two')
assert_equal('two', 0z ? 'one' : 'two') assert_equal('two', !!0z ? 'one' : 'two')
assert_equal('two', [] ? 'one' : 'two') assert_equal('two', !![] ? 'one' : 'two')
assert_equal('two', {} ? 'one' : 'two') assert_equal('two', !!{} ? 'one' : 'two')
name = 0 name = 0
assert_equal('two', name ? 'one' : 'two') assert_equal('two', name ? 'one' : 'two')
@ -117,6 +117,24 @@ def Test_expr1_trinary_vimscript()
END END
CheckScriptFailure(lines, 'E1004:', 2) CheckScriptFailure(lines, 'E1004:', 2)
lines =<< trim END
vim9script
var name = 'x' ? 1 : 2
END
CheckScriptFailure(lines, 'E1030:', 2)
lines =<< trim END
vim9script
var name = [] ? 1 : 2
END
CheckScriptFailure(lines, 'E745:', 2)
lines =<< trim END
vim9script
var name = {} ? 1 : 2
END
CheckScriptFailure(lines, 'E728:', 2)
# check after failure eval_flags is reset # check after failure eval_flags is reset
lines =<< trim END lines =<< trim END
vim9script vim9script
@ -152,6 +170,15 @@ func Test_expr1_trinary_fails()
call CheckDefFailure(["var x = 1 ? 'one' :'two'"], msg, 1) call CheckDefFailure(["var x = 1 ? 'one' :'two'"], msg, 1)
call CheckDefFailure(["var x = 1 ? 'one':'two'"], msg, 1) call CheckDefFailure(["var x = 1 ? 'one':'two'"], msg, 1)
call CheckDefFailure(["var x = 'x' ? 'one' : 'two'"], 'E1030:', 1)
call CheckDefFailure(["var x = 0z1234 ? 'one' : 'two'"], 'E974:', 1)
call CheckDefExecFailure(["var x = [] ? 'one' : 'two'"], 'E745:', 1)
call CheckDefExecFailure(["var x = {} ? 'one' : 'two'"], 'E728:', 1)
if has('float')
call CheckDefFailure(["var x = 0.1 ? 'one' : 'two'"], 'E805:', 1)
endif
" missing argument detected even when common type is used " missing argument detected even when common type is used
call CheckDefFailure([ call CheckDefFailure([
\ 'var X = FuncOne', \ 'var X = FuncOne',

View File

@ -543,7 +543,7 @@ def Test_try_catch_fails()
CheckDefFailure(['endtry'], 'E602:') CheckDefFailure(['endtry'], 'E602:')
CheckDefFailure(['while 1', 'endtry'], 'E170:') CheckDefFailure(['while 1', 'endtry'], 'E170:')
CheckDefFailure(['for i in range(5)', 'endtry'], 'E170:') CheckDefFailure(['for i in range(5)', 'endtry'], 'E170:')
CheckDefFailure(['if 2', 'endtry'], 'E171:') CheckDefFailure(['if 1', 'endtry'], 'E171:')
CheckDefFailure(['try', 'echo 1', 'endtry'], 'E1032:') CheckDefFailure(['try', 'echo 1', 'endtry'], 'E1032:')
CheckDefFailure(['throw'], 'E1015:') CheckDefFailure(['throw'], 'E1015:')

View File

@ -52,3 +52,10 @@ def CheckDefAndScriptFailure(lines: list<string>, error: string, lnum = -3)
CheckDefFailure(lines, error, lnum) CheckDefFailure(lines, error, lnum)
CheckScriptFailure(['vim9script'] + lines, error, lnum + 1) CheckScriptFailure(['vim9script'] + lines, error, lnum + 1)
enddef enddef
" Check that a command fails both when executed in a :def function and when
" used in Vim9 script.
def CheckDefExecAndScriptFailure(lines: list<string>, error: string, lnum = -3)
CheckDefExecFailure(lines, error, lnum)
CheckScriptFailure(['vim9script'] + lines, error, lnum + 1)
enddef

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 */
/**/
1798,
/**/ /**/
1797, 1797,
/**/ /**/

View File

@ -4212,7 +4212,17 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
// the condition is a constant, we know whether the ? or the : // the condition is a constant, we know whether the ? or the :
// expression is to be evaluated. // expression is to be evaluated.
has_const_expr = TRUE; has_const_expr = TRUE;
const_value = tv2bool(&ppconst->pp_tv[ppconst_used]); if (op_falsy)
const_value = tv2bool(&ppconst->pp_tv[ppconst_used]);
else
{
int error = FALSE;
const_value = tv_get_bool_chk(&ppconst->pp_tv[ppconst_used],
&error);
if (error)
return FAIL;
}
cctx->ctx_skip = save_skip == SKIP_YES || cctx->ctx_skip = save_skip == SKIP_YES ||
(op_falsy ? const_value : !const_value) ? SKIP_YES : SKIP_NOT; (op_falsy ? const_value : !const_value) ? SKIP_YES : SKIP_NOT;
@ -5637,6 +5647,23 @@ drop_scope(cctx_T *cctx)
vim_free(scope); vim_free(scope);
} }
/*
* Check that the top of the type stack has a type that can be used as a
* condition. Give an error and return FAIL if not.
*/
static int
bool_on_stack(cctx_T *cctx)
{
garray_T *stack = &cctx->ctx_type_stack;
type_T *type;
type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (type != &t_bool && type != &t_number && type != &t_any
&& need_type(type, &t_bool, -1, cctx, FALSE) == FAIL)
return FAIL;
return OK;
}
/* /*
* compile "if expr" * compile "if expr"
* *
@ -5689,8 +5716,14 @@ compile_if(char_u *arg, cctx_T *cctx)
clear_ppconst(&ppconst); clear_ppconst(&ppconst);
else if (instr->ga_len == instr_count && ppconst.pp_used == 1) else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
{ {
int error = FALSE;
int v;
// The expression results in a constant. // The expression results in a constant.
cctx->ctx_skip = tv2bool(&ppconst.pp_tv[0]) ? SKIP_NOT : SKIP_YES; v = tv_get_bool_chk(&ppconst.pp_tv[0], &error);
if (error)
return NULL;
cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES;
clear_ppconst(&ppconst); clear_ppconst(&ppconst);
} }
else else
@ -5699,6 +5732,8 @@ compile_if(char_u *arg, cctx_T *cctx)
cctx->ctx_skip = SKIP_UNKNOWN; cctx->ctx_skip = SKIP_UNKNOWN;
if (generate_ppconst(cctx, &ppconst) == FAIL) if (generate_ppconst(cctx, &ppconst) == FAIL)
return NULL; return NULL;
if (bool_on_stack(cctx) == FAIL)
return NULL;
} }
scope = new_scope(cctx, IF_SCOPE); scope = new_scope(cctx, IF_SCOPE);
@ -5764,9 +5799,15 @@ compile_elseif(char_u *arg, cctx_T *cctx)
clear_ppconst(&ppconst); clear_ppconst(&ppconst);
else if (instr->ga_len == instr_count && ppconst.pp_used == 1) else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
{ {
int error = FALSE;
int v;
// The expression results in a constant. // The expression results in a constant.
// TODO: how about nesting? // TODO: how about nesting?
cctx->ctx_skip = tv2bool(&ppconst.pp_tv[0]) ? SKIP_NOT : SKIP_YES; v = tv_get_bool_chk(&ppconst.pp_tv[0], &error);
if (error)
return NULL;
cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES;
clear_ppconst(&ppconst); clear_ppconst(&ppconst);
scope->se_u.se_if.is_if_label = -1; scope->se_u.se_if.is_if_label = -1;
} }
@ -5776,6 +5817,8 @@ compile_elseif(char_u *arg, cctx_T *cctx)
cctx->ctx_skip = SKIP_UNKNOWN; cctx->ctx_skip = SKIP_UNKNOWN;
if (generate_ppconst(cctx, &ppconst) == FAIL) if (generate_ppconst(cctx, &ppconst) == FAIL)
return NULL; return NULL;
if (bool_on_stack(cctx) == FAIL)
return NULL;
// "where" is set when ":elseif", "else" or ":endif" is found // "where" is set when ":elseif", "else" or ":endif" is found
scope->se_u.se_if.is_if_label = instr->ga_len; scope->se_u.se_if.is_if_label = instr->ga_len;
@ -6037,6 +6080,9 @@ compile_while(char_u *arg, cctx_T *cctx)
if (compile_expr0(&p, cctx) == FAIL) if (compile_expr0(&p, cctx) == FAIL)
return NULL; return NULL;
if (bool_on_stack(cctx) == FAIL)
return FAIL;
// "while_end" is set when ":endwhile" is found // "while_end" is set when ":endwhile" is found
if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label, if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label,
JUMP_IF_FALSE, cctx) == FAIL) JUMP_IF_FALSE, cctx) == FAIL)

View File

@ -1908,6 +1908,7 @@ call_def_function(
{ {
tv = STACK_TV_BOT(-1); tv = STACK_TV_BOT(-1);
if (when == JUMP_IF_COND_FALSE if (when == JUMP_IF_COND_FALSE
|| when == JUMP_IF_FALSE
|| when == JUMP_IF_COND_TRUE) || when == JUMP_IF_COND_TRUE)
{ {
SOURCING_LNUM = iptr->isn_lnum; SOURCING_LNUM = iptr->isn_lnum;
@ -3403,7 +3404,7 @@ ex_disassemble(exarg_T *eap)
} }
/* /*
* Return TRUE when "tv" is not falsey: non-zero, non-empty string, non-empty * Return TRUE when "tv" is not falsy: non-zero, non-empty string, non-empty
* list, etc. Mostly like what JavaScript does, except that empty list and * list, etc. Mostly like what JavaScript does, except that empty list and
* empty dictionary are FALSE. * empty dictionary are FALSE.
*/ */