parser: check if the last or {} block expression is valid
These checks allows for: a) `x := call() or { 'abc' }` b) `x := call() or { panic('abc') }` c) `x := call() or { exit(123) }` d) `x := call() or { continue }` e) `x := call() or { break }` f) `x := call() or { return }` ... but produce errors for: g) `x := call() or { println('an error') }` , etc.pull/4283/head^2
parent
275b20a184
commit
0024ff848d
|
@ -27,6 +27,7 @@ fn test_open_file() {
|
||||||
hello := 'hello world!'
|
hello := 'hello world!'
|
||||||
os.open_file(filename, 'r+', 0o666) or {
|
os.open_file(filename, 'r+', 0o666) or {
|
||||||
assert err == 'No such file or directory'
|
assert err == 'No such file or directory'
|
||||||
|
os.File{}
|
||||||
}
|
}
|
||||||
mut file := os.open_file(filename, 'w+', 0o666) or {
|
mut file := os.open_file(filename, 'w+', 0o666) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -212,7 +213,7 @@ fn test_is_writable_folder() {
|
||||||
tmp := os.temp_dir()
|
tmp := os.temp_dir()
|
||||||
f := os.is_writable_folder(tmp) or {
|
f := os.is_writable_folder(tmp) or {
|
||||||
eprintln('err: $err')
|
eprintln('err: $err')
|
||||||
assert false
|
false
|
||||||
}
|
}
|
||||||
assert f
|
assert f
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub struct ExprStmt {
|
||||||
pub:
|
pub:
|
||||||
expr Expr
|
expr Expr
|
||||||
typ table.Type
|
typ table.Type
|
||||||
|
pos token.Position
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IntegerLiteral {
|
pub struct IntegerLiteral {
|
||||||
|
@ -647,6 +648,7 @@ mut:
|
||||||
pub struct OrExpr {
|
pub struct OrExpr {
|
||||||
pub:
|
pub:
|
||||||
stmts []Stmt
|
stmts []Stmt
|
||||||
|
is_used bool // if the or{} block is written down or left out
|
||||||
// var_name string
|
// var_name string
|
||||||
// expr Expr
|
// expr Expr
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,6 +394,57 @@ 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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.error('require a statement in or{} block', call_expr.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expr_panic_or_exit(expr ast.Expr) bool {
|
||||||
|
match expr {
|
||||||
|
ast.CallExpr { return it.name == 'panic' || it.name == '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 {
|
||||||
|
return match stmt {
|
||||||
|
ast.Return { true }
|
||||||
|
ast.BranchStmt { true }
|
||||||
|
ast.ExprStmt { true }
|
||||||
|
else { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (c mut Checker) selector_expr(selector_expr mut ast.SelectorExpr) table.Type {
|
pub fn (c mut Checker) selector_expr(selector_expr mut ast.SelectorExpr) table.Type {
|
||||||
typ := c.expr(selector_expr.expr)
|
typ := c.expr(selector_expr.expr)
|
||||||
if typ == table.void_type_idx {
|
if typ == table.void_type_idx {
|
||||||
|
@ -795,7 +846,9 @@ pub fn (c mut Checker) expr(node ast.Expr) table.Type {
|
||||||
return it.typ
|
return it.typ
|
||||||
}
|
}
|
||||||
ast.CallExpr {
|
ast.CallExpr {
|
||||||
return c.call_expr(mut it)
|
call_ret_type := 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
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub fn (p mut Parser) call_expr(is_c bool, mod string) ast.CallExpr {
|
||||||
p.check(.lpar)
|
p.check(.lpar)
|
||||||
args := p.call_args()
|
args := p.call_args()
|
||||||
mut or_stmts := []ast.Stmt
|
mut or_stmts := []ast.Stmt
|
||||||
|
mut is_or_block_used := false
|
||||||
if p.tok.kind == .key_orelse {
|
if p.tok.kind == .key_orelse {
|
||||||
p.next()
|
p.next()
|
||||||
p.open_scope()
|
p.open_scope()
|
||||||
|
@ -33,6 +34,7 @@ pub fn (p mut Parser) call_expr(is_c bool, mod string) ast.CallExpr {
|
||||||
name: 'errcode'
|
name: 'errcode'
|
||||||
typ: table.int_type
|
typ: table.int_type
|
||||||
})
|
})
|
||||||
|
is_or_block_used = true
|
||||||
or_stmts = p.parse_block_no_scope()
|
or_stmts = p.parse_block_no_scope()
|
||||||
p.close_scope()
|
p.close_scope()
|
||||||
}
|
}
|
||||||
|
@ -44,6 +46,7 @@ pub fn (p mut Parser) call_expr(is_c bool, mod string) ast.CallExpr {
|
||||||
is_c: is_c
|
is_c: is_c
|
||||||
or_block: ast.OrExpr{
|
or_block: ast.OrExpr{
|
||||||
stmts: or_stmts
|
stmts: or_stmts
|
||||||
|
is_used: is_or_block_used
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
|
|
|
@ -434,9 +434,11 @@ pub fn (p mut Parser) stmt() ast.Stmt {
|
||||||
name: name
|
name: name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
epos := p.tok.position()
|
||||||
expr := p.expr(0)
|
expr := p.expr(0)
|
||||||
return ast.ExprStmt{
|
return ast.ExprStmt{
|
||||||
expr: expr
|
expr: expr
|
||||||
|
pos: epos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -943,6 +945,7 @@ fn (p mut Parser) dot_expr(left ast.Expr) ast.Expr {
|
||||||
p.next()
|
p.next()
|
||||||
args := p.call_args()
|
args := p.call_args()
|
||||||
mut or_stmts := []ast.Stmt
|
mut or_stmts := []ast.Stmt
|
||||||
|
mut is_or_block_used := false
|
||||||
if p.tok.kind == .key_orelse {
|
if p.tok.kind == .key_orelse {
|
||||||
p.next()
|
p.next()
|
||||||
p.open_scope()
|
p.open_scope()
|
||||||
|
@ -954,6 +957,7 @@ fn (p mut Parser) dot_expr(left ast.Expr) ast.Expr {
|
||||||
name: 'err'
|
name: 'err'
|
||||||
typ: table.string_type
|
typ: table.string_type
|
||||||
})
|
})
|
||||||
|
is_or_block_used = true
|
||||||
or_stmts = p.parse_block_no_scope()
|
or_stmts = p.parse_block_no_scope()
|
||||||
p.close_scope()
|
p.close_scope()
|
||||||
}
|
}
|
||||||
|
@ -965,6 +969,7 @@ fn (p mut Parser) dot_expr(left ast.Expr) ast.Expr {
|
||||||
is_method: true
|
is_method: true
|
||||||
or_block: ast.OrExpr{
|
or_block: ast.OrExpr{
|
||||||
stmts: or_stmts
|
stmts: or_stmts
|
||||||
|
is_used: is_or_block_used
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mut node := ast.Expr{}
|
mut node := ast.Expr{}
|
||||||
|
|
|
@ -56,10 +56,7 @@ fn test_defer_early_exit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_defer_option() {
|
fn test_defer_option() {
|
||||||
mut ok := Num{
|
mut ok := Num{0}
|
||||||
0}
|
set_num_opt(mut ok)
|
||||||
set_num_opt(mut ok) or {
|
|
||||||
assert false
|
|
||||||
}
|
|
||||||
assert ok.val == 1
|
assert ok.val == 1
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue