about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-02-14 12:59:39 +0000
committerbors <bors@rust-lang.org>2023-02-14 12:59:39 +0000
commit44568007d1f3d51b84ad58d2ddeda2badd3aeea5 (patch)
treec33883537d49a35b9d818b5b312cfa974e2c33bd
parent88b3d9f6f43a5e18a3caea1c71db64bf235d97d8 (diff)
parent4f6b5f41d49141a3907bfef2a81108a141d340a8 (diff)
downloadrust-44568007d1f3d51b84ad58d2ddeda2badd3aeea5.tar.gz
rust-44568007d1f3d51b84ad58d2ddeda2badd3aeea5.zip
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
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe.rs2
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mbe/regression.rs21
-rw-r--r--crates/ide-completion/src/context/analysis.rs9
-rw-r--r--crates/ide-completion/src/tests/expression.rs252
-rw-r--r--crates/ide-completion/src/tests/special.rs81
-rw-r--r--crates/ide-db/src/active_parameter.rs80
-rw-r--r--crates/ide/src/signature_help.rs124
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs2
-rw-r--r--crates/parser/Cargo.toml1
-rw-r--r--crates/parser/src/grammar.rs30
-rw-r--r--crates/parser/src/grammar/attributes.rs2
-rw-r--r--crates/parser/src/grammar/expressions.rs39
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs30
-rw-r--r--crates/parser/src/grammar/generic_args.rs32
-rw-r--r--crates/parser/src/grammar/generic_params.rs29
-rw-r--r--crates/parser/src/grammar/items/adt.rs25
-rw-r--r--crates/parser/src/grammar/params.rs14
-rw-r--r--crates/parser/src/grammar/paths.rs11
-rw-r--r--crates/parser/src/grammar/types.rs3
-rw-r--r--crates/parser/src/tests.rs6
-rw-r--r--crates/parser/src/tests/top_entries.rs2
-rw-r--r--crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast3
-rw-r--r--crates/parser/test_data/parser/err/0013_invalid_type.rast38
-rw-r--r--crates/parser/test_data/parser/err/0022_bad_exprs.rast18
-rw-r--r--crates/parser/test_data/parser/err/0024_many_type_parens.rast211
-rw-r--r--crates/parser/test_data/parser/err/0025_nope.rast7
-rw-r--r--crates/parser/test_data/parser/err/0042_weird_blocks.rast2
-rw-r--r--crates/parser/test_data/parser/err/0048_double_fish.rast19
-rw-r--r--crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast2
-rw-r--r--crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast77
-rw-r--r--crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs5
-rw-r--r--crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast2
33 files changed, 815 insertions, 365 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6d8a3eeb739..6ce455424af 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1174,6 +1174,7 @@ dependencies = [
  "limit",
  "rustc-ap-rustc_lexer",
  "sourcegen",
+ "stdx",
 ]
 
 [[package]]
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index 49bbc64bff1..7a3e8c3b05c 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -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"));
 }
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
index d2505e7cafe..8358a46f0a9 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
@@ -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 ";"
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 4c66f95903a..b5eb253d03e 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -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(_)))
     };
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index 043f552bd8a..c1c6a689eb1 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -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
+        "#]],
+    );
+}
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 6052b062320..cb71c7b2bde 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -2,13 +2,20 @@
 
 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(ra_fixture: &str, expect: Expect) {
+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(ra_fixture);
+    expect.assert_eq(&actual)
+}
+
 pub(crate) fn check_with_trigger_character(
     ra_fixture: &str,
     trigger_character: Option<char>,
@@ -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 }
 "#,
diff --git a/crates/ide-db/src/active_parameter.rs b/crates/ide-db/src/active_parameter.rs
index 7109c6fd188..2b6b60547b3 100644
--- a/crates/ide-db/src/active_parameter.rs
+++ b/crates/ide-db/src/active_parameter.rs
@@ -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))
+}
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index a666562f101..f70ca55a508 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -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)
+                       ^^^^
+            "#]],
+        );
+    }
 }
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 2f870d769c0..fc9b5d3ba4c 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -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);
 }
diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml
index 08359133f1a..6e962abd754 100644
--- a/crates/parser/Cargo.toml
+++ b/crates/parser/Cargo.toml
@@ -20,4 +20,5 @@ limit.workspace = true
 [dev-dependencies]
 expect-test = "1.4.0"
 
+stdx.workspace = true
 sourcegen.workspace = true
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 485b612f081..15ec9e167e0 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -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);
+}
diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs
index 0cf6a16f86a..4ecaa6e6a85 100644
--- a/crates/parser/src/grammar/attributes.rs
+++ b/crates/parser/src/grammar/attributes.rs
@@ -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);
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 7516ac3c4bd..4b080102a2c 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -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);
 }
 
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index a23f900b738..efc2603835e 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
         T!['{'],
         T!['['],
         T![|],
-        T![move],
+        T![async],
         T![box],
+        T![break],
+        T![const],
+        T![continue],
+        T![do],
+        T![for],
         T![if],
-        T![while],
+        T![let],
+        T![loop],
         T![match],
-        T![unsafe],
+        T![move],
         T![return],
-        T![yield],
-        T![do],
-        T![break],
-        T![continue],
-        T![async],
+        T![static],
         T![try],
-        T![const],
-        T![loop],
-        T![for],
+        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;
         }
     };
diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs
index c438943a002..919d9b91eba 100644
--- a/crates/parser/src/grammar/generic_args.rs
+++ b/crates/parser/src/grammar/generic_args.rs
@@ -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
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index 6db28ef1323..7fcf938babd 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -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
diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs
index e7d30516b95..17f41b8e13a 100644
--- a/crates/parser/src/grammar/items/adt.rs
+++ b/crates/parser/src/grammar/items/adt.rs
@@ -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);
 }
diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs
index 20e8e95f066..74eae9151a2 100644
--- a/crates/parser/src/grammar/params.rs
+++ b/crates/parser/src/grammar/params.rs
@@ -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![,]);
         }
     }
 
diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs
index af3b6f63cf5..1064ae9970c 100644
--- a/crates/parser/src/grammar/paths.rs
+++ b/crates/parser/src/grammar/paths.rs
@@ -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::;
diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs
index 5c6e18fee8b..7d0b156c5a0 100644
--- a/crates/parser/src/grammar/types.rs
+++ b/crates/parser/src/grammar/types.rs
@@ -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: () }
diff --git a/crates/parser/src/tests.rs b/crates/parser/src/tests.rs
index c1b4e9a7d8a..2fec765bd78 100644
--- a/crates/parser/src/tests.rs
+++ b/crates/parser/src/tests.rs
@@ -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)
diff --git a/crates/parser/src/tests/top_entries.rs b/crates/parser/src/tests/top_entries.rs
index eb640dc7fc7..49dd9e293b8 100644
--- a/crates/parser/src/tests/top_entries.rs
+++ b/crates/parser/src/tests/top_entries.rs
@@ -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(
diff --git a/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast b/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
index a0154321718..cdc01863ab0 100644
--- a/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
+++ b/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast
@@ -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
diff --git a/crates/parser/test_data/parser/err/0013_invalid_type.rast b/crates/parser/test_data/parser/err/0013_invalid_type.rast
index eec84a0c67d..b485c71ab39 100644
--- a/crates/parser/test_data/parser/err/0013_invalid_type.rast
+++ b/crates/parser/test_data/parser/err/0013_invalid_type.rast
@@ -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
diff --git a/crates/parser/test_data/parser/err/0022_bad_exprs.rast b/crates/parser/test_data/parser/err/0022_bad_exprs.rast
index 900394bd960..d97fc6c7209 100644
--- a/crates/parser/test_data/parser/err/0022_bad_exprs.rast
+++ b/crates/parser/test_data/parser/err/0022_bad_exprs.rast
@@ -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
diff --git a/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/crates/parser/test_data/parser/err/0024_many_type_parens.rast
index d374f86610b..f0dbc9b1027 100644
--- a/crates/parser/test_data/parser/err/0024_many_type_parens.rast
+++ b/crates/parser/test_data/parser/err/0024_many_type_parens.rast
@@ -168,75 +168,21 @@ 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 " "
-          BIN_EXPR
-            BIN_EXPR
-              BIN_EXPR
-                BIN_EXPR
-                  PATH_EXPR
-                    PATH
-                      PATH_SEGMENT
-                        NAME_REF
-                          IDENT "Trait"
-                  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 ")"
-            R_ANGLE ">"
-            ERROR
-              SEMICOLON ";"
-          WHITESPACE "\n    "
-          LET_EXPR
-            LET_KW "let"
-            WHITESPACE " "
-            WILDCARD_PAT
-              UNDERSCORE "_"
-            ERROR
-              COLON ":"
-          WHITESPACE " "
+        EXPR_STMT
           BIN_EXPR
             BIN_EXPR
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Box"
-              L_ANGLE "<"
               TUPLE_EXPR
                 L_PAREN "("
                 CLOSURE_EXPR
@@ -250,78 +196,117 @@ SOURCE_FILE
                 WHITESPACE " "
                 BIN_EXPR
                   BIN_EXPR
-                    BIN_EXPR
-                      BIN_EXPR
-                        PATH_EXPR
-                          PATH
-                            PATH_SEGMENT
-                              NAME_REF
-                                IDENT "Trait"
-                        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 "("
+                    PATH_EXPR
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "Trait"
+                    L_ANGLE "<"
                     ERROR
-                      QUESTION "?"
+                      LIFETIME_IDENT "'a"
+                  R_ANGLE ">"
+                R_PAREN ")"
+              WHITESPACE " "
+              PLUS "+"
+              WHITESPACE " "
+              PAREN_EXPR
+                L_PAREN "("
                 PATH_EXPR
                   PATH
                     PATH_SEGMENT
                       NAME_REF
-                        IDENT "Sized"
+                        IDENT "Copy"
                 R_PAREN ")"
             R_ANGLE ">"
             ERROR
               SEMICOLON ";"
+        WHITESPACE "\n    "
+        LET_STMT
+          LET_KW "let"
+          WHITESPACE " "
+          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 "<"
+                        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 ")"
+        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
diff --git a/crates/parser/test_data/parser/err/0025_nope.rast b/crates/parser/test_data/parser/err/0025_nope.rast
index 6b49724ec9a..b6bc0088374 100644
--- a/crates/parser/test_data/parser/err/0025_nope.rast
+++ b/crates/parser/test_data/parser/err/0025_nope.rast
@@ -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
diff --git a/crates/parser/test_data/parser/err/0042_weird_blocks.rast b/crates/parser/test_data/parser/err/0042_weird_blocks.rast
index 9cea337ce9c..1cdc6e6e719 100644
--- a/crates/parser/test_data/parser/err/0042_weird_blocks.rast
+++ b/crates/parser/test_data/parser/err/0042_weird_blocks.rast
@@ -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
diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast
index 3a05bfee1ee..207a5c24dff 100644
--- a/crates/parser/test_data/parser/err/0048_double_fish.rast
+++ b/crates/parser/test_data/parser/err/0048_double_fish.rast
@@ -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
diff --git a/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast b/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
index 56cea4b1567..ea5203fb96e 100644
--- a/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
+++ b/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast
@@ -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 `{`
diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
new file mode 100644
index 00000000000..5d0fe859c29
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast
@@ -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
diff --git a/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
new file mode 100644
index 00000000000..0e7ac9cc307
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs
@@ -0,0 +1,5 @@
+fn main() {
+    foo(bar::);
+    foo(bar:);
+    foo(bar+);
+}
diff --git a/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast b/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
index e72df374d1b..ea50ad35d74 100644
--- a/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
+++ b/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast
@@ -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