summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-10-03 22:52:39 +0200
committerBram Moolenaar <Bram@vim.org>2020-10-03 22:52:39 +0200
commit2bb2658bef9fb25b320f87147261b7154494a86f (patch)
treed191313ffa993e6b2bb20092b37b3c30bbd7b923
parent92f26c256e06277ff2ec4ce7adea1eb58c85abe0 (diff)
patch 8.2.1795: Vim9: operators && and || have a confusing resultv8.2.1795
Problem: Vim9: operators && and || have a confusing result. Solution: Make the result a boolean.
-rw-r--r--runtime/doc/vim9.txt60
-rw-r--r--src/eval.c102
-rw-r--r--src/structs.h3
-rw-r--r--src/testdir/test_vim9_assign.vim10
-rw-r--r--src/testdir/test_vim9_cmd.vim4
-rw-r--r--src/testdir/test_vim9_disassemble.vim25
-rw-r--r--src/testdir/test_vim9_expr.vim149
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h9
-rw-r--r--src/vim9compile.c56
-rw-r--r--src/vim9execute.c40
-rw-r--r--src/vim9type.c12
12 files changed, 255 insertions, 217 deletions
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index 86879dde84..0c20baeb89 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -154,25 +154,25 @@ Functions and variables are script-local by default ~
*vim9-scopes*
When using `:function` or `:def` to specify a new function at the script level
in a Vim9 script, the function is local to the script, as if "s:" was
-prefixed. Using the "s:" prefix is optional. To define or use a global
-function or variable the "g:" prefix should be used. For functions in an
-autoload script the "name#" prefix is sufficient. >
+prefixed. Using the "s:" prefix is optional. To define a global function or
+variable the "g:" prefix must be used. For functions in an autoload script
+the "name#" prefix is sufficient. >
def ThisFunction() # script-local
def s:ThisFunction() # script-local
def g:ThatFunction() # global
- def ThatFunction() # global if no local ThatFunction()
def scriptname#function() # autoload
-When using `:function` or `:def` to specify a new function inside a function,
-the function is local to the function. It is not possible to define a
-script-local function inside a function. It is possible to define a global
-function, using the "g:" prefix.
+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
+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.
-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.
+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.
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
@@ -184,7 +184,7 @@ and cannot be deleted or replaced.
Variable declarations with :var, :final and :const ~
- *vim9-declaration*
+ *vim9-declaration* *:var*
Local variables need to be declared with `:var`. Local constants need to be
declared with `:final` or `:const`. We refer to both as "variables" in this
section.
@@ -261,7 +261,7 @@ Example: >
myList = [3, 4] # Error!
myList[0] = 9 # Error!
muList->add(3) # Error!
-
+< *:final*
`:final` is used for making only the variable a constant, the value can be
changed. This is well known from Java. Example: >
final myList = [1, 2]
@@ -471,10 +471,6 @@ 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.
-Any type of variable can be used as a condition, there is no error, not even
-for using a list or job. This is very much like JavaScript, but there are a
-few exceptions.
-
type TRUE when ~
bool v:true or 1
number non-zero
@@ -490,17 +486,25 @@ few exceptions.
class when not NULL
object when not NULL (TODO: when isTrue() returns v:true)
-The boolean operators "||" and "&&" do not change the value: >
- 8 || 2 == 8
- 0 || 2 == 2
- 0 || '' == ''
- 8 && 2 == 2
- 0 && 2 == 0
- 2 && 0 == 0
- [] && 2 == []
-
-When using `..` for string concatenation arguments of simple types are always
-converted to string. >
+The boolean operators "||" and "&&" expect the values to be boolean, zero or
+one: >
+ 1 || false == true
+ 0 || 1 == true
+ 0 || false == false
+ 1 && true == true
+ 0 && 1 == false
+ 8 || 0 Error!
+ 'yes' && 0 Error!
+ [] || 99 Error!
+
+When using "!" for inverting, there is no error for using any type and the
+result is a boolean: >
+ !'yes' == false
+ var myList = [1, 2, 3]
+ !!myList == true
+
+When using "`.."` for string concatenation arguments of simple types are
+always converted to string. >
'hello ' .. 123 == 'hello 123'
'hello ' .. v:true == 'hello v:true'
diff --git a/src/eval.c b/src/eval.c
index 911f0abc67..a678d32e64 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2296,7 +2296,7 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
int orig_flags;
long result = FALSE;
typval_T var2;
- int error;
+ int error = FALSE;
int vim9script = in_vim9script();
if (evalarg == NULL)
@@ -2309,18 +2309,12 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (evaluate)
{
if (vim9script)
- {
- result = tv2bool(rettv);
- }
- else
- {
- error = FALSE;
- if (tv_get_number_chk(rettv, &error) != 0)
- result = TRUE;
- clear_tv(rettv);
- if (error)
- return FAIL;
- }
+ result = tv_get_bool_chk(rettv, &error);
+ else if (tv_get_number_chk(rettv, &error) != 0)
+ result = TRUE;
+ clear_tv(rettv);
+ if (error)
+ return FAIL;
}
/*
@@ -2362,25 +2356,26 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (evaluate && !result)
{
if (vim9script)
+ result = tv_get_bool_chk(&var2, &error);
+ else if (tv_get_number_chk(&var2, &error) != 0)
+ result = TRUE;
+ clear_tv(&var2);
+ if (error)
+ return FAIL;
+ }
+ if (evaluate)
+ {
+ if (vim9script)
{
- clear_tv(rettv);
- *rettv = var2;
- result = tv2bool(rettv);
+ rettv->v_type = VAR_BOOL;
+ rettv->vval.v_number = result ? VVAL_TRUE : VVAL_FALSE;
}
else
{
- if (tv_get_number_chk(&var2, &error) != 0)
- result = TRUE;
- clear_tv(&var2);
- if (error)
- return FAIL;
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
}
- if (evaluate && !vim9script)
- {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
- }
p = eval_next_non_blank(*arg, evalarg_used, &getnext);
}
@@ -2389,9 +2384,6 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
clear_evalarg(&local_evalarg, NULL);
else
evalarg->eval_flags = orig_flags;
-
- // Resulting value can be assigned to a bool.
- rettv->v_lock |= VAR_BOOL_OK;
}
return OK;
@@ -2430,7 +2422,7 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
int evaluate;
long result = TRUE;
typval_T var2;
- int error;
+ int error = FALSE;
int vim9script = in_vim9script();
if (evalarg == NULL)
@@ -2443,18 +2435,12 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (evaluate)
{
if (vim9script)
- {
- result = tv2bool(rettv);
- }
- else
- {
- error = FALSE;
- if (tv_get_number_chk(rettv, &error) == 0)
- result = FALSE;
- clear_tv(rettv);
- if (error)
- return FAIL;
- }
+ result = tv_get_bool_chk(rettv, &error);
+ else if (tv_get_number_chk(rettv, &error) == 0)
+ result = FALSE;
+ clear_tv(rettv);
+ if (error)
+ return FAIL;
}
/*
@@ -2466,7 +2452,7 @@ eval3(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, 2);
clear_tv(rettv);
@@ -2497,25 +2483,26 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (evaluate && result)
{
if (vim9script)
+ result = tv_get_bool_chk(&var2, &error);
+ else if (tv_get_number_chk(&var2, &error) == 0)
+ result = FALSE;
+ clear_tv(&var2);
+ if (error)
+ return FAIL;
+ }
+ if (evaluate)
+ {
+ if (vim9script)
{
- clear_tv(rettv);
- *rettv = var2;
- result = tv2bool(rettv);
+ rettv->v_type = VAR_BOOL;
+ rettv->vval.v_number = result ? VVAL_TRUE : VVAL_FALSE;
}
else
{
- if (tv_get_number_chk(&var2, &error) == 0)
- result = FALSE;
- clear_tv(&var2);
- if (error)
- return FAIL;
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
}
- if (evaluate && !vim9script)
- {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
- }
p = eval_next_non_blank(*arg, evalarg_used, &getnext);
}
@@ -2524,9 +2511,6 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
clear_evalarg(&local_evalarg, NULL);
else
evalarg->eval_flags = orig_flags;
-
- // Resulting value can be assigned to a bool.
- rettv->v_lock |= VAR_BOOL_OK;
}
return OK;
diff --git a/src/structs.h b/src/structs.h
index 85607fcece..86c6ef8992 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1382,7 +1382,7 @@ struct type_S {
typedef struct
{
vartype_T v_type;
- char v_lock; // see below: VAR_LOCKED, VAR_FIXED, VAR_BOOL_OK
+ char v_lock; // see below: VAR_LOCKED, VAR_FIXED
union
{
varnumber_T v_number; // number value
@@ -1409,7 +1409,6 @@ typedef struct
// Values for "v_lock".
#define VAR_LOCKED 1 // locked with lock(), can use unlock()
#define VAR_FIXED 2 // locked forever
-#define VAR_BOOL_OK 4 // can be convered to bool
/*
* Structure to hold an item of a list: an internal variable without a name.
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 7d3291240d..189ff4da01 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -22,11 +22,11 @@ def Test_assignment_bool()
var bool4: bool = 1
assert_equal(true, bool4)
- var bool5: bool = 'yes' && 'no'
+ var bool5: bool = 1 && true
assert_equal(true, bool5)
- var bool6: bool = [] && 99
+ var bool6: bool = 0 && 1
assert_equal(false, bool6)
- var bool7: bool = [] || #{a: 1} && 99
+ var bool7: bool = 0 || 1 && true
assert_equal(true, bool7)
var lines =<< trim END
@@ -41,9 +41,9 @@ def Test_assignment_bool()
assert_equal(false, flag)
flag = 1
assert_equal(true, flag)
- flag = 99 || 123
+ flag = 1 || true
assert_equal(true, flag)
- flag = 'yes' && []
+ flag = 1 && false
assert_equal(false, flag)
END
CheckScriptSuccess(lines)
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
index 060b083863..82d5f325e4 100644
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -72,8 +72,8 @@ def Test_if_linebreak()
var lines =<< trim END
vim9script
if 1 &&
- 2
- || 3
+ true
+ || 1
g:res = 42
endif
assert_equal(42, g:res)
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index a8dbe1a3cc..ceeae0db51 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -766,11 +766,11 @@ def Test_disassemble_and_or()
'\d LOAD arg\[-1]\_s*' ..
'\d PUSHNR 1\_s*' ..
'\d COMPAREANY ==\_s*' ..
- '\d JUMP_AND_KEEP_IF_FALSE -> \d\+\_s*' ..
+ '\d JUMP_IF_COND_FALSE -> \d\+\_s*' ..
'\d LOAD arg\[-1]\_s*' ..
'\d PUSHNR 2\_s*' ..
'\d COMPAREANY !=\_s*' ..
- '\d JUMP_AND_KEEP_IF_TRUE -> \d\+\_s*' ..
+ '\d JUMP_IF_COND_TRUE -> \d\+\_s*' ..
'\d LOAD arg\[-1]\_s*' ..
'\d\+ PUSHNR 4\_s*' ..
'\d\+ COMPAREANY ==\_s*' ..
@@ -1200,22 +1200,23 @@ def Test_disassemble_invert_bool()
enddef
def ReturnBool(): bool
- var var: bool = "no" && [] || 123
- return var
+ var name: bool = 1 && 0 || 1
+ return name
enddef
def Test_disassemble_return_bool()
var instr = execute('disassemble ReturnBool')
assert_match('ReturnBool\_s*' ..
- 'var var: bool = "no" && \[\] || 123\_s*' ..
- '0 PUSHS "no"\_s*' ..
- '1 JUMP_AND_KEEP_IF_FALSE -> 3\_s*' ..
- '2 NEWLIST size 0\_s*' ..
- '3 JUMP_AND_KEEP_IF_TRUE -> 5\_s*' ..
- '4 PUSHNR 123\_s*' ..
- '5 2BOOL (!!val)\_s*' ..
+ 'var name: bool = 1 && 0 || 1\_s*' ..
+ '0 PUSHNR 1\_s*' ..
+ '1 JUMP_IF_COND_FALSE -> 3\_s*' ..
+ '2 PUSHNR 0\_s*' ..
+ '3 COND2BOOL\_s*' ..
+ '4 JUMP_IF_COND_TRUE -> 6\_s*' ..
+ '5 PUSHNR 1\_s*' ..
+ '6 2BOOL (!!val)\_s*' ..
'\d STORE $0\_s*' ..
- 'return var\_s*' ..
+ 'return name\_s*' ..
'\d LOAD $0\_s*' ..
'\d RETURN',
instr)
diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim
index 253e469bfa..5158e1093f 100644
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -196,32 +196,32 @@ enddef
" test ||
def Test_expr2()
- assert_equal(2, 2 || 0)
- assert_equal(7, 0 ||
+ assert_equal(true, 1 || 0)
+ assert_equal(true, 0 ||
0 ||
- 7)
- assert_equal(0, 0 || 0)
- assert_equal(0, 0
+ 1)
+ assert_equal(false, 0 || 0)
+ assert_equal(false, 0
|| 0)
- assert_equal('', 0 || '')
+ assert_equal(false, 0 || false)
g:vals = []
- assert_equal(3, Record(3) || Record(1))
- assert_equal([3], g:vals)
+ assert_equal(true, Record(1) || Record(3))
+ assert_equal([1], g:vals)
g:vals = []
- assert_equal(5, Record(0) || Record(5))
- assert_equal([0, 5], g:vals)
+ assert_equal(true, Record(0) || Record(1))
+ assert_equal([0, 1], g:vals)
g:vals = []
- assert_equal(4, Record(0)
- || Record(4)
+ assert_equal(true, Record(0)
+ || Record(1)
|| Record(0))
- assert_equal([0, 4], g:vals)
+ assert_equal([0, 1], g:vals)
g:vals = []
- assert_equal(0, Record([]) || Record('') || Record(0))
- assert_equal([[], '', 0], g:vals)
+ assert_equal(false, Record(0) || Record(false) || Record(0))
+ assert_equal([0, false, 0], g:vals)
enddef
def Test_expr2_vimscript()
@@ -230,7 +230,7 @@ def Test_expr2_vimscript()
vim9script
var name = 0
|| 1
- assert_equal(1, name)
+ assert_equal(true, name)
END
CheckScriptSuccess(lines)
@@ -269,80 +269,85 @@ def Test_expr2_vimscript()
END
CheckScriptFailure(lines, 'E1004:', 2)
- # check keeping the value
+ # check evaluating to bool
lines =<< trim END
- vim9script
- assert_equal(2, 2 || 0)
- assert_equal(7, 0 ||
+ assert_equal(true, 1 || 0)
+ assert_equal(true, 0 ||
0 ||
- 7)
- assert_equal(0, 0 || 0)
- assert_equal(0, 0
+ !!7)
+ assert_equal(false, 0 || 0)
+ assert_equal(false, 0
|| 0)
- assert_equal('', 0 || '')
+ assert_equal(false, 0 || false)
g:vals = []
- assert_equal(3, Record(3) || Record(1))
- assert_equal([3], g:vals)
+ assert_equal(true, Record(true) || Record(false))
+ assert_equal([true], g:vals)
g:vals = []
- assert_equal(5, Record(0) || Record(5))
- assert_equal([0, 5], g:vals)
+ assert_equal(true, Record(0) || Record(true))
+ assert_equal([0, true], g:vals)
g:vals = []
- assert_equal(4, Record(0)
- || Record(4)
+ assert_equal(true, Record(0)
+ || Record(true)
|| Record(0))
- assert_equal([0, 4], g:vals)
+ assert_equal([0, true], g:vals)
g:vals = []
- assert_equal(0, Record([]) || Record('') || Record(0))
- assert_equal([[], '', 0], g:vals)
+ assert_equal(false, Record(0) || Record(false) || Record(0))
+ assert_equal([0, false, 0], g:vals)
END
- CheckScriptSuccess(lines)
+ CheckDefAndScriptSuccess(lines)
enddef
-func Test_expr2_fails()
- let msg = "White space required before and after '||'"
+def Test_expr2_fails()
+ var msg = "White space required before and after '||'"
call CheckDefFailure(["var x = 1||2"], msg, 1)
call CheckDefFailure(["var x = 1 ||2"], msg, 1)
call CheckDefFailure(["var x = 1|| 2"], msg, 1)
call CheckDefFailure(["var x = 1 || xxx"], 'E1001:', 1)
-endfunc
+
+ # TODO: should fail at compile time
+ call CheckDefExecFailure(["var x = 3 || 7"], 'E1023:', 1)
+ call CheckScriptFailure(["vim9script", "var x = 3 || 7"], 'E1023:', 2)
+ call CheckDefExecFailure(["var x = [] || false"], 'E745:', 1)
+ call CheckScriptFailure(["vim9script", "var x = [] || false"], 'E745:', 2)
+enddef
" test &&
def Test_expr3()
- assert_equal(0, 2 && 0)
- assert_equal(0, 0 &&
+ assert_equal(false, 1 && 0)
+ assert_equal(false, 0 &&
0 &&
- 7)
- assert_equal(7, 2
- && 3
- && 7)
- assert_equal(0, 0 && 0)
- assert_equal(0, 0 && '')
- assert_equal('', 8 && '')
+ 1)
+ assert_equal(true, 1
+ && true
+ && 1)
+ assert_equal(false, 0 && 0)
+ assert_equal(false, 0 && false)
+ assert_equal(true, 1 && true)
g:vals = []
- assert_equal(1, Record(3) && Record(1))
- assert_equal([3, 1], g:vals)
+ assert_equal(true, Record(true) && Record(1))
+ assert_equal([true, 1], g:vals)
g:vals = []
- assert_equal(0, Record(0) && Record(5))
+ assert_equal(false, Record(0) && Record(1))
assert_equal([0], g:vals)
g:vals = []
- assert_equal(0, Record(0) && Record(4) && Record(0))
+ assert_equal(false, Record(0) && Record(4) && Record(0))
assert_equal([0], g:vals)
g:vals = []
- assert_equal(0, Record(8) && Record(4) && Record(0))
- assert_equal([8, 4, 0], g:vals)
+ assert_equal(false, Record(1) && Record(true) && Record(0))
+ assert_equal([1, true, 0], g:vals)
g:vals = []
- assert_equal(0, Record([1]) && Record('z') && Record(0))
- assert_equal([[1], 'z', 0], g:vals)
+ assert_equal(false, Record(1) && Record(true) && Record(0))
+ assert_equal([1, true, 0], g:vals)
enddef
def Test_expr3_vimscript()
@@ -351,7 +356,7 @@ def Test_expr3_vimscript()
vim9script
var name = 0
&& 1
- assert_equal(0, name)
+ assert_equal(false, name)
END
CheckScriptSuccess(lines)
@@ -393,36 +398,32 @@ def Test_expr3_vimscript()
# check keeping the value
lines =<< trim END
vim9script
- assert_equal(0, 2 && 0)
- assert_equal(0, 0 &&
+ assert_equal(false, 1 && 0)
+ assert_equal(false, 0 &&
0 &&
- 7)
- assert_equal(7, 2
- && 3
- && 7)
- assert_equal(0, 0 && 0)
- assert_equal(0, 0 && '')
- assert_equal('', 8 && '')
+ 1)
+ assert_equal(true, 1
+ && true
+ && 1)
+ assert_equal(false, 0 && 0)
+ assert_equal(false, 0 && false)
+ assert_equal(false, 1 && 0)
g:vals = []
- assert_equal(1, Record(3) && Record(1))
- assert_equal([3, 1], g:vals)
+ assert_equal(true, Record(1) && Record(true))
+ assert_equal([1, true], g:vals)
g:vals = []
- assert_equal(0, Record(0) && Record(5))
+ assert_equal(false, Record(0) && Record(1))
assert_equal([0], g:vals)
g:vals = []
- assert_equal(0, Record(0) && Record(4) && Record(0))
+ assert_equal(false, Record(0) && Record(1) && Record(0))
assert_equal([0], g:vals)
g:vals = []
- assert_equal(0, Record(8) && Record(4) && Record(0))
- assert_equal([8, 4, 0], g:vals)
-
- g:vals = []
- assert_equal(0, Record([1]) && Record('z') && Record(0))
- assert_equal([[1], 'z', 0], g:vals)
+ assert_equal(false, Record(1) && Record(true) && Record(0))
+ assert_equal([1, true, 0], g:vals)
END
CheckScriptSuccess(lines)
enddef
diff --git a/src/version.c b/src/version.c
index bd57233f03..984da5f24b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1795,
+/**/
1794,
/**/
1793,
diff --git a/src/vim9.h b/src/vim9.h
index 663faa6800..537e3abe08 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -128,7 +128,8 @@ typedef enum {
ISN_GETITEM, // push list item, isn_arg.number is the index
ISN_MEMBER, // dict[member]
ISN_STRINGMEMBER, // dict.member using isn_arg.string
- ISN_2BOOL, // convert value to bool, invert if isn_arg.number != 0
+ ISN_2BOOL, // falsy/truthy to bool, invert if isn_arg.number != 0
+ ISN_COND2BOOL, // convert value to bool
ISN_2STRING, // convert value to string at isn_arg.number on stack
ISN_2STRING_ANY, // like ISN_2STRING but check type
ISN_NEGATENR, // apply "-" to number
@@ -171,8 +172,10 @@ typedef struct {
typedef enum {
JUMP_ALWAYS,
JUMP_IF_FALSE, // pop and jump if false
- JUMP_AND_KEEP_IF_TRUE, // jump if top of stack is true, drop if not
- JUMP_AND_KEEP_IF_FALSE, // jump if top of stack is false, drop if not
+ JUMP_AND_KEEP_IF_TRUE, // jump if top of stack is truthy, drop if not
+ JUMP_AND_KEEP_IF_FALSE, // jump if top of stack is falsy, drop if not
+ JUMP_IF_COND_TRUE, // jump if top of stack is true, drop if not
+ JUMP_IF_COND_FALSE, // jump if top of stack is false, drop if not
} jumpwhen_T;
// arguments to ISN_JUMP
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 88da80044e..f7dc9df203 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -706,6 +706,25 @@ generate_2BOOL(cctx_T *cctx, int invert)
return OK;
}
+/*
+ * Generate an ISN_COND2BOOL instruction.
+ */
+ static int
+generate_COND2BOOL(cctx_T *cctx)
+{
+ isn_T *isn;
+ garray_T *stack = &cctx->ctx_type_stack;
+
+ RETURN_OK_IF_SKIP(cctx);
+ if ((isn = generate_instr(cctx, ISN_COND2BOOL)) == NULL)
+ return FAIL;
+
+ // type becomes bool
+ ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool;
+
+ return OK;
+}
+
static int
generate_TYPECHECK(
cctx_T *cctx,
@@ -4003,7 +4022,7 @@ compile_and_or(
garray_T *instr = &cctx->ctx_instr;
garray_T end_ga;
garray_T *stack = &cctx->ctx_type_stack;
- type_T **typep;
+ int all_bool_values = TRUE;
/*
* Repeat until there is no following "||" or "&&"
@@ -4023,9 +4042,13 @@ compile_and_or(
return FAIL;
}
- // TODO: use ppconst if the value is a constant
+ // TODO: use ppconst if the value is a constant and check
+ // evaluating to bool
generate_ppconst(cctx, ppconst);
+ if (((type_T **)stack->ga_data)[stack->ga_len - 1] != &t_bool)
+ all_bool_values = FALSE;
+
if (ga_grow(&end_ga, 1) == FAIL)
{
ga_clear(&end_ga);
@@ -4034,7 +4057,7 @@ compile_and_or(
*(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len;
++end_ga.ga_len;
generate_JUMP(cctx, opchar == '|'
- ? JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0);
+ ? JUMP_IF_COND_TRUE : JUMP_IF_COND_FALSE, 0);
// eval the next expression
*arg = skipwhite(p + 2);
@@ -4064,19 +4087,9 @@ compile_and_or(
}
ga_clear(&end_ga);
- // The resulting type can be used as a bool.
- typep = ((type_T **)stack->ga_data) + stack->ga_len - 1;
- if (*typep != &t_bool)
- {
- type_T *type = get_type_ptr(cctx->ctx_type_list);
-
- if (type != NULL)
- {
- *type = **typep;
- type->tt_flags |= TTFLAG_BOOL_OK;
- *typep = type;
- }
- }
+ // The resulting type is converted to bool if needed.
+ if (!all_bool_values)
+ generate_COND2BOOL(cctx);
}
return OK;
@@ -4087,10 +4100,11 @@ compile_and_or(
*
* Produces instructions:
* EVAL expr4a Push result of "expr4a"
- * JUMP_AND_KEEP_IF_FALSE end
+ * JUMP_IF_COND_FALSE end
* EVAL expr4b Push result of "expr4b"
- * JUMP_AND_KEEP_IF_FALSE end
+ * JUMP_IF_COND_FALSE end
* EVAL expr4c Push result of "expr4c"
+ * COND2BOOL
* end:
*/
static int
@@ -4111,10 +4125,11 @@ compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
*
* Produces instructions:
* EVAL expr3a Push result of "expr3a"
- * JUMP_AND_KEEP_IF_TRUE end
+ * JUMP_IF_COND_TRUE end
* EVAL expr3b Push result of "expr3b"
- * JUMP_AND_KEEP_IF_TRUE end
+ * JUMP_IF_COND_TRUE end
* EVAL expr3c Push result of "expr3c"
+ * COND2BOOL
* end:
*/
static int
@@ -7415,6 +7430,7 @@ delete_instr(isn_T *isn)
case ISN_COMPARESPECIAL:
case ISN_COMPARESTRING:
case ISN_CONCAT:
+ case ISN_COND2BOOL:
case ISN_DROP:
case ISN_ECHO:
case ISN_ECHOERR:
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 4013571a0b..b1065ca8e8 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1901,14 +1901,25 @@ call_def_function(
case ISN_JUMP:
{
jumpwhen_T when = iptr->isn_arg.jump.jump_when;
+ int error = FALSE;
int jump = TRUE;
if (when != JUMP_ALWAYS)
{
tv = STACK_TV_BOT(-1);
- jump = tv2bool(tv);
+ if (when == JUMP_IF_COND_FALSE
+ || when == JUMP_IF_COND_TRUE)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ jump = tv_get_bool_chk(tv, &error);
+ if (error)
+ goto on_error;
+ }
+ else
+ jump = tv2bool(tv);
if (when == JUMP_IF_FALSE
- || when == JUMP_AND_KEEP_IF_FALSE)
+ || when == JUMP_AND_KEEP_IF_FALSE
+ || when == JUMP_IF_COND_FALSE)
jump = !jump;
if (when == JUMP_IF_FALSE || !jump)
{
@@ -2624,13 +2635,25 @@ call_def_function(
break;
case ISN_2BOOL:
+ case ISN_COND2BOOL:
{
int n;
+ int error = FALSE;
tv = STACK_TV_BOT(-1);
- n = tv2bool(tv);
- if (iptr->isn_arg.number) // invert
- n = !n;
+ if (iptr->isn_type == ISN_2BOOL)
+ {
+ n = tv2bool(tv);
+ if (iptr->isn_arg.number) // invert
+ n = !n;
+ }
+ else
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ n = tv_get_bool_chk(tv, &error);
+ if (error)
+ goto on_error;
+ }
clear_tv(tv);
tv->v_type = VAR_BOOL;
tv->vval.v_number = n ? VVAL_TRUE : VVAL_FALSE;
@@ -3192,6 +3215,12 @@ ex_disassemble(exarg_T *eap)
case JUMP_AND_KEEP_IF_FALSE:
when = "JUMP_AND_KEEP_IF_FALSE";
break;
+ case JUMP_IF_COND_FALSE:
+ when = "JUMP_IF_COND_FALSE";
+ break;
+ case JUMP_IF_COND_TRUE:
+ when = "JUMP_IF_COND_TRUE";
+ break;
}
smsg("%4d %s -> %d", current, when,
iptr->isn_arg.jump.jump_where);
@@ -3342,6 +3371,7 @@ ex_disassemble(exarg_T *eap)
iptr->isn_arg.checklen.cl_more_OK ? ">= " : "",
iptr->isn_arg.checklen.cl_min_len);
break;
+ case ISN_COND2BOOL: smsg("%4d COND2BOOL", current); break;
case ISN_2BOOL: if (iptr->isn_arg.number)
smsg("%4d INVERT (!val)", current);
else
diff --git a/src/vim9type.c b/src/vim9type.c
index b5866bc400..b50f5f18e5 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -360,13 +360,12 @@ typval2type_int(typval_T *tv, garray_T *type_gap)
need_convert_to_bool(type_T *type, typval_T *tv)
{
return type != NULL && type == &t_bool && tv->v_type != VAR_BOOL
- && ((tv->v_lock & VAR_BOOL_OK)
- || (tv->v_type == VAR_NUMBER
- && (tv->vval.v_number == 0 || tv->vval.v_number == 1)));
+ && (tv->v_type == VAR_NUMBER
+ && (tv->vval.v_number == 0 || tv->vval.v_number == 1));
}
/*
- * Get a type_T for a typval_T and handle VAR_BOOL_OK.
+ * Get a type_T for a typval_T.
* "type_list" is used to temporarily create types in.
*/
type_T *
@@ -375,9 +374,8 @@ typval2type(typval_T *tv, garray_T *type_gap)
type_T *type = typval2type_int(tv, type_gap);
if (type != NULL && type != &t_bool
- && ((tv->v_type == VAR_NUMBER
- && (tv->vval.v_number == 0 || tv->vval.v_number == 1))
- || (tv->v_lock & VAR_BOOL_OK)))
+ && (tv->v_type == VAR_NUMBER
+ && (tv->vval.v_number == 0 || tv->vval.v_number == 1)))
{
type_T *newtype = get_type_ptr(type_gap);