changeset 22860:53acb89ec9f2 v8.2.1977

patch 8.2.1977: Vim9: error for using a string in a condition is confusing Commit: https://github.com/vim/vim/commit/ea2d407f9c144bb634c59017944e4930ed7f80a2 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Nov 12 12:08:51 2020 +0100 patch 8.2.1977: Vim9: error for using a string in a condition is confusing Problem: Vim9: error for using a string in a condition is confusing. Solution: Give a more specific error. Also adjust the compile time type checking for || and &&.
author Bram Moolenaar <Bram@vim.org>
date Thu, 12 Nov 2020 12:15:04 +0100
parents 757858b35433
children 459c4d8b3a34
files src/errors.h src/proto/vim9execute.pro src/testdir/test_vim9_cmd.vim src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_expr.vim src/typval.c src/version.c src/vim9compile.c src/vim9execute.c
diffstat 9 files changed, 83 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/src/errors.h
+++ b/src/errors.h
@@ -89,8 +89,8 @@ EXTERN char e_compiling_def_function_fai
 	INIT(= N_("E1028: Compiling :def function failed"));
 EXTERN char e_expected_str_but_got_str[]
 	INIT(= N_("E1029: Expected %s but got %s"));
-EXTERN char e_using_string_as_number[]
-	INIT(= N_("E1030: Using a String as a Number"));
+EXTERN char e_using_string_as_number_str[]
+	INIT(= N_("E1030: Using a String as a Number: \"%s\""));
 EXTERN char e_cannot_use_void_value[]
 	INIT(= N_("E1031: Cannot use void value"));
 EXTERN char e_missing_catch_or_finally[]
@@ -292,4 +292,6 @@ EXTERN char e_cannot_extend_null_dict[]
 	INIT(= N_("E1133: Cannot extend a null dict"));
 EXTERN char e_cannot_extend_null_list[]
 	INIT(= N_("E1134: Cannot extend a null list"));
+EXTERN char e_using_string_as_bool_str[]
+	INIT(= N_("E1135: Using a String as a Bool: \"%s\""));
 #endif
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -4,5 +4,6 @@ void funcstack_check_refcount(funcstack_
 int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
 void ex_disassemble(exarg_T *eap);
 int tv2bool(typval_T *tv);
+void emsg_using_string_as(typval_T *tv, int as_number);
 int check_not_string(typval_T *tv);
 /* vim: set ft=c : */
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -74,7 +74,7 @@ def Test_condition_types()
       if 'text'
       endif
   END
-  CheckDefAndScriptFailure(lines, 'E1030:', 1)
+  CheckDefAndScriptFailure(lines, 'E1135:', 1)
 
   lines =<< trim END
       if [1]
@@ -88,7 +88,7 @@ def Test_condition_types()
       if g:cond
       endif
   END
-  CheckDefExecAndScriptFailure(lines, 'E1030:', 2)
+  CheckDefExecAndScriptFailure(lines, 'E1135:', 2)
 
   lines =<< trim END
       g:cond = 0
@@ -97,7 +97,7 @@ def Test_condition_types()
       endif
   END
   CheckDefFailure(lines, 'E1012:', 3)
-  CheckScriptFailure(['vim9script'] + lines, 'E1030:', 4)
+  CheckScriptFailure(['vim9script'] + lines, 'E1135:', 4)
 
   lines =<< trim END
       if g:cond
@@ -113,14 +113,14 @@ def Test_condition_types()
       elseif g:cond
       endif
   END
-  CheckDefExecAndScriptFailure(lines, 'E1030:', 3)
+  CheckDefExecAndScriptFailure(lines, 'E1135:', 3)
 
   lines =<< trim END
       while 'text'
       endwhile
   END
   CheckDefFailure(lines, 'E1012:', 1)
-  CheckScriptFailure(['vim9script'] + lines, 'E1030:', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E1135:', 2)
 
   lines =<< trim END
       while [1]
@@ -134,7 +134,7 @@ def Test_condition_types()
       while g:cond
       endwhile
   END
-  CheckDefExecAndScriptFailure(lines, 'E1030:', 2)
+  CheckDefExecAndScriptFailure(lines, 'E1135:', 2)
 enddef
 
 def Test_if_linebreak()
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -707,6 +707,7 @@ def Test_disassemble_const_expr()
             'if has("gui_running")\_s*' ..
             '\d PUSHS "gui_running"\_s*' ..
             '\d BCALL has(argc 1)\_s*' ..
+            '\d COND2BOOL\_s*' ..
             '\d JUMP_IF_FALSE -> \d\_s*' ..
             '  echo "yes"\_s*' ..
             '\d PUSHS "yes"\_s*' ..
@@ -760,14 +761,15 @@ def Test_disassemble_return_in_if()
   assert_match('ReturnInIf\_s*' ..
         'if g:cond\_s*' ..
         '0 LOADG g:cond\_s*' ..
-        '1 JUMP_IF_FALSE -> 4\_s*' ..
+        '1 COND2BOOL\_s*' ..
+        '2 JUMP_IF_FALSE -> 5\_s*' ..
         'return "yes"\_s*' ..
-        '2 PUSHS "yes"\_s*' ..
-        '3 RETURN\_s*' ..
+        '3 PUSHS "yes"\_s*' ..
+        '4 RETURN\_s*' ..
         'else\_s*' ..
         ' return "no"\_s*' ..
-        '4 PUSHS "no"\_s*' ..
-        '5 RETURN$',
+        '5 PUSHS "no"\_s*' ..
+        '6 RETURN$',
         instr)
 enddef
 
@@ -1357,16 +1359,17 @@ def Test_disassemble_return_bool()
   assert_match('ReturnBool\_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*' ..
+        '1 2BOOL (!!val)\_s*' ..
+        '2 JUMP_IF_COND_FALSE -> 5\_s*' ..
+        '3 PUSHNR 0\_s*' ..
+        '4 2BOOL (!!val)\_s*' ..
+        '5 JUMP_IF_COND_TRUE -> 8\_s*' ..
+        '6 PUSHNR 1\_s*' ..
+        '7 2BOOL (!!val)\_s*' ..
         '\d STORE $0\_s*' ..
         'return name\_s*' ..
-        '\d LOAD $0\_s*' ..   
-        '\d RETURN',
+        '\d\+ LOAD $0\_s*' ..   
+        '\d\+ RETURN',
         instr)
   assert_equal(true, InvertBool())
 enddef
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -131,7 +131,7 @@ def Test_expr1_trinary_vimscript()
       vim9script
       var name = 'x' ? 1 : 2
   END
-  CheckScriptFailure(lines, 'E1030:', 2)
+  CheckScriptFailure(lines, 'E1135:', 2)
 
   lines =<< trim END
       vim9script
@@ -180,7 +180,7 @@ 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 = 'x' ? 'one' : 'two'"], 'E1135:', 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)
@@ -356,11 +356,12 @@ def Test_expr2_fails()
   call CheckDefFailure(["var x = 1|| 2"], msg, 1)
 
   call CheckDefFailure(["var x = 1 || xxx"], 'E1001:', 1)
+  call CheckDefFailure(["var x = [] || false"], 'E1012:', 1)
+  call CheckDefFailure(["if 'yes' || 0", 'echo 0', 'endif'], 'E1012: Type mismatch; expected bool but got string', 1)
 
   # 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
 
@@ -492,6 +493,8 @@ func Test_expr3_fails()
   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(["if 'yes' && 0", 'echo 0', 'endif'], 'E1012: Type mismatch; expected bool but got string', 1)
 endfunc
 
 " global variables to use for tests with the "any" type
--- a/src/typval.c
+++ b/src/typval.c
@@ -196,7 +196,7 @@ tv_get_bool_or_number_chk(typval_T *varp
 	case VAR_STRING:
 	    if (in_vim9script())
 	    {
-		emsg(_(e_using_string_as_number));
+		emsg_using_string_as(varp, !want_bool);
 		break;
 	    }
 	    if (varp->vval.v_string != NULL)
--- 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 */
 /**/
+    1977,
+/**/
     1976,
 /**/
     1975,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -880,6 +880,28 @@ need_type(
 }
 
 /*
+ * 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)
+	return OK;
+
+    if (type == &t_any || type == &t_number)
+	// Number 0 and 1 are OK to use as a bool.  "any" could also be a bool.
+	// This requires a runtime type check.
+	return generate_COND2BOOL(cctx);
+
+    return need_type(type, &t_bool, -1, cctx, FALSE, FALSE);
+}
+
+/*
  * Generate an ISN_PUSHNR instruction.
  */
     static int
@@ -4306,8 +4328,6 @@ compile_and_or(
     {
 	garray_T	*instr = &cctx->ctx_instr;
 	garray_T	end_ga;
-	garray_T	*stack = &cctx->ctx_type_stack;
-	int		all_bool_values = TRUE;
 
 	/*
 	 * Repeat until there is no following "||" or "&&"
@@ -4331,8 +4351,12 @@ compile_and_or(
 	    // evaluating to bool
 	    generate_ppconst(cctx, ppconst);
 
-	    if (((type_T **)stack->ga_data)[stack->ga_len - 1] != &t_bool)
-		all_bool_values = FALSE;
+	    // Every part must evaluate to a bool.
+	    if (bool_on_stack(cctx) == FAIL)
+	    {
+		ga_clear(&end_ga);
+		return FAIL;
+	    }
 
 	    if (ga_grow(&end_ga, 1) == FAIL)
 	    {
@@ -4360,6 +4384,13 @@ compile_and_or(
 	}
 	generate_ppconst(cctx, ppconst);
 
+	// Every part must evaluate to a bool.
+	if (bool_on_stack(cctx) == FAIL)
+	{
+	    ga_clear(&end_ga);
+	    return FAIL;
+	}
+
 	// Fill in the end label in all jumps.
 	while (end_ga.ga_len > 0)
 	{
@@ -4371,10 +4402,6 @@ compile_and_or(
 	    isn->isn_arg.jump.jump_where = instr->ga_len;
 	}
 	ga_clear(&end_ga);
-
-	// The resulting type is converted to bool if needed.
-	if (!all_bool_values)
-	    generate_COND2BOOL(cctx);
     }
 
     return OK;
@@ -4385,11 +4412,11 @@ compile_and_or(
  *
  * Produces instructions:
  *	EVAL expr4a		Push result of "expr4a"
+ *	COND2BOOL		convert to bool if needed
  *	JUMP_IF_COND_FALSE end
  *	EVAL expr4b		Push result of "expr4b"
  *	JUMP_IF_COND_FALSE end
  *	EVAL expr4c		Push result of "expr4c"
- *	COND2BOOL
  * end:
  */
     static int
@@ -4410,11 +4437,11 @@ compile_expr3(char_u **arg, cctx_T *cctx
  *
  * Produces instructions:
  *	EVAL expr3a		Push result of "expr3a"
+ *	COND2BOOL		convert to bool if needed
  *	JUMP_IF_COND_TRUE end
  *	EVAL expr3b		Push result of "expr3b"
  *	JUMP_IF_COND_TRUE end
  *	EVAL expr3c		Push result of "expr3c"
- *	COND2BOOL
  * end:
  */
     static int
@@ -5968,23 +5995,6 @@ drop_scope(cctx_T *cctx)
 }
 
 /*
- * 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, FALSE) == FAIL)
-	return FAIL;
-    return OK;
-}
-
-/*
  * compile "if expr"
  *
  * "if expr" Produces instructions:
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3630,6 +3630,15 @@ tv2bool(typval_T *tv)
     return FALSE;
 }
 
+    void
+emsg_using_string_as(typval_T *tv, int as_number)
+{
+    semsg(_(as_number ? e_using_string_as_number_str
+						 : e_using_string_as_bool_str),
+		       tv->vval.v_string == NULL
+					   ? (char_u *)"" : tv->vval.v_string);
+}
+
 /*
  * If "tv" is a string give an error and return FAIL.
  */
@@ -3638,7 +3647,7 @@ check_not_string(typval_T *tv)
 {
     if (tv->v_type == VAR_STRING)
     {
-	emsg(_(e_using_string_as_number));
+	emsg_using_string_as(tv, TRUE);
 	clear_tv(tv);
 	return FAIL;
     }