all: support `[noreturn] fn abc() { for{} }`, mark panic/1 and exit/1with it too. (#10654)
parent
b0b4b8e65b
commit
6aecda3be8
15
doc/docs.md
15
doc/docs.md
|
@ -4763,6 +4763,21 @@ fn legacy_function() {}
|
||||||
fn inlined_function() {
|
fn inlined_function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function's calls will NOT be inlined.
|
||||||
|
[noinline]
|
||||||
|
fn function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function will NOT return to its callers.
|
||||||
|
// Such functions can be used at the end of or blocks,
|
||||||
|
// just like exit/1 or panic/1. Such functions can not
|
||||||
|
// have return types, and should end either in for{}, or
|
||||||
|
// by calling other `[noreturn]` functions.
|
||||||
|
[noreturn]
|
||||||
|
fn forever() {
|
||||||
|
for {}
|
||||||
|
}
|
||||||
|
|
||||||
// The following struct must be allocated on the heap. Therefore, it can only be used as a
|
// The following struct must be allocated on the heap. Therefore, it can only be used as a
|
||||||
// reference (`&Window`) or inside another reference (`&OuterStruct{ Window{...} }`).
|
// reference (`&Window`) or inside another reference (`&OuterStruct{ Window{...} }`).
|
||||||
[heap]
|
[heap]
|
||||||
|
|
|
@ -4,9 +4,16 @@ type FnExitCb = fn ()
|
||||||
|
|
||||||
fn C.atexit(f FnExitCb) int
|
fn C.atexit(f FnExitCb) int
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
|
fn vhalt() {
|
||||||
|
for {}
|
||||||
|
}
|
||||||
|
|
||||||
// exit terminates execution immediately and returns exit `code` to the shell.
|
// exit terminates execution immediately and returns exit `code` to the shell.
|
||||||
|
[noreturn]
|
||||||
pub fn exit(code int) {
|
pub fn exit(code int) {
|
||||||
C.exit(code)
|
C.exit(code)
|
||||||
|
vhalt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vcommithash() string {
|
fn vcommithash() string {
|
||||||
|
@ -17,6 +24,7 @@ fn vcommithash() string {
|
||||||
// recent versions of tcc print nicer backtraces automatically
|
// recent versions of tcc print nicer backtraces automatically
|
||||||
// NB: the duplication here is because tcc_backtrace should be called directly
|
// NB: the duplication here is because tcc_backtrace should be called directly
|
||||||
// inside the panic functions.
|
// inside the panic functions.
|
||||||
|
[noreturn]
|
||||||
fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
|
fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
|
||||||
// NB: the order here is important for a stabler test output
|
// NB: the order here is important for a stabler test output
|
||||||
// module is less likely to change than function, etc...
|
// module is less likely to change than function, etc...
|
||||||
|
@ -52,14 +60,17 @@ fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
|
||||||
C.exit(1)
|
C.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vhalt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
pub fn panic_optional_not_set(s string) {
|
pub fn panic_optional_not_set(s string) {
|
||||||
panic('optional not set ($s)')
|
panic('optional not set ($s)')
|
||||||
}
|
}
|
||||||
|
|
||||||
// panic prints a nice error message, then exits the process with exit code of 1.
|
// panic prints a nice error message, then exits the process with exit code of 1.
|
||||||
// It also shows a backtrace on most platforms.
|
// It also shows a backtrace on most platforms.
|
||||||
|
[noreturn]
|
||||||
pub fn panic(s string) {
|
pub fn panic(s string) {
|
||||||
$if freestanding {
|
$if freestanding {
|
||||||
bare_panic(s)
|
bare_panic(s)
|
||||||
|
@ -87,6 +98,7 @@ pub fn panic(s string) {
|
||||||
C.exit(1)
|
C.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
vhalt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed.
|
// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed.
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub fn write(fd i64, buf &byte, count u64) i64 {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
fn bare_panic(msg string) {
|
fn bare_panic(msg string) {
|
||||||
println('V panic' + msg)
|
println('V panic' + msg)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -150,6 +151,7 @@ fn bare_backtrace() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
[export: 'exit']
|
[export: 'exit']
|
||||||
|
[noreturn]
|
||||||
fn __exit(code int) {
|
fn __exit(code int) {
|
||||||
sys_exit(code)
|
sys_exit(code)
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,8 +303,10 @@ fn sys_execve(filename &byte, argv []&byte, envp []&byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 60 sys_exit
|
// 60 sys_exit
|
||||||
|
[noreturn]
|
||||||
fn sys_exit(ec int) {
|
fn sys_exit(ec int) {
|
||||||
sys_call1(60, u64(ec))
|
sys_call1(60, u64(ec))
|
||||||
|
for {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 102 sys_getuid
|
// 102 sys_getuid
|
||||||
|
|
|
@ -363,6 +363,7 @@ pub:
|
||||||
is_pub bool
|
is_pub bool
|
||||||
is_variadic bool
|
is_variadic bool
|
||||||
is_anon bool
|
is_anon bool
|
||||||
|
is_noreturn bool // true, when [noreturn] is used on a fn
|
||||||
is_manualfree bool // true, when [manualfree] is used on a fn
|
is_manualfree bool // true, when [manualfree] is used on a fn
|
||||||
is_main bool // true for `fn main()`
|
is_main bool // true for `fn main()`
|
||||||
is_test bool // true for `fn test_abcde`
|
is_test bool // true for `fn test_abcde`
|
||||||
|
@ -421,6 +422,7 @@ pub mut:
|
||||||
is_method bool
|
is_method bool
|
||||||
is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
|
is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
|
||||||
is_keep_alive bool // GC must not free arguments before fn returns
|
is_keep_alive bool // GC must not free arguments before fn returns
|
||||||
|
is_noreturn bool // whether the function/method is marked as [noreturn]
|
||||||
args []CallArg
|
args []CallArg
|
||||||
expected_arg_types []Type
|
expected_arg_types []Type
|
||||||
language Language
|
language Language
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub:
|
||||||
generic_names []string
|
generic_names []string
|
||||||
is_pub bool
|
is_pub bool
|
||||||
is_deprecated bool // `[deprecated] fn abc(){}`
|
is_deprecated bool // `[deprecated] fn abc(){}`
|
||||||
|
is_noreturn bool // `[noreturn] fn abc(){}`
|
||||||
is_unsafe bool // `[unsafe] fn abc(){}`
|
is_unsafe bool // `[unsafe] fn abc(){}`
|
||||||
is_placeholder bool
|
is_placeholder bool
|
||||||
is_main bool // `fn main(){}`
|
is_main bool // `fn main(){}`
|
||||||
|
|
|
@ -1936,6 +1936,7 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if has_method {
|
if has_method {
|
||||||
|
call_expr.is_noreturn = method.is_noreturn
|
||||||
if !method.is_pub && !c.pref.is_test && method.mod != c.mod {
|
if !method.is_pub && !c.pref.is_test && method.mod != c.mod {
|
||||||
// If a private method is called outside of the module
|
// If a private method is called outside of the module
|
||||||
// its receiver type is defined in, show an error.
|
// its receiver type is defined in, show an error.
|
||||||
|
@ -2439,10 +2440,13 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
|
||||||
c.table.fns[fn_name].usages++
|
c.table.fns[fn_name].usages++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mut is_native_builtin := false
|
||||||
if !found && c.pref.backend == .native {
|
if !found && c.pref.backend == .native {
|
||||||
if fn_name in native.builtins {
|
if fn_name in native.builtins {
|
||||||
c.table.fns[fn_name].usages++
|
c.table.fns[fn_name].usages++
|
||||||
return ast.void_type
|
found = true
|
||||||
|
func = c.table.fns[fn_name]
|
||||||
|
is_native_builtin = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found && c.pref.is_vsh {
|
if !found && c.pref.is_vsh {
|
||||||
|
@ -2457,6 +2461,9 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
|
||||||
c.table.fns[os_name].usages++
|
c.table.fns[os_name].usages++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if is_native_builtin {
|
||||||
|
return ast.void_type
|
||||||
|
}
|
||||||
// check for arg (var) of fn type
|
// check for arg (var) of fn type
|
||||||
if !found {
|
if !found {
|
||||||
if v := call_expr.scope.find_var(fn_name) {
|
if v := call_expr.scope.find_var(fn_name) {
|
||||||
|
@ -2493,6 +2500,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
|
||||||
c.error('unknown function: $fn_name', call_expr.pos)
|
c.error('unknown function: $fn_name', call_expr.pos)
|
||||||
return ast.void_type
|
return ast.void_type
|
||||||
}
|
}
|
||||||
|
call_expr.is_noreturn = func.is_noreturn
|
||||||
if !found_in_args {
|
if !found_in_args {
|
||||||
if _ := call_expr.scope.find_var(fn_name) {
|
if _ := call_expr.scope.find_var(fn_name) {
|
||||||
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
|
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
|
||||||
|
@ -2906,13 +2914,13 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
|
||||||
c.expected_or_type = ast.void_type
|
c.expected_or_type = ast.void_type
|
||||||
type_fits := c.check_types(last_stmt_typ, ret_type)
|
type_fits := c.check_types(last_stmt_typ, ret_type)
|
||||||
&& last_stmt_typ.nr_muls() == ret_type.nr_muls()
|
&& last_stmt_typ.nr_muls() == ret_type.nr_muls()
|
||||||
is_panic_or_exit := is_expr_panic_or_exit(last_stmt.expr)
|
is_noreturn := is_noreturn_callexpr(last_stmt.expr)
|
||||||
if type_fits || is_panic_or_exit {
|
if type_fits || is_noreturn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
|
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
|
||||||
if last_stmt.typ == ast.void_type {
|
if last_stmt.typ == ast.void_type {
|
||||||
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/exit/continue/break/panic',
|
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)',
|
||||||
last_stmt.pos)
|
last_stmt.pos)
|
||||||
} else {
|
} else {
|
||||||
type_name := c.table.type_to_str(last_stmt_typ)
|
type_name := c.table.type_to_str(last_stmt_typ)
|
||||||
|
@ -2942,7 +2950,7 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
|
||||||
if last_stmt.typ == ast.void_type {
|
if last_stmt.typ == ast.void_type {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if is_expr_panic_or_exit(last_stmt.expr) {
|
if is_noreturn_callexpr(last_stmt.expr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.check_types(last_stmt.typ, expr_return_type) {
|
if c.check_types(last_stmt.typ, expr_return_type) {
|
||||||
|
@ -2959,11 +2967,11 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_expr_panic_or_exit(expr ast.Expr) bool {
|
fn is_noreturn_callexpr(expr ast.Expr) bool {
|
||||||
match expr {
|
if expr is ast.CallExpr {
|
||||||
ast.CallExpr { return !expr.is_method && expr.name in ['panic', 'exit'] }
|
return expr.is_noreturn
|
||||||
else { return false }
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
|
pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
|
||||||
|
@ -7572,9 +7580,11 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
}
|
}
|
||||||
c.fn_scope = node.scope
|
c.fn_scope = node.scope
|
||||||
c.stmts(node.stmts)
|
c.stmts(node.stmts)
|
||||||
node.has_return = c.returns || has_top_return(node.stmts)
|
node_has_top_return := has_top_return(node.stmts)
|
||||||
|
node.has_return = c.returns || node_has_top_return
|
||||||
|
c.check_noreturn_fn_decl(mut node)
|
||||||
if node.language == .v && !node.no_body && node.return_type != ast.void_type && !node.has_return
|
if node.language == .v && !node.no_body && node.return_type != ast.void_type && !node.has_return
|
||||||
&& (node.is_method || node.name !in ['panic', 'exit']) {
|
&& !node.is_noreturn {
|
||||||
if c.inside_anon_fn {
|
if c.inside_anon_fn {
|
||||||
c.error('missing return at the end of an anonymous function', node.pos)
|
c.error('missing return at the end of an anonymous function', node.pos)
|
||||||
} else if !node.attrs.contains('_naked') {
|
} else if !node.attrs.contains('_naked') {
|
||||||
|
@ -7594,21 +7604,28 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
node.source_file = c.file
|
node.source_file = c.file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NB: has_top_return/1 should be called on *already checked* stmts,
|
||||||
|
// which do have their stmt.expr.is_noreturn set properly:
|
||||||
fn has_top_return(stmts []ast.Stmt) bool {
|
fn has_top_return(stmts []ast.Stmt) bool {
|
||||||
for stmt in stmts {
|
for stmt in stmts {
|
||||||
if stmt is ast.Return {
|
match stmt {
|
||||||
|
ast.Return {
|
||||||
return true
|
return true
|
||||||
} else if stmt is ast.Block {
|
}
|
||||||
|
ast.Block {
|
||||||
if has_top_return(stmt.stmts) {
|
if has_top_return(stmt.stmts) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if stmt is ast.ExprStmt {
|
}
|
||||||
|
ast.ExprStmt {
|
||||||
if stmt.expr is ast.CallExpr {
|
if stmt.expr is ast.CallExpr {
|
||||||
if !stmt.expr.is_method && stmt.expr.name in ['panic', 'exit'] {
|
if stmt.expr.is_noreturn {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
module checker
|
||||||
|
|
||||||
|
import v.ast
|
||||||
|
|
||||||
|
fn (mut c Checker) check_noreturn_fn_decl(mut node ast.FnDecl) {
|
||||||
|
if !node.is_noreturn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if node.no_body {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if uses_return_stmt(node.stmts) {
|
||||||
|
c.error('[noreturn] functions cannot use return statements', node.pos)
|
||||||
|
}
|
||||||
|
if node.return_type != ast.void_type {
|
||||||
|
c.error('[noreturn] functions cannot have return types', node.pos)
|
||||||
|
} else {
|
||||||
|
if node.stmts.len != 0 {
|
||||||
|
mut is_valid_end_of_noreturn_fn := false
|
||||||
|
last_stmt := node.stmts.last()
|
||||||
|
match last_stmt {
|
||||||
|
ast.ExprStmt {
|
||||||
|
if last_stmt.expr is ast.CallExpr {
|
||||||
|
if last_stmt.expr.should_be_skipped {
|
||||||
|
c.error('[noreturn] functions cannot end with a skippable `[if ..]` call',
|
||||||
|
last_stmt.pos)
|
||||||
|
}
|
||||||
|
if last_stmt.expr.is_noreturn {
|
||||||
|
is_valid_end_of_noreturn_fn = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ForStmt {
|
||||||
|
if last_stmt.is_inf && last_stmt.stmts.len == 0 {
|
||||||
|
is_valid_end_of_noreturn_fn = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
if !is_valid_end_of_noreturn_fn {
|
||||||
|
c.error('[noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop',
|
||||||
|
last_stmt.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uses_return_stmt(stmts []ast.Stmt) bool {
|
||||||
|
if stmts.len == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for stmt in stmts {
|
||||||
|
match stmt {
|
||||||
|
ast.Return {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ast.Block {
|
||||||
|
if uses_return_stmt(stmt.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ExprStmt {
|
||||||
|
match stmt.expr {
|
||||||
|
ast.CallExpr {
|
||||||
|
if uses_return_stmt(stmt.expr.or_block.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.MatchExpr {
|
||||||
|
for b in stmt.expr.branches {
|
||||||
|
if uses_return_stmt(b.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.SelectExpr {
|
||||||
|
for b in stmt.expr.branches {
|
||||||
|
if uses_return_stmt(b.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.IfExpr {
|
||||||
|
for b in stmt.expr.branches {
|
||||||
|
if uses_return_stmt(b.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ForStmt {
|
||||||
|
if uses_return_stmt(stmt.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ForCStmt {
|
||||||
|
if uses_return_stmt(stmt.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ForInStmt {
|
||||||
|
if uses_return_stmt(stmt.stmts) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv:4:6: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
|
||||||
|
2 | fn another() {
|
||||||
|
3 | eprintln(@FN)
|
||||||
|
4 | for {
|
||||||
|
| ^
|
||||||
|
5 | break
|
||||||
|
6 | }
|
|
@ -0,0 +1,19 @@
|
||||||
|
[noreturn]
|
||||||
|
fn another() {
|
||||||
|
eprintln(@FN)
|
||||||
|
for {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
|
fn abc() {
|
||||||
|
eprintln(@FN)
|
||||||
|
another()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
eprintln('start')
|
||||||
|
abc()
|
||||||
|
eprintln('done')
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
vlib/v/checker/tests/noreturn_with_return.vv:2:1: error: [noreturn] functions cannot use return statements
|
||||||
|
1 | [noreturn]
|
||||||
|
2 | fn another() {
|
||||||
|
| ~~~~~~~~~~~~
|
||||||
|
3 | eprintln(@FN)
|
||||||
|
4 | // for{}
|
||||||
|
vlib/v/checker/tests/noreturn_with_return.vv:6:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
|
||||||
|
4 | // for{}
|
||||||
|
5 | // exit(0)
|
||||||
|
6 | return
|
||||||
|
| ~~~~~~
|
||||||
|
7 | }
|
||||||
|
8 |
|
|
@ -0,0 +1,19 @@
|
||||||
|
[noreturn]
|
||||||
|
fn another() {
|
||||||
|
eprintln(@FN)
|
||||||
|
// for{}
|
||||||
|
// exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
|
fn abc() {
|
||||||
|
eprintln(@FN)
|
||||||
|
another()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
eprintln('start')
|
||||||
|
abc()
|
||||||
|
eprintln('done')
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv:3:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
|
||||||
|
1 | [noreturn]
|
||||||
|
2 | fn another() {
|
||||||
|
3 | eprintln(@FN)
|
||||||
|
| ~~~~~~~~~~~~~
|
||||||
|
4 | }
|
||||||
|
5 |
|
|
@ -0,0 +1,16 @@
|
||||||
|
[noreturn]
|
||||||
|
fn another() {
|
||||||
|
eprintln(@FN)
|
||||||
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
|
fn abc() {
|
||||||
|
eprintln(@FN)
|
||||||
|
another()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
eprintln('start')
|
||||||
|
abc()
|
||||||
|
eprintln('done')
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
log_and_die: error: oh no
|
|
@ -0,0 +1,14 @@
|
||||||
|
fn abc() ?int {
|
||||||
|
return error('oh no')
|
||||||
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
|
fn log_and_die(e IError) {
|
||||||
|
eprintln('${@FN}: error: $e')
|
||||||
|
exit(77)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
x := abc() or { log_and_die(err) }
|
||||||
|
println(x)
|
||||||
|
}
|
|
@ -5164,10 +5164,12 @@ fn (mut g Gen) assoc(node ast.Assoc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
fn verror(s string) {
|
fn verror(s string) {
|
||||||
util.verror('cgen error', s)
|
util.verror('cgen error', s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
fn (g &Gen) error(s string, pos token.Position) {
|
fn (g &Gen) error(s string, pos token.Position) {
|
||||||
ferror := util.formatted_error('cgen error:', s, g.file.path, pos)
|
ferror := util.formatted_error('cgen error:', s, g.file.path, pos)
|
||||||
eprintln(ferror)
|
eprintln(ferror)
|
||||||
|
|
|
@ -165,11 +165,26 @@ const c_common_macros = '
|
||||||
#define _MOV
|
#define _MOV
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__TINYC__) && defined(__has_include)
|
|
||||||
// tcc does not support has_include properly yet, turn it off completely
|
// tcc does not support has_include properly yet, turn it off completely
|
||||||
|
#if defined(__TINYC__) && defined(__has_include)
|
||||||
#undef __has_include
|
#undef __has_include
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(VNORETURN)
|
||||||
|
#if defined(__TINYC__)
|
||||||
|
#include <stdnoreturn.h>
|
||||||
|
#define VNORETURN noreturn
|
||||||
|
#endif
|
||||||
|
# if !defined(__TINYC__) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||||
|
# define VNORETURN _Noreturn
|
||||||
|
# elif defined(__GNUC__) && __GNUC__ >= 2
|
||||||
|
# define VNORETURN __attribute__((noreturn))
|
||||||
|
# endif
|
||||||
|
#ifndef VNORETURN
|
||||||
|
#define VNORETURN
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
//likely and unlikely macros
|
//likely and unlikely macros
|
||||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
|
||||||
#define _likely_(x) __builtin_expect(x,1)
|
#define _likely_(x) __builtin_expect(x,1)
|
||||||
|
|
|
@ -333,6 +333,9 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
||||||
prev_defer_stmts := g.defer_stmts
|
prev_defer_stmts := g.defer_stmts
|
||||||
g.defer_stmts = []
|
g.defer_stmts = []
|
||||||
g.stmts(node.stmts)
|
g.stmts(node.stmts)
|
||||||
|
if node.is_noreturn {
|
||||||
|
g.writeln('\twhile(1);')
|
||||||
|
}
|
||||||
// clear g.fn_mut_arg_names
|
// clear g.fn_mut_arg_names
|
||||||
|
|
||||||
if !node.has_return {
|
if !node.has_return {
|
||||||
|
@ -1315,10 +1318,23 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
|
||||||
'inline' {
|
'inline' {
|
||||||
g.write('inline ')
|
g.write('inline ')
|
||||||
}
|
}
|
||||||
'no_inline' {
|
'noinline' {
|
||||||
// since these are supported by GCC, clang and MSVC, we can consider them officially supported.
|
// since these are supported by GCC, clang and MSVC, we can consider them officially supported.
|
||||||
g.write('__NOINLINE ')
|
g.write('__NOINLINE ')
|
||||||
}
|
}
|
||||||
|
'noreturn' {
|
||||||
|
// a `[noreturn]` tag tells the compiler, that a function
|
||||||
|
// *DOES NOT RETURN* to its callsites.
|
||||||
|
// See: https://en.cppreference.com/w/c/language/_Noreturn
|
||||||
|
// Such functions should have no return type. They can be used
|
||||||
|
// in places where `panic(err)` or `exit(0)` can be used.
|
||||||
|
// panic/1 and exit/0 themselves will also be marked as
|
||||||
|
// `[noreturn]` soon.
|
||||||
|
// These functions should have busy `for{}` loops injected
|
||||||
|
// at their end, when they do not end by calling other fns
|
||||||
|
// marked by `[noreturn]`.
|
||||||
|
g.write('VNORETURN ')
|
||||||
|
}
|
||||||
'irq_handler' {
|
'irq_handler' {
|
||||||
g.write('__IRQHANDLER ')
|
g.write('__IRQHANDLER ')
|
||||||
}
|
}
|
||||||
|
|
|
@ -428,7 +428,6 @@ fn (mut g Gen) postfix_expr(node ast.PostfixExpr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not yet supported
|
|
||||||
[noreturn]
|
[noreturn]
|
||||||
fn verror(s string) {
|
fn verror(s string) {
|
||||||
util.verror('native gen error', s)
|
util.verror('native gen error', s)
|
||||||
|
|
|
@ -185,8 +185,10 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
mut is_exported := false
|
mut is_exported := false
|
||||||
mut is_unsafe := false
|
mut is_unsafe := false
|
||||||
mut is_trusted := false
|
mut is_trusted := false
|
||||||
|
mut is_noreturn := false
|
||||||
for fna in p.attrs {
|
for fna in p.attrs {
|
||||||
match fna.name {
|
match fna.name {
|
||||||
|
'noreturn' { is_noreturn = true }
|
||||||
'manualfree' { is_manualfree = true }
|
'manualfree' { is_manualfree = true }
|
||||||
'deprecated' { is_deprecated = true }
|
'deprecated' { is_deprecated = true }
|
||||||
'direct_array_access' { is_direct_arr = true }
|
'direct_array_access' { is_direct_arr = true }
|
||||||
|
@ -408,6 +410,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
generic_names: generic_names
|
generic_names: generic_names
|
||||||
is_pub: is_pub
|
is_pub: is_pub
|
||||||
is_deprecated: is_deprecated
|
is_deprecated: is_deprecated
|
||||||
|
is_noreturn: is_noreturn
|
||||||
is_unsafe: is_unsafe
|
is_unsafe: is_unsafe
|
||||||
is_main: is_main
|
is_main: is_main
|
||||||
is_test: is_test
|
is_test: is_test
|
||||||
|
@ -449,6 +452,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
||||||
return_type: return_type
|
return_type: return_type
|
||||||
return_type_pos: return_type_pos
|
return_type_pos: return_type_pos
|
||||||
params: params
|
params: params
|
||||||
|
is_noreturn: is_noreturn
|
||||||
is_manualfree: is_manualfree
|
is_manualfree: is_manualfree
|
||||||
is_deprecated: is_deprecated
|
is_deprecated: is_deprecated
|
||||||
is_exported: is_exported
|
is_exported: is_exported
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a default value of type `int`, or return/exit/continue/break/panic
|
vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a default value of type `int`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)
|
||||||
2 | m := [3, 4, 5]
|
2 | m := [3, 4, 5]
|
||||||
3 | el := m[4] or {
|
3 | el := m[4] or {
|
||||||
4 | println('error')
|
4 | println('error')
|
||||||
|
|
|
@ -141,6 +141,7 @@ pub fn source_context(kind string, source string, pos token.Position) []string {
|
||||||
return clines
|
return clines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[noreturn]
|
||||||
pub fn verror(kind string, s string) {
|
pub fn verror(kind string, s string) {
|
||||||
final_kind := bold(color(kind, kind))
|
final_kind := bold(color(kind, kind))
|
||||||
eprintln('$final_kind: $s')
|
eprintln('$final_kind: $s')
|
||||||
|
|
Loading…
Reference in New Issue