From b31906815133d6ada15b0b7f46a40a0cbede62ca Mon Sep 17 00:00:00 2001 From: Ned Palacios <7358345+nedpals@users.noreply.github.com> Date: Tue, 30 Mar 2021 15:33:29 +0800 Subject: [PATCH] ast, parser: implement simple AST poisoning (#9525) --- vlib/v/ast/ast.v | 30 +++++++++++++--------- vlib/v/checker/checker.v | 2 ++ vlib/v/fmt/fmt.v | 2 ++ vlib/v/gen/c/cgen.v | 2 ++ vlib/v/gen/js/js.v | 4 ++- vlib/v/markused/walker.v | 2 ++ vlib/v/parser/assign.v | 19 +++++--------- vlib/v/parser/for.v | 29 ++++++++------------- vlib/v/parser/parser.v | 55 ++++++++++++++++------------------------ vlib/v/parser/pratt.v | 19 +++++--------- vlib/v/parser/sql.v | 3 +-- 11 files changed, 77 insertions(+), 90 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 1a5e39e13a..9ea35f3eb8 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -14,14 +14,14 @@ pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector | ConcatExpr | DumpExpr | EnumVal | FloatLiteral | GoExpr | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | - MapInit | MatchExpr | None | OffsetOf | OrExpr | ParExpr | PostfixExpr | PrefixExpr | - RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | - StructInit | Type | TypeOf | UnsafeExpr + MapInit | MatchExpr | NodeError | None | OffsetOf | OrExpr | ParExpr | PostfixExpr | + PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | + StringLiteral | StructInit | Type | TypeOf | UnsafeExpr pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | - GoStmt | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | - SqlStmt | StructDecl | TypeDecl + GoStmt | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | NodeError | + Return | SqlStmt | StructDecl | TypeDecl // NB: when you add a new Expr or Stmt type with a .pos field, remember to update // the .position() token.Position methods too. @@ -1083,7 +1083,7 @@ pub: pub struct AsmAddressing { pub: displacement u32 // 8, 16 or 32 bit literal value - scale int = -1 // 1, 2, 4, or 8 literal + scale int = -1 // 1, 2, 4, or 8 literal mode AddressingMode pos token.Position pub mut: @@ -1408,6 +1408,12 @@ pub mut: sub_structs map[int]SqlExpr } +pub struct NodeError { +pub: + idx int // index for referencing the related ast.File error + pos token.Position +} + [inline] pub fn (expr Expr) is_blank_ident() bool { match expr { @@ -1422,12 +1428,12 @@ pub fn (expr Expr) position() token.Position { AnonFn { return expr.decl.pos } - ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, - CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, EnumVal, DumpExpr, FloatLiteral, - GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr, - None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, - SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr - { + NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, + ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, EnumVal, DumpExpr, + FloatLiteral, GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, + MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, + SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, + UnsafeExpr { return expr.pos } IfGuardExpr { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 2e03236bb2..1bd80d4397 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3324,6 +3324,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { } // c.expected_type = table.void_type match mut node { + ast.NodeError {} ast.AsmStmt { c.asm_stmt(mut node) } @@ -3950,6 +3951,7 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { return table.void_type } match mut node { + ast.NodeError {} ast.CTempVar { return node.typ } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 10d668b7e4..e97af6ecd1 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -382,6 +382,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}') } match node { + ast.NodeError {} ast.AsmStmt { f.asm_stmt(node) } @@ -480,6 +481,7 @@ pub fn (mut f Fmt) expr(node ast.Expr) { eprintln('expr: ${node.position():-42} | node: ${node.type_name():-20} | $node.str()') } match mut node { + ast.NodeError {} ast.AnonFn { f.fn_decl(node.decl) } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 374383d8f1..ddfd066e65 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1187,6 +1187,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { // g.cur_mod = node.name g.cur_mod = node } + ast.NodeError {} ast.Return { g.write_defer_stmts_when_needed() // af := g.autofree && node.exprs.len > 0 && node.exprs[0] is ast.CallExpr && !g.is_builtin_mod @@ -3013,6 +3014,7 @@ fn (mut g Gen) expr(node ast.Expr) { ast.MapInit { g.map_init(node) } + ast.NodeError {} ast.None { g.write('_const_none__') } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index a1c90c4a50..2cfa596d91 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -427,6 +427,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) { ast.Module { // skip: namespacing implemented externally } + ast.NodeError {} ast.Return { if g.defer_stmts.len > 0 { g.gen_defer_stmts() @@ -445,6 +446,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) { fn (mut g JsGen) expr(node ast.Expr) { match node { + ast.NodeError {} ast.CTempVar { g.write('/* ast.CTempVar: node.name */') } @@ -693,7 +695,7 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) { } else { g.write(' $op ') // TODO: Multiple types?? - should_cast := + should_cast := (g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables) && (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last()) diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 023af6d313..a14b74301a 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -123,6 +123,7 @@ pub fn (mut w Walker) stmt(node ast.Stmt) { ast.InterfaceDecl {} ast.Module {} ast.TypeDecl {} + ast.NodeError {} } } @@ -335,6 +336,7 @@ fn (mut w Walker) expr(node ast.Expr) { ast.UnsafeExpr { w.expr(node.expr) } + ast.NodeError {} } } diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index 2a11a5e83a..4c3c316264 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -110,8 +110,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme // a, b := a + 1, b for r in right { p.check_undefined_variables(left, r) or { - p.error('check_undefined_variables failed') - return ast.Stmt{} + return p.error('check_undefined_variables failed') } } } else if left.len > 1 { @@ -119,8 +118,8 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme for r in right { has_cross_var = p.check_cross_variables(left, r) if op !in [.assign, .decl_assign] { - p.error_with_pos('unexpected $op.str(), expecting := or = or comma', pos) - return ast.Stmt{} + return p.error_with_pos('unexpected $op.str(), expecting := or = or comma', + pos) } if has_cross_var { break @@ -133,8 +132,7 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme ast.Ident { if op == .decl_assign { if p.scope.known_var(lx.name) { - p.error_with_pos('redefinition of `$lx.name`', lx.pos) - return ast.Stmt{} + return p.error_with_pos('redefinition of `$lx.name`', lx.pos) } mut share := table.ShareType(0) if lx.info is ast.IdentVar { @@ -142,9 +140,8 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme share = iv.share if iv.is_static { if !p.pref.translated && !p.pref.is_fmt && !p.inside_unsafe_fn { - p.error_with_pos('static variables are supported only in -translated mode or in [unsafe] fn', + return p.error_with_pos('static variables are supported only in -translated mode or in [unsafe] fn', lx.pos) - return ast.Stmt{} } is_static = true } @@ -175,9 +172,8 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme } ast.IndexExpr { if op == .decl_assign { - p.error_with_pos('non-name `$lx.left[$lx.index]` on left side of `:=`', + return p.error_with_pos('non-name `$lx.left[$lx.index]` on left side of `:=`', lx.pos) - return ast.Stmt{} } lx.is_setter = true } @@ -185,9 +181,8 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme ast.PrefixExpr {} ast.SelectorExpr { if op == .decl_assign { - p.error_with_pos('struct fields can only be declared during the initialization', + return 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/for.v b/vlib/v/parser/for.v index 1dcff1f17b..252a92e591 100644 --- a/vlib/v/parser/for.v +++ b/vlib/v/parser/for.v @@ -12,8 +12,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { p.open_scope() p.inside_for = true if p.tok.kind == .key_match { - p.error('cannot use `match` in `for` loop') - return ast.Stmt{} + return p.error('cannot use `match` in `for` loop') } // defer { p.close_scope() } // Infinite loop @@ -34,8 +33,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { && p.peek_token(2).kind != .key_mut && p.peek_token(3).kind != .key_in) { // `for i := 0; i < 10; i++ {` or `for a,b := 0,1; a < 10; a++ {` 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{} + return p.error('`mut` is not needed in `for ;;` loops: use `for i := 0; i < n; i ++ {`') } mut init := ast.Stmt{} mut cond := p.new_true_expr() @@ -55,8 +53,7 @@ fn (mut p Parser) for_stmt() ast.Stmt { if p.tok.kind != .semicolon { // 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{} + return p.error('cannot use $p.tok.lit$p.peek_tok.kind as value') } cond = p.expr(0) has_cond = true @@ -112,16 +109,14 @@ fn (mut p Parser) for_stmt() ast.Stmt { val_var_pos = p.tok.position() 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{} + return p.error_with_pos('key and value in a for loop cannot be the same', + val_var_pos) } if p.scope.known_var(key_var_name) { - p.error('redefinition of key iteration variable `$key_var_name`') - return ast.Stmt{} + return p.error('redefinition of key iteration variable `$key_var_name`') } if p.scope.known_var(val_var_name) { - p.error('redefinition of value iteration variable `$val_var_name`') - return ast.Stmt{} + return p.error('redefinition of value iteration variable `$val_var_name`') } p.scope.register(ast.Var{ name: key_var_name @@ -130,13 +125,11 @@ fn (mut p Parser) for_stmt() ast.Stmt { is_tmp: true }) } else if p.scope.known_var(val_var_name) { - p.error('redefinition of value iteration variable `$val_var_name`') - return ast.Stmt{} + return p.error('redefinition of value iteration variable `$val_var_name`') } 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{} + return 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') } // arr_expr cond := p.expr(0) @@ -156,8 +149,8 @@ fn (mut p Parser) for_stmt() ast.Stmt { is_tmp: true }) if key_var_name.len > 0 { - p.error_with_pos('cannot declare index variable with range `for`', key_var_pos) - return ast.Stmt{} + return p.error_with_pos('cannot declare index variable with range `for`', + key_var_pos) } } else { // this type will be set in checker diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index b70a43fe87..6ac5193c34 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -516,8 +516,7 @@ pub fn (mut p Parser) top_stmt() ast.Stmt { return p.type_decl() } else { - p.error('wrong pub keyword usage') - return ast.Stmt{} + return p.error('wrong pub keyword usage') } } } @@ -591,8 +590,7 @@ pub fn (mut p Parser) top_stmt() ast.Stmt { } else if p.pref.is_fmt { return p.stmt(false) } else { - p.error('bad top level statement ' + p.tok.str()) - return ast.Stmt{} + return p.error('bad top level statement ' + p.tok.str()) } } } @@ -720,12 +718,10 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { pos: spos.extend(p.tok.position()) } } else if p.peek_tok.kind == .name { - p.error_with_pos('unexpected name `$p.peek_tok.lit`', p.peek_tok.position()) - return ast.Stmt{} + return p.error_with_pos('unexpected name `$p.peek_tok.lit`', p.peek_tok.position()) } 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.error_with_pos('`$p.tok.lit` evaluated but not used', p.tok.position()) } return p.parse_multi_expr(is_top_level) } @@ -759,8 +755,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } else { - p.error_with_pos('unexpected \$', p.tok.position()) - return ast.Stmt{} + return p.error_with_pos('unexpected \$', p.tok.position()) } } } @@ -820,9 +815,8 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } .key_const { - p.error_with_pos('const can only be defined at the top level (outside of functions)', + return p.error_with_pos('const can only be defined at the top level (outside of functions)', p.tok.position()) - return ast.Stmt{} } .key_asm { return p.asm_stmt(false) @@ -1483,8 +1477,8 @@ pub fn (mut p Parser) check_for_impure_v(language table.Language, pos token.Posi } } -pub fn (mut p Parser) error(s string) { - p.error_with_pos(s, p.tok.position()) +pub fn (mut p Parser) error(s string) ast.NodeError { + return p.error_with_pos(s, p.tok.position()) } pub fn (mut p Parser) warn(s string) { @@ -1495,7 +1489,7 @@ pub fn (mut p Parser) note(s string) { p.note_with_pos(s, p.tok.position()) } -pub fn (mut p Parser) error_with_pos(s string, pos token.Position) { +pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError { if p.pref.fatal_errors { exit(1) } @@ -1523,6 +1517,10 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) { // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode. p.next() } + return ast.NodeError{ + idx: p.errors.len - 1 + pos: pos + } } pub fn (mut p Parser) error_with_error(error errors.Error) { @@ -1611,8 +1609,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { left, left_comments := p.expr_list() left0 := left[0] if tok.kind == .key_mut && p.tok.kind != .decl_assign { - p.error('expecting `:=` (e.g. `mut x :=`)') - return ast.Stmt{} + return p.error('expecting `:=` (e.g. `mut x :=`)') } // TODO remove translated if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { @@ -1624,8 +1621,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { && node !is ast.PostfixExpr && !(node is ast.InfixExpr && (node as ast.InfixExpr).op in [.left_shift, .arrow]) && node !is ast.ComptimeCall && node !is ast.SelectorExpr && node !is ast.DumpExpr { - p.error_with_pos('expression evaluated but not used', node.position()) - return ast.Stmt{} + return p.error_with_pos('expression evaluated but not used', node.position()) } } } @@ -1819,12 +1815,10 @@ pub fn (mut p Parser) name_expr() ast.Expr { cap_expr = p.expr(0) } 'len', 'init' { - p.error('`$key` cannot be initialized for `chan`. Did you mean `cap`?') - return ast.Expr{} + return p.error('`$key` cannot be initialized for `chan`. Did you mean `cap`?') } else { - p.error('wrong field `$key`, expecting `cap`') - return ast.Expr{} + return p.error('wrong field `$key`, expecting `cap`') } } last_pos = p.tok.position() @@ -1843,15 +1837,13 @@ pub fn (mut p Parser) name_expr() ast.Expr { return p.string_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{} + return p.error('only `c`, `r`, `js` are recognized string prefixes, but you tried to use `$p.tok.lit`') } } // 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{} + return p.error('cannot use $opt with `byte` and `rune`') } known_var := p.mark_var_as_used(p.tok.lit) mut is_mod_cast := false @@ -2342,8 +2334,7 @@ fn (mut p Parser) string_expr() ast.Expr { has_fmt = true p.next() } else { - p.error('format specifier may only be one letter') - return ast.Expr{} + return p.error('format specifier may only be one letter') } } } @@ -3072,13 +3063,11 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { mut pos := p.tok.position() p.next() if p.tok.kind != .lcbr { - p.error_with_pos('please use `unsafe {`', p.tok.position()) - return ast.Stmt{} + return p.error_with_pos('please use `unsafe {`', p.tok.position()) } p.next() if p.inside_unsafe { - p.error_with_pos('already inside `unsafe` block', pos) - return ast.Stmt{} + return p.error_with_pos('already inside `unsafe` block', pos) } if p.tok.kind == .rcbr { // `unsafe {}` diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 00be1cf720..40536d2e30 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -69,8 +69,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { return p.if_expr(true) } else { - p.error_with_pos('unexpected `$`', p.peek_tok.position()) - return ast.Expr{} + return p.error_with_pos('unexpected `$`', p.peek_tok.position()) } } } @@ -135,8 +134,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { mut pos := p.tok.position() p.next() if p.inside_unsafe { - p.error_with_pos('already inside `unsafe` block', pos) - return ast.Expr{} + return p.error_with_pos('already inside `unsafe` block', pos) } p.inside_unsafe = true p.check(.lcbr) @@ -240,8 +238,8 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { st := p.parse_type() p.check(.comma) if p.tok.kind != .name { - p.error_with_pos('unexpected `$p.tok.lit`, expecting struct field', p.tok.position()) - return ast.Expr{} + return p.error_with_pos('unexpected `$p.tok.lit`, expecting struct field', + p.tok.position()) } field := p.tok.lit p.next() @@ -281,13 +279,11 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.struct_init(true) // short_syntax: true } else if p.tok.kind == .name { p.next() - p.error_with_pos('unexpected $p.tok, expecting `:` after struct field name', + return p.error_with_pos('unexpected $p.tok, expecting `:` after struct field name', p.tok.position()) - return ast.Expr{} } else { - p.error_with_pos('unexpected $p.tok, expecting struct field name', + return p.error_with_pos('unexpected $p.tok, expecting struct field name', p.tok.position()) - return ast.Expr{} } } p.check(.rcbr) @@ -326,8 +322,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { else { if p.tok.kind != .eof && !(p.tok.kind == .rsbr && p.inside_asm) { // eof should be handled where it happens - p.error_with_pos('invalid expression: unexpected $p.tok', p.tok.position()) - return ast.Expr{} + return p.error_with_pos('invalid expression: unexpected $p.tok', p.tok.position()) } } } diff --git a/vlib/v/parser/sql.v b/vlib/v/parser/sql.v index 06f1069a25..686005b2d5 100644 --- a/vlib/v/parser/sql.v +++ b/vlib/v/parser/sql.v @@ -52,8 +52,7 @@ fn (mut p Parser) sql_expr() ast.Expr { if p.tok.kind == .name && p.tok.lit == 'by' { p.check_name() // `by` } else { - p.error_with_pos('use `order by` in ORM queries', order_pos) - return ast.Expr{} + return p.error_with_pos('use `order by` in ORM queries', order_pos) } has_order = true order_expr = p.expr(0)