parser: prepare for better VLS integration, more accurate parser errors (#7119)

pull/7137/head
Daniel Däschle 2020-12-04 19:34:05 +01:00 committed by GitHub
parent 52fb7033c3
commit 47d0ed308d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 171 additions and 36 deletions

View File

@ -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

View File

@ -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`.

View File

@ -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).
<!-- this should be `failcompile`, but it compiles -->
```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') }
}
```

View File

@ -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{

View File

@ -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 {

View File

@ -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()

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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{}
}
}
}

View File

@ -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' {

View File

@ -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 {}`

View File

@ -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{}
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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