checker: allow again fallthrough in or{} blocks of option calls without assignment

pull/4290/head
Delyan Angelov 2020-04-08 02:19:24 +03:00
parent a6daf2f78e
commit b9ec1479e4
4 changed files with 92 additions and 42 deletions

View File

@ -24,8 +24,9 @@ fn testsuite_end() {
fn test_inode_file_type() { fn test_inode_file_type() {
filename := './test1.txt' filename := './test1.txt'
mut file := os.open_file(filename, 'w', 0o600) if file := os.open_file(filename, 'w', 0o600) {
file.close() file.close()
}
mode := os.inode(filename) mode := os.inode(filename)
os.rm(filename) os.rm(filename)
assert mode.typ == .regular assert mode.typ == .regular
@ -33,8 +34,9 @@ fn test_inode_file_type() {
fn test_inode_file_owner_permission() { fn test_inode_file_owner_permission() {
filename := './test2.txt' filename := './test2.txt'
mut file := os.open_file(filename, 'w', 0o600) if file := os.open_file(filename, 'w', 0o600) {
file.close() file.close()
}
mode := os.inode(filename) mode := os.inode(filename)
os.rm(filename) os.rm(filename)
assert mode.owner.read assert mode.owner.read

View File

@ -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) 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.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 { 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) { pub fn (c mut Checker) check_expr_opt_call(x ast.Expr, xtype table.Type, is_return_used bool){
if call_expr.or_block.is_used { match x {
stmts_len := call_expr.or_block.stmts.len ast.CallExpr {
if stmts_len != 0 { if table.type_is(it.return_type, .optional) {
last_stmt := call_expr.or_block.stmts[stmts_len - 1] c.check_or_block(it, xtype, is_return_used)
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)
} }
} 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 { fn is_expr_panic_or_exit(expr ast.Expr) bool {
match expr { match expr {
ast.CallExpr { return it.name == 'panic' || it.name == 'exit' } ast.CallExpr { return it.name in ['panic','exit'] }
else { return false } else { return false }
} }
} }
// TODO: merge to check_or_block when v can handle it // 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 { return match stmt {
ast.Return { true } ast.Return { true }
ast.BranchStmt { 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 assign_stmt.right_types << val_type
scope.update_var_type(ident.name, val_type) scope.update_var_type(ident.name, val_type)
} }
c.check_expr_opt_call(assign_stmt.right[0], right_type, true)
} else { } else {
// `a := 1` | `a,b := 1,2` // `a := 1` | `a,b := 1,2`
if assign_stmt.left.len != assign_stmt.right.len { 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.left[i] = ident
assign_stmt.right_types << val_type assign_stmt.right_types << val_type
scope.update_var_type(ident.name, 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 c.expected_type = table.void_type
@ -711,8 +737,9 @@ fn (c mut Checker) stmt(node ast.Stmt) {
} }
} }
ast.ExprStmt { ast.ExprStmt {
c.expr(it.expr) etype := c.expr(it.expr)
c.expected_type = table.void_type c.expected_type = table.void_type
c.check_expr_opt_call(it.expr, etype, false)
} }
ast.FnDecl { ast.FnDecl {
// if it.is_method { // if it.is_method {
@ -851,9 +878,7 @@ pub fn (c mut Checker) expr(node ast.Expr) table.Type {
return it.typ return it.typ
} }
ast.CallExpr { ast.CallExpr {
call_ret_type := c.call_expr(mut it) return c.call_expr(mut it)
c.check_or_block(mut it, call_ret_type)
return call_ret_type
} }
ast.CharLiteral { ast.CharLiteral {
return table.byte_type return table.byte_type

View File

@ -46,8 +46,7 @@ mut:
} }
fn test_defer_early_exit() { fn test_defer_early_exit() {
mut sum := Num{ mut sum := Num{0}
0}
for i in 0 .. 10 { for i in 0 .. 10 {
set_num(i, mut sum) set_num(i, mut sum)
} }
@ -57,6 +56,6 @@ fn test_defer_early_exit() {
fn test_defer_option() { fn test_defer_option() {
mut ok := Num{0} mut ok := Num{0}
set_num_opt(mut ok) set_num_opt(mut ok) or {}
assert ok.val == 1 assert ok.val == 1
} }

View File

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