checker: allow again fallthrough in or{} blocks of option calls without assignment
parent
a6daf2f78e
commit
b9ec1479e4
|
@ -24,8 +24,9 @@ fn testsuite_end() {
|
|||
|
||||
fn test_inode_file_type() {
|
||||
filename := './test1.txt'
|
||||
mut file := os.open_file(filename, 'w', 0o600)
|
||||
file.close()
|
||||
if file := os.open_file(filename, 'w', 0o600) {
|
||||
file.close()
|
||||
}
|
||||
mode := os.inode(filename)
|
||||
os.rm(filename)
|
||||
assert mode.typ == .regular
|
||||
|
@ -33,8 +34,9 @@ fn test_inode_file_type() {
|
|||
|
||||
fn test_inode_file_owner_permission() {
|
||||
filename := './test2.txt'
|
||||
mut file := os.open_file(filename, 'w', 0o600)
|
||||
file.close()
|
||||
if file := os.open_file(filename, 'w', 0o600) {
|
||||
file.close()
|
||||
}
|
||||
mode := os.inode(filename)
|
||||
os.rm(filename)
|
||||
assert mode.owner.read
|
||||
|
|
|
@ -215,6 +215,7 @@ fn (c mut Checker) assign_expr(assign_expr mut ast.AssignExpr) {
|
|||
right_type_sym := c.table.get_type_symbol(right_type)
|
||||
c.error('cannot assign `$right_type_sym.name` to `$left_type_sym.name`', assign_expr.pos)
|
||||
}
|
||||
c.check_expr_opt_call(assign_expr.val, right_type, true)
|
||||
}
|
||||
|
||||
pub fn (c mut Checker) call_expr(call_expr mut ast.CallExpr) table.Type {
|
||||
|
@ -396,49 +397,72 @@ pub fn (c mut Checker) call_expr(call_expr mut ast.CallExpr) table.Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn (c mut Checker) check_or_block(call_expr mut ast.CallExpr, ret_type table.Type) {
|
||||
if call_expr.or_block.is_used {
|
||||
stmts_len := call_expr.or_block.stmts.len
|
||||
if stmts_len != 0 {
|
||||
last_stmt := call_expr.or_block.stmts[stmts_len - 1]
|
||||
|
||||
if c.is_or_block_stmt_valid(last_stmt) {
|
||||
match last_stmt {
|
||||
ast.ExprStmt {
|
||||
type_fits := c.table.check(c.expr(it.expr), ret_type)
|
||||
is_panic_or_exit := is_expr_panic_or_exit(it.expr)
|
||||
if !type_fits && !is_panic_or_exit {
|
||||
type_name := c.table.get_type_symbol(c.expr(it.expr)).name
|
||||
expected_type_name := c.table.get_type_symbol(ret_type).name
|
||||
c.error('wrong return type ‘$type_name‘ in or{} block, expected ‘$expected_type_name‘', it.pos)
|
||||
}
|
||||
}
|
||||
ast.BranchStmt {
|
||||
if !(it.tok.kind in [.key_continue, .key_break]) {
|
||||
c.error('only break and continue are allowed as a final branch statement in an or{} block', it.tok.position())
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
} else {
|
||||
expected_type_name := c.table.get_type_symbol(ret_type).name
|
||||
c.error('last statement in or{} block should return ‘$expected_type_name‘', call_expr.pos)
|
||||
pub fn (c mut Checker) check_expr_opt_call(x ast.Expr, xtype table.Type, is_return_used bool){
|
||||
match x {
|
||||
ast.CallExpr {
|
||||
if table.type_is(it.return_type, .optional) {
|
||||
c.check_or_block(it, xtype, is_return_used)
|
||||
}
|
||||
} else {
|
||||
c.error('require a statement in or{} block', call_expr.pos)
|
||||
}
|
||||
else{}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (c mut Checker) check_or_block(call_expr mut ast.CallExpr, ret_type table.Type, is_ret_used bool) {
|
||||
if !call_expr.or_block.is_used {
|
||||
c.error('${call_expr.name}() returns an option, but you missed to add an `or {}` block to it', call_expr.pos)
|
||||
return
|
||||
}
|
||||
stmts_len := call_expr.or_block.stmts.len
|
||||
if stmts_len == 0 {
|
||||
if is_ret_used {
|
||||
// x := f() or {}
|
||||
c.error('assignment requires a non empty `or {}` block', call_expr.pos)
|
||||
return
|
||||
}
|
||||
// allow `f() or {}`
|
||||
return
|
||||
}
|
||||
last_stmt := call_expr.or_block.stmts[stmts_len - 1]
|
||||
if is_ret_used {
|
||||
if !c.is_last_or_block_stmt_valid(last_stmt) {
|
||||
expected_type_name := c.table.get_type_symbol(ret_type).name
|
||||
c.error('last statement in the `or {}` block should return ‘$expected_type_name‘', call_expr.pos)
|
||||
return
|
||||
}
|
||||
match last_stmt {
|
||||
ast.ExprStmt {
|
||||
type_fits := c.table.check(c.expr(it.expr), ret_type)
|
||||
is_panic_or_exit := is_expr_panic_or_exit(it.expr)
|
||||
if type_fits || is_panic_or_exit {
|
||||
return
|
||||
}
|
||||
type_name := c.table.get_type_symbol(c.expr(it.expr)).name
|
||||
expected_type_name := c.table.get_type_symbol(ret_type).name
|
||||
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`', it.pos)
|
||||
return
|
||||
}
|
||||
ast.BranchStmt {
|
||||
if !(it.tok.kind in [.key_continue, .key_break]) {
|
||||
c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block', it.tok.position())
|
||||
return
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expr_panic_or_exit(expr ast.Expr) bool {
|
||||
match expr {
|
||||
ast.CallExpr { return it.name == 'panic' || it.name == 'exit' }
|
||||
ast.CallExpr { return it.name in ['panic','exit'] }
|
||||
else { return false }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: merge to check_or_block when v can handle it
|
||||
pub fn (c mut Checker) is_or_block_stmt_valid(stmt ast.Stmt) bool {
|
||||
pub fn (c mut Checker) is_last_or_block_stmt_valid(stmt ast.Stmt) bool {
|
||||
return match stmt {
|
||||
ast.Return { true }
|
||||
ast.BranchStmt { true }
|
||||
|
@ -555,6 +579,7 @@ pub fn (c mut Checker) assign_stmt(assign_stmt mut ast.AssignStmt) {
|
|||
assign_stmt.right_types << val_type
|
||||
scope.update_var_type(ident.name, val_type)
|
||||
}
|
||||
c.check_expr_opt_call(assign_stmt.right[0], right_type, true)
|
||||
} else {
|
||||
// `a := 1` | `a,b := 1,2`
|
||||
if assign_stmt.left.len != assign_stmt.right.len {
|
||||
|
@ -581,6 +606,7 @@ pub fn (c mut Checker) assign_stmt(assign_stmt mut ast.AssignStmt) {
|
|||
assign_stmt.left[i] = ident
|
||||
assign_stmt.right_types << val_type
|
||||
scope.update_var_type(ident.name, val_type)
|
||||
c.check_expr_opt_call(assign_stmt.right[i], val_type, true)
|
||||
}
|
||||
}
|
||||
c.expected_type = table.void_type
|
||||
|
@ -711,8 +737,9 @@ fn (c mut Checker) stmt(node ast.Stmt) {
|
|||
}
|
||||
}
|
||||
ast.ExprStmt {
|
||||
c.expr(it.expr)
|
||||
etype := c.expr(it.expr)
|
||||
c.expected_type = table.void_type
|
||||
c.check_expr_opt_call(it.expr, etype, false)
|
||||
}
|
||||
ast.FnDecl {
|
||||
// if it.is_method {
|
||||
|
@ -851,9 +878,7 @@ pub fn (c mut Checker) expr(node ast.Expr) table.Type {
|
|||
return it.typ
|
||||
}
|
||||
ast.CallExpr {
|
||||
call_ret_type := c.call_expr(mut it)
|
||||
c.check_or_block(mut it, call_ret_type)
|
||||
return call_ret_type
|
||||
return c.call_expr(mut it)
|
||||
}
|
||||
ast.CharLiteral {
|
||||
return table.byte_type
|
||||
|
|
|
@ -46,8 +46,7 @@ mut:
|
|||
}
|
||||
|
||||
fn test_defer_early_exit() {
|
||||
mut sum := Num{
|
||||
0}
|
||||
mut sum := Num{0}
|
||||
for i in 0 .. 10 {
|
||||
set_num(i, mut sum)
|
||||
}
|
||||
|
@ -57,6 +56,6 @@ fn test_defer_early_exit() {
|
|||
|
||||
fn test_defer_option() {
|
||||
mut ok := Num{0}
|
||||
set_num_opt(mut ok)
|
||||
set_num_opt(mut ok) or {}
|
||||
assert ok.val == 1
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
fn err_call(ok bool) ?int {
|
||||
if ok {
|
||||
return 42
|
||||
}
|
||||
return error('Not ok!')
|
||||
}
|
||||
|
||||
fn test_if_opt() {
|
||||
if val := err_call(true) {
|
||||
eprintln(' val should be available here: $val')
|
||||
assert val == 42
|
||||
}
|
||||
assert true
|
||||
}
|
||||
|
||||
fn test_opt_with_fall_through() {
|
||||
mut x := 1
|
||||
err_call(false) or {
|
||||
eprintln(' this *should* be an error: $err')
|
||||
x++
|
||||
assert true
|
||||
}
|
||||
assert x == 2
|
||||
}
|
Loading…
Reference in New Issue