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.
This commit is contained in:
Aaron Hill 2020-10-25 17:14:19 -04:00
parent 499ebcfdf3
commit e78e9d4a06
No known key found for this signature in database
GPG key ID: B4087E510E98B164
6 changed files with 77 additions and 2 deletions

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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")

View file

@ -0,0 +1,10 @@
macro_rules! empty {
() => { }
}
fn foo() -> bool { //~ ERROR mismatched
{ true } //~ ERROR mismatched
empty!();
}
fn main() {}

View file

@ -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`.

View file

@ -40,7 +40,7 @@ macro_rules! produce_it
}
}
fn main /* 0#0 */() { }
fn main /* 0#0 */() { ; }
/*
Expansions: