checker: check all branches for return (#5763)
parent
194ecda829
commit
fb927dab60
|
@ -964,9 +964,6 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
|
pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
|
||||||
if call_expr.name == 'panic' {
|
|
||||||
c.returns = true
|
|
||||||
}
|
|
||||||
fn_name := call_expr.name
|
fn_name := call_expr.name
|
||||||
if fn_name == 'main' {
|
if fn_name == 'main' {
|
||||||
c.error('the `main` function cannot be called in the program', call_expr.pos)
|
c.error('the `main` function cannot be called in the program', call_expr.pos)
|
||||||
|
@ -1843,6 +1840,11 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
||||||
if node.has_else {
|
if node.has_else {
|
||||||
c.stmts(node.else_stmts)
|
c.stmts(node.else_stmts)
|
||||||
}
|
}
|
||||||
|
mut stmts := node.stmts.clone()
|
||||||
|
stmts << node.else_stmts
|
||||||
|
if has_return := c.has_return(stmts) {
|
||||||
|
c.returns = has_return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ast.ConstDecl {
|
ast.ConstDecl {
|
||||||
mut field_names := []string{}
|
mut field_names := []string{}
|
||||||
|
@ -2006,7 +2008,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
||||||
c.check_valid_snake_case(node.name, 'module name', node.pos)
|
c.check_valid_snake_case(node.name, 'module name', node.pos)
|
||||||
}
|
}
|
||||||
ast.Return {
|
ast.Return {
|
||||||
c.returns = true
|
// c.returns = true
|
||||||
c.return_stmt(mut node)
|
c.return_stmt(mut node)
|
||||||
c.scope_returns = true
|
c.scope_returns = true
|
||||||
}
|
}
|
||||||
|
@ -2466,6 +2468,8 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
|
||||||
c.match_exprs(mut node, cond_type_sym)
|
c.match_exprs(mut node, cond_type_sym)
|
||||||
c.expected_type = cond_type
|
c.expected_type = cond_type
|
||||||
mut ret_type := table.void_type
|
mut ret_type := table.void_type
|
||||||
|
mut require_return := false
|
||||||
|
mut branch_without_return := false
|
||||||
for branch in node.branches {
|
for branch in node.branches {
|
||||||
for expr in branch.exprs {
|
for expr in branch.exprs {
|
||||||
c.expected_type = cond_type
|
c.expected_type = cond_type
|
||||||
|
@ -2502,6 +2506,19 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if has_return := c.has_return(branch.stmts) {
|
||||||
|
if has_return {
|
||||||
|
require_return = true
|
||||||
|
} else {
|
||||||
|
branch_without_return = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if require_return && branch_without_return {
|
||||||
|
c.returns = false
|
||||||
|
} else {
|
||||||
|
// if inner if branch has not covered all branches but this one
|
||||||
|
c.returns = true
|
||||||
}
|
}
|
||||||
// if ret_type != table.void_type {
|
// if ret_type != table.void_type {
|
||||||
// node.is_expr = c.expected_type != table.void_type
|
// node.is_expr = c.expected_type != table.void_type
|
||||||
|
@ -2602,6 +2619,8 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
|
||||||
}
|
}
|
||||||
former_expected_type := c.expected_type
|
former_expected_type := c.expected_type
|
||||||
node.typ = table.void_type
|
node.typ = table.void_type
|
||||||
|
mut require_return := false
|
||||||
|
mut branch_without_return := false
|
||||||
for i, branch in node.branches {
|
for i, branch in node.branches {
|
||||||
if branch.cond is ast.ParExpr {
|
if branch.cond is ast.ParExpr {
|
||||||
c.error('unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`.',
|
c.error('unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`.',
|
||||||
|
@ -2694,6 +2713,19 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
|
||||||
branch.pos)
|
branch.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if has_return := c.has_return(branch.stmts) {
|
||||||
|
if has_return {
|
||||||
|
require_return = true
|
||||||
|
} else {
|
||||||
|
branch_without_return = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if require_return && (!node.has_else || branch_without_return) {
|
||||||
|
c.returns = false
|
||||||
|
} else {
|
||||||
|
// if inner if branch has not covered all branches but this one
|
||||||
|
c.returns = true
|
||||||
}
|
}
|
||||||
// if only untyped literals were given default to int/f64
|
// if only untyped literals were given default to int/f64
|
||||||
if node.typ == table.any_int_type {
|
if node.typ == table.any_int_type {
|
||||||
|
@ -2710,6 +2742,19 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
|
||||||
return table.bool_type
|
return table.bool_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (c Checker) has_return(stmts []ast.Stmt) ?bool {
|
||||||
|
// complexity means either more match or ifs
|
||||||
|
exprs := stmts.filter(it is ast.ExprStmt).map(it as ast.ExprStmt)
|
||||||
|
contains_comp_if := stmts.filter(it is ast.CompIf).len > 0
|
||||||
|
contains_if_match := exprs.filter(it.expr is ast.IfExpr || it.expr is ast.MatchExpr).len > 0
|
||||||
|
contains_complexity := contains_comp_if || contains_if_match
|
||||||
|
// if the inner complexity covers all paths with returns there is no need for further checks
|
||||||
|
if !contains_complexity || !c.returns {
|
||||||
|
return has_top_return(stmts)
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut c Checker) postfix_expr(node ast.PostfixExpr) table.Type {
|
pub fn (mut c Checker) postfix_expr(node ast.PostfixExpr) table.Type {
|
||||||
typ := c.expr(node.expr)
|
typ := c.expr(node.expr)
|
||||||
typ_sym := c.table.get_type_symbol(typ)
|
typ_sym := c.table.get_type_symbol(typ)
|
||||||
|
@ -2999,6 +3044,7 @@ fn (c &Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Positio
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
|
c.returns = true
|
||||||
if node.is_generic && c.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion
|
if node.is_generic && c.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion
|
||||||
// loop thru each generic type and generate a function
|
// loop thru each generic type and generate a function
|
||||||
for gen_type in c.table.fn_gen_types[node.name] {
|
for gen_type in c.table.fn_gen_types[node.name] {
|
||||||
|
@ -3075,10 +3121,36 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.stmts(node.stmts)
|
c.stmts(node.stmts)
|
||||||
|
returns := c.returns || has_top_return(node.stmts)
|
||||||
if node.language == .v && !node.no_body &&
|
if node.language == .v && !node.no_body &&
|
||||||
node.return_type != table.void_type && !c.returns &&
|
node.return_type != table.void_type && !returns &&
|
||||||
node.name !in ['panic', 'exit'] {
|
node.name !in ['panic', 'exit'] {
|
||||||
c.error('missing return at end of function `$node.name`', node.pos)
|
c.error('missing return at end of function `$node.name`', node.pos)
|
||||||
}
|
}
|
||||||
c.returns = false
|
c.returns = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_top_return(stmts []ast.Stmt) bool {
|
||||||
|
if stmts.filter(it is ast.Return).len > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
mut has_unsafe_return := false
|
||||||
|
for _, stmt in stmts {
|
||||||
|
if stmt is ast.UnsafeStmt {
|
||||||
|
for ustmt in stmt.stmts {
|
||||||
|
if ustmt is ast.Return {
|
||||||
|
has_unsafe_return = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_unsafe_return {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
exprs := stmts.filter(it is ast.ExprStmt).map(it as ast.ExprStmt)
|
||||||
|
has_panic_exit := exprs.filter(it.expr is ast.CallExpr).map(it.expr as ast.CallExpr).filter(it.name == 'panic' || it.name == 'exit').len > 0
|
||||||
|
if has_panic_exit {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_comp_if.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | $if windows {
|
||||||
|
5 |
|
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
$if windows {
|
||||||
|
|
||||||
|
} $else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_comp_if_nested.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | $if windows {
|
||||||
|
5 | if true {
|
|
@ -0,0 +1,17 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
$if windows {
|
||||||
|
if true {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} $else {
|
||||||
|
if true {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_if_match.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | if true {
|
||||||
|
5 | match 1 {
|
|
@ -0,0 +1,13 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
match 1 {
|
||||||
|
1 { return '' }
|
||||||
|
2 { }
|
||||||
|
else { return '' }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_match_if.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | match 1 {
|
||||||
|
5 | 1 { return '' }
|
|
@ -0,0 +1,15 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
match 1 {
|
||||||
|
1 { return '' }
|
||||||
|
2 {
|
||||||
|
if true {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { return '' }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_nested.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | if true {
|
||||||
|
5 | return ''
|
|
@ -0,0 +1,11 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/return_missing_simple.v:3:1: error: missing return at end of function `foo`
|
||||||
|
1 | fn main() {}
|
||||||
|
2 |
|
||||||
|
3 | fn foo() string {
|
||||||
|
| ~~~~~~~~~~~~~~~
|
||||||
|
4 | if true {
|
||||||
|
5 | return ''
|
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
$if windows {
|
||||||
|
return ''
|
||||||
|
} $else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
$if windows {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} $else {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
match 1 {
|
||||||
|
1 { return '' }
|
||||||
|
2 { return '' }
|
||||||
|
else { return '' }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
match 1 {
|
||||||
|
1 { return '' }
|
||||||
|
2 {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { return '' }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
if true {
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if true {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() string {
|
||||||
|
unsafe {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
|
@ -551,6 +551,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
|
||||||
expr: p.vweb()
|
expr: p.vweb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ast.Stmt{}
|
||||||
}
|
}
|
||||||
.key_continue, .key_break {
|
.key_continue, .key_break {
|
||||||
tok := p.tok
|
tok := p.tok
|
||||||
|
@ -601,6 +602,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
|
||||||
.key_const {
|
.key_const {
|
||||||
p.error_with_pos('const can only be defined at the top level (outside of functions)',
|
p.error_with_pos('const can only be defined at the top level (outside of functions)',
|
||||||
p.tok.position())
|
p.tok.position())
|
||||||
|
return ast.Stmt{}
|
||||||
}
|
}
|
||||||
// literals, 'if', etc. in here
|
// literals, 'if', etc. in here
|
||||||
else {
|
else {
|
||||||
|
|
Loading…
Reference in New Issue