changeset 22494:4c21f7f6f9e3 v8.2.1795

patch 8.2.1795: Vim9: operators && and || have a confusing result Commit: https://github.com/vim/vim/commit/2bb2658bef9fb25b320f87147261b7154494a86f Author: Bram Moolenaar <Bram@vim.org> Date: Sat Oct 3 22:52:39 2020 +0200 patch 8.2.1795: Vim9: operators && and || have a confusing result Problem: Vim9: operators && and || have a confusing result. Solution: Make the result a boolean.
author Bram Moolenaar <Bram@vim.org>
date Sat, 03 Oct 2020 23:00:04 +0200
parents 51353715e9a1
children f54613c0687b
files runtime/doc/vim9.txt src/eval.c src/structs.h src/testdir/test_vim9_assign.vim src/testdir/test_vim9_cmd.vim src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_expr.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c src/vim9type.c
diffstat 12 files changed, 254 insertions(+), 216 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -154,25 +154,25 @@ Functions and variables are script-local
 							*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 wo
 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  == []
+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 string concatenation arguments of simple types are always
-converted to string. >
+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'
 
--- a/src/eval.c
+++ b/src/eval.c
@@ -2296,7 +2296,7 @@ eval2(char_u **arg, typval_T *rettv, eva
 	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, eva
 	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, eva
 	    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, eva
 	    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, eva
 	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, eva
 	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, eva
 		*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, eva
 	    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, eva
 	    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;
--- 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.
--- 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)
--- 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)
--- 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)
--- 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
--- 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,
--- 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
--- 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
  *
  * 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:
--- 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
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -360,13 +360,12 @@ typval2type_int(typval_T *tv, garray_T *
 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
     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);