Skip to content

Commit

Permalink
Treat trailing semicolon as a statement in macro call
Browse files Browse the repository at this point in the history
See rust-lang#61733 (comment)

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.
  • Loading branch information
Aaron1011 committed Nov 2, 2020
1 parent 499ebcf commit e78e9d4
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 2 deletions.
7 changes: 7 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
38 changes: 37 additions & 1 deletion compiler/rustc_expand/src/placeholders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_lint/src/redundant_semicolon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions src/test/ui/macros/empty-trailing-stmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
macro_rules! empty {
() => { }
}

fn foo() -> bool { //~ ERROR mismatched
{ true } //~ ERROR mismatched
empty!();
}

fn main() {}
17 changes: 17 additions & 0 deletions src/test/ui/macros/empty-trailing-stmt.stderr
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion src/test/ui/proc-macro/meta-macro-hygiene.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ macro_rules! produce_it
}
}

fn main /* 0#0 */() { }
fn main /* 0#0 */() { ; }

/*
Expansions:
Expand Down

0 comments on commit e78e9d4

Please sign in to comment.