summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-10-03 20:17:30 +0200
committerBram Moolenaar <Bram@vim.org>2020-10-03 20:17:30 +0200
commit92f26c256e06277ff2ec4ce7adea1eb58c85abe0 (patch)
treeb4f9334e90b945a9b727b5a7ade7870b2bb06a00 /src
parentc8fe645c198e2ca55c4e3446efbbdb9b995c63ce (diff)
patch 8.2.1794: no falsy Coalescing operatorv8.2.1794
Problem: No falsy Coalescing operator. Solution: Add the "??" operator. Fix mistake with function argument count.
Diffstat (limited to 'src')
-rw-r--r--src/eval.c98
-rw-r--r--src/testdir/test_expr.vim22
-rw-r--r--src/testdir/test_vim9_disassemble.vim27
-rw-r--r--src/testdir/test_vim9_expr.vim34
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c121
-rw-r--r--src/vim9type.c4
7 files changed, 214 insertions, 94 deletions
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('\<SNR>\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
@@ -751,6 +751,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1794,
+/**/
1793,
/**/
1792,
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;
}
}