Auto merge of #14128 - Veykril:parser, r=Veykril

internal: Improve parser recovery for delimited lists

Closes https://github.com/rust-lang/rust-analyzer/issues/11188, https://github.com/rust-lang/rust-analyzer/issues/10410, https://github.com/rust-lang/rust-analyzer/issues/10173

Should probably be merged after the stable release as this might get the parser stuck if I missed something
This commit is contained in:
bors 2023-02-14 12:59:39 +00:00
commit 44568007d1
33 changed files with 817 additions and 367 deletions

1
Cargo.lock generated
View file

@ -1174,6 +1174,7 @@ dependencies = [
"limit",
"rustc-ap-rustc_lexer",
"sourcegen",
"stdx",
]
[[package]]

View file

@ -1476,7 +1476,7 @@ macro_rules! m {
/* parse error: expected identifier */
/* parse error: expected SEMICOLON */
/* parse error: expected SEMICOLON */
/* parse error: expected expression */
/* parse error: expected expression, item or let statement */
fn f() {
K::(C("0"));
}

View file

@ -830,8 +830,7 @@ macro_rules! rgb_color {
/* parse error: expected COMMA */
/* parse error: expected R_ANGLE */
/* parse error: expected SEMICOLON */
/* parse error: expected SEMICOLON */
/* parse error: expected expression */
/* parse error: expected expression, item or let statement */
pub fn new() {
let _ = 0as u32<<(8+8);
}
@ -848,21 +847,21 @@ pub fn new() {
// BLOCK_EXPR@10..31
// STMT_LIST@10..31
// L_CURLY@10..11 "{"
// LET_STMT@11..27
// LET_STMT@11..28
// LET_KW@11..14 "let"
// WILDCARD_PAT@14..15
// UNDERSCORE@14..15 "_"
// EQ@15..16 "="
// CAST_EXPR@16..27
// CAST_EXPR@16..28
// LITERAL@16..17
// INT_NUMBER@16..17 "0"
// AS_KW@17..19 "as"
// PATH_TYPE@19..27
// PATH@19..27
// PATH_SEGMENT@19..27
// PATH_TYPE@19..28
// PATH@19..28
// PATH_SEGMENT@19..28
// NAME_REF@19..22
// IDENT@19..22 "u32"
// GENERIC_ARG_LIST@22..27
// GENERIC_ARG_LIST@22..28
// L_ANGLE@22..23 "<"
// TYPE_ARG@23..27
// DYN_TRAIT_TYPE@23..27
@ -877,9 +876,9 @@ pub fn new() {
// ERROR@25..26
// INT_NUMBER@25..26 "8"
// PLUS@26..27 "+"
// EXPR_STMT@27..28
// LITERAL@27..28
// INT_NUMBER@27..28 "8"
// CONST_ARG@27..28
// LITERAL@27..28
// INT_NUMBER@27..28 "8"
// ERROR@28..29
// R_PAREN@28..29 ")"
// SEMICOLON@29..30 ";"

View file

@ -668,8 +668,15 @@ fn classify_name_ref(
};
let after_if_expr = |node: SyntaxNode| {
let prev_expr = (|| {
let node = match node.parent().and_then(ast::ExprStmt::cast) {
Some(stmt) => stmt.syntax().clone(),
None => node,
};
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
ast::ExprStmt::cast(prev_sibling)?.expr()
ast::ExprStmt::cast(prev_sibling.clone())
.and_then(|it| it.expr())
.or_else(|| ast::Expr::cast(prev_sibling))
})();
matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
};

View file

@ -745,3 +745,255 @@ fn return_value_no_block() {
r#"fn f() -> i32 { match () { () => return $0 } }"#,
);
}
#[test]
fn else_completion_after_if() {
check_empty(
r#"
fn foo() { if foo {} $0 }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw else
kw else if
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
check_empty(
r#"
fn foo() { if foo {} el$0 }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw else
kw else if
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
check_empty(
r#"
fn foo() { bar(if foo {} $0) }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw crate::
kw else
kw else if
kw false
kw for
kw if
kw if let
kw loop
kw match
kw return
kw self::
kw true
kw unsafe
kw while
kw while let
"#]],
);
check_empty(
r#"
fn foo() { bar(if foo {} el$0) }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw crate::
kw else
kw else if
kw false
kw for
kw if
kw if let
kw loop
kw match
kw return
kw self::
kw true
kw unsafe
kw while
kw while let
"#]],
);
check_empty(
r#"
fn foo() { if foo {} $0 let x = 92; }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw else
kw else if
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
check_empty(
r#"
fn foo() { if foo {} el$0 let x = 92; }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw else
kw else if
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
check_empty(
r#"
fn foo() { if foo {} el$0 { let x = 92; } }
"#,
expect![[r#"
fn foo() fn()
bt u32
kw const
kw crate::
kw else
kw else if
kw enum
kw extern
kw false
kw fn
kw for
kw if
kw if let
kw impl
kw let
kw loop
kw match
kw mod
kw return
kw self::
kw static
kw struct
kw trait
kw true
kw type
kw union
kw unsafe
kw use
kw while
kw while let
sn macro_rules
sn pd
sn ppd
"#]],
);
}

View file

@ -2,10 +2,17 @@
use expect_test::{expect, Expect};
use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character};
use crate::tests::{
check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character,
};
fn check_no_kw(ra_fixture: &str, expect: Expect) {
let actual = completion_list_no_kw(ra_fixture);
expect.assert_eq(&actual)
}
fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list_no_kw(ra_fixture);
let actual = completion_list(ra_fixture);
expect.assert_eq(&actual)
}
@ -59,7 +66,7 @@ fn _alpha() {}
#[test]
fn completes_prelude() {
check(
check_no_kw(
r#"
//- /main.rs edition:2018 crate:main deps:std
fn foo() { let x: $0 }
@ -81,7 +88,7 @@ pub mod prelude {
#[test]
fn completes_prelude_macros() {
check(
check_no_kw(
r#"
//- /main.rs edition:2018 crate:main deps:std
fn f() {$0}
@ -110,7 +117,7 @@ mod macros {
#[test]
fn completes_std_prelude_if_core_is_defined() {
check(
check_no_kw(
r#"
//- /main.rs crate:main deps:core,std
fn foo() { let x: $0 }
@ -140,7 +147,7 @@ pub mod prelude {
#[test]
fn respects_doc_hidden() {
check(
check_no_kw(
r#"
//- /lib.rs crate:lib deps:std
fn f() {
@ -168,7 +175,7 @@ pub mod prelude {
#[test]
fn respects_doc_hidden_in_assoc_item_list() {
check(
check_no_kw(
r#"
//- /lib.rs crate:lib deps:std
struct S;
@ -195,7 +202,7 @@ pub mod prelude {
#[test]
fn associated_item_visibility() {
check(
check_no_kw(
r#"
//- /lib.rs crate:lib new_source_root:library
pub struct S;
@ -222,7 +229,7 @@ fn foo() { let _ = lib::S::$0 }
#[test]
fn completes_union_associated_method() {
check(
check_no_kw(
r#"
union U {};
impl U { fn m() { } }
@ -237,7 +244,7 @@ fn foo() { let _ = U::$0 }
#[test]
fn completes_trait_associated_method_1() {
check(
check_no_kw(
r#"
trait Trait { fn m(); }
@ -251,7 +258,7 @@ fn foo() { let _ = Trait::$0 }
#[test]
fn completes_trait_associated_method_2() {
check(
check_no_kw(
r#"
trait Trait { fn m(); }
@ -268,7 +275,7 @@ fn foo() { let _ = S::$0 }
#[test]
fn completes_trait_associated_method_3() {
check(
check_no_kw(
r#"
trait Trait { fn m(); }
@ -285,7 +292,7 @@ fn foo() { let _ = <S as Trait>::$0 }
#[test]
fn completes_ty_param_assoc_ty() {
check(
check_no_kw(
r#"
trait Super {
type Ty;
@ -318,7 +325,7 @@ fn foo<T: Sub>() { T::$0 }
#[test]
fn completes_self_param_assoc_ty() {
check(
check_no_kw(
r#"
trait Super {
type Ty;
@ -358,7 +365,7 @@ impl<T> Sub for Wrap<T> {
#[test]
fn completes_type_alias() {
check(
check_no_kw(
r#"
struct S;
impl S { fn foo() {} }
@ -376,7 +383,7 @@ fn main() { T::$0; }
#[test]
fn completes_qualified_macros() {
check(
check_no_kw(
r#"
#[macro_export]
macro_rules! foo { () => {} }
@ -392,7 +399,7 @@ fn main() { let _ = crate::$0 }
#[test]
fn does_not_complete_non_fn_macros() {
check(
check_no_kw(
r#"
mod m {
#[rustc_builtin_macro]
@ -403,7 +410,7 @@ fn f() {m::$0}
"#,
expect![[r#""#]],
);
check(
check_no_kw(
r#"
mod m {
#[rustc_builtin_macro]
@ -418,7 +425,7 @@ fn f() {m::$0}
#[test]
fn completes_reexported_items_under_correct_name() {
check(
check_no_kw(
r#"
fn foo() { self::m::$0 }
@ -475,7 +482,7 @@ mod p {
#[test]
fn completes_in_simple_macro_call() {
check(
check_no_kw(
r#"
macro_rules! m { ($e:expr) => { $e } }
fn main() { m!(self::f$0); }
@ -490,7 +497,7 @@ fn foo() {}
#[test]
fn function_mod_share_name() {
check(
check_no_kw(
r#"
fn foo() { self::m::$0 }
@ -508,7 +515,7 @@ mod m {
#[test]
fn completes_hashmap_new() {
check(
check_no_kw(
r#"
struct RandomState;
struct HashMap<K, V, S = RandomState> {}
@ -529,7 +536,7 @@ fn foo() {
#[test]
fn completes_variant_through_self() {
cov_mark::check!(completes_variant_through_self);
check(
check_no_kw(
r#"
enum Foo {
Bar,
@ -552,7 +559,7 @@ impl Foo {
#[test]
fn completes_non_exhaustive_variant_within_the_defining_crate() {
check(
check_no_kw(
r#"
enum Foo {
#[non_exhaustive]
@ -570,7 +577,7 @@ fn foo(self) {
"#]],
);
check(
check_no_kw(
r#"
//- /main.rs crate:main deps:e
fn foo(self) {
@ -593,7 +600,7 @@ enum Foo {
#[test]
fn completes_primitive_assoc_const() {
cov_mark::check!(completes_primitive_assoc_const);
check(
check_no_kw(
r#"
//- /lib.rs crate:lib deps:core
fn f() {
@ -618,7 +625,7 @@ impl u8 {
#[test]
fn completes_variant_through_alias() {
cov_mark::check!(completes_variant_through_alias);
check(
check_no_kw(
r#"
enum Foo {
Bar
@ -636,7 +643,7 @@ fn main() {
#[test]
fn respects_doc_hidden2() {
check(
check_no_kw(
r#"
//- /lib.rs crate:lib deps:dep
fn f() {
@ -665,7 +672,7 @@ pub mod m {}
#[test]
fn type_anchor_empty() {
check(
check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@ -688,7 +695,7 @@ fn bar() -> Bar {
#[test]
fn type_anchor_type() {
check(
check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@ -715,7 +722,7 @@ fn bar() -> Bar {
#[test]
fn type_anchor_type_trait() {
check(
check_no_kw(
r#"
trait Foo {
fn foo() -> Self;
@ -741,7 +748,7 @@ fn bar() -> Bar {
#[test]
fn completes_fn_in_pub_trait_generated_by_macro() {
check(
check_no_kw(
r#"
mod other_mod {
macro_rules! make_method {
@ -775,7 +782,7 @@ fn main() {
#[test]
fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
check(
check_no_kw(
r#"
mod other_mod {
macro_rules! make_method {
@ -815,7 +822,7 @@ fn main() {
#[test]
fn completes_const_in_pub_trait_generated_by_macro() {
check(
check_no_kw(
r#"
mod other_mod {
macro_rules! make_const {
@ -847,7 +854,7 @@ fn main() {
#[test]
fn completes_locals_from_macros() {
check(
check_no_kw(
r#"
macro_rules! x {
@ -875,7 +882,7 @@ fn main() {
#[test]
fn regression_12644() {
check(
check_no_kw(
r#"
macro_rules! __rust_force_expr {
($e:expr) => {
@ -974,7 +981,7 @@ fn foo { crate:::$0 }
"#,
expect![""],
);
check(
check_no_kw(
r#"
fn foo { crate::::$0 }
"#,

View file

@ -2,9 +2,10 @@
use either::Either;
use hir::{Semantics, Type};
use parser::T;
use syntax::{
ast::{self, HasArgList, HasName},
AstNode, SyntaxToken,
match_ast, AstNode, NodeOrToken, SyntaxToken,
};
use crate::RootDatabase;
@ -58,7 +59,7 @@ pub fn callable_for_node(
calling_node: &ast::CallableExpr,
token: &SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> {
let callable = match &calling_node {
let callable = match calling_node {
ast::CallableExpr::Call(call) => {
let expr = call.expr()?;
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
@ -66,13 +67,78 @@ pub fn callable_for_node(
ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
}?;
let active_param = if let Some(arg_list) = calling_node.arg_list() {
let param = arg_list
.args()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
.count();
Some(param)
Some(
arg_list
.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.filter(|t| t.kind() == T![,])
.take_while(|t| t.text_range().start() <= token.text_range().start())
.count(),
)
} else {
None
};
Some((callable, active_param))
}
pub fn generic_def_for_node(
sema: &Semantics<'_, RootDatabase>,
generic_arg_list: &ast::GenericArgList,
token: &SyntaxToken,
) -> Option<(hir::GenericDef, usize, bool)> {
let parent = generic_arg_list.syntax().parent()?;
let def = match_ast! {
match parent {
ast::PathSegment(ps) => {
let res = sema.resolve_path(&ps.parent_path())?;
let generic_def: hir::GenericDef = match res {
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
hir::PathResolution::BuiltinAttr(_)
| hir::PathResolution::ToolModule(_)
| hir::PathResolution::Local(_)
| hir::PathResolution::TypeParam(_)
| hir::PathResolution::ConstParam(_)
| hir::PathResolution::SelfType(_)
| hir::PathResolution::DeriveHelper(_) => return None,
};
generic_def
},
ast::AssocTypeArg(_) => {
// FIXME: We don't record the resolutions for this anywhere atm
return None;
},
ast::MethodCallExpr(mcall) => {
// recv.method::<$0>()
let method = sema.resolve_method_call(&mcall)?;
method.into()
},
_ => return None,
}
};
let active_param = generic_arg_list
.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.filter(|t| t.kind() == T![,])
.take_while(|t| t.text_range().start() <= token.text_range().start())
.count();
let first_arg_is_non_lifetime = generic_arg_list
.generic_args()
.next()
.map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
Some((def, active_param, first_arg_is_non_lifetime))
}

View file

@ -7,12 +7,16 @@ use either::Either;
use hir::{
AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait,
};
use ide_db::{active_parameter::callable_for_node, base_db::FilePosition, FxIndexMap};
use ide_db::{
active_parameter::{callable_for_node, generic_def_for_node},
base_db::FilePosition,
FxIndexMap,
};
use stdx::format_to;
use syntax::{
algo,
ast::{self, HasArgList},
match_ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, TextSize,
match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize,
};
use crate::RootDatabase;
@ -105,10 +109,10 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
// Stop at multi-line expressions, since the signature of the outer call is not very
// helpful inside them.
if let Some(expr) = ast::Expr::cast(node.clone()) {
if expr.syntax().text().contains_char('\n')
&& expr.syntax().kind() != SyntaxKind::RECORD_EXPR
if !matches!(expr, ast::Expr::RecordExpr(..))
&& expr.syntax().text().contains_char('\n')
{
return None;
break;
}
}
}
@ -122,18 +126,16 @@ fn signature_help_for_call(
token: SyntaxToken,
) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef
let mut node = arg_list.syntax().parent()?;
let mut nodes = arg_list.syntax().ancestors().skip(1);
let calling_node = loop {
if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
if callable
if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) {
let inside_callable = callable
.arg_list()
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
{
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()));
if inside_callable {
break callable;
}
}
node = node.parent()?;
};
let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?;
@ -216,59 +218,11 @@ fn signature_help_for_call(
fn signature_help_for_generics(
sema: &Semantics<'_, RootDatabase>,
garg_list: ast::GenericArgList,
arg_list: ast::GenericArgList,
token: SyntaxToken,
) -> Option<SignatureHelp> {
let arg_list = garg_list
.syntax()
.ancestors()
.filter_map(ast::GenericArgList::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
let mut active_parameter = arg_list
.generic_args()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
.count();
let first_arg_is_non_lifetime = arg_list
.generic_args()
.next()
.map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
let mut generics_def = if let Some(path) =
arg_list.syntax().ancestors().find_map(ast::Path::cast)
{
let res = sema.resolve_path(&path)?;
let generic_def: hir::GenericDef = match res {
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
hir::PathResolution::BuiltinAttr(_)
| hir::PathResolution::ToolModule(_)
| hir::PathResolution::Local(_)
| hir::PathResolution::TypeParam(_)
| hir::PathResolution::ConstParam(_)
| hir::PathResolution::SelfType(_)
| hir::PathResolution::DeriveHelper(_) => return None,
};
generic_def
} else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
{
// recv.method::<$0>()
let method = sema.resolve_method_call(&method_call)?;
method.into()
} else {
return None;
};
let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) =
generic_def_for_node(sema, &arg_list, &token)?;
let mut res = SignatureHelp {
doc: None,
signature: String::new(),
@ -307,9 +261,9 @@ fn signature_help_for_generics(
// eg. `None::<u8>`
// We'll use the signature of the enum, but include the docs of the variant.
res.doc = it.docs(db).map(|it| it.into());
let it = it.parent_enum(db);
format_to!(res.signature, "enum {}", it.name(db));
generics_def = it.into();
let enum_ = it.parent_enum(db);
format_to!(res.signature, "enum {}", enum_.name(db));
generics_def = enum_.into();
}
// These don't have generic args that can be specified
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
@ -388,16 +342,13 @@ fn signature_help_for_record_lit(
record: ast::RecordExpr,
token: SyntaxToken,
) -> Option<SignatureHelp> {
let arg_list = record
.syntax()
.ancestors()
.filter_map(ast::RecordExpr::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
let active_parameter = arg_list
let active_parameter = record
.record_expr_field_list()?
.fields()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
.syntax()
.children_with_tokens()
.filter_map(syntax::NodeOrToken::into_token)
.filter(|t| t.kind() == syntax::T![,])
.take_while(|t| t.text_range().start() <= token.text_range().start())
.count();
let mut res = SignatureHelp {
@ -1594,4 +1545,27 @@ impl S {
"#]],
);
}
#[test]
fn test_enum_in_nested_method_in_lambda() {
check(
r#"
enum A {
A,
B
}
fn bar(_: A) { }
fn main() {
let foo = Foo;
std::thread::spawn(move || { bar(A:$0) } );
}
"#,
expect![[r#"
fn bar(_: A)
^^^^
"#]],
);
}
}

View file

@ -1126,5 +1126,5 @@ fn benchmark_syntax_highlighting_parser() {
.filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
.count()
};
assert_eq!(hash, 1609);
assert_eq!(hash, 1608);
}

View file

@ -20,4 +20,5 @@ limit.workspace = true
[dev-dependencies]
expect-test = "1.4.0"
stdx.workspace = true
sourcegen.workspace = true

View file

@ -200,6 +200,8 @@ impl BlockLike {
}
}
const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
match p.current() {
T![pub] => {
@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) {
p.eat(T!['}']);
m.complete(p, ERROR);
}
/// The `parser` passed this is required to at least consume one token if it returns `true`.
/// If the `parser` returns false, parsing will stop.
fn delimited(
p: &mut Parser<'_>,
bra: SyntaxKind,
ket: SyntaxKind,
delim: SyntaxKind,
first_set: TokenSet,
mut parser: impl FnMut(&mut Parser<'_>) -> bool,
) {
p.bump(bra);
while !p.at(ket) && !p.at(EOF) {
if !parser(p) {
break;
}
if !p.at(delim) {
if p.at_ts(first_set) {
p.error(format!("expected {:?}", delim));
} else {
break;
}
} else {
p.bump(delim);
}
}
p.expect(ket);
}

View file

@ -1,5 +1,7 @@
use super::*;
pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]);
pub(super) fn inner_attrs(p: &mut Parser<'_>) {
while p.at(T![#]) && p.nth(1) == T![!] {
attr(p, true);

View file

@ -1,5 +1,7 @@
mod atom;
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*;
pub(crate) use self::atom::{block_expr, match_arm_list};
@ -68,6 +70,12 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) {
Err(m) => m,
};
if !p.at_ts(EXPR_FIRST) {
p.err_and_bump("expected expression, item or let statement");
m.abandon(p);
return;
}
if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) {
// test no_semi_after_block
@ -227,6 +235,12 @@ fn expr_bp(
attributes::outer_attrs(p);
m
});
if !p.at_ts(EXPR_FIRST) {
p.err_recover("expected expression", atom::EXPR_RECOVERY_SET);
m.abandon(p);
return None;
}
let mut lhs = match lhs(p, r) {
Some((lhs, blocklike)) => {
let lhs = lhs.extend_to(p, m);
@ -551,23 +565,20 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
m.complete(p, CAST_EXPR)
}
// test_err arg_list_recovery
// fn main() {
// foo(bar::);
// foo(bar:);
// foo(bar+);
// }
fn arg_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
let m = p.start();
p.bump(T!['(']);
while !p.at(T![')']) && !p.at(EOF) {
// test arg_with_attr
// fn main() {
// foo(#[attr] 92)
// }
if !expr(p) {
break;
}
if !p.at(T![')']) && !p.expect(T![,]) {
break;
}
}
p.eat(T![')']);
// test arg_with_attr
// fn main() {
// foo(#[attr] 92)
// }
delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr);
m.complete(p, ARG_LIST);
}

View file

@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
T!['{'],
T!['['],
T![|],
T![move],
T![box],
T![if],
T![while],
T![match],
T![unsafe],
T![return],
T![yield],
T![do],
T![break],
T![continue],
T![async],
T![try],
T![box],
T![break],
T![const],
T![loop],
T![continue],
T![do],
T![for],
T![if],
T![let],
T![loop],
T![match],
T![move],
T![return],
T![static],
T![try],
T![unsafe],
T![while],
T![yield],
LIFETIME_IDENT,
]));
const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]);
pub(super) const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![')'], T![']']]);
pub(super) fn atom_expr(
p: &mut Parser<'_>,
@ -116,7 +118,7 @@ pub(super) fn atom_expr(
// fn main() {
// 'loop: impl
// }
p.error("expected a loop");
p.error("expected a loop or block");
m.complete(p, ERROR);
return None;
}
@ -157,7 +159,7 @@ pub(super) fn atom_expr(
T![for] => for_expr(p, None),
_ => {
p.err_recover("expected expression", EXPR_RECOVERY_SET);
p.err_and_bump("expected expression");
return None;
}
};

View file

@ -5,27 +5,35 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo
if p.at(T![::]) && p.nth(2) == T![<] {
m = p.start();
p.bump(T![::]);
p.bump(T![<]);
} else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
m = p.start();
p.bump(T![<]);
} else {
return;
}
while !p.at(EOF) && !p.at(T![>]) {
generic_arg(p);
if !p.at(T![>]) && !p.expect(T![,]) {
break;
}
}
p.expect(T![>]);
delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
m.complete(p, GENERIC_ARG_LIST);
}
const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[
LIFETIME_IDENT,
IDENT,
T!['{'],
T![true],
T![false],
T![-],
INT_NUMBER,
FLOAT_NUMBER,
CHAR,
BYTE,
STRING,
BYTE_STRING,
])
.union(types::TYPE_FIRST);
// test generic_arg
// type T = S<i32>;
fn generic_arg(p: &mut Parser<'_>) {
fn generic_arg(p: &mut Parser<'_>) -> bool {
match p.current() {
LIFETIME_IDENT => lifetime_arg(p),
T!['{'] | T![true] | T![false] | T![-] => const_arg(p),
@ -68,8 +76,10 @@ fn generic_arg(p: &mut Parser<'_>) {
}
}
}
_ => type_arg(p),
_ if p.at_ts(types::TYPE_FIRST) => type_arg(p),
_ => return false,
}
true
}
// test lifetime_arg

View file

@ -1,3 +1,5 @@
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*;
pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
@ -11,32 +13,31 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
fn generic_param_list(p: &mut Parser<'_>) {
assert!(p.at(T![<]));
let m = p.start();
p.bump(T![<]);
delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| {
// test generic_param_attribute
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
let m = p.start();
attributes::outer_attrs(p);
generic_param(p, m)
});
while !p.at(EOF) && !p.at(T![>]) {
generic_param(p);
if !p.at(T![>]) && !p.expect(T![,]) {
break;
}
}
p.expect(T![>]);
m.complete(p, GENERIC_PARAM_LIST);
}
fn generic_param(p: &mut Parser<'_>) {
let m = p.start();
// test generic_param_attribute
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
attributes::outer_attrs(p);
const GENERIC_PARAM_FIRST: TokenSet = TokenSet::new(&[IDENT, LIFETIME_IDENT, T![const]]);
fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool {
match p.current() {
LIFETIME_IDENT => lifetime_param(p, m),
IDENT => type_param(p, m),
T![const] => const_param(p, m),
_ => {
m.abandon(p);
p.err_and_bump("expected type parameter");
p.err_and_bump("expected generic parameter");
return false;
}
}
true
}
// test lifetime_param

View file

@ -1,3 +1,5 @@
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*;
// test struct_item
@ -141,28 +143,31 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
}
}
const TUPLE_FIELD_FIRST: TokenSet =
types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST);
fn tuple_field_list(p: &mut Parser<'_>) {
assert!(p.at(T!['(']));
let m = p.start();
p.bump(T!['(']);
while !p.at(T![')']) && !p.at(EOF) {
delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| {
let m = p.start();
// test tuple_field_attrs
// struct S (#[attr] f32);
attributes::outer_attrs(p);
opt_visibility(p, true);
let has_vis = opt_visibility(p, true);
if !p.at_ts(types::TYPE_FIRST) {
p.error("expected a type");
m.complete(p, ERROR);
break;
if has_vis {
m.complete(p, ERROR);
} else {
m.abandon(p);
}
return false;
}
types::type_(p);
m.complete(p, TUPLE_FIELD);
true
});
if !p.at(T![')']) {
p.expect(T![,]);
}
}
p.expect(T![')']);
m.complete(p, TUPLE_FIELD_LIST);
}

View file

@ -1,3 +1,5 @@
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*;
// test param_list
@ -66,14 +68,20 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) {
}
};
if !p.at_ts(PARAM_FIRST) {
if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
p.error("expected value parameter");
m.abandon(p);
break;
}
param(p, m, flavor);
if !p.at(ket) {
p.expect(T![,]);
if !p.at(T![,]) {
if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
p.error("expected `,`");
} else {
break;
}
} else {
p.bump(T![,]);
}
}

View file

@ -67,6 +67,10 @@ fn path_for_qualifier(
}
}
const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet =
items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]]));
const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET;
fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
let m = p.start();
// test qual_paths
@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
m.complete(p, NAME_REF);
}
_ => {
p.err_recover("expected identifier", items::ITEM_RECOVERY_SET);
let recover_set = match mode {
Mode::Use => items::ITEM_RECOVERY_SET,
Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET,
Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET,
};
p.err_recover("expected identifier", recover_set);
if empty {
// test_err empty_segment
// use crate::;

View file

@ -17,8 +17,9 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[
T![Self],
]));
const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
T![')'],
T![>],
T![,],
// test_err struct_field_recover
// struct S { f pub g: () }

View file

@ -15,6 +15,7 @@ use crate::{LexedStr, TopEntryPoint};
#[test]
fn lex_ok() {
for case in TestCase::list("lexer/ok") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let actual = lex(&case.text);
expect_file![case.rast].assert_eq(&actual)
}
@ -23,6 +24,7 @@ fn lex_ok() {
#[test]
fn lex_err() {
for case in TestCase::list("lexer/err") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let actual = lex(&case.text);
expect_file![case.rast].assert_eq(&actual)
}
@ -46,6 +48,7 @@ fn lex(text: &str) -> String {
#[test]
fn parse_ok() {
for case in TestCase::list("parser/ok") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual);
@ -55,6 +58,7 @@ fn parse_ok() {
#[test]
fn parse_inline_ok() {
for case in TestCase::list("parser/inline/ok") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual);
@ -64,6 +68,7 @@ fn parse_inline_ok() {
#[test]
fn parse_err() {
for case in TestCase::list("parser/err") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual)
@ -73,6 +78,7 @@ fn parse_err() {
#[test]
fn parse_inline_err() {
for case in TestCase::list("parser/inline/err") {
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
expect_file![case.rast].assert_eq(&actual)

View file

@ -65,7 +65,7 @@ fn macro_stmt() {
MACRO_STMTS
ERROR
SHEBANG "#!/usr/bin/rust"
error 0: expected expression
error 0: expected expression, item or let statement
"##]],
);
check(

View file

@ -44,8 +44,7 @@ SOURCE_FILE
IDENT "T"
SEMICOLON ";"
WHITESPACE "\n"
error 9: expected type parameter
error 11: expected COMMA
error 9: expected generic parameter
error 11: expected R_ANGLE
error 11: expected `;`, `{`, or `(`
error 12: expected an item

View file

@ -43,17 +43,14 @@ SOURCE_FILE
IDENT "Box"
GENERIC_ARG_LIST
L_ANGLE "<"
TYPE_ARG
ERROR
AT "@"
WHITESPACE " "
TUPLE_FIELD
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Any"
ERROR
ERROR
AT "@"
WHITESPACE " "
MACRO_CALL
PATH
PATH_SEGMENT
NAME_REF
IDENT "Any"
ERROR
R_ANGLE ">"
ERROR
@ -69,17 +66,14 @@ SOURCE_FILE
ERROR
SEMICOLON ";"
WHITESPACE "\n\n"
error 67: expected type
error 68: expected COMMA
error 68: expected R_ANGLE
error 68: expected COMMA
error 68: expected R_ANGLE
error 68: expected COMMA
error 68: expected R_ANGLE
error 68: expected COMMA
error 72: expected COMMA
error 72: expected a type
error 72: expected R_PAREN
error 67: expected R_ANGLE
error 67: expected R_ANGLE
error 67: expected R_ANGLE
error 67: expected R_PAREN
error 67: expected SEMICOLON
error 67: expected an item
error 72: expected BANG
error 72: expected `{`, `[`, `(`
error 72: expected SEMICOLON
error 72: expected an item
error 73: expected an item

View file

@ -145,27 +145,29 @@ SOURCE_FILE
error 16: expected expression
error 17: expected R_BRACK
error 17: expected SEMICOLON
error 17: expected expression
error 17: expected expression, item or let statement
error 25: expected a name
error 26: expected `;`, `{`, or `(`
error 30: expected pattern
error 31: expected SEMICOLON
error 53: expected expression
error 54: expected R_PAREN
error 54: expected SEMICOLON
error 54: expected expression
error 54: expected expression, item or let statement
error 60: expected type
error 60: expected `{`
error 60: expected expression
error 60: expected expression, item or let statement
error 65: expected pattern
error 65: expected SEMICOLON
error 65: expected expression
error 65: expected expression, item or let statement
error 92: expected expression
error 93: expected R_PAREN
error 93: expected SEMICOLON
error 93: expected expression
error 95: expected expression
error 96: expected expression
error 93: expected expression, item or let statement
error 95: expected expression, item or let statement
error 96: expected expression, item or let statement
error 103: expected a name
error 104: expected `{`
error 108: expected pattern
error 108: expected SEMICOLON
error 108: expected expression
error 108: expected expression, item or let statement

View file

@ -168,44 +168,44 @@ SOURCE_FILE
L_PAREN "("
ERROR
QUESTION "?"
EXPR_STMT
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Sized"
TYPE_ARG
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Sized"
ERROR
R_PAREN ")"
WHITESPACE " "
ERROR
PLUS "+"
WHITESPACE " "
TUPLE_EXPR
L_PAREN "("
CLOSURE_EXPR
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
EXPR_STMT
BIN_EXPR
BIN_EXPR
BIN_EXPR
TUPLE_EXPR
L_PAREN "("
CLOSURE_EXPR
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
L_ANGLE "<"
ERROR
LIFETIME_IDENT "'a"
R_ANGLE ">"
ERROR
R_PAREN ")"
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
L_ANGLE "<"
ERROR
LIFETIME_IDENT "'a"
R_ANGLE ">"
R_PAREN ")"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
@ -220,108 +220,93 @@ SOURCE_FILE
R_ANGLE ">"
ERROR
SEMICOLON ";"
WHITESPACE "\n "
LET_EXPR
LET_KW "let"
WHITESPACE " "
WILDCARD_PAT
UNDERSCORE "_"
ERROR
COLON ":"
WHITESPACE "\n "
LET_STMT
LET_KW "let"
WHITESPACE " "
BIN_EXPR
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Box"
L_ANGLE "<"
TUPLE_EXPR
L_PAREN "("
CLOSURE_EXPR
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
BIN_EXPR
BIN_EXPR
BIN_EXPR
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
WILDCARD_PAT
UNDERSCORE "_"
COLON ":"
WHITESPACE " "
DYN_TRAIT_TYPE
TYPE_BOUND_LIST
TYPE_BOUND
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Box"
GENERIC_ARG_LIST
L_ANGLE "<"
ERROR
LIFETIME_IDENT "'a"
R_ANGLE ">"
ERROR
R_PAREN ")"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
PAREN_EXPR
L_PAREN "("
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "Copy"
R_PAREN ")"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
PAREN_EXPR
L_PAREN "("
ERROR
QUESTION "?"
PATH_EXPR
TYPE_ARG
PAREN_TYPE
L_PAREN "("
FOR_TYPE
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
GENERIC_ARG_LIST
L_ANGLE "<"
LIFETIME_ARG
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
R_PAREN ")"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
TYPE_BOUND
L_PAREN "("
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Copy"
R_PAREN ")"
WHITESPACE " "
PLUS "+"
WHITESPACE " "
TYPE_BOUND
L_PAREN "("
QUESTION "?"
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Sized"
R_PAREN ")"
R_ANGLE ">"
ERROR
SEMICOLON ";"
ERROR
R_ANGLE ">"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
error 88: expected COMMA
error 88: expected R_ANGLE
error 121: expected SEMICOLON
error 121: expected expression
error 121: expected expression, item or let statement
error 140: expected type
error 141: expected R_PAREN
error 141: expected COMMA
error 141: expected R_ANGLE
error 141: expected SEMICOLON
error 146: expected R_ANGLE
error 146: expected SEMICOLON
error 146: expected expression
error 148: expected expression
error 146: expected expression, item or let statement
error 148: expected expression, item or let statement
error 158: expected `|`
error 158: expected COMMA
error 165: expected expression
error 168: expected expression
error 179: expected expression
error 180: expected COMMA
error 190: expected EQ
error 190: expected expression
error 191: expected COMMA
error 204: expected `|`
error 204: expected COMMA
error 211: expected expression
error 214: expected expression
error 228: expected expression
error 229: expected R_PAREN
error 229: expected COMMA
error 236: expected expression
error 237: expected COMMA
error 237: expected expression
error 237: expected R_PAREN
error 180: expected SEMICOLON
error 215: expected R_ANGLE
error 235: expected SEMICOLON
error 235: expected expression, item or let statement

View file

@ -156,8 +156,7 @@ SOURCE_FILE
PATH_SEGMENT
NAME_REF
IDENT "i32"
WHITESPACE " "
ERROR
WHITESPACE " "
ERROR
L_CURLY "{"
R_CURLY "}"
@ -199,10 +198,8 @@ error 95: expected type
error 95: expected COMMA
error 96: expected field
error 98: expected field declaration
error 371: expected R_PAREN
error 371: expected COMMA
error 372: expected a type
error 372: expected R_PAREN
error 372: expected COMMA
error 372: expected enum variant
error 374: expected enum variant
error 494: expected pattern

View file

@ -72,4 +72,4 @@ SOURCE_FILE
error 24: expected existential, fn, trait or impl
error 41: expected existential, fn, trait or impl
error 56: expected a block
error 75: expected a loop
error 75: expected a loop or block

View file

@ -12,7 +12,7 @@ SOURCE_FILE
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
@ -41,13 +41,14 @@ SOURCE_FILE
COLON2 "::"
ERROR
L_ANGLE "<"
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "nope"
SHR ">>"
TYPE_ARG
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "nope"
R_ANGLE ">"
R_ANGLE ">"
ERROR
SEMICOLON ";"
WHITESPACE "\n"
@ -114,8 +115,6 @@ SOURCE_FILE
WHITESPACE "\n"
error 30: expected identifier
error 31: expected COMMA
error 31: expected R_ANGLE
error 31: expected SEMICOLON
error 37: expected expression
error 75: expected identifier
error 76: expected SEMICOLON

View file

@ -23,6 +23,6 @@ SOURCE_FILE
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
error 22: expected a loop
error 22: expected a loop or block
error 27: expected type
error 27: expected `{`

View file

@ -0,0 +1,77 @@
SOURCE_FILE
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "main"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "foo"
ARG_LIST
L_PAREN "("
PATH_EXPR
PATH
PATH
PATH_SEGMENT
NAME_REF
IDENT "bar"
COLON2 "::"
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "foo"
ARG_LIST
L_PAREN "("
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "bar"
ERROR
COLON ":"
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
CALL_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "foo"
ARG_LIST
L_PAREN "("
BIN_EXPR
PATH_EXPR
PATH
PATH_SEGMENT
NAME_REF
IDENT "bar"
PLUS "+"
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"
error 25: expected identifier
error 39: expected COMMA
error 39: expected expression
error 55: expected expression

View file

@ -0,0 +1,5 @@
fn main() {
foo(bar::);
foo(bar:);
foo(bar+);
}

View file

@ -49,5 +49,5 @@ SOURCE_FILE
R_CURLY "}"
WHITESPACE "\n"
error 6: missing type for function parameter
error 6: expected COMMA
error 6: expected `,`
error 16: missing type for function parameter