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() {
|
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
|
||||||
|
|
|
@ -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{}
|
||||||
}
|
}
|
||||||
} 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
|
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)
|
c.error('last statement in the `or {}` block should return ‘$expected_type_name‘', call_expr.pos)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
match last_stmt {
|
||||||
c.error('require a statement in or{} block', call_expr.pos)
|
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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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