From 4791a7a63bcc980f12d6b95e53439e77fb54a40c Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Sat, 25 Jul 2026 16:44:06 +0000
Subject: [PATCH 25/30] expand: ignore trailing semicolon of tail-position
 macro expansions

rustc accepts a macro expansion ending in `;` when the invocation is in
expression position (the `semicolon_in_expressions_from_macros`
backcompat rule). mrustc kept the semicolon, so a block whose tail was
such a macro evaluated to `()` -> bogus "Type mismatch between <T> and
()" with a null span. Now, when the invocation had no semicolon and was
the last line of its block, the expansion's final line has its
semicolon cleared (unless it is a let binding). Seen with syn 0.13.9,
whose `parens!`/`named!` combinators expand through an arm that appends
`;` in tail position (blocks xsv).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNXc7PddzJE4X1swqbe3Xn
---
 src/expand/mod.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/expand/mod.cpp b/src/expand/mod.cpp
index 17d91f2f..c4519f33 100644
--- a/src/expand/mod.cpp
+++ b/src/expand/mod.cpp
@@ -993,6 +993,16 @@ struct CExpandExpr:
                     if( it->has_semicolon && !new_nodes.empty() ) {
                         new_nodes.back().has_semicolon = true;
                     }
+                    // If this macro invocation had NO semicolon and was the tail node of the
+                    // block, then it's in expression position - any trailing semicolon that the
+                    // expansion introduced on its own last line must be dropped (rustc's
+                    // `semicolon_in_expressions_from_macros` backcompat behaviour), so the block
+                    // still evaluates to the expansion's value rather than `()`.
+                    else if( !it->has_semicolon && (it + 1) == node.m_nodes.end() && !new_nodes.empty() ) {
+                        if( !dynamic_cast<::AST::ExprNode_LetBinding*>(new_nodes.back().node.get()) ) {
+                            new_nodes.back().has_semicolon = false;
+                        }
+                    }
                     it = node.m_nodes.erase(it);
                     it = node.m_nodes.insert(it, ::std::make_move_iterator(new_nodes.begin()), ::std::make_move_iterator(new_nodes.end()));
                     // NOTE: Doesn't advance the iterator above, we want to re-visit the new node
-- 
2.43.0

