diff --git a/vlib/net/websocket/utils.v b/vlib/net/websocket/utils.v index 50feeffb30..cf16d16aea 100644 --- a/vlib/net/websocket/utils.v +++ b/vlib/net/websocket/utils.v @@ -23,7 +23,7 @@ fn htonl64(payload_len u64) []u8 { // create_masking_key returns a new masking key to use when masking websocket messages fn create_masking_key() []u8 { - return rand.bytes(4) or { [0, 0, 0, 0] } + return rand.bytes(4) or { [u8(0), 0, 0, 0] } } // create_key_challenge_response creates a key challenge response from security key diff --git a/vlib/picohttpparser/misc_test.v b/vlib/picohttpparser/misc_test.v index a3fb7a54b8..0cdff9f033 100644 --- a/vlib/picohttpparser/misc_test.v +++ b/vlib/picohttpparser/misc_test.v @@ -19,7 +19,10 @@ pub fn test_u64toa_large_values() { mut buf := [20]u8{} len := unsafe { - u64toa(&buf[0], v) or { assert err.msg() == 'Maximum size of 100MB exceeded!' } + u64toa(&buf[0], v) or { + assert err.msg() == 'Maximum size of 100MB exceeded!' + 0 + } } if v < 100_000_000 { @@ -38,7 +41,10 @@ pub fn test_u64toa_edge_cases() { // Test zero value len := unsafe { - u64toa(&buf[0], 0) or { assert false } + u64toa(&buf[0], 0) or { + assert false + 0 + } } assert len == 1 diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index c9a22a27da..c3e11bab6e 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -126,6 +126,7 @@ mut: generic_fns map[string]bool // register generic fns that needs recheck once inside_sql bool // to handle sql table fields pseudo variables inside_selector_expr bool + inside_or_block_value bool // true inside or-block where its value is used `f(g() or { true })` inside_interface_deref bool inside_decl_rhs bool inside_if_guard bool // true inside the guard condition of `if x := opt() {}` @@ -1354,16 +1355,25 @@ fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return return } if node.stmts.len == 0 { - if ret_type != ast.void_type { - // x := f() or {} - c.error('assignment requires a non empty `or {}` block', node.pos) + if expr is ast.CallExpr && expr.is_return_used { + // x := f() or {}, || f() or {} etc + c.error('expression requires a non empty `or {}` block', node.pos) + } else if expr !is ast.CallExpr && ret_type != ast.void_type { + // _ := sql db {... } or { } + c.error('expression requires a non empty `or {}` block', node.pos) } // allow `f() or {}` return } mut valid_stmts := node.stmts.filter(it !is ast.SemicolonStmt) mut last_stmt := if valid_stmts.len > 0 { valid_stmts.last() } else { node.stmts.last() } - c.check_or_last_stmt(mut last_stmt, ret_type, expr_return_type.clear_option_and_result()) + if expr !is ast.CallExpr || (expr is ast.CallExpr && expr.is_return_used) { + // requires a block returning an unwrapped type of expr return type + c.check_or_last_stmt(mut last_stmt, ret_type, expr_return_type.clear_option_and_result()) + } else { + // allow f() or { var = 123 } + c.check_or_last_stmt(mut last_stmt, ast.void_type, expr_return_type.clear_option_and_result()) + } } fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) { @@ -1372,6 +1382,10 @@ fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr ast.ExprStmt { c.expected_type = ret_type c.expected_or_type = ret_type.clear_option_and_result() + if c.inside_or_block_value && stmt.expr is ast.None && ret_type.has_flag(.option) { + // return call() or { none } where fn returns an Option type + return + } last_stmt_typ := c.expr(mut stmt.expr) if last_stmt_typ.has_flag(.option) || last_stmt_typ == ast.none_type { if stmt.expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr, ast.None, ast.CastExpr] { @@ -1437,9 +1451,11 @@ fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr } ast.Return {} else { - expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) - c.error('last statement in the `or {}` block should be an expression of type `${expected_type_name}` or exit parent scope', - stmt.pos) + if stmt !is ast.AssertStmt || c.inside_or_block_value { + expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) + c.error('last statement in the `or {}` block should be an expression of type `${expected_type_name}` or exit parent scope', + stmt.pos) + } } } } else if mut stmt is ast.ExprStmt { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 76fba44f17..59b4394004 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -737,9 +737,17 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { } } } + old_expected_or_type := c.expected_or_type c.expected_or_type = node.return_type.clear_flag(.result) c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) - c.expected_or_type = ast.void_type + + if node.or_block.kind == .block { + old_inside_or_block_value := c.inside_or_block_value + c.inside_or_block_value = true + c.check_or_expr(node.or_block, typ, c.expected_or_type, node) + c.inside_or_block_value = old_inside_or_block_value + } + c.expected_or_type = old_expected_or_type if !c.inside_const && c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.is_main && !c.table.cur_fn.is_test { diff --git a/vlib/v/checker/lambda_expr.v b/vlib/v/checker/lambda_expr.v index d7c628030d..464e546ab6 100644 --- a/vlib/v/checker/lambda_expr.v +++ b/vlib/v/checker/lambda_expr.v @@ -65,6 +65,9 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as is_expr: false typ: return_type } + if mut node.expr is ast.CallExpr && node.expr.is_return_used { + node.expr.is_return_used = false + } } else { stmts << ast.Return{ pos: node.pos diff --git a/vlib/v/checker/or_block_assert_err.out b/vlib/v/checker/or_block_assert_err.out new file mode 100644 index 0000000000..1074f6e07e --- /dev/null +++ b/vlib/v/checker/or_block_assert_err.out @@ -0,0 +1,12 @@ +vlib/v/checker/or_block_assert_err.vv:10:22: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope + 8 | + 9 | f() or { assert true } + 10 | a := f() or { assert true } + | ~~~~~~ + 11 | dump(a) + 12 | g(f() or { assert true }) +vlib/v/checker/or_block_assert_err.vv:12:19: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope + 10 | a := f() or { assert true } + 11 | dump(a) + 12 | g(f() or { assert true }) + | ~~~~~~ diff --git a/vlib/v/checker/or_block_assert_err.vv b/vlib/v/checker/or_block_assert_err.vv new file mode 100644 index 0000000000..29b5dad552 --- /dev/null +++ b/vlib/v/checker/or_block_assert_err.vv @@ -0,0 +1,12 @@ +fn g(x int) { + dump(x) +} + +fn f() ?int { + return none +} + +f() or { assert true } +a := f() or { assert true } +dump(a) +g(f() or { assert true }) diff --git a/vlib/v/checker/tests/call_empty_or_block_err.out b/vlib/v/checker/tests/call_empty_or_block_err.out new file mode 100644 index 0000000000..eed78724a9 --- /dev/null +++ b/vlib/v/checker/tests/call_empty_or_block_err.out @@ -0,0 +1,56 @@ +vlib/v/checker/tests/call_empty_or_block_err.vv:9:2: warning: unused variable: `a` + 7 | + 8 | fn main() { + 9 | a := foo() or { foo() or {} } + | ^ + 10 | + 11 | // must be error +vlib/v/checker/tests/call_empty_or_block_err.vv:12:2: warning: unused variable: `y` + 10 | + 11 | // must be error + 12 | y := if c := foo() { + | ^ + 13 | dump(c) + 14 | bar() or {} +vlib/v/checker/tests/call_empty_or_block_err.vv:20:2: warning: unused variable: `z` + 18 | + 19 | // ok + 20 | z := if d := foo() { + | ^ + 21 | dump(d) + 22 | bar() or {} +vlib/v/checker/tests/call_empty_or_block_err.vv:29:2: warning: unused variable: `w` + 27 | + 28 | // ok + 29 | w := foo() or { + | ^ + 30 | bar() or {} + 31 | false +vlib/v/checker/tests/call_empty_or_block_err.vv:35:2: warning: unused variable: `b` + 33 | + 34 | // ok + 35 | b := foo() or { + | ^ + 36 | foo() or {} + 37 | false +vlib/v/checker/tests/call_empty_or_block_err.vv:9:24: error: expression requires a non empty `or {}` block + 7 | + 8 | fn main() { + 9 | a := foo() or { foo() or {} } + | ~~~~~ + 10 | + 11 | // must be error +vlib/v/checker/tests/call_empty_or_block_err.vv:14:9: error: expression requires a non empty `or {}` block + 12 | y := if c := foo() { + 13 | dump(c) + 14 | bar() or {} + | ~~~~~ + 15 | } else { + 16 | false +vlib/v/checker/tests/call_empty_or_block_err.vv:14:3: error: the final expression in `if` or `match`, must have a value of a non-void type + 12 | y := if c := foo() { + 13 | dump(c) + 14 | bar() or {} + | ~~~~~ + 15 | } else { + 16 | false diff --git a/vlib/v/checker/tests/call_empty_or_block_err.vv b/vlib/v/checker/tests/call_empty_or_block_err.vv new file mode 100644 index 0000000000..f58e00bad1 --- /dev/null +++ b/vlib/v/checker/tests/call_empty_or_block_err.vv @@ -0,0 +1,39 @@ +fn foo() !bool { + return true +} + +fn bar() ! { +} + +fn main() { + a := foo() or { foo() or {} } + + // must be error + y := if c := foo() { + dump(c) + bar() or {} + } else { + false + } + + // ok + z := if d := foo() { + dump(d) + bar() or {} + true + } else { + false + } + + // ok + w := foo() or { + bar() or {} + false + } + + // ok + b := foo() or { + foo() or {} + false + } +} diff --git a/vlib/v/checker/tests/fn_call_or_block_err.out b/vlib/v/checker/tests/fn_call_or_block_err.out new file mode 100644 index 0000000000..bb43071669 --- /dev/null +++ b/vlib/v/checker/tests/fn_call_or_block_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/fn_call_or_block_err.vv:7:18: error: wrong return type `int literal` in the `or {}` block, expected `string` + 5 | } + 6 | + 7 | y := Aa(f() or { 2 }) + | ^ + 8 | println(y) diff --git a/vlib/v/checker/tests/fn_call_or_block_err.vv b/vlib/v/checker/tests/fn_call_or_block_err.vv new file mode 100644 index 0000000000..db43465a87 --- /dev/null +++ b/vlib/v/checker/tests/fn_call_or_block_err.vv @@ -0,0 +1,8 @@ +type Aa = string | int + +fn f() !string { + return '' +} + +y := Aa(f() or { 2 }) +println(y) diff --git a/vlib/v/checker/tests/lambda_or_block_err.out b/vlib/v/checker/tests/lambda_or_block_err.out new file mode 100644 index 0000000000..62964daf63 --- /dev/null +++ b/vlib/v/checker/tests/lambda_or_block_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/lambda_or_block_err.vv:10:10: error: cannot use `!int` as type `int` in return argument + 8 | + 9 | fn main() { + 10 | foo(|i| bar(i)) + | ~~~~~~ + 11 | foo(|i| bar(i) or {}) + 12 | foo(|i| bar(i) or { 0 }) +vlib/v/checker/tests/lambda_or_block_err.vv:11:17: error: expression requires a non empty `or {}` block + 9 | fn main() { + 10 | foo(|i| bar(i)) + 11 | foo(|i| bar(i) or {}) + | ~~~~~ + 12 | foo(|i| bar(i) or { 0 }) + 13 | } diff --git a/vlib/v/checker/tests/lambda_or_block_err.vv b/vlib/v/checker/tests/lambda_or_block_err.vv new file mode 100644 index 0000000000..18314d07b4 --- /dev/null +++ b/vlib/v/checker/tests/lambda_or_block_err.vv @@ -0,0 +1,13 @@ +fn foo(callback fn (int) int) { + dump([1, 2, 3].map(callback)) +} + +fn bar(a int) !int { + return a +} + +fn main() { + foo(|i| bar(i)) + foo(|i| bar(i) or {}) + foo(|i| bar(i) or { 0 }) +} diff --git a/vlib/v/checker/tests/or_block_check_err.out b/vlib/v/checker/tests/or_block_check_err.out index c4fb27bba8..854bbe1d66 100644 --- a/vlib/v/checker/tests/or_block_check_err.out +++ b/vlib/v/checker/tests/or_block_check_err.out @@ -1,11 +1,11 @@ -vlib/v/checker/tests/or_block_check_err.vv:6:36: error: assignment requires a non empty `or {}` block +vlib/v/checker/tests/or_block_check_err.vv:6:36: error: expression requires a non empty `or {}` block 4 | 5 | fn main() { 6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b') | ~~~~~ 7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b') 8 | -vlib/v/checker/tests/or_block_check_err.vv:7:37: error: assignment requires a non empty `or {}` block +vlib/v/checker/tests/or_block_check_err.vv:7:37: error: expression requires a non empty `or {}` block 5 | fn main() { 6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b') 7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b') diff --git a/vlib/v/checker/tests/orm_no_default_value.out b/vlib/v/checker/tests/orm_no_default_value.out index 43d11d19b5..104496004e 100644 --- a/vlib/v/checker/tests/orm_no_default_value.out +++ b/vlib/v/checker/tests/orm_no_default_value.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/orm_no_default_value.vv:11:4: error: assignment requires a non empty `or {}` block +vlib/v/checker/tests/orm_no_default_value.vv:11:4: error: expression requires a non empty `or {}` block 9 | _ := sql db { 10 | select from Person 11 | } or { diff --git a/vlib/v/eval/eval.v b/vlib/v/eval/eval.v index 8f615be284..726fc9071f 100644 --- a/vlib/v/eval/eval.v +++ b/vlib/v/eval/eval.v @@ -276,6 +276,7 @@ pub fn (mut e Eval) register_symbol(stmt ast.Stmt, mod string, file string) { } } +@[noreturn] fn (e &Eval) error(msg string) { eprintln('> V interpreter backtrace:') e.print_backtrace() diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index 9ae2a07634..295070b598 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -188,7 +188,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { // Always use this in -autofree, since ?: can have tmp expressions that have to be freed. needs_tmp_var := g.need_tmp_var_in_if(node) needs_conds_order := g.needs_conds_order(node) - tmp := if needs_tmp_var { g.new_tmp_var() } else { '' } + tmp := if node.typ != ast.void_type && needs_tmp_var { g.new_tmp_var() } else { '' } mut cur_line := '' mut raw_state := false if needs_tmp_var { @@ -210,7 +210,9 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } cur_line = g.go_before_last_stmt() g.empty_line = true - g.writeln('${styp} ${tmp}; /* if prepend */') + if tmp != '' { + g.writeln('${styp} ${tmp}; /* if prepend */') + } if g.infix_left_var_name.len > 0 { g.writeln('if (${g.infix_left_var_name}) {') g.indent++ diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index c9c7450746..044c5495c1 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -787,7 +787,9 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { right_op_pos := p.tok.pos() old_assign_rhs := p.inside_assign_rhs - p.inside_assign_rhs = true + if op in [.decl_assign, .assign] { + p.inside_assign_rhs = true + } right = p.expr(precedence) p.inside_assign_rhs = old_assign_rhs if op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && mut right is ast.PrefixExpr { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 57eff4bb46..4648d53641 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -872,7 +872,10 @@ fn (mut p Parser) anon_fn() ast.AnonFn { if p.tok.kind == .lcbr { tmp := p.label_names p.label_names = [] + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = false stmts = p.parse_block_no_scope(false) + p.inside_assign_rhs = old_assign_rhs label_names = p.label_names.clone() p.label_names = tmp } @@ -1164,16 +1167,19 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { fn (mut p Parser) spawn_expr() ast.SpawnExpr { p.next() spos := p.tok.pos() + old_inside_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = false expr := p.expr(0) - call_expr := if expr is ast.CallExpr { + p.inside_assign_rhs = old_inside_assign_rhs + mut call_expr := if expr is ast.CallExpr { expr } else { p.error_with_pos('expression in `spawn` must be a function call', expr.pos()) ast.CallExpr{ - scope: p.scope - is_return_used: true + scope: p.scope } } + call_expr.is_return_used = true pos := spos.extend(p.prev_tok.pos()) p.register_auto_import('sync.threads') p.table.gostmts++ @@ -1186,16 +1192,19 @@ fn (mut p Parser) spawn_expr() ast.SpawnExpr { fn (mut p Parser) go_expr() ast.GoExpr { p.next() spos := p.tok.pos() + old_inside_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = false expr := p.expr(0) - call_expr := if expr is ast.CallExpr { + p.inside_assign_rhs = old_inside_assign_rhs + mut call_expr := if expr is ast.CallExpr { expr } else { p.error_with_pos('expression in `go` must be a function call', expr.pos()) ast.CallExpr{ - scope: p.scope - is_return_used: true + scope: p.scope } } + call_expr.is_return_used = true pos := spos.extend(p.prev_tok.pos()) // p.register_auto_import('coroutines') p.table.gostmts++ diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 7ebc25b119..0327d306f2 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -170,13 +170,6 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { } p.open_scope() stmts := p.parse_block_no_scope(false) - // if the last expr is a callexpr mark its return as used - if p.inside_assign_rhs && stmts.len > 0 && stmts.last() is ast.ExprStmt { - mut last_expr := stmts.last() as ast.ExprStmt - if mut last_expr.expr is ast.CallExpr { - last_expr.expr.is_return_used = true - } - } branches << ast.IfBranch{ cond: cond stmts: stmts @@ -361,12 +354,6 @@ fn (mut p Parser) match_expr() ast.MatchExpr { pos := branch_first_pos.extend_with_last_line(branch_last_pos, p.prev_tok.line_nr) branch_pos := branch_first_pos.extend_with_last_line(p.tok.pos(), p.tok.line_nr) post_comments := p.eat_comments() - if p.inside_assign_rhs && stmts.len > 0 && stmts.last() is ast.ExprStmt { - mut last_expr := stmts.last() as ast.ExprStmt - if mut last_expr.expr is ast.CallExpr { - last_expr.expr.is_return_used = true - } - } branches << ast.MatchBranch{ exprs: exprs ecmnts: ecmnts diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1db20ec8db..22798f4936 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -593,6 +593,8 @@ fn (mut p Parser) parse_block() []ast.Stmt { fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { p.check(.lcbr) mut stmts := []ast.Stmt{cap: 20} + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = false if p.tok.kind != .rcbr { mut count := 0 for p.tok.kind !in [.eof, .rcbr] { @@ -608,13 +610,51 @@ fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { } } } + p.inside_assign_rhs = old_assign_rhs if is_top_level { p.top_level_statement_end() } p.check(.rcbr) + // on assignment the last callexpr must be marked as return used recursively + if p.inside_assign_rhs && stmts.len > 0 { + mut last_stmt := stmts.last() + p.mark_last_call_return_as_used(mut last_stmt) + } return stmts } +fn (mut p Parser) mark_last_call_return_as_used(mut last_stmt ast.Stmt) { + match mut last_stmt { + ast.ExprStmt { + match mut last_stmt.expr { + ast.CallExpr { + // last stmt on block is CallExpr + last_stmt.expr.is_return_used = true + } + ast.IfExpr { + // last stmt on block is: if .. { foo() } else { bar() } + for mut branch in last_stmt.expr.branches { + if branch.stmts.len > 0 { + mut last_if_stmt := branch.stmts.last() + p.mark_last_call_return_as_used(mut last_if_stmt) + } + } + } + ast.ConcatExpr { + // last stmt on block is: a, b, c := ret1(), ret2(), ret3() + for mut expr in last_stmt.expr.vals { + if mut expr is ast.CallExpr { + expr.is_return_used = true + } + } + } + else {} + } + } + else {} + } +} + fn (mut p Parser) next() { p.prev_tok = p.tok p.tok = p.peek_tok diff --git a/vlib/v/tests/fns/call_or_empty_block_test.v b/vlib/v/tests/fns/call_or_empty_block_test.v new file mode 100644 index 0000000000..945eb6d5db --- /dev/null +++ b/vlib/v/tests/fns/call_or_empty_block_test.v @@ -0,0 +1,17 @@ +fn foo() ! { +} + +fn bar() ?int { + return 1 +} + +fn test_main() { + y := if a := bar() { + dump(a) + foo() or {} + true + } else { + false + } + assert y +} diff --git a/vlib/v/tests/options/option_call_on_orexpr_test.v b/vlib/v/tests/options/option_call_on_orexpr_test.v index 5a8de2f86a..0e956770d7 100644 --- a/vlib/v/tests/options/option_call_on_orexpr_test.v +++ b/vlib/v/tests/options/option_call_on_orexpr_test.v @@ -10,7 +10,7 @@ fn find_startswith_string(a []string, search string) ?string { fn find_any_startswith_string(a []string, b []string, search string) ?string { // cannot convert 'struct _option_string' to 'struct string' // V wants the or {} block to return a string, but find_startswith_string returns ?string - return find_startswith_string(a, search) or { find_startswith_string(b, search) } + return find_startswith_string(a, search) or { find_startswith_string(b, search)? } } fn find_any_startswith_string_unwrapped(a []string, b []string, search string) ?string { @@ -26,4 +26,10 @@ fn test_main() { var2 := find_any_startswith_string_unwrapped(['foobar', 'barfoo'], ['deadbeef', 'beefdead'], 'dead') dump(var2) + assert var2 != none + + var3 := find_any_startswith_string_unwrapped(['foobar', 'barfoo'], ['deadbeef', 'beefdead'], + 'error') + dump(var3) + assert var3 == none }