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() {
|
||||
}
|
||||
|
||||
// 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
|
||||
// reference (`&Window`) or inside another reference (`&OuterStruct{ Window{...} }`).
|
||||
[heap]
|
||||
|
|
|
@ -4,9 +4,16 @@ type FnExitCb = fn ()
|
|||
|
||||
fn C.atexit(f FnExitCb) int
|
||||
|
||||
[noreturn]
|
||||
fn vhalt() {
|
||||
for {}
|
||||
}
|
||||
|
||||
// exit terminates execution immediately and returns exit `code` to the shell.
|
||||
[noreturn]
|
||||
pub fn exit(code int) {
|
||||
C.exit(code)
|
||||
vhalt()
|
||||
}
|
||||
|
||||
fn vcommithash() string {
|
||||
|
@ -17,6 +24,7 @@ fn vcommithash() string {
|
|||
// recent versions of tcc print nicer backtraces automatically
|
||||
// NB: the duplication here is because tcc_backtrace should be called directly
|
||||
// inside the panic functions.
|
||||
[noreturn]
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
vhalt()
|
||||
}
|
||||
|
||||
[noreturn]
|
||||
pub fn panic_optional_not_set(s string) {
|
||||
panic('optional not set ($s)')
|
||||
}
|
||||
|
||||
// panic prints a nice error message, then exits the process with exit code of 1.
|
||||
// It also shows a backtrace on most platforms.
|
||||
[noreturn]
|
||||
pub fn panic(s string) {
|
||||
$if freestanding {
|
||||
bare_panic(s)
|
||||
|
@ -87,6 +98,7 @@ pub fn panic(s string) {
|
|||
C.exit(1)
|
||||
}
|
||||
}
|
||||
vhalt()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
[noreturn]
|
||||
fn bare_panic(msg string) {
|
||||
println('V panic' + msg)
|
||||
exit(1)
|
||||
|
@ -150,6 +151,7 @@ fn bare_backtrace() string {
|
|||
}
|
||||
|
||||
[export: 'exit']
|
||||
[noreturn]
|
||||
fn __exit(code int) {
|
||||
sys_exit(code)
|
||||
}
|
||||
|
|
|
@ -303,8 +303,10 @@ fn sys_execve(filename &byte, argv []&byte, envp []&byte) int {
|
|||
}
|
||||
|
||||
// 60 sys_exit
|
||||
[noreturn]
|
||||
fn sys_exit(ec int) {
|
||||
sys_call1(60, u64(ec))
|
||||
for {}
|
||||
}
|
||||
|
||||
// 102 sys_getuid
|
||||
|
|
|
@ -363,6 +363,7 @@ pub:
|
|||
is_pub bool
|
||||
is_variadic 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_main bool // true for `fn main()`
|
||||
is_test bool // true for `fn test_abcde`
|
||||
|
@ -421,6 +422,7 @@ pub mut:
|
|||
is_method bool
|
||||
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_noreturn bool // whether the function/method is marked as [noreturn]
|
||||
args []CallArg
|
||||
expected_arg_types []Type
|
||||
language Language
|
||||
|
|
|
@ -73,6 +73,7 @@ pub:
|
|||
generic_names []string
|
||||
is_pub bool
|
||||
is_deprecated bool // `[deprecated] fn abc(){}`
|
||||
is_noreturn bool // `[noreturn] fn abc(){}`
|
||||
is_unsafe bool // `[unsafe] fn abc(){}`
|
||||
is_placeholder bool
|
||||
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 {
|
||||
call_expr.is_noreturn = method.is_noreturn
|
||||
if !method.is_pub && !c.pref.is_test && method.mod != c.mod {
|
||||
// If a private method is called outside of the module
|
||||
// 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++
|
||||
}
|
||||
}
|
||||
mut is_native_builtin := false
|
||||
if !found && c.pref.backend == .native {
|
||||
if fn_name in native.builtins {
|
||||
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 {
|
||||
|
@ -2457,6 +2461,9 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
|
|||
c.table.fns[os_name].usages++
|
||||
}
|
||||
}
|
||||
if is_native_builtin {
|
||||
return ast.void_type
|
||||
}
|
||||
// check for arg (var) of fn type
|
||||
if !found {
|
||||
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)
|
||||
return ast.void_type
|
||||
}
|
||||
call_expr.is_noreturn = func.is_noreturn
|
||||
if !found_in_args {
|
||||
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`',
|
||||
|
@ -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
|
||||
type_fits := c.check_types(last_stmt_typ, ret_type)
|
||||
&& last_stmt_typ.nr_muls() == ret_type.nr_muls()
|
||||
is_panic_or_exit := is_expr_panic_or_exit(last_stmt.expr)
|
||||
if type_fits || is_panic_or_exit {
|
||||
is_noreturn := is_noreturn_callexpr(last_stmt.expr)
|
||||
if type_fits || is_noreturn {
|
||||
return
|
||||
}
|
||||
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
|
||||
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)
|
||||
} else {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if is_expr_panic_or_exit(last_stmt.expr) {
|
||||
if is_noreturn_callexpr(last_stmt.expr) {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
match expr {
|
||||
ast.CallExpr { return !expr.is_method && expr.name in ['panic', 'exit'] }
|
||||
else { return false }
|
||||
fn is_noreturn_callexpr(expr ast.Expr) bool {
|
||||
if expr is ast.CallExpr {
|
||||
return expr.is_noreturn
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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.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
|
||||
&& (node.is_method || node.name !in ['panic', 'exit']) {
|
||||
&& !node.is_noreturn {
|
||||
if c.inside_anon_fn {
|
||||
c.error('missing return at the end of an anonymous function', node.pos)
|
||||
} 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for stmt in stmts {
|
||||
if stmt is ast.Return {
|
||||
match stmt {
|
||||
ast.Return {
|
||||
return true
|
||||
} else if stmt is ast.Block {
|
||||
}
|
||||
ast.Block {
|
||||
if has_top_return(stmt.stmts) {
|
||||
return true
|
||||
}
|
||||
} else if stmt is ast.ExprStmt {
|
||||
}
|
||||
ast.ExprStmt {
|
||||
if stmt.expr is ast.CallExpr {
|
||||
if !stmt.expr.is_method && stmt.expr.name in ['panic', 'exit'] {
|
||||
if stmt.expr.is_noreturn {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
util.verror('cgen error', s)
|
||||
}
|
||||
|
||||
[noreturn]
|
||||
fn (g &Gen) error(s string, pos token.Position) {
|
||||
ferror := util.formatted_error('cgen error:', s, g.file.path, pos)
|
||||
eprintln(ferror)
|
||||
|
|
|
@ -165,11 +165,26 @@ const c_common_macros = '
|
|||
#define _MOV
|
||||
#endif
|
||||
|
||||
#if defined(__TINYC__) && defined(__has_include)
|
||||
// tcc does not support has_include properly yet, turn it off completely
|
||||
#if defined(__TINYC__) && defined(__has_include)
|
||||
#undef __has_include
|
||||
#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
|
||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
|
||||
#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
|
||||
g.defer_stmts = []
|
||||
g.stmts(node.stmts)
|
||||
if node.is_noreturn {
|
||||
g.writeln('\twhile(1);')
|
||||
}
|
||||
// clear g.fn_mut_arg_names
|
||||
|
||||
if !node.has_return {
|
||||
|
@ -1315,10 +1318,23 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
|
|||
'inline' {
|
||||
g.write('inline ')
|
||||
}
|
||||
'no_inline' {
|
||||
'noinline' {
|
||||
// since these are supported by GCC, clang and MSVC, we can consider them officially supported.
|
||||
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' {
|
||||
g.write('__IRQHANDLER ')
|
||||
}
|
||||
|
|
|
@ -428,7 +428,6 @@ fn (mut g Gen) postfix_expr(node ast.PostfixExpr) {
|
|||
}
|
||||
}
|
||||
|
||||
// not yet supported
|
||||
[noreturn]
|
||||
fn verror(s string) {
|
||||
util.verror('native gen error', s)
|
||||
|
|
|
@ -185,8 +185,10 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
mut is_exported := false
|
||||
mut is_unsafe := false
|
||||
mut is_trusted := false
|
||||
mut is_noreturn := false
|
||||
for fna in p.attrs {
|
||||
match fna.name {
|
||||
'noreturn' { is_noreturn = true }
|
||||
'manualfree' { is_manualfree = true }
|
||||
'deprecated' { is_deprecated = true }
|
||||
'direct_array_access' { is_direct_arr = true }
|
||||
|
@ -408,6 +410,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
generic_names: generic_names
|
||||
is_pub: is_pub
|
||||
is_deprecated: is_deprecated
|
||||
is_noreturn: is_noreturn
|
||||
is_unsafe: is_unsafe
|
||||
is_main: is_main
|
||||
is_test: is_test
|
||||
|
@ -449,6 +452,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
return_type: return_type
|
||||
return_type_pos: return_type_pos
|
||||
params: params
|
||||
is_noreturn: is_noreturn
|
||||
is_manualfree: is_manualfree
|
||||
is_deprecated: is_deprecated
|
||||
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]
|
||||
3 | el := m[4] or {
|
||||
4 | println('error')
|
||||
|
|
|
@ -141,6 +141,7 @@ pub fn source_context(kind string, source string, pos token.Position) []string {
|
|||
return clines
|
||||
}
|
||||
|
||||
[noreturn]
|
||||
pub fn verror(kind string, s string) {
|
||||
final_kind := bold(color(kind, kind))
|
||||
eprintln('$final_kind: $s')
|
||||
|
|
Loading…
Reference in New Issue