Skip to content

Commit e78e9d4

Browse files
committed
Treat trailing semicolon as a statement in macro call
See #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.
1 parent 499ebcf commit e78e9d4

File tree

6 files changed

+77
-2
lines changed

6 files changed

+77
-2
lines changed

compiler/rustc_ast/src/ast.rs

+7
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,13 @@ pub struct Stmt {
905905
}
906906

907907
impl Stmt {
908+
pub fn has_trailing_semicolon(&self) -> bool {
909+
match &self.kind {
910+
StmtKind::Semi(_) => true,
911+
StmtKind::MacCall(mac) => matches!(mac.style, MacStmtStyle::Semicolon),
912+
_ => false,
913+
}
914+
}
908915
pub fn add_trailing_semicolon(mut self) -> Self {
909916
self.kind = match self.kind {
910917
StmtKind::Expr(expr) => StmtKind::Semi(expr),

compiler/rustc_expand/src/placeholders.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,44 @@ impl<'a, 'b> MutVisitor for PlaceholderExpander<'a, 'b> {
310310
};
311311

312312
if style == ast::MacStmtStyle::Semicolon {
313+
// Implement the proposal described in
314+
// https://github.com/rust-lang/rust/issues/61733#issuecomment-509626449
315+
//
316+
// The macro invocation expands to the list of statements.
317+
// If the list of statements is empty, then 'parse'
318+
// the trailing semicolon on the original invocation
319+
// as an empty statement. That is:
320+
//
321+
// `empty();` is parsed as a single `StmtKind::Empty`
322+
//
323+
// If the list of statements is non-empty, see if the
324+
// final statement alreayd has a trailing semicolon.
325+
//
326+
// If it doesn't have a semicolon, then 'parse' the trailing semicolon
327+
// from the invocation as part of the final statement,
328+
// using `stmt.add_trailing_semicolon()`
329+
//
330+
// If it does have a semicolon, then 'parse' the trailing semicolon
331+
// from the invocation as a new StmtKind::Empty
332+
333+
// FIXME: We will need to preserve the original
334+
// semicolon token and span as part of #15701
335+
let empty_stmt = ast::Stmt {
336+
id: ast::DUMMY_NODE_ID,
337+
kind: ast::StmtKind::Empty,
338+
span: DUMMY_SP,
339+
tokens: None,
340+
};
341+
313342
if let Some(stmt) = stmts.pop() {
314-
stmts.push(stmt.add_trailing_semicolon());
343+
if stmt.has_trailing_semicolon() {
344+
stmts.push(stmt);
345+
stmts.push(empty_stmt);
346+
} else {
347+
stmts.push(stmt.add_trailing_semicolon());
348+
}
349+
} else {
350+
stmts.push(empty_stmt);
315351
}
316352
}
317353

compiler/rustc_lint/src/redundant_semicolon.rs

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ impl EarlyLintPass for RedundantSemicolons {
4242

4343
fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) {
4444
if let Some((span, multiple)) = seq.take() {
45+
// FIXME: Find a better way of ignoring the trailing
46+
// semicolon from macro expansion
47+
if span == rustc_span::DUMMY_SP {
48+
return;
49+
}
4550
cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
4651
let (msg, rem) = if multiple {
4752
("unnecessary trailing semicolons", "remove these semicolons")
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
macro_rules! empty {
2+
() => { }
3+
}
4+
5+
fn foo() -> bool { //~ ERROR mismatched
6+
{ true } //~ ERROR mismatched
7+
empty!();
8+
}
9+
10+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/empty-trailing-stmt.rs:6:7
3+
|
4+
LL | { true }
5+
| ^^^^ expected `()`, found `bool`
6+
7+
error[E0308]: mismatched types
8+
--> $DIR/empty-trailing-stmt.rs:5:13
9+
|
10+
LL | fn foo() -> bool {
11+
| --- ^^^^ expected `bool`, found `()`
12+
| |
13+
| implicitly returns `()` as its body has no tail or `return` expression
14+
15+
error: aborting due to 2 previous errors
16+
17+
For more information about this error, try `rustc --explain E0308`.

src/test/ui/proc-macro/meta-macro-hygiene.stdout

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ macro_rules! produce_it
4040
}
4141
}
4242

43-
fn main /* 0#0 */() { }
43+
fn main /* 0#0 */() { ; }
4444

4545
/*
4646
Expansions:

0 commit comments

Comments
 (0)