checker: fix missing or-block check for callexpr (fix #22835) (#22840)

This commit is contained in:
Felipe Pena 2024-11-13 07:30:06 -03:00 committed by GitHub
parent 7c3c5891b0
commit 62bdf990d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 294 additions and 37 deletions

View file

@ -23,7 +23,7 @@ fn htonl64(payload_len u64) []u8 {
// create_masking_key returns a new masking key to use when masking websocket messages // create_masking_key returns a new masking key to use when masking websocket messages
fn create_masking_key() []u8 { 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 // create_key_challenge_response creates a key challenge response from security key

View file

@ -19,7 +19,10 @@ pub fn test_u64toa_large_values() {
mut buf := [20]u8{} mut buf := [20]u8{}
len := unsafe { 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 { if v < 100_000_000 {
@ -38,7 +41,10 @@ pub fn test_u64toa_edge_cases() {
// Test zero value // Test zero value
len := unsafe { len := unsafe {
u64toa(&buf[0], 0) or { assert false } u64toa(&buf[0], 0) or {
assert false
0
}
} }
assert len == 1 assert len == 1

View file

@ -126,6 +126,7 @@ mut:
generic_fns map[string]bool // register generic fns that needs recheck once generic_fns map[string]bool // register generic fns that needs recheck once
inside_sql bool // to handle sql table fields pseudo variables inside_sql bool // to handle sql table fields pseudo variables
inside_selector_expr bool 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_interface_deref bool
inside_decl_rhs bool inside_decl_rhs bool
inside_if_guard bool // true inside the guard condition of `if x := opt() {}` 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 return
} }
if node.stmts.len == 0 { if node.stmts.len == 0 {
if ret_type != ast.void_type { if expr is ast.CallExpr && expr.is_return_used {
// x := f() or {} // x := f() or {}, || f() or {} etc
c.error('assignment requires a non empty `or {}` block', node.pos) 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 {}` // allow `f() or {}`
return return
} }
mut valid_stmts := node.stmts.filter(it !is ast.SemicolonStmt) 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() } mut last_stmt := if valid_stmts.len > 0 { valid_stmts.last() } else { node.stmts.last() }
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()) 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) { 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 { ast.ExprStmt {
c.expected_type = ret_type c.expected_type = ret_type
c.expected_or_type = ret_type.clear_option_and_result() 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) last_stmt_typ := c.expr(mut stmt.expr)
if last_stmt_typ.has_flag(.option) || last_stmt_typ == ast.none_type { 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] { if stmt.expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr, ast.None, ast.CastExpr] {
@ -1437,11 +1451,13 @@ fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr
} }
ast.Return {} ast.Return {}
else { else {
if stmt !is ast.AssertStmt || c.inside_or_block_value {
expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) 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', c.error('last statement in the `or {}` block should be an expression of type `${expected_type_name}` or exit parent scope',
stmt.pos) stmt.pos)
} }
} }
}
} else if mut stmt is ast.ExprStmt { } else if mut stmt is ast.ExprStmt {
match mut stmt.expr { match mut stmt.expr {
ast.IfExpr { ast.IfExpr {

View file

@ -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.expected_or_type = node.return_type.clear_flag(.result)
c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) 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 if !c.inside_const && c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.is_main
&& !c.table.cur_fn.is_test { && !c.table.cur_fn.is_test {

View file

@ -65,6 +65,9 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as
is_expr: false is_expr: false
typ: return_type typ: return_type
} }
if mut node.expr is ast.CallExpr && node.expr.is_return_used {
node.expr.is_return_used = false
}
} else { } else {
stmts << ast.Return{ stmts << ast.Return{
pos: node.pos pos: node.pos

View file

@ -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 })
| ~~~~~~

View file

@ -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 })

View file

@ -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

View file

@ -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
}
}

View file

@ -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)

View file

@ -0,0 +1,8 @@
type Aa = string | int
fn f() !string {
return ''
}
y := Aa(f() or { 2 })
println(y)

View file

@ -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 | }

View file

@ -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 })
}

View file

@ -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 | 4 |
5 | fn main() { 5 | fn main() {
6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b') 6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b')
| ~~~~~ | ~~~~~
7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b') 7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b')
8 | 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() { 5 | fn main() {
6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b') 6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b')
7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b') 7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b')

View file

@ -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 { 9 | _ := sql db {
10 | select from Person 10 | select from Person
11 | } or { 11 | } or {

View file

@ -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) { fn (e &Eval) error(msg string) {
eprintln('> V interpreter backtrace:') eprintln('> V interpreter backtrace:')
e.print_backtrace() e.print_backtrace()

View file

@ -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. // 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_tmp_var := g.need_tmp_var_in_if(node)
needs_conds_order := g.needs_conds_order(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 cur_line := ''
mut raw_state := false mut raw_state := false
if needs_tmp_var { if needs_tmp_var {
@ -210,7 +210,9 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
} }
cur_line = g.go_before_last_stmt() cur_line = g.go_before_last_stmt()
g.empty_line = true g.empty_line = true
if tmp != '' {
g.writeln('${styp} ${tmp}; /* if prepend */') g.writeln('${styp} ${tmp}; /* if prepend */')
}
if g.infix_left_var_name.len > 0 { if g.infix_left_var_name.len > 0 {
g.writeln('if (${g.infix_left_var_name}) {') g.writeln('if (${g.infix_left_var_name}) {')
g.indent++ g.indent++

View file

@ -787,7 +787,9 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr {
right_op_pos := p.tok.pos() right_op_pos := p.tok.pos()
old_assign_rhs := p.inside_assign_rhs old_assign_rhs := p.inside_assign_rhs
if op in [.decl_assign, .assign] {
p.inside_assign_rhs = true p.inside_assign_rhs = true
}
right = p.expr(precedence) right = p.expr(precedence)
p.inside_assign_rhs = old_assign_rhs p.inside_assign_rhs = old_assign_rhs
if op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && mut right is ast.PrefixExpr { if op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && mut right is ast.PrefixExpr {

View file

@ -872,7 +872,10 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
if p.tok.kind == .lcbr { if p.tok.kind == .lcbr {
tmp := p.label_names tmp := p.label_names
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) stmts = p.parse_block_no_scope(false)
p.inside_assign_rhs = old_assign_rhs
label_names = p.label_names.clone() label_names = p.label_names.clone()
p.label_names = tmp 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 { fn (mut p Parser) spawn_expr() ast.SpawnExpr {
p.next() p.next()
spos := p.tok.pos() spos := p.tok.pos()
old_inside_assign_rhs := p.inside_assign_rhs
p.inside_assign_rhs = false
expr := p.expr(0) 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 expr
} else { } else {
p.error_with_pos('expression in `spawn` must be a function call', expr.pos()) p.error_with_pos('expression in `spawn` must be a function call', expr.pos())
ast.CallExpr{ ast.CallExpr{
scope: p.scope scope: p.scope
is_return_used: true
} }
} }
call_expr.is_return_used = true
pos := spos.extend(p.prev_tok.pos()) pos := spos.extend(p.prev_tok.pos())
p.register_auto_import('sync.threads') p.register_auto_import('sync.threads')
p.table.gostmts++ p.table.gostmts++
@ -1186,16 +1192,19 @@ fn (mut p Parser) spawn_expr() ast.SpawnExpr {
fn (mut p Parser) go_expr() ast.GoExpr { fn (mut p Parser) go_expr() ast.GoExpr {
p.next() p.next()
spos := p.tok.pos() spos := p.tok.pos()
old_inside_assign_rhs := p.inside_assign_rhs
p.inside_assign_rhs = false
expr := p.expr(0) 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 expr
} else { } else {
p.error_with_pos('expression in `go` must be a function call', expr.pos()) p.error_with_pos('expression in `go` must be a function call', expr.pos())
ast.CallExpr{ ast.CallExpr{
scope: p.scope scope: p.scope
is_return_used: true
} }
} }
call_expr.is_return_used = true
pos := spos.extend(p.prev_tok.pos()) pos := spos.extend(p.prev_tok.pos())
// p.register_auto_import('coroutines') // p.register_auto_import('coroutines')
p.table.gostmts++ p.table.gostmts++

View file

@ -170,13 +170,6 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr {
} }
p.open_scope() p.open_scope()
stmts := p.parse_block_no_scope(false) 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{ branches << ast.IfBranch{
cond: cond cond: cond
stmts: stmts 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) 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) branch_pos := branch_first_pos.extend_with_last_line(p.tok.pos(), p.tok.line_nr)
post_comments := p.eat_comments() 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{ branches << ast.MatchBranch{
exprs: exprs exprs: exprs
ecmnts: ecmnts ecmnts: ecmnts

View file

@ -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 { fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt {
p.check(.lcbr) p.check(.lcbr)
mut stmts := []ast.Stmt{cap: 20} mut stmts := []ast.Stmt{cap: 20}
old_assign_rhs := p.inside_assign_rhs
p.inside_assign_rhs = false
if p.tok.kind != .rcbr { if p.tok.kind != .rcbr {
mut count := 0 mut count := 0
for p.tok.kind !in [.eof, .rcbr] { 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 { if is_top_level {
p.top_level_statement_end() p.top_level_statement_end()
} }
p.check(.rcbr) 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 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() { fn (mut p Parser) next() {
p.prev_tok = p.tok p.prev_tok = p.tok
p.tok = p.peek_tok p.tok = p.peek_tok

View file

@ -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
}

View file

@ -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 { fn find_any_startswith_string(a []string, b []string, search string) ?string {
// cannot convert 'struct _option_string' to 'struct string' // cannot convert 'struct _option_string' to 'struct string'
// V wants the or {} block to return a string, but find_startswith_string returns ?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 { 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'], var2 := find_any_startswith_string_unwrapped(['foobar', 'barfoo'], ['deadbeef', 'beefdead'],
'dead') 'dead')
dump(var2) dump(var2)
assert var2 != none
var3 := find_any_startswith_string_unwrapped(['foobar', 'barfoo'], ['deadbeef', 'beefdead'],
'error')
dump(var3)
assert var3 == none
} }