From 47d0ed308d630c9bbae14d7dabf1e5bc73d862a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Fri, 4 Dec 2020 19:34:05 +0100 Subject: [PATCH] parser: prepare for better VLS integration, more accurate parser errors (#7119) --- cmd/tools/check-md.v | 18 ++++++--------- cmd/v/help/build.txt | 3 +++ doc/docs.md | 9 ++++---- vlib/v/checker/checker.v | 3 +++ vlib/v/parser/assign.v | 20 +++++++++++------ vlib/v/parser/comptime.v | 5 +++++ vlib/v/parser/containers.v | 2 ++ vlib/v/parser/fn.v | 19 ++++++++++------ vlib/v/parser/for.v | 9 ++++++++ vlib/v/parser/if_match.v | 15 +++++++++++++ vlib/v/parser/parse_type.v | 8 +++++++ vlib/v/parser/parser.v | 46 ++++++++++++++++++++++++++++++++++++++ vlib/v/parser/pratt.v | 17 +++++++++++--- vlib/v/parser/sql.v | 13 ++++++++--- vlib/v/parser/struct.v | 13 +++++++++++ vlib/v/pref/pref.v | 4 ++++ vlib/v/scanner/scanner.v | 3 +++ 17 files changed, 171 insertions(+), 36 deletions(-) diff --git a/cmd/tools/check-md.v b/cmd/tools/check-md.v index 675fd19e8c..0a7a3e1abf 100644 --- a/cmd/tools/check-md.v +++ b/cmd/tools/check-md.v @@ -191,14 +191,12 @@ fn (mut f MDFile) check_examples() (int, int) { mut should_cleanup_vfile := true // eprintln('>>> checking example $vfile ...') vcontent := e.text.join('\n') - os.write_file(vfile, vcontent) or { - panic(err) - } + os.write_file(vfile, vcontent) or { panic(err) } mut acommands := e.command.split(' ') for command in acommands { match command { 'compile' { - res := os.system('"$vexe" -silent -o x.c $vfile') + res := os.system('"$vexe" -w -Wfatal-errors -o x.c $vfile') os.rm('x.c') or { } if res != 0 { eprintln(eline(f.path, e.sline, 0, 'example failed to compile')) @@ -210,7 +208,7 @@ fn (mut f MDFile) check_examples() (int, int) { oks++ } 'live' { - res := os.system('"$vexe" -silent -live -o x.c $vfile') + res := os.system('"$vexe" -w -Wfatal-errors -live -o x.c $vfile') if res != 0 { eprintln(eline(f.path, e.sline, 0, 'example failed to compile with -live')) eprintln(vcontent) @@ -221,7 +219,7 @@ fn (mut f MDFile) check_examples() (int, int) { oks++ } 'failcompile' { - res := os.system('"$vexe" -silent -o x.c $vfile') + res := os.system('"$vexe" -w -Wfatal-errors -o x.c $vfile') os.rm('x.c') or { } if res == 0 { eprintln(eline(f.path, e.sline, 0, '`failcompile` example compiled')) @@ -233,7 +231,7 @@ fn (mut f MDFile) check_examples() (int, int) { oks++ } 'oksyntax' { - res := os.system('"$vexe" -silent -check-syntax $vfile') + res := os.system('"$vexe" -w -Wfatal-errors -check-syntax $vfile') if res != 0 { eprintln(eline(f.path, e.sline, 0, '`oksyntax` example with invalid syntax')) eprintln(vcontent) @@ -244,7 +242,7 @@ fn (mut f MDFile) check_examples() (int, int) { oks++ } 'badsyntax' { - res := os.system('"$vexe" -silent -check-syntax $vfile') + res := os.system('"$vexe" -w -Wfatal-errors -check-syntax $vfile') if res == 0 { eprintln(eline(f.path, e.sline, 0, '`badsyntax` example can be parsed fine')) eprintln(vcontent) @@ -262,9 +260,7 @@ fn (mut f MDFile) check_examples() (int, int) { } } if should_cleanup_vfile { - os.rm(vfile) or { - panic(err) - } + os.rm(vfile) or { panic(err) } } } return errors, oks diff --git a/cmd/v/help/build.txt b/cmd/v/help/build.txt index aafb06354a..31a2bb86cb 100644 --- a/cmd/v/help/build.txt +++ b/cmd/v/help/build.txt @@ -124,6 +124,9 @@ The build flags are shared by the build and run commands: -W Treat all warnings as errors, even in development builds. + + -Wfatal-errors + Unconditionally exit with exit(1) after the first error. Useful for scripts/tooling that calls V. For C-specific build flags, use `v help build-c`. diff --git a/doc/docs.md b/doc/docs.md index cefaaa9375..f0dbea6006 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -297,8 +297,7 @@ In development mode the compiler will warn you that you haven't used the variabl In production mode (enabled by passing the `-prod` flag to v – `v -prod foo.v`) it will not compile at all (like in Go). - -```v +```v failcompile fn main() { a := 10 if true { @@ -1736,9 +1735,9 @@ color = .green println(color) // "green" match color { - .red { ... } - .green { ... } - .blue { ... } + .red { println('the color was red') } + .green { println('the color was green') } + .blue { println('the color was blue') } } ``` diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index d9c4330014..84005d9c79 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4391,6 +4391,9 @@ fn (mut c Checker) warn_or_error(message string, pos token.Position, warn bool) return } if !warn { + if c.pref.fatal_errors { + exit(1) + } c.nr_errors++ if pos.line_nr !in c.error_lines { err := errors.Error{ diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index 2731bc6072..f6980651bc 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -11,33 +11,34 @@ fn (mut p Parser) assign_stmt() ast.Stmt { return p.partial_assign_stmt(exprs, comments) } -fn (mut p Parser) check_undefined_variables(exprs []ast.Expr, val ast.Expr) { +fn (mut p Parser) check_undefined_variables(exprs []ast.Expr, val ast.Expr) ? { match val { ast.Ident { for expr in exprs { if expr is ast.Ident { if expr.name == val.name { p.error_with_pos('undefined variable: `$val.name`', val.pos) + return error('undefined variable: `$val.name`') } } } } ast.InfixExpr { - p.check_undefined_variables(exprs, val.left) - p.check_undefined_variables(exprs, val.right) + p.check_undefined_variables(exprs, val.left) ? + p.check_undefined_variables(exprs, val.right) ? } ast.ParExpr { - p.check_undefined_variables(exprs, val.expr) + p.check_undefined_variables(exprs, val.expr) ? } ast.PostfixExpr { - p.check_undefined_variables(exprs, val.expr) + p.check_undefined_variables(exprs, val.expr) ? } ast.PrefixExpr { - p.check_undefined_variables(exprs, val.right) + p.check_undefined_variables(exprs, val.right) ? } ast.StringInterLiteral { for expr_ in val.exprs { - p.check_undefined_variables(exprs, expr_) + p.check_undefined_variables(exprs, expr_) ? } } else {} @@ -105,6 +106,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme has_cross_var = p.check_cross_variables(left, r) if op !in [.assign, .decl_assign] { p.error('unexpected $op.str(), expecting := or = or comma') + return ast.Stmt{} } if has_cross_var { break @@ -118,6 +120,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme if op == .decl_assign { if p.scope.known_var(lx.name) { p.error_with_pos('redefinition of `$lx.name`', lx.pos) + return ast.Stmt{} } mut share := table.ShareType(0) if lx.info is ast.IdentVar { @@ -127,6 +130,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme if !p.pref.translated { p.error_with_pos('static variables are supported only in -translated mode', lx.pos) + return ast.Stmt{} } is_static = true } @@ -159,6 +163,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme if op == .decl_assign { p.error_with_pos('non-name `$lx.left[$lx.index]` on left side of `:=`', lx.pos) + return ast.Stmt{} } lx.is_setter = true } @@ -168,6 +173,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme if op == .decl_assign { p.error_with_pos('struct fields can only be declared during the initialization', lx.pos) + return ast.Stmt{} } } else { diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 656d9939ed..1a6c0ac7a3 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -51,12 +51,14 @@ fn (mut p Parser) vweb() ast.ComptimeCall { n := p.check_name() // skip `vweb.html()` TODO if n != 'vweb' { p.error(error_msg) + return ast.ComptimeCall{} } p.check(.dot) } n := p.check_name() // (.name) if n != 'html' && n != 'tmpl' { p.error(error_msg) + return ast.ComptimeCall{} } is_html := n == 'html' p.check(.lpar) @@ -85,8 +87,10 @@ fn (mut p Parser) vweb() ast.ComptimeCall { if !os.exists(path) { if is_html { p.error('vweb HTML template "$path" not found') + return ast.ComptimeCall{} } else { p.error('template file "$path" not found') + return ast.ComptimeCall{} } } // println('path is now "$path"') @@ -171,6 +175,7 @@ fn (mut p Parser) comp_for() ast.CompFor { kind = .fields } else { p.error('unknown kind `$for_val`, available are: `methods` or `fields`') + return ast.CompFor{} } spos := p.tok.position() stmts := p.parse_block() diff --git a/vlib/v/parser/containers.v b/vlib/v/parser/containers.v index 2b92b4cc40..c5cc9b4998 100644 --- a/vlib/v/parser/containers.v +++ b/vlib/v/parser/containers.v @@ -65,6 +65,7 @@ fn (mut p Parser) array_init() ast.ArrayInit { n := p.check_name() if n != 'init' { p.error_with_pos('expected `init:`, not `$n`', pos) + return ast.ArrayInit{} } p.check(.colon) has_default = true @@ -117,6 +118,7 @@ fn (mut p Parser) array_init() ast.ArrayInit { } else { p.error('wrong field `$key`, expecting `len`, `cap`, or `init`') + return ast.ArrayInit{} } } if p.tok.kind != .rcbr { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c0bfa6a500..b873c0aad9 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -111,7 +111,7 @@ pub fn (mut p Parser) call_args() []ast.CallArg { for p.tok.kind != .rpar { if p.tok.kind == .eof { p.error_with_pos('unexpected eof reached, while parsing call argument', start_pos) - break + return [] } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic @@ -197,6 +197,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { rec_type = p.parse_type_with_mut(rec_mut) if is_amp && rec_mut { p.error('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`') + return ast.FnDecl{} } if is_shared { rec_type = rec_type.set_flag(.shared_f) @@ -220,11 +221,13 @@ fn (mut p Parser) fn_decl() ast.FnDecl { name = if language == .js { p.check_js_name() } else { p.check_name() } if language == .v && !p.pref.translated && util.contains_capital(name) { p.error('function names cannot contain uppercase letters, use snake_case instead') + return ast.FnDecl{} } type_sym := p.table.get_type_symbol(rec_type) // interfaces are handled in the checker, methods can not be defined on them this way if is_method && (type_sym.has_method(name) && type_sym.kind != .interface_) { p.error('duplicate method `$name`') + return ast.FnDecl{} } } if p.tok.kind in [.plus, .minus, .mul, .div, .mod] { @@ -245,7 +248,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { for param in params { if p.scope.known_var(param.name) { p.error_with_pos('redefinition of parameter `$param.name`', param.pos) - break + return ast.FnDecl{} } p.scope.register(ast.Var{ name: param.name @@ -274,6 +277,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { // we could also check if kind is .array, .array_fixed, .map instead of mod.len if type_sym.mod.len > 0 && type_sym.mod != p.mod && type_sym.language == .v { p.error('cannot define new methods on non-local type $type_sym.name') + return ast.FnDecl{} } // p.warn('reg method $type_sym.name . $name ()') type_sym_method_idx = type_sym.register_method(table.Fn{ @@ -329,6 +333,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.close_scope() if !no_body && are_args_type_only { p.error_with_pos('functions with type only args can not have bodies', body_start_pos) + return ast.FnDecl{} } return ast.FnDecl{ name: name @@ -452,7 +457,7 @@ fn (mut p Parser) fn_args() ([]table.Param, bool, bool) { } } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic`or `shared`', pos) - break + return []table.Param{}, false, false } // if arg_type.is_ptr() { // p.error('cannot mut') @@ -473,7 +478,7 @@ fn (mut p Parser) fn_args() ([]table.Param, bool, bool) { if is_variadic { p.error_with_pos('cannot use ...(variadic) with non-final parameter no $arg_no', pos) - break + return []table.Param{}, false, false } p.next() } @@ -488,7 +493,7 @@ fn (mut p Parser) fn_args() ([]table.Param, bool, bool) { arg_no++ if arg_no > 1024 { p.error_with_pos('too many args', pos) - break + return []table.Param{}, false, false } } } else { @@ -534,7 +539,7 @@ fn (mut p Parser) fn_args() ([]table.Param, bool, bool) { } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic` or `shared`', pos) - break + return []table.Param{}, false, false } typ = typ.set_nr_muls(1) if is_shared { @@ -560,7 +565,7 @@ fn (mut p Parser) fn_args() ([]table.Param, bool, bool) { if is_variadic && p.tok.kind == .comma { p.error_with_pos('cannot use ...(variadic) with non-final parameter $arg_name', arg_pos[i]) - break + return []table.Param{}, false, false } } if p.tok.kind != .rpar { diff --git a/vlib/v/parser/for.v b/vlib/v/parser/for.v index ad8891c01c..924faf5925 100644 --- a/vlib/v/parser/for.v +++ b/vlib/v/parser/for.v @@ -13,6 +13,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { p.inside_for = true if p.tok.kind == .key_match { p.error('cannot use `match` in `for` loop') + return ast.Stmt{} } // defer { p.close_scope() } // Infinite loop @@ -29,6 +30,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { // `for i := 0; i < 10; i++ {` if p.tok.kind == .key_mut { p.error('`mut` is not needed in `for ;;` loops: use `for i := 0; i < n; i ++ {`') + return ast.Stmt{} } mut init := ast.Stmt{} mut cond := p.new_true_expr() @@ -47,6 +49,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { // Disallow `for i := 0; i++; i < ...` if p.tok.kind == .name && p.peek_tok.kind in [.inc, .dec] { p.error('cannot use $p.tok.lit$p.peek_tok.kind as value') + return ast.Stmt{} } cond = p.expr(0) has_cond = true @@ -87,12 +90,15 @@ fn (mut p Parser) for_stmt() ast.Stmt { val_var_name = p.check_name() if key_var_name == val_var_name && key_var_name != '_' { p.error_with_pos('key and value in a for loop cannot be the same', val_var_pos) + return ast.Stmt{} } if p.scope.known_var(key_var_name) { p.error('redefinition of key iteration variable `$key_var_name`') + return ast.Stmt{} } if p.scope.known_var(val_var_name) { p.error('redefinition of value iteration variable `$val_var_name`') + return ast.Stmt{} } p.scope.register(ast.Var{ name: key_var_name @@ -101,10 +107,12 @@ fn (mut p Parser) for_stmt() ast.Stmt { }) } else if p.scope.known_var(val_var_name) { p.error('redefinition of value iteration variable `$val_var_name`') + return ast.Stmt{} } p.check(.key_in) if p.tok.kind == .name && p.tok.lit in [key_var_name, val_var_name] { p.error('in a `for x in array` loop, the key or value iteration variable `$p.tok.lit` can not be the same as the array variable') + return ast.Stmt{} } // arr_expr cond := p.expr(0) @@ -124,6 +132,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { }) if key_var_name.len > 0 { p.error_with_pos('cannot declare index variable with range `for`', key_var_pos) + return ast.Stmt{} } } else { // this type will be set in checker diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 5d8ec8c2fb..4d6bac25b6 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -35,6 +35,7 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { comments << p.eat_comments() if p.tok.kind == .key_match { p.error('cannot use `match` with `if` statements') + return ast.IfExpr{} } if p.tok.kind == .lcbr { // else { @@ -82,6 +83,7 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { p.check(.key_if) if p.tok.kind == .key_match { p.error('cannot use `match` with `if` statements') + return ast.IfExpr{} } comments << p.eat_comments() mut cond := ast.Expr{} @@ -129,6 +131,7 @@ fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { if is_comptime { if p.tok.kind == .key_else { p.error('use `\$else` instead of `else` in compile-time `if` branches') + return ast.IfExpr{} } if p.peek_tok.kind == .key_else { p.check(.dollar) @@ -199,6 +202,7 @@ fn (mut p Parser) match_expr() ast.MatchExpr { if p.tok.kind == .dotdot { p.error_with_pos('match only supports inclusive (`...`) ranges, not exclusive (`..`)', p.tok.position()) + return ast.MatchExpr{} } else if p.tok.kind == .ellipsis { p.next() expr2 := p.expr(0) @@ -283,10 +287,12 @@ fn (mut p Parser) select_expr() ast.SelectExpr { if has_timeout { p.error_with_pos('timeout `> t` and `else` are mutually exclusive `select` keys', p.tok.position()) + return ast.SelectExpr{} } if has_else { p.error_with_pos('at most one `else` branch allowed in `select` block', p.tok.position()) + return ast.SelectExpr{} } is_else = true has_else = true @@ -295,10 +301,12 @@ fn (mut p Parser) select_expr() ast.SelectExpr { if has_else { p.error_with_pos('`else` and timeout `> t` are mutually exclusive `select` keys', p.tok.position()) + return ast.SelectExpr{} } if has_timeout { p.error_with_pos('at most one timeout `> t` branch allowed in `select` block', p.tok.position()) + return ast.SelectExpr{} } is_timeout = true has_timeout = true @@ -318,6 +326,7 @@ fn (mut p Parser) select_expr() ast.SelectExpr { exprs, comments := p.expr_list() if exprs.len != 1 { p.error('only one expression allowed as `select` key') + return ast.SelectExpr{} } if p.tok.kind in [.assign, .decl_assign] { stmt = p.partial_assign_stmt(exprs, comments) @@ -335,17 +344,20 @@ fn (mut p Parser) select_expr() ast.SelectExpr { ast.ExprStmt { if !stmt.is_expr { p.error_with_pos('select: invalid expression', stmt.pos) + return ast.SelectExpr{} } else { match mut stmt.expr { ast.InfixExpr { if stmt.expr.op != .arrow { p.error_with_pos('select key: `<-` operator expected', stmt.expr.pos) + return ast.SelectExpr{} } } else { p.error_with_pos('select key: send expression (`ch <- x`) expected', stmt.pos) + return ast.SelectExpr{} } } } @@ -357,16 +369,19 @@ fn (mut p Parser) select_expr() ast.SelectExpr { if expr.op != .arrow { p.error_with_pos('select key: `<-` operator expected', expr.pos) + return ast.SelectExpr{} } } else { p.error_with_pos('select key: receive expression expected', stmt.right[0].position()) + return ast.SelectExpr{} } } } else { p.error_with_pos('select: transmission statement expected', stmt.position()) + return ast.SelectExpr{} } } } diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index d71f12a1ed..73996839ba 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -44,6 +44,7 @@ pub fn (mut p Parser) parse_map_type() table.Type { // if key_type_sym.kind != .string { if key_type.idx() != table.string_type_idx { p.error('maps can only have string keys for now') + return 0 } p.check(.rsbr) value_type := p.parse_type() @@ -151,6 +152,7 @@ pub fn (mut p Parser) parse_type() table.Type { } if p.tok.kind == .mul { p.error('use `&Type` instead of `*Type` when declaring references') + return 0 } mut nr_amps := 0 // &Type @@ -167,6 +169,7 @@ pub fn (mut p Parser) parse_type() table.Type { typ = p.parse_any_type(language, nr_muls > 0, true) if typ == table.void_type { p.error_with_pos('use `?` instead of `?void`', pos) + return 0 } } if is_optional { @@ -184,6 +187,7 @@ pub fn (mut p Parser) parse_type() table.Type { p.error('V arrays are already references behind the scenes, there is no need to use a reference to an array (e.g. use `[]string` instead of `&[]string`). If you need to modify an array in a function, use a mutable argument instead: `fn foo(mut s []string) {}`.') + return 0 } } return typ @@ -200,6 +204,7 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check // /if !(p.tok.lit in p.table.imports) { if !p.known_import(name) { p.error('unknown module `$p.tok.lit`') + return 0 } if p.tok.lit in p.imports { p.register_used_import(p.tok.lit) @@ -210,6 +215,7 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check name = '${p.imports[name]}.$p.tok.lit' if !p.tok.lit[0].is_capital() { p.error('imported types must start with a capital letter') + return 0 } } else if p.expr_mod != '' && !p.in_generic_params { // p.expr_mod is from the struct and not from the generic parameter name = p.expr_mod + '.' + name @@ -231,6 +237,7 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check // multiple return if is_ptr { p.error('parse_type: unexpected `&` before multiple returns') + return 0 } return p.parse_multi_return_type() } @@ -248,6 +255,7 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check if name == '' { // This means the developer is using some wrong syntax like `x: int` instead of `x int` p.error('bad type syntax') + return 0 } match name { 'voidptr' { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1ce283ca86..b89e0b3455 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -359,6 +359,7 @@ pub fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { if c > 1000000 { p.error_with_pos('parsed over $c statements from fn $p.cur_fn_name, the parser is probably stuck', p.tok.position()) + return [] } } } @@ -396,8 +397,10 @@ fn (mut p Parser) check(expected token.Kind) { if p.tok.kind != expected { if p.tok.kind == .name { p.error('unexpected name `$p.tok.lit`, expecting `$expected.str()`') + return } else { p.error('unexpected `$p.tok.kind.str()`, expecting `$expected.str()`') + return } } p.next() @@ -645,9 +648,11 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } else if p.peek_tok.kind == .name { p.error_with_pos('unexpected name `$p.peek_tok.lit`', p.peek_tok.position()) + return ast.Stmt{} } else if !p.inside_if_expr && !p.inside_match_body && !p.inside_or_expr && p.peek_tok.kind in [.rcbr, .eof] && !p.mark_var_as_used(p.tok.lit) { p.error_with_pos('`$p.tok.lit` evaluated but not used', p.tok.position()) + return ast.Stmt{} } } return p.parse_multi_expr(is_top_level) @@ -761,11 +766,13 @@ fn (mut p Parser) attributes() { attr := p.parse_attr() if p.attrs.contains(attr.name) { p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position())) + return } if attr.is_ctdefine { if has_ctdefine { p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`', start_pos.extend(p.prev_tok.position())) + return } else { has_ctdefine = true } @@ -777,11 +784,13 @@ fn (mut p Parser) attributes() { break } p.error('unexpected `$p.tok.kind.str()`, expecting `;`') + return } p.next() } if p.attrs.len == 0 { p.error_with_pos('attributes cannot be empty', p.prev_tok.position().extend(p.tok.position())) + return } } @@ -808,8 +817,10 @@ fn (mut p Parser) parse_attr() table.Attr { name = p.check_name() if name == 'unsafe_fn' { p.error_with_pos('please use `[unsafe]` instead', p.tok.position()) + return table.Attr{} } else if name == 'trusted_fn' { p.error_with_pos('please use `[trusted]` instead', p.tok.position()) + return table.Attr{} } if p.tok.kind == .colon { p.next() @@ -841,6 +852,9 @@ pub fn (mut p Parser) warn(s string) { } pub fn (mut p Parser) error_with_pos(s string, pos token.Position) { + if p.pref.fatal_errors { + exit(1) + } mut kind := 'error:' if p.pref.output_mode == .stdout { if p.pref.is_verbose { @@ -902,6 +916,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { left0 := left[0] if tok.kind == .key_mut && p.tok.kind != .decl_assign { p.error('expecting `:=` (e.g. `mut x :=`)') + return ast.Stmt{} } if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { return p.partial_assign_stmt(left, left_comments) @@ -910,6 +925,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { !(left0 is ast.InfixExpr && (left0 as ast.InfixExpr).op in [.left_shift, .arrow]) && left0 !is ast.ComptimeCall { p.error_with_pos('expression evaluated but not used', left0.position()) + return ast.Stmt{} } if left.len == 1 { return ast.ExprStmt{ @@ -980,6 +996,7 @@ pub fn (mut p Parser) parse_ident(language table.Language) ast.Ident { } } else { p.error('unexpected token `$p.tok.lit`') + return ast.Ident{} } return ast.Ident{} } @@ -1040,9 +1057,11 @@ pub fn (mut p Parser) name_expr() ast.Expr { } 'len', 'init' { p.error('`$key` cannot be initialized for `chan`. Did you mean `cap`?') + return ast.Expr{} } else { p.error('wrong field `$key`, expecting `cap`') + return ast.Expr{} } } last_pos = p.tok.position() @@ -1062,12 +1081,14 @@ pub fn (mut p Parser) name_expr() ast.Expr { } else { // don't allow any other string prefix except `r`, `js` and `c` p.error('only `c`, `r`, `js` are recognized string prefixes, but you tried to use `$p.tok.lit`') + return ast.Expr{} } } // don't allow r`byte` and c`byte` if p.tok.lit in ['r', 'c'] && p.peek_tok.kind == .chartoken { opt := if p.tok.lit == 'r' { '`r` (raw string)' } else { '`c` (c string)' } p.error('cannot use $opt with `byte` and `rune`') + return ast.Expr{} } known_var := p.mark_var_as_used(p.tok.lit) mut is_mod_cast := false @@ -1321,6 +1342,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { args := p.call_args() if is_filter && args.len != 1 { p.error('needs exactly 1 argument') + return ast.Expr{} } p.check(.rpar) mut or_stmts := []ast.Stmt{} @@ -1486,6 +1508,7 @@ fn (mut p Parser) string_expr() ast.Expr { p.next() } else { p.error('format specifier may only be one letter') + return ast.Expr{} } } } @@ -1544,13 +1567,16 @@ fn (mut p Parser) module_decl() ast.Module { name = p.check_name() if module_pos.line_nr != pos.line_nr { p.error_with_pos('`module` and `$name` must be at same line', pos) + return ast.Module{} } pos = p.tok.position() if module_pos.line_nr == pos.line_nr && p.tok.kind != .comment { if p.tok.kind != .name { p.error_with_pos('`module x` syntax error', pos) + return ast.Module{} } else { p.error_with_pos('`module x` can only declare one module', pos) + return ast.Module{} } } module_pos = module_pos.extend(pos) @@ -1584,10 +1610,12 @@ fn (mut p Parser) import_stmt() ast.Import { pos := p.tok.position() if p.tok.kind == .lpar { p.error_with_pos('`import()` has been deprecated, use `import x` instead', pos) + return ast.Import{} } mut mod_name := p.check_name() if import_pos.line_nr != pos.line_nr { p.error_with_pos('`import` statements must be a single line', pos) + return ast.Import{} } mut mod_alias := mod_name for p.tok.kind == .dot { @@ -1595,9 +1623,11 @@ fn (mut p Parser) import_stmt() ast.Import { pos_t := p.tok.position() if p.tok.kind != .name { p.error_with_pos('module syntax error, please use `x.y.z`', pos) + return ast.Import{} } if import_pos.line_nr != pos_t.line_nr { p.error_with_pos('`import` and `submodule` must be at same line', pos) + return ast.Import{} } submod_name := p.check_name() mod_name += '.' + submod_name @@ -1608,6 +1638,7 @@ fn (mut p Parser) import_stmt() ast.Import { mod_alias = p.check_name() if mod_alias == mod_name.split('.').last() { p.error_with_pos('import alias `$mod_name as $mod_alias` is redundant', p.prev_tok.position()) + return ast.Import{} } } mut node := ast.Import{ @@ -1623,6 +1654,7 @@ fn (mut p Parser) import_stmt() ast.Import { if import_pos.line_nr == pos_t.line_nr { if p.tok.kind !in [.lcbr, .eof, .comment] { p.error_with_pos('cannot import multiple modules at a time', pos_t) + return ast.Import{} } } p.imports[mod_alias] = mod_name @@ -1639,10 +1671,12 @@ fn (mut p Parser) import_syms(mut parent ast.Import) { pos_t := p.tok.position() if p.tok.kind == .rcbr { // closed too early p.error_with_pos('empty `$parent.mod` import set, remove `{}`', pos_t) + return } if p.tok.kind != .name { // not a valid inner name p.error_with_pos('import syntax error, please specify a valid fn or type name', pos_t) + return } for p.tok.kind == .name { pos := p.tok.position() @@ -1697,6 +1731,7 @@ fn (mut p Parser) import_syms(mut parent ast.Import) { } if p.tok.kind != .rcbr { p.error_with_pos('import syntax error, no closing `}`', p.tok.position()) + return } p.next() } @@ -1739,6 +1774,7 @@ fn (mut p Parser) const_decl() ast.ConstDecl { p.check(.assign) if p.tok.kind == .key_fn { p.error('const initializer fn literal is not a constant') + return ast.ConstDecl{} } expr := p.expr(0) field := ast.ConstField{ @@ -1795,12 +1831,14 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { p.mod != 'ui' && p.mod != 'gg2' && p.mod != 'uiold' && !p.pref.enable_globals && !p.pref.is_fmt && p.mod !in global_enabled_mods { p.error('use `v --enable-globals ...` to enable globals') + return ast.GlobalDecl{} } start_pos := p.tok.position() end_pos := p.tok.position() p.check(.key_global) if p.tok.kind != .lpar { p.error('globals must be grouped, e.g. `__global ( a = int(1) )`') + return ast.GlobalDecl{} } p.next() // ( mut fields := []ast.GlobalField{} @@ -1819,11 +1857,13 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { typ := p.parse_type() if p.tok.kind == .assign { p.error('global assign must have the type around the value, use `__global ( name = type(value) )`') + return ast.GlobalDecl{} } mut expr := ast.Expr{} if has_expr { if p.tok.kind != .lpar { p.error('global assign must have a type and value, use `__global ( name = type(value) )` or `__global ( name type )`') + return ast.GlobalDecl{} } p.next() // ( expr = p.expr(0) @@ -1862,6 +1902,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { if enum_name.len == 1 { p.error_with_pos('single letter capital names are reserved for generic template types.', end_pos) + return ast.EnumDecl{} } name := p.prepend_mod(enum_name) p.check(.lcbr) @@ -1896,11 +1937,13 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { if is_flag { if fields.len > 32 { p.error('when an enum is used as bit field, it must have a max of 32 fields') + return ast.EnumDecl{} } for f in fields { if f.has_expr { p.error_with_pos('when an enum is used as a bit field, you can not assign custom values', f.pos) + return ast.EnumDecl{} } } pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' } @@ -1950,6 +1993,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { if name.len == 1 && name[0].is_capital() { p.error_with_pos('single letter capital names are reserved for generic template types.', decl_pos) + return ast.TypeDecl{} } mut sum_variants := []ast.SumTypeVariant{} p.check(.assign) @@ -2155,10 +2199,12 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { p.next() if p.tok.kind != .lcbr { p.error_with_pos('please use `unsafe {`', p.tok.position()) + return ast.Stmt{} } p.next() if p.inside_unsafe { p.error_with_pos('already inside `unsafe` block', pos) + return ast.Stmt{} } if p.tok.kind == .rcbr { // `unsafe {}` diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index a2a36f85a7..6baded79c1 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -50,9 +50,16 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { } .dollar { match p.peek_tok.kind { - .name { return p.vweb() } - .key_if { return p.if_expr(true) } - else { p.error_with_pos('unexpected `$`', p.peek_tok.position()) } + .name { + return p.vweb() + } + .key_if { + return p.if_expr(true) + } + else { + p.error_with_pos('unexpected `$`', p.peek_tok.position()) + return ast.Expr{} + } } } .chartoken { @@ -100,6 +107,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.next() if p.inside_unsafe { p.error_with_pos('already inside `unsafe` block', pos) + return ast.Expr{} } p.inside_unsafe = true p.check(.lcbr) @@ -193,9 +201,11 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.next() s := if p.tok.lit != '' { '`$p.tok.lit`' } else { p.tok.kind.str() } p.error_with_pos('unexpected $s, expecting `:`', p.tok.position()) + return ast.Expr{} } else { p.error_with_pos('unexpected `$p.tok.lit`, expecting struct key', p.tok.position()) + return ast.Expr{} } } p.check(.rcbr) @@ -235,6 +245,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { // eof should be handled where it happens p.error_with_pos('invalid expression: unexpected $p.tok.kind.str() token', p.tok.position()) + return ast.Expr{} } } } diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 13c4387de1..646d5bbe44 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -52,6 +52,7 @@ fn (mut p Parser) sql_expr() ast.Expr { p.check_name() // `by` } else { p.error_with_pos('use `order by` in ORM queries', order_pos) + return ast.Expr{} } has_order = true order_expr = p.expr(0) @@ -139,6 +140,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } else { p.error('can only insert variables') + return ast.SqlStmt{} } } } @@ -147,9 +149,11 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { mut update_exprs := []ast.Expr{cap: 5} if kind == .insert && n != 'into' { p.error('expecting `into`') + return ast.SqlStmt{} } else if kind == .update { if n != 'set' { p.error('expecting `set`') + return ast.SqlStmt{} } for { column := p.check_name() @@ -164,6 +168,7 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } } else if kind == .delete && n != 'from' { p.error('expecting `from`') + return ast.SqlStmt{} } mut table_type := table.Type(0) mut where_expr := ast.Expr{} @@ -179,13 +184,13 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { idx := p.table.find_type_idx(p.prepend_mod(table_name)) table_type = table.new_type(idx) } - p.check_sql_keyword('where') + p.check_sql_keyword('where') or { return ast.SqlStmt{} } where_expr = p.expr(0) } else if kind == .delete { table_type = p.parse_type() sym := p.table.get_type_symbol(table_type) table_name = sym.name - p.check_sql_keyword('where') + p.check_sql_keyword('where') or { return ast.SqlStmt{} } where_expr = p.expr(0) } p.check(.rcbr) @@ -202,8 +207,10 @@ fn (mut p Parser) sql_stmt() ast.SqlStmt { } } -fn (mut p Parser) check_sql_keyword(name string) { +fn (mut p Parser) check_sql_keyword(name string) ?bool { if p.check_name() != name { p.error('orm: expecting `$name`') + return none } + return true } diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 33dfbdc62b..2c822cdcd4 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -45,6 +45,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { if name.len == 1 && name[0].is_capital() { p.error_with_pos('single letter capital names are reserved for generic template types.', name_pos) + return ast.StructDecl{} } mut generic_types := []table.Type{} if p.tok.kind == .lt { @@ -61,13 +62,16 @@ fn (mut p Parser) struct_decl() ast.StructDecl { no_body := p.tok.kind != .lcbr if language == .v && no_body { p.error('`$p.tok.lit` lacks body') + return ast.StructDecl{} } if language == .v && p.mod != 'builtin' && name.len > 0 && !name[0].is_capital() && !p.pref.translated { p.error_with_pos('struct name `$name` must begin with capital letter', name_pos) + return ast.StructDecl{} } if name.len == 1 { p.error_with_pos('struct names must have more than one character', name_pos) + return ast.StructDecl{} } // println('struct decl $name') mut ast_fields := []ast.StructField{} @@ -100,6 +104,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { if p.tok.kind == .key_mut { if pub_mut_pos != -1 { p.error('redefinition of `pub mut` section') + return ast.StructDecl{} } p.next() pub_mut_pos = fields.len @@ -109,6 +114,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } else { if pub_pos != -1 { p.error('redefinition of `pub` section') + return ast.StructDecl{} } pub_pos = fields.len is_field_pub = true @@ -119,6 +125,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } else if p.tok.kind == .key_mut { if mut_pos != -1 { p.error('redefinition of `mut` section') + return ast.StructDecl{} } p.next() p.check(.colon) @@ -129,6 +136,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } else if p.tok.kind == .key_global { if global_pos != -1 { p.error('redefinition of `global` section') + return ast.StructDecl{} } p.next() p.check(.colon) @@ -172,6 +180,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { field_name = symbol_name if typ in embedded_structs { p.error_with_pos('cannot embed `$field_name` more than once', type_pos) + return ast.StructDecl{} } embedded_structs << typ } else { @@ -275,6 +284,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { if ret == -1 { p.error_with_pos('cannot register struct `$name`, another type with this name exists', name_pos) + return ast.StructDecl{} } p.expr_mod = '' return ast.StructDecl{ @@ -390,6 +400,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { if reg_idx == -1 { p.error_with_pos('cannot register interface `$interface_name`, another type with this name exists', name_pos) + return ast.InterfaceDecl{} } typ := table.new_type(reg_idx) mut ts := p.table.get_type_symbol(typ) @@ -403,9 +414,11 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { name := p.check_name() if ts.has_method(name) { p.error_with_pos('duplicate method `$name`', method_start_pos) + return ast.InterfaceDecl{} } if util.contains_capital(name) { p.error('interface methods cannot contain uppercase letters, use snake_case instead') + return ast.InterfaceDecl{} } // field_names << name args2, _, _ := p.fn_args() // TODO merge table.Param and ast.Arg to avoid this diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 650f23d513..721d2e5362 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -123,6 +123,7 @@ pub mut: skip_running bool // when true, do no try to run the produced file (set by b.cc(), when -o x.c or -o x.js) skip_warnings bool // like C's "-w" warns_are_errors bool // -W, like C's "-Werror", treat *every* warning is an error + fatal_errors bool // unconditionally exit after the first error with exit(1) reuse_tmpc bool // do not use random names for .tmp.c and .tmp.c.rsp files, and do not remove them use_color ColorOutput // whether the warnings/errors should use ANSI color escapes. is_parallel bool @@ -172,6 +173,9 @@ pub fn parse_args(args []string) (&Preferences, string) { '-progress' { // processed by testing tools in cmd/tools/modules/testing/common.v } + '-Wfatal-errors' { + res.fatal_errors = true + } '-silent' { res.output_mode = .silent } diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 3a45574247..4e333bca46 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -1236,6 +1236,9 @@ pub fn (mut s Scanner) error(msg string) { eprintln(util.formatted_error('error:', msg, s.file_path, pos)) exit(1) } else { + if s.pref.fatal_errors { + exit(1) + } s.errors << errors.Error{ file_path: s.file_path pos: pos