# HG changeset patch # User Bram Moolenaar # Date 1622834104 -7200 # Node ID 7c1375eb16363f0b03e98e17c661c460edce0f62 # Parent 05a337d1fe7300b74dfe439526ca0275c329a979 patch 8.2.2936: Vim9: converting number to bool uses wrong stack offset Commit: https://github.com/vim/vim/commit/5fa9b24440d677c1aa00084d0cf84638b1e1a0d5 Author: Bram Moolenaar 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. diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- 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()) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- 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() diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim9.h b/src/vim9.h --- 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; }; diff --git a/src/vim9compile.c b/src/vim9compile.c --- 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 != '`') diff --git a/src/vim9execute.c b/src/vim9execute.c --- 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,