From 92f26c256e06277ff2ec4ce7adea1eb58c85abe0 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 3 Oct 2020 20:17:30 +0200 Subject: patch 8.2.1794: no falsy Coalescing operator Problem: No falsy Coalescing operator. Solution: Add the "??" operator. Fix mistake with function argument count. --- src/eval.c | 98 ++++++++++++++------------- src/testdir/test_expr.vim | 22 +++++++ src/testdir/test_vim9_disassemble.vim | 27 ++++++++ src/testdir/test_vim9_expr.vim | 34 +++++++++- src/version.c | 2 + src/vim9compile.c | 121 +++++++++++++++++++++------------- src/vim9type.c | 4 ++ 7 files changed, 214 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/eval.c b/src/eval.c index c8c4f6e5f4..911f0abc67 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2110,6 +2110,7 @@ eval0( /* * Handle top level expression: * expr2 ? expr1 : expr1 + * expr2 ?? expr1 * * "arg" must point to the first non-white of the expression. * "arg" is advanced to just after the recognized expression. @@ -2135,6 +2136,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg) p = eval_next_non_blank(*arg, evalarg, &getnext); if (*p == '?') { + int op_falsy = p[1] == '?'; int result; typval_T var2; evalarg_T *evalarg_used = evalarg; @@ -2168,11 +2170,12 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { int error = FALSE; - if (in_vim9script()) + if (in_vim9script() || op_falsy) result = tv2bool(rettv); else if (tv_get_number_chk(rettv, &error) != 0) result = TRUE; - clear_tv(rettv); + if (error || !op_falsy || !result) + clear_tv(rettv); if (error) return FAIL; } @@ -2180,6 +2183,8 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg) /* * Get the second variable. Recursive! */ + if (op_falsy) + ++*arg; if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[1])) { error_white_both(p, 1); @@ -2187,62 +2192,67 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg) return FAIL; } *arg = skipwhite_and_linebreak(*arg + 1, evalarg_used); - evalarg_used->eval_flags = result ? orig_flags - : orig_flags & ~EVAL_EVALUATE; - if (eval1(arg, rettv, evalarg_used) == FAIL) + evalarg_used->eval_flags = (op_falsy ? !result : result) + ? orig_flags : orig_flags & ~EVAL_EVALUATE; + if (eval1(arg, &var2, evalarg_used) == FAIL) { evalarg_used->eval_flags = orig_flags; return FAIL; } + if (!op_falsy || !result) + *rettv = var2; - /* - * Check for the ":". - */ - p = eval_next_non_blank(*arg, evalarg_used, &getnext); - if (*p != ':') - { - emsg(_(e_missing_colon)); - if (evaluate && result) - clear_tv(rettv); - evalarg_used->eval_flags = orig_flags; - return FAIL; - } - if (getnext) - *arg = eval_next_line(evalarg_used); - else + if (!op_falsy) { - if (evaluate && in_vim9script() && !VIM_ISWHITE(p[-1])) + /* + * Check for the ":". + */ + p = eval_next_non_blank(*arg, evalarg_used, &getnext); + if (*p != ':') + { + emsg(_(e_missing_colon)); + if (evaluate && result) + clear_tv(rettv); + evalarg_used->eval_flags = orig_flags; + return FAIL; + } + if (getnext) + *arg = eval_next_line(evalarg_used); + else + { + if (evaluate && in_vim9script() && !VIM_ISWHITE(p[-1])) + { + error_white_both(p, 1); + clear_tv(rettv); + evalarg_used->eval_flags = orig_flags; + return FAIL; + } + *arg = p; + } + + /* + * Get the third variable. Recursive! + */ + if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[1])) { error_white_both(p, 1); clear_tv(rettv); evalarg_used->eval_flags = orig_flags; return FAIL; } - *arg = p; - } - - /* - * Get the third variable. Recursive! - */ - if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[1])) - { - error_white_both(p, 1); - clear_tv(rettv); - evalarg_used->eval_flags = orig_flags; - return FAIL; - } - *arg = skipwhite_and_linebreak(*arg + 1, evalarg_used); - evalarg_used->eval_flags = !result ? orig_flags + *arg = skipwhite_and_linebreak(*arg + 1, evalarg_used); + evalarg_used->eval_flags = !result ? orig_flags : orig_flags & ~EVAL_EVALUATE; - if (eval1(arg, &var2, evalarg_used) == FAIL) - { - if (evaluate && result) - clear_tv(rettv); - evalarg_used->eval_flags = orig_flags; - return FAIL; + if (eval1(arg, &var2, evalarg_used) == FAIL) + { + if (evaluate && result) + clear_tv(rettv); + evalarg_used->eval_flags = orig_flags; + return FAIL; + } + if (evaluate && !result) + *rettv = var2; } - if (evaluate && !result) - *rettv = var2; if (evalarg == NULL) clear_evalarg(&local_evalarg, NULL); diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim index cfae760d49..1086534dca 100644 --- a/src/testdir/test_expr.vim +++ b/src/testdir/test_expr.vim @@ -42,6 +42,28 @@ func Test_version() call assert_false(has('patch-9.9.1')) endfunc +func Test_op_falsy() + call assert_equal(v:true, v:true ?? 456) + call assert_equal(123, 123 ?? 456) + call assert_equal('yes', 'yes' ?? 456) + call assert_equal(0z00, 0z00 ?? 456) + call assert_equal([1], [1] ?? 456) + call assert_equal(#{one: 1}, #{one: 1} ?? 456) + if has('float') + call assert_equal(0.1, 0.1 ?? 456) + endif + + call assert_equal(456, v:false ?? 456) + call assert_equal(456, 0 ?? 456) + call assert_equal(456, '' ?? 456) + call assert_equal(456, 0z ?? 456) + call assert_equal(456, [] ?? 456) + call assert_equal(456, {} ?? 456) + if has('float') + call assert_equal(456, 0.0 ?? 456) + endif +endfunc + func Test_dict() let d = {'': 'empty', 'a': 'a', 0: 'zero'} call assert_equal('empty', d['']) diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim index f1f358af9b..a8dbe1a3cc 100644 --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -1326,6 +1326,33 @@ def Test_disassemble_compare() delete('Xdisassemble') enddef +def s:FalsyOp() + echo g:flag ?? "yes" + echo [] ?? "empty list" + echo "" ?? "empty string" +enddef + +def Test_dsassemble_falsy_op() + var res = execute('disass s:FalsyOp') + assert_match('\\d*_FalsyOp\_s*' .. + 'echo g:flag ?? "yes"\_s*' .. + '0 LOADG g:flag\_s*' .. + '1 JUMP_AND_KEEP_IF_TRUE -> 3\_s*' .. + '2 PUSHS "yes"\_s*' .. + '3 ECHO 1\_s*' .. + 'echo \[\] ?? "empty list"\_s*' .. + '4 NEWLIST size 0\_s*' .. + '5 JUMP_AND_KEEP_IF_TRUE -> 7\_s*' .. + '6 PUSHS "empty list"\_s*' .. + '7 ECHO 1\_s*' .. + 'echo "" ?? "empty string"\_s*' .. + '\d\+ PUSHS "empty string"\_s*' .. + '\d\+ ECHO 1\_s*' .. + '\d\+ PUSHNR 0\_s*' .. + '\d\+ RETURN', + res) +enddef + def Test_disassemble_compare_const() var cases = [ ['"xx" == "yy"', false], diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index fa33089e84..253e469bfa 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -12,7 +12,7 @@ def FuncTwo(arg: number): number enddef " test cond ? expr : expr -def Test_expr1() +def Test_expr1_trinary() assert_equal('one', true ? 'one' : 'two') assert_equal('one', 1 ? 'one' : @@ -61,7 +61,7 @@ def Test_expr1() assert_equal(123, Z(3)) enddef -def Test_expr1_vimscript() +def Test_expr1_trinary_vimscript() # check line continuation var lines =<< trim END vim9script @@ -139,7 +139,7 @@ def Test_expr1_vimscript() CheckScriptSuccess(lines) enddef -func Test_expr1_fails() +func Test_expr1_trinary_fails() call CheckDefFailure(["var x = 1 ? 'one'"], "Missing ':' after '?'", 1) let msg = "White space required before and after '?'" @@ -160,6 +160,34 @@ func Test_expr1_fails() \ 'Z()'], 'E119:', 4) endfunc +def Test_expr1_falsy() + var lines =<< trim END + assert_equal(v:true, v:true ?? 456) + assert_equal(123, 123 ?? 456) + assert_equal('yes', 'yes' ?? 456) + assert_equal([1], [1] ?? 456) + assert_equal(#{one: 1}, #{one: 1} ?? 456) + if has('float') + assert_equal(0.1, 0.1 ?? 456) + endif + + assert_equal(456, v:false ?? 456) + assert_equal(456, 0 ?? 456) + assert_equal(456, '' ?? 456) + assert_equal(456, [] ?? 456) + assert_equal(456, {} ?? 456) + if has('float') + assert_equal(456, 0.0 ?? 456) + endif + END + CheckDefAndScriptSuccess(lines) + + var msg = "White space required before and after '??'" + call CheckDefFailure(["var x = 1?? 'one' : 'two'"], msg, 1) + call CheckDefFailure(["var x = 1 ??'one' : 'two'"], msg, 1) + call CheckDefFailure(["var x = 1??'one' : 'two'"], msg, 1) +enddef + " TODO: define inside test function def Record(val: any): any g:vals->add(val) diff --git a/src/version.c b/src/version.c index beca185c76..bd57233f03 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1794, /**/ 1793, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index 32def6c796..88da80044e 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -4132,14 +4132,20 @@ compile_expr2(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) /* * Toplevel expression: expr2 ? expr1a : expr1b - * * Produces instructions: - * EVAL expr2 Push result of "expr" + * EVAL expr2 Push result of "expr2" * JUMP_IF_FALSE alt jump if false * EVAL expr1a * JUMP_ALWAYS end * alt: EVAL expr1b * end: + * + * Toplevel expression: expr2 ?? expr1 + * Produces instructions: + * EVAL expr2 Push result of "expr2" + * JUMP_AND_KEEP_IF_TRUE end jump if true + * EVAL expr1 + * end: */ static int compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) @@ -4162,13 +4168,13 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) p = may_peek_next_line(cctx, *arg, &next); if (*p == '?') { + int op_falsy = p[1] == '?'; garray_T *instr = &cctx->ctx_instr; garray_T *stack = &cctx->ctx_type_stack; int alt_idx = instr->ga_len; int end_idx = 0; isn_T *isn; type_T *type1 = NULL; - type_T *type2; int has_const_expr = FALSE; int const_value = FALSE; int save_skip = cctx->ctx_skip; @@ -4179,9 +4185,10 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) p = skipwhite(*arg); } - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1 + op_falsy])) { - semsg(_(e_white_space_required_before_and_after_str), "?"); + semsg(_(e_white_space_required_before_and_after_str), + op_falsy ? "??" : "?"); return FAIL; } @@ -4191,20 +4198,32 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) // expression is to be evaluated. has_const_expr = TRUE; const_value = tv2bool(&ppconst->pp_tv[ppconst_used]); - clear_tv(&ppconst->pp_tv[ppconst_used]); - --ppconst->pp_used; - cctx->ctx_skip = save_skip == SKIP_YES || !const_value - ? SKIP_YES : SKIP_NOT; + cctx->ctx_skip = save_skip == SKIP_YES || + (op_falsy ? const_value : !const_value) ? SKIP_YES : SKIP_NOT; + + if (op_falsy && cctx->ctx_skip == SKIP_YES) + // "left ?? right" and "left" is truthy: produce "left" + generate_ppconst(cctx, ppconst); + else + { + clear_tv(&ppconst->pp_tv[ppconst_used]); + --ppconst->pp_used; + } } else { generate_ppconst(cctx, ppconst); - generate_JUMP(cctx, JUMP_IF_FALSE, 0); + if (op_falsy) + end_idx = instr->ga_len; + generate_JUMP(cctx, op_falsy + ? JUMP_AND_KEEP_IF_TRUE : JUMP_IF_FALSE, 0); + if (op_falsy) + type1 = ((type_T **)stack->ga_data)[stack->ga_len]; } // evaluate the second expression; any type is accepted - *arg = skipwhite(p + 1); - if (may_get_next_line(p + 1, arg, cctx) == FAIL) + *arg = skipwhite(p + 1 + op_falsy); + if (may_get_next_line(p + 1 + op_falsy, arg, cctx) == FAIL) return FAIL; if (compile_expr1(arg, cctx, ppconst) == FAIL) return FAIL; @@ -4213,56 +4232,64 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { generate_ppconst(cctx, ppconst); - // remember the type and drop it - --stack->ga_len; - type1 = ((type_T **)stack->ga_data)[stack->ga_len]; + if (!op_falsy) + { + // remember the type and drop it + --stack->ga_len; + type1 = ((type_T **)stack->ga_data)[stack->ga_len]; - end_idx = instr->ga_len; - generate_JUMP(cctx, JUMP_ALWAYS, 0); + end_idx = instr->ga_len; + generate_JUMP(cctx, JUMP_ALWAYS, 0); - // jump here from JUMP_IF_FALSE - isn = ((isn_T *)instr->ga_data) + alt_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; + // jump here from JUMP_IF_FALSE + isn = ((isn_T *)instr->ga_data) + alt_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } } - // Check for the ":". - p = may_peek_next_line(cctx, *arg, &next); - if (*p != ':') + if (!op_falsy) { - emsg(_(e_missing_colon)); - return FAIL; - } - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } + // Check for the ":". + p = may_peek_next_line(cctx, *arg, &next); + if (*p != ':') + { + emsg(_(e_missing_colon)); + return FAIL; + } + if (next != NULL) + { + *arg = next_line_from_context(cctx, TRUE); + p = skipwhite(*arg); + } - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) - { - semsg(_(e_white_space_required_before_and_after_str), ":"); - return FAIL; - } + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) + { + semsg(_(e_white_space_required_before_and_after_str), ":"); + return FAIL; + } - // evaluate the third expression - if (has_const_expr) - cctx->ctx_skip = save_skip == SKIP_YES || const_value + // evaluate the third expression + if (has_const_expr) + cctx->ctx_skip = save_skip == SKIP_YES || const_value ? SKIP_YES : SKIP_NOT; - *arg = skipwhite(p + 1); - if (may_get_next_line(p + 1, arg, cctx) == FAIL) - return FAIL; - if (compile_expr1(arg, cctx, ppconst) == FAIL) - return FAIL; + *arg = skipwhite(p + 1); + if (may_get_next_line(p + 1, arg, cctx) == FAIL) + return FAIL; + if (compile_expr1(arg, cctx, ppconst) == FAIL) + return FAIL; + } if (!has_const_expr) { + type_T **typep; + generate_ppconst(cctx, ppconst); // If the types differ, the result has a more generic type. - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - common_type(type1, type2, &type2, cctx->ctx_type_list); + typep = ((type_T **)stack->ga_data) + stack->ga_len - 1; + common_type(type1, *typep, typep, cctx->ctx_type_list); - // jump here from JUMP_ALWAYS + // jump here from JUMP_ALWAYS or JUMP_AND_KEEP_IF_TRUE isn = ((isn_T *)instr->ga_data) + end_idx; isn->isn_arg.jump.jump_where = instr->ga_len; } diff --git a/src/vim9type.c b/src/vim9type.c index 931b575b66..b5866bc400 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -924,6 +924,10 @@ common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap) } else *dest = alloc_func_type(common, -1, type_gap); + // Use the minimum of min_argcount. + (*dest)->tt_min_argcount = + type1->tt_min_argcount < type2->tt_min_argcount + ? type1->tt_min_argcount : type2->tt_min_argcount; return; } } -- cgit v1.2.3