changeset 24796:7c1375eb1636 v8.2.2936

patch 8.2.2936: Vim9: converting number to bool uses wrong stack offset Commit: https://github.com/vim/vim/commit/5fa9b24440d677c1aa00084d0cf84638b1e1a0d5 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jun 4 21:00:32 2021 +0200 patch 8.2.2936: Vim9: converting number to bool uses wrong stack offset Problem: Vim9: converting number to bool uses wrong stack offset. (Salman Halim) Solution: Include the offset in the 2BOOL command.
author Bram Moolenaar <Bram@vim.org>
date Fri, 04 Jun 2021 21:15:04 +0200
parents 05a337d1fe73
children d76650e23588
files src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_expr.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 6 files changed, 108 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -1650,11 +1650,11 @@ def Test_disassemble_invert_bool()
         '\d STORE $0\_s*' ..
         'var invert = !flag\_s*' ..
         '\d LOAD $0\_s*' ..
-        '\d INVERT (!val)\_s*' ..
+        '\d INVERT -1 (!val)\_s*' ..
         '\d STORE $1\_s*' ..
         'var res = !!flag\_s*' ..
         '\d LOAD $0\_s*' ..
-        '\d 2BOOL (!!val)\_s*' ..
+        '\d 2BOOL -1 (!!val)\_s*' ..
         '\d STORE $2\_s*',
         instr)
   assert_equal(true, InvertBool())
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -2480,6 +2480,25 @@ def Test_expr7_dict_vim9script()
   endif
 enddef
 
+def Test_expr7_call_2bool()
+  var lines =<< trim END
+      vim9script
+
+      def BrokenCall(nr: number, mode: bool, use: string): void
+        assert_equal(3, nr)
+        assert_equal(false, mode)
+        assert_equal('ab', use)
+      enddef
+
+      def TestBrokenCall(): void
+        BrokenCall(3, 0, 'ab')
+      enddef
+
+      TestBrokenCall()
+  END
+  CheckScriptSuccess(lines)
+enddef
+
 let g:oneString = 'one'
 
 def Test_expr_member()
--- 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 */
 /**/
+    2936,
+/**/
     2935,
 /**/
     2934,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -148,9 +148,9 @@ 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,	    // falsy/truthy to bool, invert if isn_arg.number != 0
+    ISN_2BOOL,	    // falsy/truthy to bool, uses isn_arg.tobool
     ISN_COND2BOOL,  // convert value to bool
-    ISN_2STRING,    // convert value to string at isn_arg.number on stack
+    ISN_2STRING,    // convert value to string at isn_arg.tostring on stack
     ISN_2STRING_ANY, // like ISN_2STRING but check type
     ISN_NEGATENR,   // apply "-" to number
 
@@ -369,6 +369,18 @@ typedef struct {
     cexprref_T *cexpr_ref;
 } cexpr_T;
 
+// arguments to ISN_2STRING and ISN_2STRING_ANY
+typedef struct {
+    int		offset;
+    int		tolerant;
+} tostring_T;
+
+// arguments to ISN_2BOOL
+typedef struct {
+    int		offset;
+    int		invert;
+} tobool_T;
+
 /*
  * Instruction
  */
@@ -414,6 +426,8 @@ struct isn_S {
 	subs_T		    subs;
 	cexpr_T		    cexpr;
 	isn_T		    *instr;
+	tostring_T	    tostring;
+	tobool_T	    tobool;
     } isn_arg;
 };
 
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -577,9 +577,10 @@ generate_instr_type(cctx_T *cctx, isntyp
 /*
  * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING.
  * But only for simple types.
- */
-    static int
-may_generate_2STRING(int offset, cctx_T *cctx)
+ * When "tolerant" is TRUE convert most types to string, e.g. a List.
+ */
+    static int
+may_generate_2STRING(int offset, int tolerant, cctx_T *cctx)
 {
     isn_T	*isn;
     isntype_T	isntype = ISN_2STRING;
@@ -606,12 +607,20 @@ may_generate_2STRING(int offset, cctx_T 
 			 isntype = ISN_2STRING_ANY;
 			 break;
 
+	// conversion possible when tolerant
+	case VAR_LIST:
+			 if (tolerant)
+			 {
+			     isntype = ISN_2STRING_ANY;
+			     break;
+			 }
+			 // FALLTHROUGH
+
 	// conversion not possible
 	case VAR_VOID:
 	case VAR_BLOB:
 	case VAR_FUNC:
 	case VAR_PARTIAL:
-	case VAR_LIST:
 	case VAR_DICT:
 	case VAR_JOB:
 	case VAR_CHANNEL:
@@ -623,7 +632,8 @@ may_generate_2STRING(int offset, cctx_T 
     *type = &t_string;
     if ((isn = generate_instr(cctx, isntype)) == NULL)
 	return FAIL;
-    isn->isn_arg.number = offset;
+    isn->isn_arg.tostring.offset = offset;
+    isn->isn_arg.tostring.tolerant = tolerant;
 
     return OK;
 }
@@ -886,9 +896,10 @@ generate_COMPARE(cctx_T *cctx, exprtype_
 
 /*
  * Generate an ISN_2BOOL instruction.
- */
-    static int
-generate_2BOOL(cctx_T *cctx, int invert)
+ * "offset" is the offset in the type stack.
+ */
+    static int
+generate_2BOOL(cctx_T *cctx, int invert, int offset)
 {
     isn_T	*isn;
     garray_T	*stack = &cctx->ctx_type_stack;
@@ -896,10 +907,11 @@ generate_2BOOL(cctx_T *cctx, int invert)
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL)
 	return FAIL;
-    isn->isn_arg.number = invert;
+    isn->isn_arg.tobool.invert = invert;
+    isn->isn_arg.tobool.offset = offset;
 
     // type becomes bool
-    ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool;
+    ((type_T **)stack->ga_data)[stack->ga_len + offset] = &t_bool;
 
     return OK;
 }
@@ -1008,7 +1020,7 @@ need_type(
     {
 	// Using "0", "1" or the result of an expression with "&&" or "||" as a
 	// boolean is OK but requires a conversion.
-	generate_2BOOL(cctx, FALSE);
+	generate_2BOOL(cctx, FALSE, offset);
 	return OK;
     }
 
@@ -2782,7 +2794,7 @@ compile_member(int is_slice, cctx_T *cct
 		return FAIL;
 	    *typep = &t_any;
 	}
-	if (may_generate_2STRING(-1, cctx) == FAIL)
+	if (may_generate_2STRING(-1, FALSE, cctx) == FAIL)
 	    return FAIL;
 	if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
 	    return FAIL;
@@ -3625,7 +3637,7 @@ compile_dict(char_u **arg, cctx_T *cctx,
 	    }
 	    if (isn->isn_type == ISN_PUSHS)
 		key = isn->isn_arg.string;
-	    else if (may_generate_2STRING(-1, cctx) == FAIL)
+	    else if (may_generate_2STRING(-1, FALSE, cctx) == FAIL)
 		return FAIL;
 	    *arg = skipwhite(*arg);
 	    if (**arg != ']')
@@ -4026,7 +4038,7 @@ compile_leader(cctx_T *cctx, int numeric
 		    invert = !invert;
 		--p;
 	    }
-	    if (generate_2BOOL(cctx, invert) == FAIL)
+	    if (generate_2BOOL(cctx, invert, -1) == FAIL)
 		return FAIL;
 	}
     }
@@ -4849,8 +4861,8 @@ compile_expr5(char_u **arg, cctx_T *cctx
 	    ppconst->pp_is_const = FALSE;
 	    if (*op == '.')
 	    {
-		if (may_generate_2STRING(-2, cctx) == FAIL
-			|| may_generate_2STRING(-1, cctx) == FAIL)
+		if (may_generate_2STRING(-2, FALSE, cctx) == FAIL
+			|| may_generate_2STRING(-1, FALSE, cctx) == FAIL)
 		    return FAIL;
 		generate_instr_drop(cctx, ISN_CONCAT, 1);
 	    }
@@ -6420,7 +6432,8 @@ compile_assign_unlet(
 	    emsg(e_cannot_use_range_with_dictionary);
 	    return FAIL;
 	}
-	if (dest_type == VAR_DICT && may_generate_2STRING(-1, cctx) == FAIL)
+	if (dest_type == VAR_DICT
+			      && may_generate_2STRING(-1, FALSE, cctx) == FAIL)
 	    return FAIL;
 	if (dest_type == VAR_LIST || dest_type == VAR_BLOB)
 	{
@@ -8383,7 +8396,7 @@ compile_throw(char_u *arg, cctx_T *cctx 
 	return NULL;
     if (cctx->ctx_skip == SKIP_YES)
 	return p;
-    if (may_generate_2STRING(-1, cctx) == FAIL)
+    if (may_generate_2STRING(-1, FALSE, cctx) == FAIL)
 	return NULL;
     if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL)
 	return NULL;
@@ -8618,7 +8631,7 @@ compile_exec(char_u *line, exarg_T *eap,
 	    p += 2;
 	    if (compile_expr0(&p, cctx) == FAIL)
 		return NULL;
-	    may_generate_2STRING(-1, cctx);
+	    may_generate_2STRING(-1, TRUE, cctx);
 	    ++count;
 	    p = skipwhite(p);
 	    if (*p != '`')
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -980,7 +980,7 @@ store_var(char_u *name, typval_T *tv)
  * Return FAIL if not allowed.
  */
     static int
-do_2string(typval_T *tv, int is_2string_any)
+do_2string(typval_T *tv, int is_2string_any, int tolerant)
 {
     if (tv->v_type != VAR_STRING)
     {
@@ -995,6 +995,22 @@ do_2string(typval_T *tv, int is_2string_
 		case VAR_NUMBER:
 		case VAR_FLOAT:
 		case VAR_BLOB:	break;
+
+		case VAR_LIST:
+				if (tolerant)
+				{
+				    char_u *p;
+
+				    str = typval2string(tv, TRUE);
+				    clear_tv(tv);
+				    tv->v_type = VAR_STRING;
+				    tv->vval.v_string = str;
+				    // TODO: escaping
+				    while ((p = vim_strchr(str, '\n')) != NULL)
+					*p = ' ';
+				    return OK;
+				}
+				// FALLTHROUGH
 		default:	to_string_error(tv->v_type);
 				return FAIL;
 	    }
@@ -2055,7 +2071,7 @@ exec_instructions(ectx_T *ectx)
 		    {
 			dest_type = tv_dest->v_type;
 			if (dest_type == VAR_DICT)
-			    status = do_2string(tv_idx, TRUE);
+			    status = do_2string(tv_idx, TRUE, FALSE);
 			else if (dest_type == VAR_LIST
 					       && tv_idx->v_type != VAR_NUMBER)
 			{
@@ -3770,15 +3786,16 @@ exec_instructions(ectx_T *ectx)
 		    int n;
 		    int error = FALSE;
 
-		    tv = STACK_TV_BOT(-1);
 		    if (iptr->isn_type == ISN_2BOOL)
 		    {
+			tv = STACK_TV_BOT(iptr->isn_arg.tobool.offset);
 			n = tv2bool(tv);
-			if (iptr->isn_arg.number)  // invert
+			if (iptr->isn_arg.tobool.invert)
 			    n = !n;
 		    }
 		    else
 		    {
+			tv = STACK_TV_BOT(-1);
 			SOURCING_LNUM = iptr->isn_lnum;
 			n = tv_get_bool_chk(tv, &error);
 			if (error)
@@ -3793,8 +3810,9 @@ exec_instructions(ectx_T *ectx)
 	    case ISN_2STRING:
 	    case ISN_2STRING_ANY:
 		SOURCING_LNUM = iptr->isn_lnum;
-		if (do_2string(STACK_TV_BOT(iptr->isn_arg.number),
-			iptr->isn_type == ISN_2STRING_ANY) == FAIL)
+		if (do_2string(STACK_TV_BOT(iptr->isn_arg.tostring.offset),
+				iptr->isn_type == ISN_2STRING_ANY,
+				      iptr->isn_arg.tostring.tolerant) == FAIL)
 			    goto on_error;
 		break;
 
@@ -5122,26 +5140,30 @@ list_instructions(char *pfx, isn_T *inst
 		      break;
 		  }
 	    case ISN_COND2BOOL: smsg("%s%4d COND2BOOL", pfx, current); break;
-	    case ISN_2BOOL: if (iptr->isn_arg.number)
-				smsg("%s%4d INVERT (!val)", pfx, current);
+	    case ISN_2BOOL: if (iptr->isn_arg.tobool.invert)
+				smsg("%s%4d INVERT %d (!val)", pfx, current,
+					 iptr->isn_arg.tobool.offset);
 			    else
-				smsg("%s%4d 2BOOL (!!val)", pfx, current);
+				smsg("%s%4d 2BOOL %d (!!val)", pfx, current,
+					 iptr->isn_arg.tobool.offset);
 			    break;
 	    case ISN_2STRING: smsg("%s%4d 2STRING stack[%lld]", pfx, current,
-					 (varnumber_T)(iptr->isn_arg.number));
+				 (varnumber_T)(iptr->isn_arg.tostring.offset));
 			      break;
-	    case ISN_2STRING_ANY: smsg("%s%4d 2STRING_ANY stack[%lld]", pfx, current,
-					 (varnumber_T)(iptr->isn_arg.number));
+	    case ISN_2STRING_ANY: smsg("%s%4d 2STRING_ANY stack[%lld]",
+								  pfx, current,
+				 (varnumber_T)(iptr->isn_arg.tostring.offset));
 			      break;
-	    case ISN_RANGE: smsg("%s%4d RANGE %s", pfx, current, iptr->isn_arg.string);
+	    case ISN_RANGE: smsg("%s%4d RANGE %s", pfx, current,
+							 iptr->isn_arg.string);
 			    break;
 	    case ISN_PUT:
 	        if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE_ABOVE)
 		    smsg("%s%4d PUT %c above range",
-				       pfx, current, iptr->isn_arg.put.put_regname);
+				  pfx, current, iptr->isn_arg.put.put_regname);
 		else if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE)
 		    smsg("%s%4d PUT %c range",
-				       pfx, current, iptr->isn_arg.put.put_regname);
+				  pfx, current, iptr->isn_arg.put.put_regname);
 		else
 		    smsg("%s%4d PUT %c %ld", pfx, current,
 						 iptr->isn_arg.put.put_regname,