Skip to content

Commit 2ab18ce

Browse files
authored
Auto merge of #34660 - jseyfried:fix_parse_stmt, r=nrc
Fix bugs in macro-expanded statement parsing Fixes #34543. This is a [breaking-change]. For example, the following would break: ```rust macro_rules! m { () => { println!("") println!("") //^ Semicolons are now required on macro-expanded non-braced macro invocations //| in statement positions. let x = 0 //^ Semicolons are now required on macro-expanded `let` statements //| that are followed by more statements, so this would break. let y = 0 //< (this would still be allowed to reduce breakage in the wild) } fn main() { m!() } ``` r? @eddyb
2 parents 617039b + 57fac56 commit 2ab18ce

File tree

9 files changed

+125
-119
lines changed

9 files changed

+125
-119
lines changed

src/doc/book/macros.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ invocation site. Code such as the following will not work:
328328

329329
```rust,ignore
330330
macro_rules! foo {
331-
() => (let x = 3);
331+
() => (let x = 3;);
332332
}
333333
334334
fn main() {
@@ -342,7 +342,7 @@ tagged with the right syntax context.
342342

343343
```rust
344344
macro_rules! foo {
345-
($v:ident) => (let $v = 3);
345+
($v:ident) => (let $v = 3;);
346346
}
347347

348348
fn main() {

src/librustc_save_analysis/dump_visitor.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ macro_rules! down_cast_data {
5757
data
5858
} else {
5959
span_bug!($sp, "unexpected data kind: {:?}", $id);
60-
}
60+
};
6161
};
6262
}
6363

src/libsyntax/ast.rs

+13
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,19 @@ pub struct Stmt {
804804
pub span: Span,
805805
}
806806

807+
impl Stmt {
808+
pub fn add_trailing_semicolon(mut self) -> Self {
809+
self.node = match self.node {
810+
StmtKind::Expr(expr) => StmtKind::Semi(expr),
811+
StmtKind::Mac(mac) => StmtKind::Mac(mac.map(|(mac, _style, attrs)| {
812+
(mac, MacStmtStyle::Semicolon, attrs)
813+
})),
814+
node @ _ => node,
815+
};
816+
self
817+
}
818+
}
819+
807820
impl fmt::Debug for Stmt {
808821
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
809822
write!(f, "stmt({}: {})", self.id.to_string(), pprust::stmt_to_string(self))

src/libsyntax/ext/expand.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -444,14 +444,7 @@ fn expand_stmt(stmt: Stmt, fld: &mut MacroExpander) -> SmallVector<Stmt> {
444444
// semicolon to the final statement produced by expansion.
445445
if style == MacStmtStyle::Semicolon {
446446
if let Some(stmt) = fully_expanded.pop() {
447-
fully_expanded.push(Stmt {
448-
id: stmt.id,
449-
node: match stmt.node {
450-
StmtKind::Expr(expr) => StmtKind::Semi(expr),
451-
_ => stmt.node /* might already have a semi */
452-
},
453-
span: stmt.span,
454-
});
447+
fully_expanded.push(stmt.add_trailing_semicolon());
455448
}
456449
}
457450

src/libsyntax/ext/tt/macro_rules.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl<'a> MacResult for ParserAnyMacro<'a> {
123123
let mut parser = self.parser.borrow_mut();
124124
match parser.token {
125125
token::Eof => break,
126-
_ => match parser.parse_stmt() {
126+
_ => match parser.parse_full_stmt(true) {
127127
Ok(maybe_stmt) => match maybe_stmt {
128128
Some(stmt) => ret.push(stmt),
129129
None => (),

src/libsyntax/parse/parser.rs

+82-103
Original file line numberDiff line numberDiff line change
@@ -3789,7 +3789,13 @@ impl<'a> Parser<'a> {
37893789
self.span_err(self.last_span, message);
37903790
}
37913791

3792-
/// Parse a statement. may include decl.
3792+
/// Parse a statement. This stops just before trailing semicolons on everything but items.
3793+
/// e.g. a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed.
3794+
///
3795+
/// Also, if a macro begins an expression statement, this only parses the macro. For example,
3796+
/// ```rust
3797+
/// vec![1].into_iter(); //< `parse_stmt` only parses the "vec![1]"
3798+
/// ```
37933799
pub fn parse_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
37943800
Ok(self.parse_stmt_())
37953801
}
@@ -4038,36 +4044,14 @@ impl<'a> Parser<'a> {
40384044
let mut stmts = vec![];
40394045

40404046
while !self.eat(&token::CloseDelim(token::Brace)) {
4041-
let Stmt {node, span, ..} = if let Some(s) = self.parse_stmt_() {
4042-
s
4047+
if let Some(stmt) = self.parse_full_stmt(false)? {
4048+
stmts.push(stmt);
40434049
} else if self.token == token::Eof {
40444050
break;
40454051
} else {
40464052
// Found only `;` or `}`.
40474053
continue;
40484054
};
4049-
4050-
match node {
4051-
StmtKind::Expr(e) => {
4052-
self.handle_expression_like_statement(e, span, &mut stmts)?;
4053-
}
4054-
StmtKind::Mac(mac) => {
4055-
self.handle_macro_in_block(mac.unwrap(), span, &mut stmts)?;
4056-
}
4057-
_ => { // all other kinds of statements:
4058-
let mut hi = span.hi;
4059-
if classify::stmt_ends_with_semi(&node) {
4060-
self.expect(&token::Semi)?;
4061-
hi = self.last_span.hi;
4062-
}
4063-
4064-
stmts.push(Stmt {
4065-
id: ast::DUMMY_NODE_ID,
4066-
node: node,
4067-
span: mk_sp(span.lo, hi)
4068-
});
4069-
}
4070-
}
40714055
}
40724056

40734057
Ok(P(ast::Block {
@@ -4078,93 +4062,88 @@ impl<'a> Parser<'a> {
40784062
}))
40794063
}
40804064

4081-
fn handle_macro_in_block(&mut self,
4082-
(mac, style, attrs): (ast::Mac, MacStmtStyle, ThinVec<Attribute>),
4083-
span: Span,
4084-
stmts: &mut Vec<Stmt>)
4085-
-> PResult<'a, ()> {
4086-
if style == MacStmtStyle::NoBraces {
4087-
// statement macro without braces; might be an
4088-
// expr depending on whether a semicolon follows
4089-
match self.token {
4090-
token::Semi => {
4091-
stmts.push(Stmt {
4092-
id: ast::DUMMY_NODE_ID,
4093-
node: StmtKind::Mac(P((mac, MacStmtStyle::Semicolon, attrs))),
4094-
span: mk_sp(span.lo, self.span.hi),
4095-
});
4096-
self.bump();
4097-
}
4098-
_ => {
4099-
let e = self.mk_mac_expr(span.lo, span.hi, mac.node, ThinVec::new());
4100-
let lo = e.span.lo;
4101-
let e = self.parse_dot_or_call_expr_with(e, lo, attrs)?;
4102-
let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
4103-
self.handle_expression_like_statement(e, span, stmts)?;
4104-
}
4105-
}
4106-
} else {
4107-
// statement macro; might be an expr
4108-
match self.token {
4109-
token::Semi => {
4110-
stmts.push(Stmt {
4111-
id: ast::DUMMY_NODE_ID,
4112-
node: StmtKind::Mac(P((mac, MacStmtStyle::Semicolon, attrs))),
4113-
span: mk_sp(span.lo, self.span.hi),
4114-
});
4115-
self.bump();
4116-
}
4117-
_ => {
4118-
stmts.push(Stmt {
4119-
id: ast::DUMMY_NODE_ID,
4120-
node: StmtKind::Mac(P((mac, style, attrs))),
4121-
span: span
4122-
});
4065+
/// Parse a statement, including the trailing semicolon.
4066+
/// This parses expression statements that begin with macros correctly (c.f. `parse_stmt`).
4067+
pub fn parse_full_stmt(&mut self, macro_expanded: bool) -> PResult<'a, Option<Stmt>> {
4068+
let mut stmt = match self.parse_stmt_() {
4069+
Some(stmt) => stmt,
4070+
None => return Ok(None),
4071+
};
4072+
4073+
if let StmtKind::Mac(mac) = stmt.node {
4074+
if mac.1 != MacStmtStyle::NoBraces ||
4075+
self.token == token::Semi || self.token == token::Eof {
4076+
stmt.node = StmtKind::Mac(mac);
4077+
} else {
4078+
// We used to incorrectly stop parsing macro-expanded statements here.
4079+
// If the next token will be an error anyway but could have parsed with the
4080+
// earlier behavior, stop parsing here and emit a warning to avoid breakage.
4081+
if macro_expanded && self.token.can_begin_expr() && match self.token {
4082+
// These tokens can continue an expression, so we can't stop parsing and warn.
4083+
token::OpenDelim(token::Paren) | token::OpenDelim(token::Bracket) |
4084+
token::BinOp(token::Minus) | token::BinOp(token::Star) |
4085+
token::BinOp(token::And) | token::BinOp(token::Or) |
4086+
token::AndAnd | token::OrOr |
4087+
token::DotDot | token::DotDotDot => false,
4088+
_ => true,
4089+
} {
4090+
self.warn_missing_semicolon();
4091+
stmt.node = StmtKind::Mac(mac);
4092+
return Ok(Some(stmt));
41234093
}
4094+
4095+
let (mac, _style, attrs) = mac.unwrap();
4096+
let e = self.mk_mac_expr(stmt.span.lo, stmt.span.hi, mac.node, ThinVec::new());
4097+
let e = self.parse_dot_or_call_expr_with(e, stmt.span.lo, attrs)?;
4098+
let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
4099+
stmt.node = StmtKind::Expr(e);
41244100
}
41254101
}
4126-
Ok(())
4102+
4103+
stmt = self.handle_trailing_semicolon(stmt, macro_expanded)?;
4104+
Ok(Some(stmt))
41274105
}
41284106

4129-
fn handle_expression_like_statement(&mut self,
4130-
e: P<Expr>,
4131-
span: Span,
4132-
stmts: &mut Vec<Stmt>)
4133-
-> PResult<'a, ()> {
4134-
// expression without semicolon
4135-
if classify::expr_requires_semi_to_be_stmt(&e) {
4136-
// Just check for errors and recover; do not eat semicolon yet.
4137-
if let Err(mut e) =
4138-
self.expect_one_of(&[], &[token::Semi, token::CloseDelim(token::Brace)])
4139-
{
4140-
e.emit();
4141-
self.recover_stmt();
4107+
fn handle_trailing_semicolon(&mut self, mut stmt: Stmt, macro_expanded: bool)
4108+
-> PResult<'a, Stmt> {
4109+
match stmt.node {
4110+
StmtKind::Expr(ref expr) if self.token != token::Eof => {
4111+
// expression without semicolon
4112+
if classify::expr_requires_semi_to_be_stmt(expr) {
4113+
// Just check for errors and recover; do not eat semicolon yet.
4114+
if let Err(mut e) =
4115+
self.expect_one_of(&[], &[token::Semi, token::CloseDelim(token::Brace)])
4116+
{
4117+
e.emit();
4118+
self.recover_stmt();
4119+
}
4120+
}
4121+
}
4122+
StmtKind::Local(..) => {
4123+
// We used to incorrectly allow a macro-expanded let statement to lack a semicolon.
4124+
if macro_expanded && self.token != token::Semi {
4125+
self.warn_missing_semicolon();
4126+
} else {
4127+
self.expect_one_of(&[token::Semi], &[])?;
4128+
}
41424129
}
4130+
_ => {}
41434131
}
41444132

4145-
match self.token {
4146-
token::Semi => {
4147-
self.bump();
4148-
let span_with_semi = Span {
4149-
lo: span.lo,
4150-
hi: self.last_span.hi,
4151-
expn_id: span.expn_id,
4152-
};
4153-
stmts.push(Stmt {
4154-
id: ast::DUMMY_NODE_ID,
4155-
node: StmtKind::Semi(e),
4156-
span: span_with_semi,
4157-
});
4158-
}
4159-
_ => {
4160-
stmts.push(Stmt {
4161-
id: ast::DUMMY_NODE_ID,
4162-
node: StmtKind::Expr(e),
4163-
span: span
4164-
});
4165-
}
4133+
if self.eat(&token::Semi) {
4134+
stmt = stmt.add_trailing_semicolon();
41664135
}
4167-
Ok(())
4136+
4137+
stmt.span.hi = self.last_span.hi;
4138+
Ok(stmt)
4139+
}
4140+
4141+
fn warn_missing_semicolon(&self) {
4142+
self.diagnostic().struct_span_warn(self.span, {
4143+
&format!("expected `;`, found `{}`", self.this_token_to_string())
4144+
}).note({
4145+
"This was erroneously allowed and will become a hard error in a future release"
4146+
}).emit();
41684147
}
41694148

41704149
// Parses a sequence of bounds if a `:` is found,

src/libsyntax/print/pprust.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1638,9 +1638,8 @@ impl<'a> State<'a> {
16381638
_ => token::Paren
16391639
};
16401640
try!(self.print_mac(&mac, delim));
1641-
match style {
1642-
ast::MacStmtStyle::Braces => {}
1643-
_ => try!(word(&mut self.s, ";")),
1641+
if style == ast::MacStmtStyle::Semicolon {
1642+
try!(word(&mut self.s, ";"));
16441643
}
16451644
}
16461645
}

src/test/compile-fail/macro-incomplete-parse.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ macro_rules! ignored_item {
1919
}
2020

2121
macro_rules! ignored_expr {
22-
() => ( 1, //~ ERROR expected expression, found `,`
22+
() => ( 1, //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `,`
2323
2 )
2424
}
2525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(rustc_attrs)]
12+
#![allow(unused)]
13+
14+
macro_rules! m {
15+
($($e1:expr),*; $($e2:expr),*) => {
16+
$( let x = $e1 )*; //~ WARN expected `;`
17+
$( println!("{}", $e2) )*; //~ WARN expected `;`
18+
}
19+
}
20+
21+
#[rustc_error]
22+
fn main() { m!(0, 0; 0, 0); } //~ ERROR compilation successful

0 commit comments

Comments
 (0)