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`
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.
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
imported) before looking for a global function. However, it is recommended to
always use "g:" to refer to a local function for clarity. In all cases the
function must be defined before used. That is when it is first called or when
`:defcompile` causes the call to be compiled.
search for the function:
- in the function scope
- in the script scope, possibly imported
- in the list of global functions
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
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 are mostly working like they do in JavaScript. A
difference is made where JavaScript does not work like most people expect.
Specifically, an empty list is falsy.
Conditions and expressions are mostly working like they do in other languages.
Some values are different from legacy Vim script:
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
number non-zero
float non-zero
@ -498,13 +513,13 @@ one: >
[] || 99 Error!
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
var myList = [1, 2, 3]
!!myList == true
!![] == false
!![1, 2, 3] == true
When using "`.."` for string concatenation arguments of simple types are
always converted to string. >
always converted to string: >
'hello ' .. 123 == 'hello 123'
'hello ' .. v:true == 'hello v:true'

View File

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

View File

@ -42,6 +42,16 @@ func Test_version()
call assert_false(has('patch-9.9.1'))
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()
call assert_equal(v:true, v:true ?? 456)
call assert_equal(123, 123 ?? 456)

View File

@ -68,6 +68,74 @@ def Test_echo_linebreak()
CheckScriptSuccess(lines)
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()
var lines =<< trim END
vim9script

View File

@ -18,27 +18,27 @@ def Test_expr1_trinary()
'one' :
'two')
if has('float')
assert_equal('one', 0.1 ? 'one' : 'two')
assert_equal('one', !!0.1 ? 'one' : 'two')
endif
assert_equal('one', 'x' ? 'one' : 'two')
assert_equal('one', 'x'
assert_equal('one', !!'x' ? 'one' : 'two')
assert_equal('one', !!'x'
? 'one'
: 'two')
assert_equal('one', 0z1234 ? 'one' : 'two')
assert_equal('one', [0] ? 'one' : 'two')
assert_equal('one', #{x: 0} ? 'one' : 'two')
assert_equal('one', !!0z1234 ? 'one' : 'two')
assert_equal('one', !![0] ? 'one' : 'two')
assert_equal('one', !!#{x: 0} ? 'one' : 'two')
var name = 1
assert_equal('one', name ? 'one' : 'two')
assert_equal('two', false ? 'one' : 'two')
assert_equal('two', 0 ? 'one' : 'two')
if has('float')
assert_equal('two', 0.0 ? 'one' : 'two')
assert_equal('two', !!0.0 ? 'one' : 'two')
endif
assert_equal('two', '' ? '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', !!0z ? 'one' : 'two')
assert_equal('two', !![] ? 'one' : 'two')
assert_equal('two', !!{} ? 'one' : 'two')
name = 0
assert_equal('two', name ? 'one' : 'two')
@ -117,6 +117,24 @@ def Test_expr1_trinary_vimscript()
END
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
lines =<< trim END
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 = '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
call CheckDefFailure([
\ 'var X = FuncOne',

View File

@ -543,7 +543,7 @@ def Test_try_catch_fails()
CheckDefFailure(['endtry'], 'E602:')
CheckDefFailure(['while 1', '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(['throw'], 'E1015:')

View File

@ -52,3 +52,10 @@ def CheckDefAndScriptFailure(lines: list<string>, error: string, lnum = -3)
CheckDefFailure(lines, error, lnum)
CheckScriptFailure(['vim9script'] + lines, error, lnum + 1)
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[] =
{ /* Add new patch number below this line */
/**/
1798,
/**/
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 :
// expression is to be evaluated.
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 ||
(op_falsy ? const_value : !const_value) ? SKIP_YES : SKIP_NOT;
@ -5637,6 +5647,23 @@ drop_scope(cctx_T *cctx)
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"
*
@ -5689,8 +5716,14 @@ compile_if(char_u *arg, cctx_T *cctx)
clear_ppconst(&ppconst);
else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
{
int error = FALSE;
int v;
// 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);
}
else
@ -5699,6 +5732,8 @@ compile_if(char_u *arg, cctx_T *cctx)
cctx->ctx_skip = SKIP_UNKNOWN;
if (generate_ppconst(cctx, &ppconst) == FAIL)
return NULL;
if (bool_on_stack(cctx) == FAIL)
return NULL;
}
scope = new_scope(cctx, IF_SCOPE);
@ -5764,9 +5799,15 @@ compile_elseif(char_u *arg, cctx_T *cctx)
clear_ppconst(&ppconst);
else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
{
int error = FALSE;
int v;
// The expression results in a constant.
// 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);
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;
if (generate_ppconst(cctx, &ppconst) == FAIL)
return NULL;
if (bool_on_stack(cctx) == FAIL)
return NULL;
// "where" is set when ":elseif", "else" or ":endif" is found
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)
return NULL;
if (bool_on_stack(cctx) == FAIL)
return FAIL;
// "while_end" is set when ":endwhile" is found
if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label,
JUMP_IF_FALSE, cctx) == FAIL)

View File

@ -1908,6 +1908,7 @@ call_def_function(
{
tv = STACK_TV_BOT(-1);
if (when == JUMP_IF_COND_FALSE
|| when == JUMP_IF_FALSE
|| when == JUMP_IF_COND_TRUE)
{
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
* empty dictionary are FALSE.
*/