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", "limit",
"rustc-ap-rustc_lexer", "rustc-ap-rustc_lexer",
"sourcegen", "sourcegen",
"stdx",
] ]
[[package]] [[package]]

View file

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

View file

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

View file

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

View file

@ -2,9 +2,10 @@
use either::Either; use either::Either;
use hir::{Semantics, Type}; use hir::{Semantics, Type};
use parser::T;
use syntax::{ use syntax::{
ast::{self, HasArgList, HasName}, ast::{self, HasArgList, HasName},
AstNode, SyntaxToken, match_ast, AstNode, NodeOrToken, SyntaxToken,
}; };
use crate::RootDatabase; use crate::RootDatabase;
@ -58,7 +59,7 @@ pub fn callable_for_node(
calling_node: &ast::CallableExpr, calling_node: &ast::CallableExpr,
token: &SyntaxToken, token: &SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> { ) -> Option<(hir::Callable, Option<usize>)> {
let callable = match &calling_node { let callable = match calling_node {
ast::CallableExpr::Call(call) => { ast::CallableExpr::Call(call) => {
let expr = call.expr()?; let expr = call.expr()?;
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db) 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), ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
}?; }?;
let active_param = if let Some(arg_list) = calling_node.arg_list() { let active_param = if let Some(arg_list) = calling_node.arg_list() {
let param = arg_list Some(
.args() arg_list
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) .syntax()
.count(); .children_with_tokens()
Some(param) .filter_map(NodeOrToken::into_token)
.filter(|t| t.kind() == T![,])
.take_while(|t| t.text_range().start() <= token.text_range().start())
.count(),
)
} else { } else {
None None
}; };
Some((callable, active_param)) 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::{ use hir::{
AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait, 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 stdx::format_to;
use syntax::{ use syntax::{
algo, algo,
ast::{self, HasArgList}, ast::{self, HasArgList},
match_ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, TextSize, match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize,
}; };
use crate::RootDatabase; 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 // Stop at multi-line expressions, since the signature of the outer call is not very
// helpful inside them. // helpful inside them.
if let Some(expr) = ast::Expr::cast(node.clone()) { if let Some(expr) = ast::Expr::cast(node.clone()) {
if expr.syntax().text().contains_char('\n') if !matches!(expr, ast::Expr::RecordExpr(..))
&& expr.syntax().kind() != SyntaxKind::RECORD_EXPR && expr.syntax().text().contains_char('\n')
{ {
return None; break;
} }
} }
} }
@ -122,18 +126,16 @@ fn signature_help_for_call(
token: SyntaxToken, token: SyntaxToken,
) -> Option<SignatureHelp> { ) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef // 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 { let calling_node = loop {
if let Some(callable) = ast::CallableExpr::cast(node.clone()) { if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) {
if callable let inside_callable = callable
.arg_list() .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; break callable;
} }
} }
node = node.parent()?;
}; };
let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?; 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( fn signature_help_for_generics(
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
garg_list: ast::GenericArgList, arg_list: ast::GenericArgList,
token: SyntaxToken, token: SyntaxToken,
) -> Option<SignatureHelp> { ) -> Option<SignatureHelp> {
let arg_list = garg_list let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) =
.syntax() generic_def_for_node(sema, &arg_list, &token)?;
.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 res = SignatureHelp { let mut res = SignatureHelp {
doc: None, doc: None,
signature: String::new(), signature: String::new(),
@ -307,9 +261,9 @@ fn signature_help_for_generics(
// eg. `None::<u8>` // eg. `None::<u8>`
// We'll use the signature of the enum, but include the docs of the variant. // We'll use the signature of the enum, but include the docs of the variant.
res.doc = it.docs(db).map(|it| it.into()); res.doc = it.docs(db).map(|it| it.into());
let it = it.parent_enum(db); let enum_ = it.parent_enum(db);
format_to!(res.signature, "enum {}", it.name(db)); format_to!(res.signature, "enum {}", enum_.name(db));
generics_def = it.into(); generics_def = enum_.into();
} }
// These don't have generic args that can be specified // These don't have generic args that can be specified
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
@ -388,16 +342,13 @@ fn signature_help_for_record_lit(
record: ast::RecordExpr, record: ast::RecordExpr,
token: SyntaxToken, token: SyntaxToken,
) -> Option<SignatureHelp> { ) -> Option<SignatureHelp> {
let arg_list = record let active_parameter = record
.syntax()
.ancestors()
.filter_map(ast::RecordExpr::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
let active_parameter = arg_list
.record_expr_field_list()? .record_expr_field_list()?
.fields() .syntax()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) .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(); .count();
let mut res = SignatureHelp { 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)) .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
.count() .count()
}; };
assert_eq!(hash, 1609); assert_eq!(hash, 1608);
} }

View file

@ -20,4 +20,5 @@ limit.workspace = true
[dev-dependencies] [dev-dependencies]
expect-test = "1.4.0" expect-test = "1.4.0"
stdx.workspace = true
sourcegen.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 { fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
match p.current() { match p.current() {
T![pub] => { T![pub] => {
@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) {
p.eat(T!['}']); p.eat(T!['}']);
m.complete(p, ERROR); 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::*; use super::*;
pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]);
pub(super) fn inner_attrs(p: &mut Parser<'_>) { pub(super) fn inner_attrs(p: &mut Parser<'_>) {
while p.at(T![#]) && p.nth(1) == T![!] { while p.at(T![#]) && p.nth(1) == T![!] {
attr(p, true); attr(p, true);

View file

@ -1,5 +1,7 @@
mod atom; mod atom;
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*; use super::*;
pub(crate) use self::atom::{block_expr, match_arm_list}; 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, 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 let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) { if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) {
// test no_semi_after_block // test no_semi_after_block
@ -227,6 +235,12 @@ fn expr_bp(
attributes::outer_attrs(p); attributes::outer_attrs(p);
m 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) { let mut lhs = match lhs(p, r) {
Some((lhs, blocklike)) => { Some((lhs, blocklike)) => {
let lhs = lhs.extend_to(p, m); 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) m.complete(p, CAST_EXPR)
} }
// test_err arg_list_recovery
// fn main() {
// foo(bar::);
// foo(bar:);
// foo(bar+);
// }
fn arg_list(p: &mut Parser<'_>) { fn arg_list(p: &mut Parser<'_>) {
assert!(p.at(T!['('])); assert!(p.at(T!['(']));
let m = p.start(); let m = p.start();
p.bump(T!['(']); // test arg_with_attr
while !p.at(T![')']) && !p.at(EOF) { // fn main() {
// test arg_with_attr // foo(#[attr] 92)
// fn main() { // }
// foo(#[attr] 92) delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr);
// }
if !expr(p) {
break;
}
if !p.at(T![')']) && !p.expect(T![,]) {
break;
}
}
p.eat(T![')']);
m.complete(p, ARG_LIST); m.complete(p, ARG_LIST);
} }

View file

@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
T!['{'], T!['{'],
T!['['], 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![async],
T![try], T![box],
T![break],
T![const], T![const],
T![loop], T![continue],
T![do],
T![for], 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, 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( pub(super) fn atom_expr(
p: &mut Parser<'_>, p: &mut Parser<'_>,
@ -116,7 +118,7 @@ pub(super) fn atom_expr(
// fn main() { // fn main() {
// 'loop: impl // 'loop: impl
// } // }
p.error("expected a loop"); p.error("expected a loop or block");
m.complete(p, ERROR); m.complete(p, ERROR);
return None; return None;
} }
@ -157,7 +159,7 @@ pub(super) fn atom_expr(
T![for] => for_expr(p, None), T![for] => for_expr(p, None),
_ => { _ => {
p.err_recover("expected expression", EXPR_RECOVERY_SET); p.err_and_bump("expected expression");
return None; 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![<] { if p.at(T![::]) && p.nth(2) == T![<] {
m = p.start(); m = p.start();
p.bump(T![::]); p.bump(T![::]);
p.bump(T![<]);
} else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] { } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
m = p.start(); m = p.start();
p.bump(T![<]);
} else { } else {
return; return;
} }
while !p.at(EOF) && !p.at(T![>]) { delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
generic_arg(p);
if !p.at(T![>]) && !p.expect(T![,]) {
break;
}
}
p.expect(T![>]);
m.complete(p, GENERIC_ARG_LIST); 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 // test generic_arg
// type T = S<i32>; // type T = S<i32>;
fn generic_arg(p: &mut Parser<'_>) { fn generic_arg(p: &mut Parser<'_>) -> bool {
match p.current() { match p.current() {
LIFETIME_IDENT => lifetime_arg(p), LIFETIME_IDENT => lifetime_arg(p),
T!['{'] | T![true] | T![false] | T![-] => const_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 // test lifetime_arg

View file

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

View file

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

View file

@ -1,3 +1,5 @@
use crate::grammar::attributes::ATTRIBUTE_FIRST;
use super::*; use super::*;
// test param_list // 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"); p.error("expected value parameter");
m.abandon(p); m.abandon(p);
break; break;
} }
param(p, m, flavor); param(p, m, flavor);
if !p.at(ket) { if !p.at(T![,]) {
p.expect(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) { fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
let m = p.start(); let m = p.start();
// test qual_paths // test qual_paths
@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
m.complete(p, NAME_REF); 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 { if empty {
// test_err empty_segment // test_err empty_segment
// use crate::; // use crate::;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -145,27 +145,29 @@ SOURCE_FILE
error 16: expected expression error 16: expected expression
error 17: expected R_BRACK error 17: expected R_BRACK
error 17: expected SEMICOLON error 17: expected SEMICOLON
error 17: expected expression error 17: expected expression, item or let statement
error 25: expected a name error 25: expected a name
error 26: expected `;`, `{`, or `(` error 26: expected `;`, `{`, or `(`
error 30: expected pattern error 30: expected pattern
error 31: expected SEMICOLON error 31: expected SEMICOLON
error 53: expected expression error 53: expected expression
error 54: expected R_PAREN
error 54: expected SEMICOLON error 54: expected SEMICOLON
error 54: expected expression error 54: expected expression, item or let statement
error 60: expected type error 60: expected type
error 60: expected `{` error 60: expected `{`
error 60: expected expression error 60: expected expression, item or let statement
error 65: expected pattern error 65: expected pattern
error 65: expected SEMICOLON error 65: expected SEMICOLON
error 65: expected expression error 65: expected expression, item or let statement
error 92: expected expression error 92: expected expression
error 93: expected R_PAREN
error 93: expected SEMICOLON error 93: expected SEMICOLON
error 93: expected expression error 93: expected expression, item or let statement
error 95: expected expression error 95: expected expression, item or let statement
error 96: expected expression error 96: expected expression, item or let statement
error 103: expected a name error 103: expected a name
error 104: expected `{` error 104: expected `{`
error 108: expected pattern error 108: expected pattern
error 108: expected SEMICOLON 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 "(" L_PAREN "("
ERROR ERROR
QUESTION "?" QUESTION "?"
EXPR_STMT TYPE_ARG
PATH_EXPR PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Sized" IDENT "Sized"
ERROR ERROR
R_PAREN ")" R_PAREN ")"
WHITESPACE " " WHITESPACE " "
ERROR ERROR
PLUS "+" PLUS "+"
WHITESPACE " " WHITESPACE " "
TUPLE_EXPR EXPR_STMT
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
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 BIN_EXPR
PATH_EXPR BIN_EXPR
PATH PATH_EXPR
PATH_SEGMENT PATH
NAME_REF PATH_SEGMENT
IDENT "Trait" NAME_REF
L_ANGLE "<" IDENT "Trait"
ERROR L_ANGLE "<"
LIFETIME_IDENT "'a" ERROR
R_ANGLE ">" LIFETIME_IDENT "'a"
ERROR R_ANGLE ">"
R_PAREN ")" R_PAREN ")"
WHITESPACE " " WHITESPACE " "
PLUS "+" PLUS "+"
WHITESPACE " " WHITESPACE " "
@ -220,108 +220,93 @@ SOURCE_FILE
R_ANGLE ">" R_ANGLE ">"
ERROR ERROR
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n " WHITESPACE "\n "
LET_EXPR LET_STMT
LET_KW "let" LET_KW "let"
WHITESPACE " "
WILDCARD_PAT
UNDERSCORE "_"
ERROR
COLON ":"
WHITESPACE " " WHITESPACE " "
BIN_EXPR WILDCARD_PAT
BIN_EXPR UNDERSCORE "_"
PATH_EXPR COLON ":"
PATH WHITESPACE " "
PATH_SEGMENT DYN_TRAIT_TYPE
NAME_REF TYPE_BOUND_LIST
IDENT "Box" TYPE_BOUND
L_ANGLE "<" PATH_TYPE
TUPLE_EXPR PATH
L_PAREN "(" PATH_SEGMENT
CLOSURE_EXPR NAME_REF
FOR_KW "for" IDENT "Box"
GENERIC_PARAM_LIST GENERIC_ARG_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"
L_ANGLE "<" L_ANGLE "<"
ERROR TYPE_ARG
LIFETIME_IDENT "'a" PAREN_TYPE
R_ANGLE ">" L_PAREN "("
ERROR FOR_TYPE
R_PAREN ")" FOR_KW "for"
WHITESPACE " " GENERIC_PARAM_LIST
PLUS "+" L_ANGLE "<"
WHITESPACE " " LIFETIME_PARAM
PAREN_EXPR LIFETIME
L_PAREN "(" LIFETIME_IDENT "'a"
PATH_EXPR R_ANGLE ">"
PATH WHITESPACE " "
PATH_SEGMENT PATH_TYPE
NAME_REF PATH
IDENT "Copy" PATH_SEGMENT
R_PAREN ")" NAME_REF
WHITESPACE " " IDENT "Trait"
PLUS "+" GENERIC_ARG_LIST
WHITESPACE " " L_ANGLE "<"
PAREN_EXPR LIFETIME_ARG
L_PAREN "(" LIFETIME
ERROR LIFETIME_IDENT "'a"
QUESTION "?" R_ANGLE ">"
PATH_EXPR 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
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Sized" IDENT "Sized"
R_PAREN ")" R_PAREN ")"
R_ANGLE ">" ERROR
ERROR R_ANGLE ">"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
R_CURLY "}" R_CURLY "}"
WHITESPACE "\n" WHITESPACE "\n"
error 88: expected COMMA
error 88: expected R_ANGLE error 88: expected R_ANGLE
error 121: expected SEMICOLON error 121: expected SEMICOLON
error 121: expected expression error 121: expected expression, item or let statement
error 140: expected type error 140: expected type
error 141: expected R_PAREN error 141: expected R_PAREN
error 141: expected COMMA error 141: expected COMMA
error 141: expected R_ANGLE error 146: expected R_ANGLE
error 141: expected SEMICOLON
error 146: expected SEMICOLON error 146: expected SEMICOLON
error 146: expected expression error 146: expected expression, item or let statement
error 148: expected expression error 148: expected expression, item or let statement
error 158: expected `|` error 158: expected `|`
error 158: expected COMMA error 158: expected COMMA
error 165: expected expression error 165: expected expression
error 168: expected expression error 168: expected expression
error 179: expected expression error 179: expected expression
error 180: expected COMMA error 180: expected SEMICOLON
error 190: expected EQ error 215: expected R_ANGLE
error 190: expected expression error 235: expected SEMICOLON
error 191: expected COMMA error 235: expected expression, item or let statement
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

View file

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

View file

@ -72,4 +72,4 @@ SOURCE_FILE
error 24: expected existential, fn, trait or impl error 24: expected existential, fn, trait or impl
error 41: expected existential, fn, trait or impl error 41: expected existential, fn, trait or impl
error 56: expected a block 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 STMT_LIST
L_CURLY "{" L_CURLY "{"
WHITESPACE "\n " WHITESPACE "\n "
EXPR_STMT BIN_EXPR
PATH_EXPR PATH_EXPR
PATH PATH
PATH_SEGMENT PATH_SEGMENT
@ -41,13 +41,14 @@ SOURCE_FILE
COLON2 "::" COLON2 "::"
ERROR ERROR
L_ANGLE "<" L_ANGLE "<"
BIN_EXPR TYPE_ARG
PATH_EXPR PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "nope" IDENT "nope"
SHR ">>" R_ANGLE ">"
R_ANGLE ">"
ERROR ERROR
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
@ -114,8 +115,6 @@ SOURCE_FILE
WHITESPACE "\n" WHITESPACE "\n"
error 30: expected identifier error 30: expected identifier
error 31: expected COMMA error 31: expected COMMA
error 31: expected R_ANGLE
error 31: expected SEMICOLON
error 37: expected expression error 37: expected expression
error 75: expected identifier error 75: expected identifier
error 76: expected SEMICOLON error 76: expected SEMICOLON

View file

@ -23,6 +23,6 @@ SOURCE_FILE
WHITESPACE "\n" WHITESPACE "\n"
R_CURLY "}" R_CURLY "}"
WHITESPACE "\n" WHITESPACE "\n"
error 22: expected a loop error 22: expected a loop or block
error 27: expected type error 27: expected type
error 27: expected `{` 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 "}" R_CURLY "}"
WHITESPACE "\n" WHITESPACE "\n"
error 6: missing type for function parameter error 6: missing type for function parameter
error 6: expected COMMA error 6: expected `,`
error 16: missing type for function parameter error 16: missing type for function parameter