about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAleksey Kladov <aleksey.kladov@gmail.com>2021-09-25 19:51:54 +0300
committerAleksey Kladov <aleksey.kladov@gmail.com>2021-09-25 22:19:27 +0300
commit56964c9bd383b80cf57df8d232876ada0686509f (patch)
tree99d7a29de0f5483f85f8e3b86b15a413cf7a481e
parent7dc331faefe3f84daffa13ce7c0847fcf23fb279 (diff)
downloadrust-56964c9bd383b80cf57df8d232876ada0686509f.tar.gz
rust-56964c9bd383b80cf57df8d232876ada0686509f.zip
feat: allow attributes on all expressions
Attrs are syntactically valid on any expression, even if they are not
allowed semantically everywhere yet.
-rw-r--r--crates/hir_def/src/body/lower.rs6
-rw-r--r--crates/parser/src/grammar.rs2
-rw-r--r--crates/parser/src/grammar/attributes.rs2
-rw-r--r--crates/parser/src/grammar/expressions.rs145
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs19
-rw-r--r--crates/parser/src/parser.rs3
-rw-r--r--crates/syntax/test_data/parser/err/0009_broken_struct_type_parameter.rast6
-rw-r--r--crates/syntax/test_data/parser/err/0022_bad_exprs.rast36
-rw-r--r--crates/syntax/test_data/parser/err/0024_many_type_parens.rast26
-rw-r--r--crates/syntax/test_data/parser/err/0043_weird_blocks.rast12
-rw-r--r--crates/syntax/test_data/parser/inline/err/0002_misplaced_label_err.rast12
-rw-r--r--crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast60
-rw-r--r--crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rs4
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast108
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rast59
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs4
-rw-r--r--crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rast58
-rw-r--r--crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rs3
-rw-r--r--crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rast69
-rw-r--r--crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rs4
20 files changed, 303 insertions, 335 deletions
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 759f2cc8657..a34c18d6d0c 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -650,8 +650,10 @@ impl ExprCollector<'_> {
                 self.statements_in_scope.push(Statement::Let { pat, type_ref, initializer });
             }
             ast::Stmt::ExprStmt(stmt) => {
-                if self.check_cfg(&stmt).is_none() {
-                    return;
+                if let Some(expr) = stmt.expr() {
+                    if self.check_cfg(&expr).is_none() {
+                        return;
+                    }
                 }
                 let has_semi = stmt.semicolon_token().is_some();
                 // Note that macro could be expended to multiple statements
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index d0b07f59314..3d0ad6735ed 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -63,7 +63,7 @@ pub(crate) mod entry_points {
     pub(crate) use types::type_;
 
     pub(crate) fn expr(p: &mut Parser) {
-        let _ = expressions::expr_with_attrs(p);
+        let _ = expressions::expr(p);
     }
 
     pub(crate) fn stmt(p: &mut Parser) {
diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs
index 80d7b09b3e1..574629f31a0 100644
--- a/crates/parser/src/grammar/attributes.rs
+++ b/crates/parser/src/grammar/attributes.rs
@@ -41,7 +41,7 @@ pub(super) fn meta(p: &mut Parser) {
     match p.current() {
         T![=] => {
             p.bump(T![=]);
-            if expressions::expr(p).0.is_none() {
+            if !expressions::expr(p) {
                 p.error("expected expression");
             }
         }
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 30d3d4421ac..645101e2f7f 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -13,35 +13,19 @@ pub(super) enum StmtWithSemi {
 
 const EXPR_FIRST: TokenSet = LHS_FIRST;
 
-pub(super) fn expr(p: &mut Parser) -> (Option<CompletedMarker>, BlockLike) {
+pub(super) fn expr(p: &mut Parser) -> bool {
     let r = Restrictions { forbid_structs: false, prefer_stmt: false };
-    expr_bp(p, r, 1)
+    expr_bp(p, None, r, 1).is_some()
 }
 
-pub(super) fn expr_with_attrs(p: &mut Parser) -> bool {
-    let m = p.start();
-    let has_attrs = p.at(T![#]);
-    attributes::outer_attrs(p);
-
-    let (cm, _block_like) = expr(p);
-    let success = cm.is_some();
-
-    match (has_attrs, cm) {
-        (true, Some(cm)) => cm.extend_to(p, m),
-        _ => m.abandon(p),
-    }
-
-    success
-}
-
-pub(super) fn expr_stmt(p: &mut Parser) -> (Option<CompletedMarker>, BlockLike) {
+pub(super) fn expr_stmt(p: &mut Parser, m: Option<Marker>) -> Option<(CompletedMarker, BlockLike)> {
     let r = Restrictions { forbid_structs: false, prefer_stmt: true };
-    expr_bp(p, r, 1)
+    expr_bp(p, m, r, 1)
 }
 
 fn expr_no_struct(p: &mut Parser) {
     let r = Restrictions { forbid_structs: true, prefer_stmt: false };
-    expr_bp(p, r, 1);
+    expr_bp(p, None, r, 1);
 }
 
 pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
@@ -53,7 +37,6 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
     //     #[C] #[D] {}
     //     #[D] return ();
     // }
-    let has_attrs = p.at(T![#]);
     attributes::outer_attrs(p);
 
     if p.at(T![let]) {
@@ -68,61 +51,39 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
         Err(m) => m,
     };
 
-    let (cm, blocklike) = expr_stmt(p);
-    let kind = cm.as_ref().map(|cm| cm.kind()).unwrap_or(ERROR);
-
-    if has_attrs {
-        if matches!(kind, BIN_EXPR | RANGE_EXPR) {
-            // test_err attr_on_expr_not_allowed
+    if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
+        if !(p.at(T!['}']) || (prefer_expr && p.at(EOF))) {
+            // test no_semi_after_block
             // fn foo() {
-            //    #[A] 1 + 2;
-            //    #[B] if true {};
+            //     if true {}
+            //     loop {}
+            //     match () {}
+            //     while true {}
+            //     for _ in () {}
+            //     {}
+            //     {}
+            //     macro_rules! test {
+            //          () => {}
+            //     }
+            //     test!{}
             // }
-            p.error(format!("attributes are not allowed on {:?}", kind));
-        }
-    }
-
-    if p.at(T!['}']) || (prefer_expr && p.at(EOF)) {
-        // test attr_on_last_expr_in_block
-        // fn foo() {
-        //     { #[A] bar!()? }
-        //     #[B] &()
-        // }
-        match cm {
-            Some(cm) => cm.extend_to(p, m),
-            None => m.abandon(p),
-        }
-    } else {
-        // test no_semi_after_block
-        // fn foo() {
-        //     if true {}
-        //     loop {}
-        //     match () {}
-        //     while true {}
-        //     for _ in () {}
-        //     {}
-        //     {}
-        //     macro_rules! test {
-        //          () => {}
-        //     }
-        //     test!{}
-        // }
-
-        match with_semi {
-            StmtWithSemi::No => (),
-            StmtWithSemi::Optional => {
-                p.eat(T![;]);
-            }
-            StmtWithSemi::Yes => {
-                if blocklike.is_block() {
+            let m = cm.precede(p);
+            match with_semi {
+                StmtWithSemi::No => (),
+                StmtWithSemi::Optional => {
                     p.eat(T![;]);
-                } else {
-                    p.expect(T![;]);
+                }
+                StmtWithSemi::Yes => {
+                    if blocklike.is_block() {
+                        p.eat(T![;]);
+                    } else {
+                        p.expect(T![;]);
+                    }
                 }
             }
-        }
 
-        m.complete(p, EXPR_STMT);
+            m.complete(p, EXPR_STMT);
+        }
     }
 
     // test let_stmt
@@ -138,7 +99,7 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
         if p.eat(T![=]) {
             // test let_stmt_init
             // fn f() { let x = 92; }
-            expressions::expr_with_attrs(p);
+            expressions::expr(p);
         }
 
         match with_semi {
@@ -234,20 +195,34 @@ fn current_op(p: &Parser) -> (u8, SyntaxKind) {
 }
 
 // Parses expression with binding power of at least bp.
-fn expr_bp(p: &mut Parser, mut r: Restrictions, bp: u8) -> (Option<CompletedMarker>, BlockLike) {
+fn expr_bp(
+    p: &mut Parser,
+    m: Option<Marker>,
+    mut r: Restrictions,
+    bp: u8,
+) -> Option<(CompletedMarker, BlockLike)> {
+    let m = m.unwrap_or_else(|| {
+        let m = p.start();
+        attributes::outer_attrs(p);
+        m
+    });
     let mut lhs = match lhs(p, r) {
         Some((lhs, blocklike)) => {
+            let lhs = lhs.extend_to(p, m);
             if r.prefer_stmt && blocklike.is_block() {
                 // test stmt_bin_expr_ambiguity
                 // fn f() {
                 //     let _ = {1} & 2;
                 //     {1} &2;
                 // }
-                return (Some(lhs), BlockLike::Block);
+                return Some((lhs, BlockLike::Block));
             }
             lhs
         }
-        None => return (None, BlockLike::NotBlock),
+        None => {
+            m.abandon(p);
+            return None;
+        }
     };
 
     loop {
@@ -285,10 +260,10 @@ fn expr_bp(p: &mut Parser, mut r: Restrictions, bp: u8) -> (Option<CompletedMark
             }
         }
 
-        expr_bp(p, Restrictions { prefer_stmt: false, ..r }, op_bp + 1);
+        expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp + 1);
         lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
     }
-    (Some(lhs), BlockLike::NotBlock)
+    Some((lhs, BlockLike::NotBlock))
 }
 
 const LHS_FIRST: TokenSet =
@@ -341,9 +316,10 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)>
                     m = p.start();
                     p.bump(op);
                     if p.at_ts(EXPR_FIRST) && !(r.forbid_structs && p.at(T!['{'])) {
-                        expr_bp(p, r, 2);
+                        expr_bp(p, None, r, 2);
                     }
-                    return Some((m.complete(p, RANGE_EXPR), BlockLike::NotBlock));
+                    let cm = m.complete(p, RANGE_EXPR);
+                    return Some((cm, BlockLike::NotBlock));
                 }
             }
 
@@ -353,12 +329,15 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)>
             //    {p}.x = 10;
             // }
             let (lhs, blocklike) = atom::atom_expr(p, r)?;
-            return Some(postfix_expr(p, lhs, blocklike, !(r.prefer_stmt && blocklike.is_block())));
+            let (cm, block_like) =
+                postfix_expr(p, lhs, blocklike, !(r.prefer_stmt && blocklike.is_block()));
+            return Some((cm, block_like));
         }
     };
     // parse the interior of the unary expression
-    expr_bp(p, r, 255);
-    Some((m.complete(p, kind), BlockLike::NotBlock))
+    expr_bp(p, None, r, 255);
+    let cm = m.complete(p, kind);
+    Some((cm, BlockLike::NotBlock))
 }
 
 fn postfix_expr(
@@ -536,7 +515,7 @@ fn arg_list(p: &mut Parser) {
         // fn main() {
         //     foo(#[attr] 92)
         // }
-        if !expr_with_attrs(p) {
+        if !expr(p) {
             break;
         }
         if !p.at(T![')']) && !p.expect(T![,]) {
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index f0048aa3014..f6e9a5f1b34 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -176,7 +176,7 @@ fn tuple_expr(p: &mut Parser) -> CompletedMarker {
 
         // test tuple_attrs
         // const A: (i64, i64) = (1, #[cfg(test)] 2);
-        if !expr_with_attrs(p) {
+        if !expr(p) {
             break;
         }
 
@@ -209,7 +209,7 @@ fn array_expr(p: &mut Parser) -> CompletedMarker {
 
         // test array_attrs
         // const A: &[i64] = &[1, #[cfg(test)] 2];
-        if !expr_with_attrs(p) {
+        if !expr(p) {
             break;
         }
 
@@ -438,7 +438,10 @@ fn match_arm(p: &mut Parser) {
         match_guard(p);
     }
     p.expect(T![=>]);
-    let blocklike = expr_stmt(p).1;
+    let blocklike = match expr_stmt(p, None) {
+        Some((_, blocklike)) => blocklike,
+        None => BlockLike::NotBlock,
+    };
 
     // test match_arms_commas
     // fn foo() {
@@ -619,14 +622,14 @@ fn meta_var_expr(p: &mut Parser) -> CompletedMarker {
     assert!(p.at(L_DOLLAR));
     let m = p.start();
     p.bump(L_DOLLAR);
-    let (completed, _is_block) =
-        expr_bp(p, Restrictions { forbid_structs: false, prefer_stmt: false }, 1);
+    let expr = expr_bp(p, None, Restrictions { forbid_structs: false, prefer_stmt: false }, 1);
 
-    match (completed, p.current()) {
-        (Some(it), R_DOLLAR) => {
+    match (expr, p.current()) {
+        (Some((cm, _)), R_DOLLAR) => {
             p.bump(R_DOLLAR);
+            // FIXME: this leaves the dollar hanging in the air...
             m.abandon(p);
-            it
+            cm
         }
         _ => {
             while !p.at(R_DOLLAR) {
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index ff2af130be4..1f9961bb924 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -339,7 +339,7 @@ impl CompletedMarker {
     }
 
     /// Extends this completed marker *to the left* up to `m`.
-    pub(crate) fn extend_to(self, p: &mut Parser, mut m: Marker) {
+    pub(crate) fn extend_to(self, p: &mut Parser, mut m: Marker) -> CompletedMarker {
         m.bomb.defuse();
         let idx = m.pos as usize;
         match &mut p.events[idx] {
@@ -348,6 +348,7 @@ impl CompletedMarker {
             }
             _ => unreachable!(),
         }
+        self
     }
 
     pub(crate) fn kind(&self) -> SyntaxKind {
diff --git a/crates/syntax/test_data/parser/err/0009_broken_struct_type_parameter.rast b/crates/syntax/test_data/parser/err/0009_broken_struct_type_parameter.rast
index dacf71aa165..2d4c689c7c6 100644
--- a/crates/syntax/test_data/parser/err/0009_broken_struct_type_parameter.rast
+++ b/crates/syntax/test_data/parser/err/0009_broken_struct_type_parameter.rast
@@ -26,9 +26,8 @@ SOURCE_FILE@0..43
           PATH_SEGMENT@23..24
             NAME_REF@23..24
               IDENT@23..24 "f"
-    EXPR_STMT@24..25
-      ERROR@24..25
-        COLON@24..25 ":"
+    ERROR@24..25
+      COLON@24..25 ":"
     WHITESPACE@25..26 " "
     PATH_EXPR@26..29
       PATH@26..29
@@ -55,4 +54,3 @@ error 15..15: expected an item
 error 17..17: expected an item
 error 24..24: expected SEMICOLON
 error 24..24: expected expression
-error 25..25: expected SEMICOLON
diff --git a/crates/syntax/test_data/parser/err/0022_bad_exprs.rast b/crates/syntax/test_data/parser/err/0022_bad_exprs.rast
index 71fb19783ad..7e6d82bfb49 100644
--- a/crates/syntax/test_data/parser/err/0022_bad_exprs.rast
+++ b/crates/syntax/test_data/parser/err/0022_bad_exprs.rast
@@ -24,9 +24,8 @@ SOURCE_FILE@0..112
           WHITESPACE@15..16 " "
           ERROR@16..17
             AT@16..17 "@"
-      EXPR_STMT@17..18
-        ERROR@17..18
-          COMMA@17..18 ","
+      ERROR@17..18
+        COMMA@17..18 ","
       WHITESPACE@18..19 " "
       STRUCT@19..26
         STRUCT_KW@19..25 "struct"
@@ -71,15 +70,13 @@ SOURCE_FILE@0..112
             WHITESPACE@52..53 " "
             ERROR@53..54
               AT@53..54 "@"
-      EXPR_STMT@54..55
-        ERROR@54..55
-          COMMA@54..55 ","
+      ERROR@54..55
+        COMMA@54..55 ","
       WHITESPACE@55..56 " "
       IMPL@56..60
         IMPL_KW@56..60 "impl"
-      EXPR_STMT@60..61
-        ERROR@60..61
-          COMMA@60..61 ","
+      ERROR@60..61
+        COMMA@60..61 ","
       WHITESPACE@61..62 " "
       LET_STMT@62..65
         LET_KW@62..65 "let"
@@ -122,16 +119,13 @@ SOURCE_FILE@0..112
             WHITESPACE@91..92 " "
             ERROR@92..93
               AT@92..93 "@"
-      EXPR_STMT@93..94
-        ERROR@93..94
-          COMMA@93..94 ","
+      ERROR@93..94
+        COMMA@93..94 ","
       WHITESPACE@94..95 " "
-      EXPR_STMT@95..96
-        ERROR@95..96
-          R_BRACK@95..96 "]"
-      EXPR_STMT@96..97
-        ERROR@96..97
-          COMMA@96..97 ","
+      ERROR@95..96
+        R_BRACK@95..96 "]"
+      ERROR@96..97
+        COMMA@96..97 ","
       WHITESPACE@97..98 " "
       TRAIT@98..104
         TRAIT_KW@98..103 "trait"
@@ -149,7 +143,6 @@ error 16..16: expected expression
 error 17..17: expected R_BRACK
 error 17..17: expected SEMICOLON
 error 17..17: expected expression
-error 18..18: expected SEMICOLON
 error 25..25: expected a name
 error 26..26: expected `;`, `{`, or `(`
 error 30..30: expected pattern
@@ -157,22 +150,17 @@ error 31..31: expected SEMICOLON
 error 53..53: expected expression
 error 54..54: expected SEMICOLON
 error 54..54: expected expression
-error 55..55: expected SEMICOLON
 error 60..60: expected type
 error 60..60: expected `{`
 error 60..60: expected expression
-error 61..61: expected SEMICOLON
 error 65..65: expected pattern
 error 65..65: expected SEMICOLON
 error 65..65: expected expression
 error 92..92: expected expression
 error 93..93: expected SEMICOLON
 error 93..93: expected expression
-error 94..94: expected SEMICOLON
 error 95..95: expected expression
-error 96..96: expected SEMICOLON
 error 96..96: expected expression
-error 97..97: expected SEMICOLON
 error 103..103: expected a name
 error 104..104: expected `{`
 error 108..108: expected pattern
diff --git a/crates/syntax/test_data/parser/err/0024_many_type_parens.rast b/crates/syntax/test_data/parser/err/0024_many_type_parens.rast
index be4a62940bd..d29f12c1e51 100644
--- a/crates/syntax/test_data/parser/err/0024_many_type_parens.rast
+++ b/crates/syntax/test_data/parser/err/0024_many_type_parens.rast
@@ -143,10 +143,9 @@ SOURCE_FILE@0..240
                             LIFETIME_IDENT@117..119 "'a"
                         R_ANGLE@119..120 ">"
               R_PAREN@120..121 ")"
-      EXPR_STMT@121..123
-        ERROR@121..122
-          R_ANGLE@121..122 ">"
-        SEMICOLON@122..123 ";"
+      ERROR@121..122
+        R_ANGLE@121..122 ">"
+      SEMICOLON@122..123 ";"
       WHITESPACE@123..128 "\n    "
       LET_STMT@128..141
         LET_KW@128..131 "let"
@@ -173,13 +172,11 @@ SOURCE_FILE@0..240
             PATH_SEGMENT@141..146
               NAME_REF@141..146
                 IDENT@141..146 "Sized"
-      EXPR_STMT@146..147
-        ERROR@146..147
-          R_PAREN@146..147 ")"
+      ERROR@146..147
+        R_PAREN@146..147 ")"
       WHITESPACE@147..148 " "
-      EXPR_STMT@148..149
-        ERROR@148..149
-          PLUS@148..149 "+"
+      ERROR@148..149
+        PLUS@148..149 "+"
       WHITESPACE@149..150 " "
       EXPR_STMT@150..180
         TUPLE_EXPR@150..180
@@ -288,10 +285,9 @@ SOURCE_FILE@0..240
                     NAME_REF@229..234
                       IDENT@229..234 "Sized"
               R_PAREN@234..235 ")"
-      EXPR_STMT@235..237
-        ERROR@235..236
-          R_ANGLE@235..236 ">"
-        SEMICOLON@236..237 ";"
+      ERROR@235..236
+        R_ANGLE@235..236 ">"
+      SEMICOLON@236..237 ";"
       WHITESPACE@237..238 "\n"
       R_CURLY@238..239 "}"
   WHITESPACE@239..240 "\n"
@@ -306,9 +302,7 @@ error 141..141: expected R_ANGLE
 error 141..141: expected SEMICOLON
 error 146..146: expected SEMICOLON
 error 146..146: expected expression
-error 147..147: expected SEMICOLON
 error 148..148: expected expression
-error 149..149: expected SEMICOLON
 error 155..155: expected type
 error 158..158: expected IN_KW
 error 165..165: expected expression
diff --git a/crates/syntax/test_data/parser/err/0043_weird_blocks.rast b/crates/syntax/test_data/parser/err/0043_weird_blocks.rast
index e24f01e2920..797a0896482 100644
--- a/crates/syntax/test_data/parser/err/0043_weird_blocks.rast
+++ b/crates/syntax/test_data/parser/err/0043_weird_blocks.rast
@@ -51,12 +51,11 @@ SOURCE_FILE@0..83
       BLOCK_EXPR@66..80
         L_CURLY@66..67 "{"
         WHITESPACE@67..68 " "
-        EXPR_STMT@68..75
-          ERROR@68..75
-            LABEL@68..75
-              LIFETIME@68..74
-                LIFETIME_IDENT@68..74 "'label"
-              COLON@74..75 ":"
+        ERROR@68..75
+          LABEL@68..75
+            LIFETIME@68..74
+              LIFETIME_IDENT@68..74 "'label"
+            COLON@74..75 ":"
         WHITESPACE@75..76 " "
         LITERAL@76..78
           INT_NUMBER@76..78 "92"
@@ -69,4 +68,3 @@ error 24..24: expected existential, fn, trait or impl
 error 41..41: expected existential, fn, trait or impl
 error 56..56: expected a block
 error 75..75: expected a loop
-error 75..75: expected SEMICOLON
diff --git a/crates/syntax/test_data/parser/inline/err/0002_misplaced_label_err.rast b/crates/syntax/test_data/parser/inline/err/0002_misplaced_label_err.rast
index 97bb5059d7a..3774dd0733e 100644
--- a/crates/syntax/test_data/parser/inline/err/0002_misplaced_label_err.rast
+++ b/crates/syntax/test_data/parser/inline/err/0002_misplaced_label_err.rast
@@ -11,12 +11,11 @@ SOURCE_FILE@0..30
     BLOCK_EXPR@10..29
       L_CURLY@10..11 "{"
       WHITESPACE@11..16 "\n    "
-      EXPR_STMT@16..22
-        ERROR@16..22
-          LABEL@16..22
-            LIFETIME@16..21
-              LIFETIME_IDENT@16..21 "'loop"
-            COLON@21..22 ":"
+      ERROR@16..22
+        LABEL@16..22
+          LIFETIME@16..21
+            LIFETIME_IDENT@16..21 "'loop"
+          COLON@21..22 ":"
       WHITESPACE@22..23 " "
       IMPL@23..27
         IMPL_KW@23..27 "impl"
@@ -24,6 +23,5 @@ SOURCE_FILE@0..30
       R_CURLY@28..29 "}"
   WHITESPACE@29..30 "\n"
 error 22..22: expected a loop
-error 22..22: expected SEMICOLON
 error 27..27: expected type
 error 27..27: expected `{`
diff --git a/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast b/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast
deleted file mode 100644
index 7b8b7284f9c..00000000000
--- a/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast
+++ /dev/null
@@ -1,60 +0,0 @@
-SOURCE_FILE@0..48
-  FN@0..47
-    FN_KW@0..2 "fn"
-    WHITESPACE@2..3 " "
-    NAME@3..6
-      IDENT@3..6 "foo"
-    PARAM_LIST@6..8
-      L_PAREN@6..7 "("
-      R_PAREN@7..8 ")"
-    WHITESPACE@8..9 " "
-    BLOCK_EXPR@9..47
-      L_CURLY@9..10 "{"
-      WHITESPACE@10..14 "\n   "
-      EXPR_STMT@14..25
-        ATTR@14..18
-          POUND@14..15 "#"
-          L_BRACK@15..16 "["
-          META@16..17
-            PATH@16..17
-              PATH_SEGMENT@16..17
-                NAME_REF@16..17
-                  IDENT@16..17 "A"
-          R_BRACK@17..18 "]"
-        WHITESPACE@18..19 " "
-        BIN_EXPR@19..24
-          LITERAL@19..20
-            INT_NUMBER@19..20 "1"
-          WHITESPACE@20..21 " "
-          PLUS@21..22 "+"
-          WHITESPACE@22..23 " "
-          LITERAL@23..24
-            INT_NUMBER@23..24 "2"
-        SEMICOLON@24..25 ";"
-      WHITESPACE@25..29 "\n   "
-      EXPR_STMT@29..45
-        ATTR@29..33
-          POUND@29..30 "#"
-          L_BRACK@30..31 "["
-          META@31..32
-            PATH@31..32
-              PATH_SEGMENT@31..32
-                NAME_REF@31..32
-                  IDENT@31..32 "B"
-          R_BRACK@32..33 "]"
-        WHITESPACE@33..34 " "
-        IF_EXPR@34..44
-          IF_KW@34..36 "if"
-          WHITESPACE@36..37 " "
-          CONDITION@37..41
-            LITERAL@37..41
-              TRUE_KW@37..41 "true"
-          WHITESPACE@41..42 " "
-          BLOCK_EXPR@42..44
-            L_CURLY@42..43 "{"
-            R_CURLY@43..44 "}"
-        SEMICOLON@44..45 ";"
-      WHITESPACE@45..46 "\n"
-      R_CURLY@46..47 "}"
-  WHITESPACE@47..48 "\n"
-error 24..24: attributes are not allowed on BIN_EXPR
diff --git a/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rs b/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
deleted file mode 100644
index d725a07ce96..00000000000
--- a/crates/syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-fn foo() {
-   #[A] 1 + 2;
-   #[B] if true {};
-}
diff --git a/crates/syntax/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast b/crates/syntax/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
index 178204fece4..2a4a52eeb50 100644
--- a/crates/syntax/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
@@ -12,17 +12,17 @@ SOURCE_FILE@0..82
       L_CURLY@9..10 "{"
       WHITESPACE@10..15 "\n    "
       EXPR_STMT@15..26
-        ATTR@15..19
-          POUND@15..16 "#"
-          L_BRACK@16..17 "["
-          META@17..18
-            PATH@17..18
-              PATH_SEGMENT@17..18
-                NAME_REF@17..18
-                  IDENT@17..18 "A"
-          R_BRACK@18..19 "]"
-        WHITESPACE@19..20 " "
-        CALL_EXPR@20..25
+        CALL_EXPR@15..25
+          ATTR@15..19
+            POUND@15..16 "#"
+            L_BRACK@16..17 "["
+            META@17..18
+              PATH@17..18
+                PATH_SEGMENT@17..18
+                  NAME_REF@17..18
+                    IDENT@17..18 "A"
+            R_BRACK@18..19 "]"
+          WHITESPACE@19..20 " "
           PATH_EXPR@20..23
             PATH@20..23
               PATH_SEGMENT@20..23
@@ -34,17 +34,17 @@ SOURCE_FILE@0..82
         SEMICOLON@25..26 ";"
       WHITESPACE@26..31 "\n    "
       EXPR_STMT@31..42
-        ATTR@31..35
-          POUND@31..32 "#"
-          L_BRACK@32..33 "["
-          META@33..34
-            PATH@33..34
-              PATH_SEGMENT@33..34
-                NAME_REF@33..34
-                  IDENT@33..34 "B"
-          R_BRACK@34..35 "]"
-        WHITESPACE@35..36 " "
-        MACRO_CALL@36..42
+        MACRO_CALL@31..42
+          ATTR@31..35
+            POUND@31..32 "#"
+            L_BRACK@32..33 "["
+            META@33..34
+              PATH@33..34
+                PATH_SEGMENT@33..34
+                  NAME_REF@33..34
+                    IDENT@33..34 "B"
+            R_BRACK@34..35 "]"
+          WHITESPACE@35..36 " "
           PATH@36..39
             PATH_SEGMENT@36..39
               NAME_REF@36..39
@@ -55,42 +55,42 @@ SOURCE_FILE@0..82
             R_CURLY@41..42 "}"
       WHITESPACE@42..47 "\n    "
       EXPR_STMT@47..59
-        ATTR@47..51
-          POUND@47..48 "#"
-          L_BRACK@48..49 "["
-          META@49..50
-            PATH@49..50
-              PATH_SEGMENT@49..50
-                NAME_REF@49..50
-                  IDENT@49..50 "C"
-          R_BRACK@50..51 "]"
-        WHITESPACE@51..52 " "
-        ATTR@52..56
-          POUND@52..53 "#"
-          L_BRACK@53..54 "["
-          META@54..55
-            PATH@54..55
-              PATH_SEGMENT@54..55
-                NAME_REF@54..55
-                  IDENT@54..55 "D"
-          R_BRACK@55..56 "]"
-        WHITESPACE@56..57 " "
-        BLOCK_EXPR@57..59
+        BLOCK_EXPR@47..59
+          ATTR@47..51
+            POUND@47..48 "#"
+            L_BRACK@48..49 "["
+            META@49..50
+              PATH@49..50
+                PATH_SEGMENT@49..50
+                  NAME_REF@49..50
+                    IDENT@49..50 "C"
+            R_BRACK@50..51 "]"
+          WHITESPACE@51..52 " "
+          ATTR@52..56
+            POUND@52..53 "#"
+            L_BRACK@53..54 "["
+            META@54..55
+              PATH@54..55
+                PATH_SEGMENT@54..55
+                  NAME_REF@54..55
+                    IDENT@54..55 "D"
+            R_BRACK@55..56 "]"
+          WHITESPACE@56..57 " "
           L_CURLY@57..58 "{"
           R_CURLY@58..59 "}"
       WHITESPACE@59..64 "\n    "
       EXPR_STMT@64..79
-        ATTR@64..68
-          POUND@64..65 "#"
-          L_BRACK@65..66 "["
-          META@66..67
-            PATH@66..67
-              PATH_SEGMENT@66..67
-                NAME_REF@66..67
-                  IDENT@66..67 "D"
-          R_BRACK@67..68 "]"
-        WHITESPACE@68..69 " "
-        RETURN_EXPR@69..78
+        RETURN_EXPR@64..78
+          ATTR@64..68
+            POUND@64..65 "#"
+            L_BRACK@65..66 "["
+            META@66..67
+              PATH@66..67
+                PATH_SEGMENT@66..67
+                  NAME_REF@66..67
+                    IDENT@66..67 "D"
+            R_BRACK@67..68 "]"
+          WHITESPACE@68..69 " "
           RETURN_KW@69..75 "return"
           WHITESPACE@75..76 " "
           TUPLE_EXPR@76..78
diff --git a/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rast b/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rast
deleted file mode 100644
index 9daac234ac8..00000000000
--- a/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rast
+++ /dev/null
@@ -1,59 +0,0 @@
-SOURCE_FILE@0..47
-  FN@0..46
-    FN_KW@0..2 "fn"
-    WHITESPACE@2..3 " "
-    NAME@3..6
-      IDENT@3..6 "foo"
-    PARAM_LIST@6..8
-      L_PAREN@6..7 "("
-      R_PAREN@7..8 ")"
-    WHITESPACE@8..9 " "
-    BLOCK_EXPR@9..46
-      L_CURLY@9..10 "{"
-      WHITESPACE@10..15 "\n    "
-      EXPR_STMT@15..31
-        BLOCK_EXPR@15..31
-          L_CURLY@15..16 "{"
-          WHITESPACE@16..17 " "
-          TRY_EXPR@17..29
-            ATTR@17..21
-              POUND@17..18 "#"
-              L_BRACK@18..19 "["
-              META@19..20
-                PATH@19..20
-                  PATH_SEGMENT@19..20
-                    NAME_REF@19..20
-                      IDENT@19..20 "A"
-              R_BRACK@20..21 "]"
-            WHITESPACE@21..22 " "
-            MACRO_CALL@22..28
-              PATH@22..25
-                PATH_SEGMENT@22..25
-                  NAME_REF@22..25
-                    IDENT@22..25 "bar"
-              BANG@25..26 "!"
-              TOKEN_TREE@26..28
-                L_PAREN@26..27 "("
-                R_PAREN@27..28 ")"
-            QUESTION@28..29 "?"
-          WHITESPACE@29..30 " "
-          R_CURLY@30..31 "}"
-      WHITESPACE@31..36 "\n    "
-      REF_EXPR@36..44
-        ATTR@36..40
-          POUND@36..37 "#"
-          L_BRACK@37..38 "["
-          META@38..39
-            PATH@38..39
-              PATH_SEGMENT@38..39
-                NAME_REF@38..39
-                  IDENT@38..39 "B"
-          R_BRACK@39..40 "]"
-        WHITESPACE@40..41 " "
-        AMP@41..42 "&"
-        TUPLE_EXPR@42..44
-          L_PAREN@42..43 "("
-          R_PAREN@43..44 ")"
-      WHITESPACE@44..45 "\n"
-      R_CURLY@45..46 "}"
-  WHITESPACE@46..47 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs b/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
deleted file mode 100644
index 9c5c8eb3615..00000000000
--- a/crates/syntax/test_data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-fn foo() {
-    { #[A] bar!()? }
-    #[B] &()
-}
diff --git a/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rast b/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rast
new file mode 100644
index 00000000000..925fa4cbf6c
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rast
@@ -0,0 +1,58 @@
+SOURCE_FILE@0..44
+  FN@0..43
+    FN_KW@0..2 "fn"
+    WHITESPACE@2..3 " "
+    NAME@3..4
+      IDENT@3..4 "f"
+    PARAM_LIST@4..6
+      L_PAREN@4..5 "("
+      R_PAREN@5..6 ")"
+    WHITESPACE@6..7 " "
+    BLOCK_EXPR@7..43
+      L_CURLY@7..8 "{"
+      WHITESPACE@8..13 "\n    "
+      PAREN_EXPR@13..41
+        L_PAREN@13..14 "("
+        BIN_EXPR@14..40
+          TRY_EXPR@14..23
+            ATTR@14..18
+              POUND@14..15 "#"
+              L_BRACK@15..16 "["
+              META@16..17
+                PATH@16..17
+                  PATH_SEGMENT@16..17
+                    NAME_REF@16..17
+                      IDENT@16..17 "a"
+              R_BRACK@17..18 "]"
+            WHITESPACE@18..19 " "
+            PATH_EXPR@19..22
+              PATH@19..22
+                PATH_SEGMENT@19..22
+                  NAME_REF@19..22
+                    IDENT@19..22 "lhs"
+            QUESTION@22..23 "?"
+          WHITESPACE@23..24 " "
+          PLUS@24..25 "+"
+          WHITESPACE@25..26 " "
+          AWAIT_EXPR@26..40
+            ATTR@26..30
+              POUND@26..27 "#"
+              L_BRACK@27..28 "["
+              META@28..29
+                PATH@28..29
+                  PATH_SEGMENT@28..29
+                    NAME_REF@28..29
+                      IDENT@28..29 "b"
+              R_BRACK@29..30 "]"
+            WHITESPACE@30..31 " "
+            PATH_EXPR@31..34
+              PATH@31..34
+                PATH_SEGMENT@31..34
+                  NAME_REF@31..34
+                    IDENT@31..34 "rhs"
+            DOT@34..35 "."
+            AWAIT_KW@35..40 "await"
+        R_PAREN@40..41 ")"
+      WHITESPACE@41..42 "\n"
+      R_CURLY@42..43 "}"
+  WHITESPACE@43..44 "\n"
diff --git a/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rs b/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rs
new file mode 100644
index 00000000000..d8b7a3832a9
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0070_expr_attr_placement.rs
@@ -0,0 +1,3 @@
+fn f() {
+    (#[a] lhs? + #[b] rhs.await)
+}
diff --git a/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rast b/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rast
new file mode 100644
index 00000000000..3a00212e80b
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rast
@@ -0,0 +1,69 @@
+SOURCE_FILE@0..52
+  FN@0..51
+    FN_KW@0..2 "fn"
+    WHITESPACE@2..3 " "
+    NAME@3..6
+      IDENT@3..6 "foo"
+    PARAM_LIST@6..8
+      L_PAREN@6..7 "("
+      R_PAREN@7..8 ")"
+    WHITESPACE@8..9 " "
+    BLOCK_EXPR@9..51
+      L_CURLY@9..10 "{"
+      WHITESPACE@10..15 "\n    "
+      EXPR_STMT@15..36
+        BLOCK_EXPR@15..36
+          ATTR@15..19
+            POUND@15..16 "#"
+            L_BRACK@16..17 "["
+            META@17..18
+              PATH@17..18
+                PATH_SEGMENT@17..18
+                  NAME_REF@17..18
+                    IDENT@17..18 "A"
+            R_BRACK@18..19 "]"
+          WHITESPACE@19..20 " "
+          L_CURLY@20..21 "{"
+          WHITESPACE@21..22 " "
+          TRY_EXPR@22..34
+            ATTR@22..26
+              POUND@22..23 "#"
+              L_BRACK@23..24 "["
+              META@24..25
+                PATH@24..25
+                  PATH_SEGMENT@24..25
+                    NAME_REF@24..25
+                      IDENT@24..25 "B"
+              R_BRACK@25..26 "]"
+            WHITESPACE@26..27 " "
+            MACRO_CALL@27..33
+              PATH@27..30
+                PATH_SEGMENT@27..30
+                  NAME_REF@27..30
+                    IDENT@27..30 "bar"
+              BANG@30..31 "!"
+              TOKEN_TREE@31..33
+                L_PAREN@31..32 "("
+                R_PAREN@32..33 ")"
+            QUESTION@33..34 "?"
+          WHITESPACE@34..35 " "
+          R_CURLY@35..36 "}"
+      WHITESPACE@36..41 "\n    "
+      REF_EXPR@41..49
+        ATTR@41..45
+          POUND@41..42 "#"
+          L_BRACK@42..43 "["
+          META@43..44
+            PATH@43..44
+              PATH_SEGMENT@43..44
+                NAME_REF@43..44
+                  IDENT@43..44 "C"
+          R_BRACK@44..45 "]"
+        WHITESPACE@45..46 " "
+        AMP@46..47 "&"
+        TUPLE_EXPR@47..49
+          L_PAREN@47..48 "("
+          R_PAREN@48..49 ")"
+      WHITESPACE@49..50 "\n"
+      R_CURLY@50..51 "}"
+  WHITESPACE@51..52 "\n"
diff --git a/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rs b/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rs
new file mode 100644
index 00000000000..b4d5204bc0d
--- /dev/null
+++ b/crates/syntax/test_data/parser/ok/0071_stmt_attr_placement.rs
@@ -0,0 +1,4 @@
+fn foo() {
+    #[A] { #[B] bar!()? }
+    #[C] &()
+}