Repository: rust-analyzer/rust-analyzer
Pull request #11904 (2022-04-05 15:46:18)

internal: Wrap macros in expr position in `MacroExpr` node
This lets us distinguish them from macros in item position just by looking at the syntax tree.

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11854

bors r+


Patch:
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 6f5908ceb7..91d02ab5c9 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -110,8 +110,8 @@ impl SourceAnalyzer {
 
     fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
         let src = match expr {
-            ast::Expr::MacroCall(call) => {
-                self.expand_expr(db, InFile::new(self.file_id, call.clone()))?
+            ast::Expr::MacroExpr(expr) => {
+                self.expand_expr(db, InFile::new(self.file_id, expr.macro_call()?.clone()))?
             }
             _ => InFile::new(self.file_id, expr.clone()),
         };
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index d1cfc501e5..f0cb39ec36 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -506,7 +506,8 @@ impl ExprCollector<'_> {
                     None => self.alloc_expr(Expr::Missing, syntax_ptr),
                 }
             }
-            ast::Expr::MacroCall(e) => {
+            ast::Expr::MacroExpr(e) => {
+                let e = e.macro_call()?;
                 let macro_ptr = AstPtr::new(&e);
                 let id = self.collect_macro_call(e, macro_ptr.clone(), true, |this, expansion| {
                     expansion.map(|it| this.collect_expr(it))
@@ -629,7 +630,11 @@ impl ExprCollector<'_> {
                 }
                 let has_semi = stmt.semicolon_token().is_some();
                 // Note that macro could be expended to multiple statements
-                if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
+                if let Some(ast::Expr::MacroExpr(e)) = stmt.expr() {
+                    let m = match e.macro_call() {
+                        Some(it) => it,
+                        None => return,
+                    };
                     let macro_ptr = AstPtr::new(&m);
                     let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
 
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index b576815f87..3fa2d9d32c 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -48,22 +48,33 @@ impl<'a> Ctx<'a> {
     pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree {
         self.tree.top_level = stmts
             .statements()
-            .filter_map(|stmt| match stmt {
-                ast::Stmt::Item(item) => Some(item),
-                // Macro calls can be both items and expressions. The syntax library always treats
-                // them as expressions here, so we undo that.
-                ast::Stmt::ExprStmt(es) => match es.expr()? {
-                    ast::Expr::MacroCall(call) => {
-                        cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree);
-                        Some(call.into())
-                    }
+            .filter_map(|stmt| {
+                match stmt {
+                    ast::Stmt::Item(item) => Some(item),
+                    // Macro calls can be both items and expressions. The syntax library always treats
+                    // them as expressions here, so we undo that.
+                    ast::Stmt::ExprStmt(es) => match es.expr()? {
+                        ast::Expr::MacroExpr(expr) => {
+                            cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree);
+                            Some(expr.macro_call()?.into())
+                        }
+                        _ => None,
+                    },
                     _ => None,
-                },
-                _ => None,
+                }
             })
             .flat_map(|item| self.lower_mod_item(&item))
             .collect();
 
+        if let Some(ast::Expr::MacroExpr(tail_macro)) = stmts.expr() {
+            if let Some(call) = tail_macro.macro_call() {
+                cov_mark::hit!(macro_stmt_with_trailing_macro_expr);
+                if let Some(mod_item) = self.lower_mod_item(&call.into()) {
+                    self.tree.top_level.push(mod_item);
+                }
+            }
+        }
+
         self.tree
     }
 
@@ -75,7 +86,7 @@ impl<'a> Ctx<'a> {
                 // Macro calls can be both items and expressions. The syntax library always treats
                 // them as expressions here, so we undo that.
                 ast::Stmt::ExprStmt(es) => match es.expr()? {
-                    ast::Expr::MacroCall(call) => self.lower_mod_item(&call.into()),
+                    ast::Expr::MacroExpr(expr) => self.lower_mod_item(&expr.macro_call()?.into()),
                     _ => None,
                 },
                 _ => None,
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 99cb493f32..537ea087f9 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -885,6 +885,16 @@ impl ExpandTo {
             None => return ExpandTo::Statements,
         };
 
+        // FIXME: macros in statement position are treated as expression statements, they should
+        // probably be their own statement kind. The *grand*parent indicates what's valid.
+        if parent.kind() == MACRO_EXPR
+            && parent
+                .parent()
+                .map_or(true, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS))
+        {
+            return ExpandTo::Statements;
+        }
+
         match parent.kind() {
             MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => ExpandTo::Items,
             MACRO_STMTS | EXPR_STMT | STMT_LIST => ExpandTo::Statements,
@@ -895,23 +905,10 @@ impl ExpandTo {
             | CLOSURE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR | INDEX_EXPR | LET_EXPR
             | MATCH_ARM | MATCH_EXPR | MATCH_GUARD | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR
             | PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR_FIELD | REF_EXPR | RETURN_EXPR | TRY_EXPR
-            | TUPLE_EXPR | WHILE_EXPR => ExpandTo::Expr,
+            | TUPLE_EXPR | WHILE_EXPR | MACRO_EXPR => ExpandTo::Expr,
             _ => {
-                match ast::LetStmt::cast(parent) {
-                    Some(let_stmt) => {
-                        if let Some(true) = let_stmt.initializer().map(|it| it.syntax() == syn) {
-                            ExpandTo::Expr
-                        } else if let Some(true) = let_stmt.ty().map(|it| it.syntax() == syn) {
-                            ExpandTo::Type
-                        } else {
-                            ExpandTo::Pattern
-                        }
-                    }
-                    None => {
-                        // Unknown , Just guess it is `Items`
-                        ExpandTo::Items
-                    }
-                }
+                // Unknown , Just guess it is `Items`
+                ExpandTo::Items
             }
         }
     }
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index baa015354f..84fdbc60f8 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -156,7 +156,7 @@ fn highlight_exit_points(
                     highlights.push(HighlightedRange { category: None, range: token.text_range() });
                 }
             }
-            ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
+            ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
                     highlights.push(HighlightedRange {
                         category: None,
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 1771b56fe3..9003e7cd34 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -163,19 +163,20 @@ fn test() {
                         L_CURLY@10..11 "{"
                         WHITESPACE@11..16 "\n    "
                         EXPR_STMT@16..58
-                          MACRO_CALL@16..57
-                            PATH@16..22
-                              PATH_SEGMENT@16..22
-                                NAME_REF@16..22
-                                  IDENT@16..22 "assert"
-                            BANG@22..23 "!"
-                            TOKEN_TREE@23..57
-                              L_PAREN@23..24 "("
-                              STRING@24..52 "\"\n    fn foo() {\n     ..."
-                              COMMA@52..53 ","
-                              WHITESPACE@53..54 " "
-                              STRING@54..56 "\"\""
-                              R_PAREN@56..57 ")"
+                          MACRO_EXPR@16..57
+                            MACRO_CALL@16..57
+                              PATH@16..22
+                                PATH_SEGMENT@16..22
+                                  NAME_REF@16..22
+                                    IDENT@16..22 "assert"
+                              BANG@22..23 "!"
+                              TOKEN_TREE@23..57
+                                L_PAREN@23..24 "("
+                                STRING@24..52 "\"\n    fn foo() {\n     ..."
+                                COMMA@52..53 ","
+                                WHITESPACE@53..54 " "
+                                STRING@54..56 "\"\""
+                                R_PAREN@56..57 ")"
                           SEMICOLON@57..58 ";"
                         WHITESPACE@58..59 "\n"
                         R_CURLY@59..60 "}"
@@ -214,19 +215,20 @@ fn test() {
 }"#,
             expect![[r#"
                 EXPR_STMT@16..58
-                  MACRO_CALL@16..57
-                    PATH@16..22
-                      PATH_SEGMENT@16..22
-                        NAME_REF@16..22
-                          IDENT@16..22 "assert"
-                    BANG@22..23 "!"
-                    TOKEN_TREE@23..57
-                      L_PAREN@23..24 "("
-                      STRING@24..52 "\"\n    fn foo() {\n     ..."
-                      COMMA@52..53 ","
-                      WHITESPACE@53..54 " "
-                      STRING@54..56 "\"\""
-                      R_PAREN@56..57 ")"
+                  MACRO_EXPR@16..57
+                    MACRO_CALL@16..57
+                      PATH@16..22
+                        PATH_SEGMENT@16..22
+                          NAME_REF@16..22
+                            IDENT@16..22 "assert"
+                      BANG@22..23 "!"
+                      TOKEN_TREE@23..57
+                        L_PAREN@23..24 "("
+                        STRING@24..52 "\"\n    fn foo() {\n     ..."
+                        COMMA@52..53 ","
+                        WHITESPACE@53..54 " "
+                        STRING@54..56 "\"\""
+                        R_PAREN@56..57 ")"
                   SEMICOLON@57..58 ";"
             "#]],
         );
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 95a774df05..0095012791 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -601,6 +601,17 @@ sdasdasdasdasd
         );
     }
 
+    #[test]
+    fn noop_in_item_position_with_macro() {
+        type_char_noop('{', r#"$0println!();"#);
+        type_char_noop(
+            '{',
+            r#"
+fn main() $0println!("hello");
+}"#,
+        );
+    }
+
     #[test]
     fn adds_closing_brace_for_use_tree() {
         type_char(
diff --git a/crates/ide_assists/src/handlers/convert_bool_then.rs b/crates/ide_assists/src/handlers/convert_bool_then.rs
index f07fde07c0..f9ec9326b6 100644
--- a/crates/ide_assists/src/handlers/convert_bool_then.rs
+++ b/crates/ide_assists/src/handlers/convert_bool_then.rs
@@ -111,7 +111,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) ->
                     | ast::Expr::ForExpr(_)
                     | ast::Expr::IfExpr(_)
                     | ast::Expr::LoopExpr(_)
-                    | ast::Expr::MacroCall(_)
+                    | ast::Expr::MacroExpr(_)
                     | ast::Expr::MatchExpr(_)
                     | ast::Expr::PrefixExpr(_)
                     | ast::Expr::RangeExpr(_)
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index c26e4b1c75..b2cda87f93 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -649,8 +649,8 @@ impl FunctionBody {
             ast::Expr::PathExpr(path_expr) => {
                 cb(path_expr.path().and_then(|it| it.as_single_name_ref()))
             }
-            ast::Expr::MacroCall(call) => {
-                if let Some(tt) = call.token_tree() {
+            ast::Expr::MacroExpr(expr) => {
+                if let Some(tt) = expr.macro_call().and_then(|call| call.token_tree()) {
                     tt.syntax()
                         .children_with_tokens()
                         .flat_map(SyntaxElement::into_token)
@@ -923,7 +923,7 @@ fn reference_is_exclusive(
 
 /// checks if this expr requires `&mut` access, recurses on field access
 fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
-    if let ast::Expr::MacroCall(_) = expr {
+    if let ast::Expr::MacroExpr(_) = expr {
         // FIXME: expand macro and check output for mutable usages of the variable?
         return None;
     }
@@ -1015,7 +1015,7 @@ fn path_element_of_reference(
         None
     })?;
     stdx::always!(
-        matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)),
+        matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroExpr(_)),
         "unexpected expression type for variable usage: {:?}",
         path
     );
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs
index ebea2d5dea..01cd32f55c 100644
--- a/crates/ide_assists/src/handlers/remove_dbg.rs
+++ b/crates/ide_assists/src/handlers/remove_dbg.rs
@@ -39,15 +39,16 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
         .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
         .collect::<Option<Vec<ast::Expr>>>()?;
 
-    let parent = macro_call.syntax().parent()?;
+    let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
+    let parent = macro_expr.syntax().parent()?;
     let (range, text) = match &*input_expressions {
         // dbg!()
         [] => {
             match_ast! {
                 match parent {
                     ast::StmtList(__) => {
-                        let range = macro_call.syntax().text_range();
-                        let range = match whitespace_start(macro_call.syntax().prev_sibling_or_token()) {
+                        let range = macro_expr.syntax().text_range();
+                        let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) {
                             Some(start) => range.cover_offset(start),
                             None => range,
                         };
diff --git a/crates/ide_db/src/syntax_helpers/node_ext.rs b/crates/ide_db/src/syntax_helpers/node_ext.rs
index 3d72045838..f011a4a48a 100644
--- a/crates/ide_db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide_db/src/syntax_helpers/node_ext.rs
@@ -62,11 +62,6 @@ pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent<ast::Expr>)
         match ast::Stmt::cast(node.clone()) {
             // Don't skip subtree since we want to process the expression child next
             Some(ast::Stmt::ExprStmt(_)) | Some(ast::Stmt::LetStmt(_)) => (),
-            // This might be an expression
-            Some(ast::Stmt::Item(ast::Item::MacroCall(mcall))) => {
-                cb(WalkEvent::Enter(ast::Expr::MacroCall(mcall)));
-                preorder.skip_subtree();
-            }
             // skip inner items which might have their own expressions
             Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
             None => {
@@ -319,7 +314,7 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
         | ast::Expr::ForExpr(_)
         | ast::Expr::IndexExpr(_)
         | ast::Expr::Literal(_)
-        | ast::Expr::MacroCall(_)
+        | ast::Expr::MacroExpr(_)
         | ast::Expr::MacroStmts(_)
         | ast::Expr::MethodCallExpr(_)
         | ast::Expr::ParenExpr(_)
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 5d652fb8dc..b063c73a9d 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -552,7 +552,7 @@ fn path_expr(p: &mut Parser, r: Restrictions) -> (CompletedMarker, BlockLike) {
         }
         T![!] if !p.at(T![!=]) => {
             let block_like = items::macro_call_after_excl(p);
-            (m.complete(p, MACRO_CALL), block_like)
+            (m.complete(p, MACRO_CALL).precede(p).complete(p, MACRO_EXPR), block_like)
         }
         _ => (m.complete(p, PATH_EXPR), BlockLike::NotBlock),
     }
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index e5f8e8199e..d137a02ee8 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -605,6 +605,7 @@ fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
     if p.nth_at(1, T![!]) {
         // test try_macro_fallback
         // fn foo() { try!(Ok(())); }
+        let macro_call = p.start();
         let path = p.start();
         let path_segment = p.start();
         let name_ref = p.start();
@@ -613,7 +614,8 @@ fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
         path_segment.complete(p, PATH_SEGMENT);
         path.complete(p, PATH);
         let _block_like = items::macro_call_after_excl(p);
-        return m.complete(p, MACRO_CALL);
+        macro_call.complete(p, MACRO_CALL);
+        return m.complete(p, MACRO_EXPR);
     }
 
     p.bump(T![try]);
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 297809976e..628fa745e7 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -190,6 +190,7 @@ pub enum SyntaxKind {
     YIELD_EXPR,
     LET_EXPR,
     UNDERSCORE_EXPR,
+    MACRO_EXPR,
     MATCH_EXPR,
     MATCH_ARM_LIST,
     MATCH_ARM,
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index cb58486eff..4e8d4b478b 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -342,7 +342,7 @@ Expr =
 | IndexExpr
 | Literal
 | LoopExpr
-| MacroCall
+| MacroExpr
 | MacroStmts
 | MatchExpr
 | MethodCallExpr
@@ -360,6 +360,9 @@ Expr =
 | LetExpr
 | UnderscoreExpr
 
+MacroExpr =
+  MacroCall
+
 Literal =
   Attr* value:(
     'int_number' | 'float_number'
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 6f236c01ce..f51282fff4 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -918,6 +918,14 @@ impl LoopExpr {
     pub fn loop_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![loop]) }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MacroExpr {
+    pub(crate) syntax: SyntaxNode,
+}
+impl MacroExpr {
+    pub fn macro_call(&self) -> Option<MacroCall> { support::child(&self.syntax) }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct MatchExpr {
     pub(crate) syntax: SyntaxNode,
@@ -1518,7 +1526,7 @@ pub enum Expr {
     IndexExpr(IndexExpr),
     Literal(Literal),
     LoopExpr(LoopExpr),
-    MacroCall(MacroCall),
+    MacroExpr(MacroExpr),
     MacroStmts(MacroStmts),
     MatchExpr(MatchExpr),
     MethodCallExpr(MethodCallExpr),
@@ -2532,6 +2540,17 @@ impl AstNode for LoopExpr {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for MacroExpr {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_EXPR }
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AstNode for MatchExpr {
     fn can_cast(kind: SyntaxKind) -> bool { kind == MATCH_EXPR }
     fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -3313,8 +3332,8 @@ impl From<Literal> for Expr {
 impl From<LoopExpr> for Expr {
     fn from(node: LoopExpr) -> Expr { Expr::LoopExpr(node) }
 }
-impl From<MacroCall> for Expr {
-    fn from(node: MacroCall) -> Expr { Expr::MacroCall(node) }
+impl From<MacroExpr> for Expr {
+    fn from(node: MacroExpr) -> Expr { Expr::MacroExpr(node) }
 }
 impl From<MacroStmts> for Expr {
     fn from(node: MacroStmts) -> Expr { Expr::MacroStmts(node) }
@@ -3369,7 +3388,7 @@ impl AstNode for Expr {
         match kind {
             ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BLOCK_EXPR | BOX_EXPR | BREAK_EXPR | CALL_EXPR
             | CAST_EXPR | CLOSURE_EXPR | CONTINUE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR
-            | INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_CALL | MACRO_STMTS | MATCH_EXPR
+            | INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_EXPR | MACRO_STMTS | MATCH_EXPR
             | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR
             | RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR
             | YIELD_EXPR | LET_EXPR | UNDERSCORE_EXPR => true,
@@ -3394,7 +3413,7 @@ impl AstNode for Expr {
             INDEX_EXPR => Expr::IndexExpr(IndexExpr { syntax }),
             LITERAL => Expr::Literal(Literal { syntax }),
             LOOP_EXPR => Expr::LoopExpr(LoopExpr { syntax }),
-            MACRO_CALL => Expr::MacroCall(MacroCall { syntax }),
+            MACRO_EXPR => Expr::MacroExpr(MacroExpr { syntax }),
             MACRO_STMTS => Expr::MacroStmts(MacroStmts { syntax }),
             MATCH_EXPR => Expr::MatchExpr(MatchExpr { syntax }),
             METHOD_CALL_EXPR => Expr::MethodCallExpr(MethodCallExpr { syntax }),
@@ -3433,7 +3452,7 @@ impl AstNode for Expr {
             Expr::IndexExpr(it) => &it.syntax,
             Expr::Literal(it) => &it.syntax,
             Expr::LoopExpr(it) => &it.syntax,
-            Expr::MacroCall(it) => &it.syntax,
+            Expr::MacroExpr(it) => &it.syntax,
             Expr::MacroStmts(it) => &it.syntax,
             Expr::MatchExpr(it) => &it.syntax,
             Expr::MethodCallExpr(it) => &it.syntax,
@@ -4506,6 +4525,11 @@ impl std::fmt::Display for LoopExpr {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for MacroExpr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for MatchExpr {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)


Test patch:
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index 1d84da48fb..c9601f8552 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -79,6 +79,34 @@ fn main() { n_nuple!(1,2,3); }
     );
 }
 
+#[test]
+fn issue_3642_bad_macro_stackover() {
+    lower(
+        r#"
+#[macro_export]
+macro_rules! match_ast {
+    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
+
+    (match ($node:expr) {
+        $( ast::$ast:ident($it:ident) => $res:expr, )*
+        _ => $catch_all:expr $(,)?
+    }) => {{
+        $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
+        { $catch_all }
+    }};
+}
+
+fn main() {
+    let anchor = match_ast! {
+        match parent {
+            as => {},
+            _ => return None
+        }
+    };
+}"#,
+    );
+}
+
 #[test]
 fn macro_resolve() {
     // Regression test for a path resolution bug introduced with inner item handling.
diff --git a/crates/hir_def/src/body/tests/block.rs b/crates/hir_def/src/body/tests/block.rs
index da1e99c829..0748212889 100644
--- a/crates/hir_def/src/body/tests/block.rs
+++ b/crates/hir_def/src/body/tests/block.rs
@@ -371,3 +371,27 @@ fn outer() {
         "#]],
     );
 }
+
+#[test]
+fn stmt_macro_expansion_with_trailing_expr() {
+    cov_mark::check!(macro_stmt_with_trailing_macro_expr);
+    check_at(
+        r#"
+macro_rules! mac {
+    () => { mac!($) };
+    ($x:tt) => { fn inner() {} };
+}
+fn foo() {
+    mac!();
+    $0
+}
+        "#,
+        expect![[r#"
+            block scope
+            inner: v
+
+            crate
+            foo: v
+        "#]],
+    )
+}
diff --git a/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs
index 919dd6c07f..a28478ff4d 100644
--- a/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -249,8 +249,7 @@ macro_rules! format_args {
 
 fn main() {
     let _ =
-        // +errors
-        format_args!("{} {:?}", a.);
+        format_args!/*+errors*/("{} {:?}", a.);
 }
 "#,
         expect![[r##"
diff --git a/crates/hir_def/src/macro_expansion_tests/mbe.rs b/crates/hir_def/src/macro_expansion_tests/mbe.rs
index 2cd70a84c5..c8c7b19aca 100644
--- a/crates/hir_def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir_def/src/macro_expansion_tests/mbe.rs
@@ -530,8 +530,7 @@ macro_rules! m {
 }
 
 fn f() -> i32 {
-    // +tree
-    m!{}
+    m!/*+tree*/{}
 }
 "#,
         expect![[r#"
diff --git a/crates/hir_ty/src/tests/regression.rs b/crates/hir_ty/src/tests/regression.rs
index 65eb2da5af..be2b733a4b 100644
--- a/crates/hir_ty/src/tests/regression.rs
+++ b/crates/hir_ty/src/tests/regression.rs
@@ -444,34 +444,6 @@ fn test() {
     );
 }
 
-#[test]
-fn issue_3642_bad_macro_stackover() {
-    check_no_mismatches(
-        r#"
-#[macro_export]
-macro_rules! match_ast {
-    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
-
-    (match ($node:expr) {
-        $( ast::$ast:ident($it:ident) => $res:expr, )*
-        _ => $catch_all:expr $(,)?
-    }) => {{
-        $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
-        { $catch_all }
-    }};
-}
-
-fn main() {
-    let anchor = match_ast! {
-        match parent {
-            as => {},
-            _ => return None
-        }
-    };
-}"#,
-    );
-}
-
 #[test]
 fn issue_3999_slice() {
     check_infer(
diff --git a/crates/parser/test_data/parser/err/0018_incomplete_fn.rast b/crates/parser/test_data/parser/err/0018_incomplete_fn.rast
index 799720b557..6524d8e8fa 100644
--- a/crates/parser/test_data/parser/err/0018_incomplete_fn.rast
+++ b/crates/parser/test_data/parser/err/0018_incomplete_fn.rast
@@ -104,15 +104,16 @@ SOURCE_FILE
                         IDENT "entries"
                       COLON ":"
                       WHITESPACE " "
-                      MACRO_CALL
-                        PATH
-                          PATH_SEGMENT
-                            NAME_REF
-                              IDENT "vec"
-                        BANG "!"
-                        TOKEN_TREE
-                          L_BRACK "["
-                          R_BRACK "]"
+                      MACRO_EXPR
+                        MACRO_CALL
+                          PATH
+                            PATH_SEGMENT
+                              NAME_REF
+                                IDENT "vec"
+                          BANG "!"
+                          TOKEN_TREE
+                            L_BRACK "["
+                            R_BRACK "]"
                     WHITESPACE " "
                     R_CURLY "}"
                 R_PAREN ")"
diff --git a/crates/parser/test_data/parser/err/0023_mismatched_paren.rast b/crates/parser/test_data/parser/err/0023_mismatched_paren.rast
index 1176400634..4064a7a1ff 100644
--- a/crates/parser/test_data/parser/err/0023_mismatched_paren.rast
+++ b/crates/parser/test_data/parser/err/0023_mismatched_paren.rast
@@ -12,26 +12,27 @@ SOURCE_FILE
       STMT_LIST
         L_CURLY "{"
         WHITESPACE "\n    "
-        MACRO_CALL
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "foo"
-          BANG "!"
-          WHITESPACE " "
-          TOKEN_TREE
-            L_PAREN "("
-            WHITESPACE "\n        "
-            IDENT "bar"
-            COMMA ","
+        MACRO_EXPR
+          MACRO_CALL
+            PATH
+              PATH_SEGMENT
+                NAME_REF
+                  IDENT "foo"
+            BANG "!"
             WHITESPACE " "
-            STRING "\"baz\""
-            COMMA ","
-            WHITESPACE " "
-            INT_NUMBER "1"
-            COMMA ","
-            WHITESPACE " "
-            FLOAT_NUMBER "2.0"
+            TOKEN_TREE
+              L_PAREN "("
+              WHITESPACE "\n        "
+              IDENT "bar"
+              COMMA ","
+              WHITESPACE " "
+              STRING "\"baz\""
+              COMMA ","
+              WHITESPACE " "
+              INT_NUMBER "1"
+              COMMA ","
+              WHITESPACE " "
+              FLOAT_NUMBER "2.0"
         WHITESPACE "\n    "
         R_CURLY "}"
   WHITESPACE " "
diff --git a/crates/parser/test_data/parser/inline/ok/0053_path_expr.rast b/crates/parser/test_data/parser/inline/ok/0053_path_expr.rast
index abd8accf41..48e123ab11 100644
--- a/crates/parser/test_data/parser/inline/ok/0053_path_expr.rast
+++ b/crates/parser/test_data/parser/inline/ok/0053_path_expr.rast
@@ -81,15 +81,16 @@ SOURCE_FILE
           WHITESPACE " "
           EQ "="
           WHITESPACE " "
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "format"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
-              R_PAREN ")"
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "format"
+              BANG "!"
+              TOKEN_TREE
+                L_PAREN "("
+                R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
diff --git a/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.rast b/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.rast
index e4e215593e..f89cc15e72 100644
--- a/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.rast
+++ b/crates/parser/test_data/parser/inline/ok/0096_no_semi_after_block.rast
@@ -110,15 +110,16 @@ SOURCE_FILE
             WHITESPACE "\n    "
             R_CURLY "}"
         WHITESPACE "\n    "
-        MACRO_CALL
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "test"
-          BANG "!"
-          TOKEN_TREE
-            L_CURLY "{"
-            R_CURLY "}"
+        MACRO_EXPR
+          MACRO_CALL
+            PATH
+              PATH_SEGMENT
+                NAME_REF
+                  IDENT "test"
+            BANG "!"
+            TOKEN_TREE
+              L_CURLY "{"
+              R_CURLY "}"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast b/crates/parser/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
index cafd059f24..81b7f2b3cb 100644
--- a/crates/parser/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
+++ b/crates/parser/test_data/parser/inline/ok/0126_attr_on_expr_stmt.rast
@@ -35,7 +35,7 @@ SOURCE_FILE
           SEMICOLON ";"
         WHITESPACE "\n    "
         EXPR_STMT
-          MACRO_CALL
+          MACRO_EXPR
             ATTR
               POUND "#"
               L_BRACK "["
@@ -46,14 +46,15 @@ SOURCE_FILE
                       IDENT "B"
               R_BRACK "]"
             WHITESPACE " "
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "bar"
-            BANG "!"
-            TOKEN_TREE
-              L_CURLY "{"
-              R_CURLY "}"
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "bar"
+              BANG "!"
+              TOKEN_TREE
+                L_CURLY "{"
+                R_CURLY "}"
         WHITESPACE "\n    "
         EXPR_STMT
           BLOCK_EXPR
diff --git a/crates/parser/test_data/parser/inline/ok/0159_try_macro_fallback.rast b/crates/parser/test_data/parser/inline/ok/0159_try_macro_fallback.rast
index d11ac22efa..0adb678fa6 100644
--- a/crates/parser/test_data/parser/inline/ok/0159_try_macro_fallback.rast
+++ b/crates/parser/test_data/parser/inline/ok/0159_try_macro_fallback.rast
@@ -13,22 +13,23 @@ SOURCE_FILE
         L_CURLY "{"
         WHITESPACE " "
         EXPR_STMT
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "try"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
-              IDENT "Ok"
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "try"
+              BANG "!"
               TOKEN_TREE
                 L_PAREN "("
+                IDENT "Ok"
                 TOKEN_TREE
                   L_PAREN "("
+                  TOKEN_TREE
+                    L_PAREN "("
+                    R_PAREN ")"
                   R_PAREN ")"
                 R_PAREN ")"
-              R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE " "
         R_CURLY "}"
diff --git a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast
index 4ec703e517..9f06e2a945 100644
--- a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast
+++ b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast
@@ -396,24 +396,25 @@ SOURCE_FILE
           SEMICOLON ";"
         WHITESPACE "\n    "
         EXPR_STMT
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "assert"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "assert"
+              BANG "!"
               TOKEN_TREE
                 L_PAREN "("
-                IDENT "i"
-                DOT "."
-                IDENT "get"
                 TOKEN_TREE
                   L_PAREN "("
+                  IDENT "i"
+                  DOT "."
+                  IDENT "get"
+                  TOKEN_TREE
+                    L_PAREN "("
+                    R_PAREN ")"
                   R_PAREN ")"
                 R_PAREN ")"
-              R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
@@ -887,24 +888,7 @@ SOURCE_FILE
           PAREN_EXPR
             L_PAREN "("
             BIN_EXPR
-              MACRO_CALL
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "assert"
-                BANG "!"
-                TOKEN_TREE
-                  L_PAREN "("
-                  TOKEN_TREE
-                    L_PAREN "("
-                    TRUE_KW "true"
-                    R_PAREN ")"
-                  R_PAREN ")"
-              WHITESPACE " "
-              EQ2 "=="
-              WHITESPACE " "
-              PAREN_EXPR
-                L_PAREN "("
+              MACRO_EXPR
                 MACRO_CALL
                   PATH
                     PATH_SEGMENT
@@ -913,11 +897,30 @@ SOURCE_FILE
                   BANG "!"
                   TOKEN_TREE
                     L_PAREN "("
-                    IDENT "p"
                     TOKEN_TREE
                       L_PAREN "("
+                      TRUE_KW "true"
                       R_PAREN ")"
                     R_PAREN ")"
+              WHITESPACE " "
+              EQ2 "=="
+              WHITESPACE " "
+              PAREN_EXPR
+                L_PAREN "("
+                MACRO_EXPR
+                  MACRO_CALL
+                    PATH
+                      PATH_SEGMENT
+                        NAME_REF
+                          IDENT "assert"
+                    BANG "!"
+                    TOKEN_TREE
+                      L_PAREN "("
+                      IDENT "p"
+                      TOKEN_TREE
+                        L_PAREN "("
+                        R_PAREN ")"
+                      R_PAREN ")"
                 R_PAREN ")"
             R_PAREN ")"
           SEMICOLON ";"
@@ -934,22 +937,23 @@ SOURCE_FILE
           PAREN_EXPR
             L_PAREN "("
             BIN_EXPR
-              MACRO_CALL
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "assert"
-                BANG "!"
-                TOKEN_TREE
-                  L_PAREN "("
+              MACRO_EXPR
+                MACRO_CALL
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "assert"
+                  BANG "!"
                   TOKEN_TREE
                     L_PAREN "("
-                    IDENT "p"
                     TOKEN_TREE
                       L_PAREN "("
+                      IDENT "p"
+                      TOKEN_TREE
+                        L_PAREN "("
+                        R_PAREN ")"
                       R_PAREN ")"
                     R_PAREN ")"
-                  R_PAREN ")"
               WHITESPACE " "
               EQ2 "=="
               WHITESPACE " "
@@ -978,19 +982,20 @@ SOURCE_FILE
           PAREN_EXPR
             L_PAREN "("
             BIN_EXPR
-              MACRO_CALL
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "println"
-                BANG "!"
-                TOKEN_TREE
-                  L_PAREN "("
-                  STRING "\"{}\""
-                  COMMA ","
-                  WHITESPACE " "
-                  INT_NUMBER "0"
-                  R_PAREN ")"
+              MACRO_EXPR
+                MACRO_CALL
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "println"
+                  BANG "!"
+                  TOKEN_TREE
+                    L_PAREN "("
+                    STRING "\"{}\""
+                    COMMA ","
+                    WHITESPACE " "
+                    INT_NUMBER "0"
+                    R_PAREN ")"
               WHITESPACE " "
               EQ2 "=="
               WHITESPACE " "
@@ -1130,16 +1135,17 @@ SOURCE_FILE
                             WHITESPACE " "
                             FAT_ARROW "=>"
                             WHITESPACE " "
-                            MACRO_CALL
-                              PATH
-                                PATH_SEGMENT
-                                  NAME_REF
-                                    IDENT "panic"
-                              BANG "!"
-                              TOKEN_TREE
-                                L_PAREN "("
-                                STRING "\"wat\""
-                                R_PAREN ")"
+                            MACRO_EXPR
+                              MACRO_CALL
+                                PATH
+                                  PATH_SEGMENT
+                                    NAME_REF
+                                      IDENT "panic"
+                                BANG "!"
+                                TOKEN_TREE
+                                  L_PAREN "("
+                                  STRING "\"wat\""
+                                  R_PAREN ")"
                           WHITESPACE " "
                           R_CURLY "}"
                       WHITESPACE " "
@@ -1176,16 +1182,17 @@ SOURCE_FILE
           WHITESPACE " "
           EQ "="
           WHITESPACE " "
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "println"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
-              STRING "\"lincoln\""
-              R_PAREN ")"
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "println"
+              BANG "!"
+              TOKEN_TREE
+                L_PAREN "("
+                STRING "\"lincoln\""
+                R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE " "
         R_CURLY "}"
@@ -1204,107 +1211,108 @@ SOURCE_FILE
         L_CURLY "{"
         WHITESPACE "\n    "
         EXPR_STMT
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "assert_eq"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
-              IDENT "String"
-              COLON ":"
-              COLON ":"
-              IDENT "from"
-              TOKEN_TREE
-                L_PAREN "("
-                STRING "\"..................................................\""
-                R_PAREN ")"
-              COMMA ","
-              WHITESPACE "\n               "
-              IDENT "format"
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "assert_eq"
               BANG "!"
               TOKEN_TREE
                 L_PAREN "("
-                STRING "\"{:?}\""
+                IDENT "String"
+                COLON ":"
+                COLON ":"
+                IDENT "from"
+                TOKEN_TREE
+                  L_PAREN "("
+                  STRING "\"..................................................\""
+                  R_PAREN ")"
                 COMMA ","
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE "\n                               "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
-                WHITESPACE " "
-                DOT "."
-                DOT "."
+                WHITESPACE "\n               "
+                IDENT "format"
+                BANG "!"
+                TOKEN_TREE
+                  L_PAREN "("
+                  STRING "\"{:?}\""
+                  COMMA ","
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE "\n                               "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  WHITESPACE " "
+                  DOT "."
+                  DOT "."
+                  R_PAREN ")"
                 R_PAREN ")"
-              R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
@@ -1353,141 +1361,142 @@ SOURCE_FILE
               L_CURLY "{"
               WHITESPACE "\n        "
               EXPR_STMT
-                MACRO_CALL
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "assert_eq"
-                  BANG "!"
-                  TOKEN_TREE
-                    L_PAREN "("
-                    INT_NUMBER "8u8"
-                    COMMA ","
-                    WHITESPACE " "
+                MACRO_EXPR
+                  MACRO_CALL
+                    PATH
+                      PATH_SEGMENT
+                        NAME_REF
+                          IDENT "assert_eq"
+                    BANG "!"
                     TOKEN_TREE
-                      L_CURLY "{"
-                      WHITESPACE "\n            "
-                      IDENT "macro_rules"
-                      BANG "!"
-                      WHITESPACE " "
-                      IDENT "u8"
+                      L_PAREN "("
+                      INT_NUMBER "8u8"
+                      COMMA ","
                       WHITESPACE " "
                       TOKEN_TREE
                         L_CURLY "{"
-                        WHITESPACE "\n                "
-                        TOKEN_TREE
-                          L_PAREN "("
-                          IDENT "u8"
-                          R_PAREN ")"
+                        WHITESPACE "\n            "
+                        IDENT "macro_rules"
+                        BANG "!"
                         WHITESPACE " "
-                        EQ "="
-                        R_ANGLE ">"
+                        IDENT "u8"
                         WHITESPACE " "
                         TOKEN_TREE
                           L_CURLY "{"
-                          WHITESPACE "\n                    "
-                          MOD_KW "mod"
+                          WHITESPACE "\n                "
+                          TOKEN_TREE
+                            L_PAREN "("
+                            IDENT "u8"
+                            R_PAREN ")"
                           WHITESPACE " "
-                          IDENT "u8"
+                          EQ "="
+                          R_ANGLE ">"
                           WHITESPACE " "
                           TOKEN_TREE
                             L_CURLY "{"
-                            WHITESPACE "\n                        "
-                            PUB_KW "pub"
-                            WHITESPACE " "
-                            FN_KW "fn"
+                            WHITESPACE "\n                    "
+                            MOD_KW "mod"
                             WHITESPACE " "
                             IDENT "u8"
-                            L_ANGLE "<"
-                            LIFETIME_IDENT "'u8"
-                            COLON ":"
-                            WHITESPACE " "
-                            LIFETIME_IDENT "'u8"
-                            WHITESPACE " "
-                            PLUS "+"
                             WHITESPACE " "
-                            LIFETIME_IDENT "'u8"
-                            R_ANGLE ">"
                             TOKEN_TREE
-                              L_PAREN "("
+                              L_CURLY "{"
+                              WHITESPACE "\n                        "
+                              PUB_KW "pub"
+                              WHITESPACE " "
+                              FN_KW "fn"
+                              WHITESPACE " "
                               IDENT "u8"
+                              L_ANGLE "<"
+                              LIFETIME_IDENT "'u8"
                               COLON ":"
                               WHITESPACE " "
+                              LIFETIME_IDENT "'u8"
+                              WHITESPACE " "
+                              PLUS "+"
+                              WHITESPACE " "
+                              LIFETIME_IDENT "'u8"
+                              R_ANGLE ">"
+                              TOKEN_TREE
+                                L_PAREN "("
+                                IDENT "u8"
+                                COLON ":"
+                                WHITESPACE " "
+                                AMP "&"
+                                LIFETIME_IDENT "'u8"
+                                WHITESPACE " "
+                                IDENT "u8"
+                                R_PAREN ")"
+                              WHITESPACE " "
+                              MINUS "-"
+                              R_ANGLE ">"
+                              WHITESPACE " "
                               AMP "&"
                               LIFETIME_IDENT "'u8"
                               WHITESPACE " "
                               IDENT "u8"
-                              R_PAREN ")"
-                            WHITESPACE " "
-                            MINUS "-"
-                            R_ANGLE ">"
-                            WHITESPACE " "
-                            AMP "&"
-                            LIFETIME_IDENT "'u8"
-                            WHITESPACE " "
-                            IDENT "u8"
-                            WHITESPACE " "
-                            TOKEN_TREE
-                              L_CURLY "{"
-                              WHITESPACE "\n                            "
-                              STRING "\"u8\""
-                              SEMICOLON ";"
-                              WHITESPACE "\n                            "
-                              IDENT "u8"
-                              WHITESPACE "\n                        "
+                              WHITESPACE " "
+                              TOKEN_TREE
+                                L_CURLY "{"
+                                WHITESPACE "\n                            "
+                                STRING "\"u8\""
+                                SEMICOLON ";"
+                                WHITESPACE "\n                            "
+                                IDENT "u8"
+                                WHITESPACE "\n                        "
+                                R_CURLY "}"
+                              WHITESPACE "\n                    "
                               R_CURLY "}"
-                            WHITESPACE "\n                    "
+                            WHITESPACE "\n                "
                             R_CURLY "}"
-                          WHITESPACE "\n                "
+                          SEMICOLON ";"
+                          WHITESPACE "\n            "
                           R_CURLY "}"
+                        WHITESPACE "\n\n            "
+                        IDENT "u8"
+                        BANG "!"
+                        TOKEN_TREE
+                          L_PAREN "("
+                          IDENT "u8"
+                          R_PAREN ")"
                         SEMICOLON ";"
                         WHITESPACE "\n            "
-                        R_CURLY "}"
-                      WHITESPACE "\n\n            "
-                      IDENT "u8"
-                      BANG "!"
-                      TOKEN_TREE
-                        L_PAREN "("
+                        LET_KW "let"
+                        WHITESPACE " "
+                        AMP "&"
                         IDENT "u8"
-                        R_PAREN ")"
-                      SEMICOLON ";"
-                      WHITESPACE "\n            "
-                      LET_KW "let"
-                      WHITESPACE " "
-                      AMP "&"
-                      IDENT "u8"
-                      COLON ":"
-                      WHITESPACE " "
-                      AMP "&"
-                      IDENT "u8"
-                      WHITESPACE " "
-                      EQ "="
-                      WHITESPACE " "
-                      IDENT "u8"
-                      COLON ":"
-                      COLON ":"
-                      IDENT "u8"
-                      TOKEN_TREE
-                        L_PAREN "("
+                        COLON ":"
+                        WHITESPACE " "
                         AMP "&"
-                        INT_NUMBER "8u8"
-                        R_PAREN ")"
-                      SEMICOLON ";"
-                      WHITESPACE "\n            "
-                      CRATE_KW "crate"
-                      COLON ":"
-                      COLON ":"
-                      IDENT "u8"
-                      TOKEN_TREE
-                        L_PAREN "("
-                        INT_NUMBER "0u8"
-                        R_PAREN ")"
-                      SEMICOLON ";"
-                      WHITESPACE "\n            "
-                      IDENT "u8"
-                      WHITESPACE "\n        "
-                      R_CURLY "}"
-                    R_PAREN ")"
+                        IDENT "u8"
+                        WHITESPACE " "
+                        EQ "="
+                        WHITESPACE " "
+                        IDENT "u8"
+                        COLON ":"
+                        COLON ":"
+                        IDENT "u8"
+                        TOKEN_TREE
+                          L_PAREN "("
+                          AMP "&"
+                          INT_NUMBER "8u8"
+                          R_PAREN ")"
+                        SEMICOLON ";"
+                        WHITESPACE "\n            "
+                        CRATE_KW "crate"
+                        COLON ":"
+                        COLON ":"
+                        IDENT "u8"
+                        TOKEN_TREE
+                          L_PAREN "("
+                          INT_NUMBER "0u8"
+                          R_PAREN ")"
+                        SEMICOLON ";"
+                        WHITESPACE "\n            "
+                        IDENT "u8"
+                        WHITESPACE "\n        "
+                        R_CURLY "}"
+                      R_PAREN ")"
                 SEMICOLON ";"
               WHITESPACE "\n    "
               R_CURLY "}"
@@ -1508,69 +1517,70 @@ SOURCE_FILE
         L_CURLY "{"
         WHITESPACE "\n    "
         EXPR_STMT
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "assert_eq"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
-              IDENT "String"
-              COLON ":"
-              COLON ":"
-              IDENT "from"
-              TOKEN_TREE
-                L_PAREN "("
-                STRING "\"><>\""
-                R_PAREN ")"
-              COMMA ","
-              WHITESPACE "\n               "
-              IDENT "String"
-              COLON ":"
-              COLON ":"
-              L_ANGLE "<"
-              R_ANGLE ">"
-              COLON ":"
-              COLON ":"
-              IDENT "from"
-              COLON ":"
-              COLON ":"
-              L_ANGLE "<"
-              R_ANGLE ">"
-              TOKEN_TREE
-                L_PAREN "("
-                STRING "\"><>\""
-                R_PAREN ")"
-              DOT "."
-              IDENT "chars"
-              COLON ":"
-              COLON ":"
-              L_ANGLE "<"
-              R_ANGLE ">"
-              TOKEN_TREE
-                L_PAREN "("
-                R_PAREN ")"
-              DOT "."
-              IDENT "rev"
-              COLON ":"
-              COLON ":"
-              L_ANGLE "<"
-              R_ANGLE ">"
-              TOKEN_TREE
-                L_PAREN "("
-                R_PAREN ")"
-              DOT "."
-              IDENT "collect"
-              COLON ":"
-              COLON ":"
-              L_ANGLE "<"
-              IDENT "String"
-              R_ANGLE ">"
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "assert_eq"
+              BANG "!"
               TOKEN_TREE
                 L_PAREN "("
+                IDENT "String"
+                COLON ":"
+                COLON ":"
+                IDENT "from"
+                TOKEN_TREE
+                  L_PAREN "("
+                  STRING "\"><>\""
+                  R_PAREN ")"
+                COMMA ","
+                WHITESPACE "\n               "
+                IDENT "String"
+                COLON ":"
+                COLON ":"
+                L_ANGLE "<"
+                R_ANGLE ">"
+                COLON ":"
+                COLON ":"
+                IDENT "from"
+                COLON ":"
+                COLON ":"
+                L_ANGLE "<"
+                R_ANGLE ">"
+                TOKEN_TREE
+                  L_PAREN "("
+                  STRING "\"><>\""
+                  R_PAREN ")"
+                DOT "."
+                IDENT "chars"
+                COLON ":"
+                COLON ":"
+                L_ANGLE "<"
+                R_ANGLE ">"
+                TOKEN_TREE
+                  L_PAREN "("
+                  R_PAREN ")"
+                DOT "."
+                IDENT "rev"
+                COLON ":"
+                COLON ":"
+                L_ANGLE "<"
+                R_ANGLE ">"
+                TOKEN_TREE
+                  L_PAREN "("
+                  R_PAREN ")"
+                DOT "."
+                IDENT "collect"
+                COLON ":"
+                COLON ":"
+                L_ANGLE "<"
+                IDENT "String"
+                R_ANGLE ">"
+                TOKEN_TREE
+                  L_PAREN "("
+                  R_PAREN ")"
                 R_PAREN ")"
-              R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
@@ -1741,17 +1751,18 @@ SOURCE_FILE
           SEMICOLON ";"
         WHITESPACE "\n    "
         EXPR_STMT
-          MACRO_CALL
-            PATH
-              PATH_SEGMENT
-                NAME_REF
-                  IDENT "assert"
-            BANG "!"
-            TOKEN_TREE
-              L_PAREN "("
+          MACRO_EXPR
+            MACRO_CALL
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "assert"
               BANG "!"
-              IDENT "val"
-              R_PAREN ")"
+              TOKEN_TREE
+                L_PAREN "("
+                BANG "!"
+                IDENT "val"
+                R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n"
         R_CURLY "}"
diff --git a/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast b/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast
index ff2121a58a..24a589ca2e 100644
--- a/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast
+++ b/crates/parser/test_data/parser/ok/0042_ufcs_call_list.rast
@@ -42,15 +42,16 @@ SOURCE_FILE
           STMT_LIST
             L_CURLY "{"
             WHITESPACE "\n        "
-            MACRO_CALL
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "unimplemented"
-              BANG "!"
-              TOKEN_TREE
-                L_PAREN "("
-                R_PAREN ")"
+            MACRO_EXPR
+              MACRO_CALL
+                PATH
+                  PATH_SEGMENT
+                    NAME_REF
+                      IDENT "unimplemented"
+                BANG "!"
+                TOKEN_TREE
+                  L_PAREN "("
+                  R_PAREN ")"
             WHITESPACE "\n    "
             R_CURLY "}"
       WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast b/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast
index 81797c35ba..1cafc775cd 100644
--- a/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast
+++ b/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast
@@ -38,15 +38,16 @@ SOURCE_FILE
                           IDENT "B"
                   R_BRACK "]"
                 WHITESPACE " "
-                MACRO_CALL
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "bar"
-                  BANG "!"
-                  TOKEN_TREE
-                    L_PAREN "("
-                    R_PAREN ")"
+                MACRO_EXPR
+                  MACRO_CALL
+                    PATH
+                      PATH_SEGMENT
+                        NAME_REF
+                          IDENT "bar"
+                    BANG "!"
+                    TOKEN_TREE
+                      L_PAREN "("
+                      R_PAREN ")"
                 QUESTION "?"
               WHITESPACE " "
               R_CURLY "}"
diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs
index e808cb4be4..2f6932a1ad 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -144,6 +144,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "YIELD_EXPR",
         "LET_EXPR",
         "UNDERSCORE_EXPR",
+        "MACRO_EXPR",
         "MATCH_EXPR",
         "MATCH_ARM_LIST",
         "MATCH_ARM",