From e78e9d4a06192cfbb9e1417fdd7a0753d51684a3 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 25 Oct 2020 17:14:19 -0400 Subject: [PATCH] Treat trailing semicolon as a statement in macro call See https://github.com/rust-lang/rust/issues/61733#issuecomment-716188981 We now preserve the trailing semicolon in a macro invocation, even if the macro expands to nothing. As a result, the following code no longer compiles: ```rust macro_rules! empty { () => { } } fn foo() -> bool { //~ ERROR mismatched { true } //~ ERROR mismatched empty!(); } ``` Previously, `{ true }` would be considered the trailing expression, even though there's a semicolon in `empty!();` This makes macro expansion more token-based. --- compiler/rustc_ast/src/ast.rs | 7 ++++ compiler/rustc_expand/src/placeholders.rs | 38 ++++++++++++++++++- .../rustc_lint/src/redundant_semicolon.rs | 5 +++ src/test/ui/macros/empty-trailing-stmt.rs | 10 +++++ src/test/ui/macros/empty-trailing-stmt.stderr | 17 +++++++++ .../ui/proc-macro/meta-macro-hygiene.stdout | 2 +- 6 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/macros/empty-trailing-stmt.rs create mode 100644 src/test/ui/macros/empty-trailing-stmt.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 7d5e235c88526..f13d67b9c1584 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -905,6 +905,13 @@ pub struct Stmt { } impl Stmt { + pub fn has_trailing_semicolon(&self) -> bool { + match &self.kind { + StmtKind::Semi(_) => true, + StmtKind::MacCall(mac) => matches!(mac.style, MacStmtStyle::Semicolon), + _ => false, + } + } pub fn add_trailing_semicolon(mut self) -> Self { self.kind = match self.kind { StmtKind::Expr(expr) => StmtKind::Semi(expr), diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 1bc14ae41bf29..0cffca1727124 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -310,8 +310,44 @@ impl<'a, 'b> MutVisitor for PlaceholderExpander<'a, 'b> { }; if style == ast::MacStmtStyle::Semicolon { + // Implement the proposal described in + // https://github.com/rust-lang/rust/issues/61733#issuecomment-509626449 + // + // The macro invocation expands to the list of statements. + // If the list of statements is empty, then 'parse' + // the trailing semicolon on the original invocation + // as an empty statement. That is: + // + // `empty();` is parsed as a single `StmtKind::Empty` + // + // If the list of statements is non-empty, see if the + // final statement alreayd has a trailing semicolon. + // + // If it doesn't have a semicolon, then 'parse' the trailing semicolon + // from the invocation as part of the final statement, + // using `stmt.add_trailing_semicolon()` + // + // If it does have a semicolon, then 'parse' the trailing semicolon + // from the invocation as a new StmtKind::Empty + + // FIXME: We will need to preserve the original + // semicolon token and span as part of #15701 + let empty_stmt = ast::Stmt { + id: ast::DUMMY_NODE_ID, + kind: ast::StmtKind::Empty, + span: DUMMY_SP, + tokens: None, + }; + if let Some(stmt) = stmts.pop() { - stmts.push(stmt.add_trailing_semicolon()); + if stmt.has_trailing_semicolon() { + stmts.push(stmt); + stmts.push(empty_stmt); + } else { + stmts.push(stmt.add_trailing_semicolon()); + } + } else { + stmts.push(empty_stmt); } } diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs index a31deb87ff0d0..84cc7b68d4ca9 100644 --- a/compiler/rustc_lint/src/redundant_semicolon.rs +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -42,6 +42,11 @@ impl EarlyLintPass for RedundantSemicolons { fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) { if let Some((span, multiple)) = seq.take() { + // FIXME: Find a better way of ignoring the trailing + // semicolon from macro expansion + if span == rustc_span::DUMMY_SP { + return; + } cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| { let (msg, rem) = if multiple { ("unnecessary trailing semicolons", "remove these semicolons") diff --git a/src/test/ui/macros/empty-trailing-stmt.rs b/src/test/ui/macros/empty-trailing-stmt.rs new file mode 100644 index 0000000000000..3d78ed4a4759a --- /dev/null +++ b/src/test/ui/macros/empty-trailing-stmt.rs @@ -0,0 +1,10 @@ +macro_rules! empty { + () => { } +} + +fn foo() -> bool { //~ ERROR mismatched + { true } //~ ERROR mismatched + empty!(); +} + +fn main() {} diff --git a/src/test/ui/macros/empty-trailing-stmt.stderr b/src/test/ui/macros/empty-trailing-stmt.stderr new file mode 100644 index 0000000000000..e88b12712fb8c --- /dev/null +++ b/src/test/ui/macros/empty-trailing-stmt.stderr @@ -0,0 +1,17 @@ +error[E0308]: mismatched types + --> $DIR/empty-trailing-stmt.rs:6:7 + | +LL | { true } + | ^^^^ expected `()`, found `bool` + +error[E0308]: mismatched types + --> $DIR/empty-trailing-stmt.rs:5:13 + | +LL | fn foo() -> bool { + | --- ^^^^ expected `bool`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/proc-macro/meta-macro-hygiene.stdout b/src/test/ui/proc-macro/meta-macro-hygiene.stdout index 81cebae17aeba..a067b7b5411dd 100644 --- a/src/test/ui/proc-macro/meta-macro-hygiene.stdout +++ b/src/test/ui/proc-macro/meta-macro-hygiene.stdout @@ -40,7 +40,7 @@ macro_rules! produce_it } } -fn main /* 0#0 */() { } +fn main /* 0#0 */() { ; } /* Expansions: