Skip to content

Commit 06ca016

Browse files
authored
Auto merge of #34886 - jseyfried:improve_stmt_matchers, r=eddyb
macros: fix bug in `stmt` matchers Today, `stmt` matchers stop too early when parsing expression statements that begin with non-braced macro invocations. For example, ```rust fn main() { macro_rules! m { ($s:stmt;) => { $s } } id!(vec![].push(0);); //^ Before this PR, the `stmt` matcher only consumes "vec![]", so this is an error. //| After this PR, the `stmt` matcher consumes "vec![].push(0)", so this compiles. } ``` This change is backwards compatible due to the follow set for `stmt`. r? @eddyb
2 parents 6cc49e5 + bd1ad76 commit 06ca016

File tree

2 files changed

+48
-49
lines changed

2 files changed

+48
-49
lines changed

src/libsyntax/parse/parser.rs

+31-49
Original file line numberDiff line numberDiff line change
@@ -3789,13 +3789,8 @@ impl<'a> Parser<'a> {
37893789

37903790
/// Parse a statement. This stops just before trailing semicolons on everything but items.
37913791
/// e.g. a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed.
3792-
///
3793-
/// Also, if a macro begins an expression statement, this only parses the macro. For example,
3794-
/// ```rust
3795-
/// vec![1].into_iter(); //< `parse_stmt` only parses the "vec![1]"
3796-
/// ```
37973792
pub fn parse_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
3798-
Ok(self.parse_stmt_())
3793+
Ok(self.parse_stmt_(true))
37993794
}
38003795

38013796
// Eat tokens until we can be relatively sure we reached the end of the
@@ -3859,15 +3854,15 @@ impl<'a> Parser<'a> {
38593854
}
38603855
}
38613856

3862-
fn parse_stmt_(&mut self) -> Option<Stmt> {
3863-
self.parse_stmt_without_recovery().unwrap_or_else(|mut e| {
3857+
fn parse_stmt_(&mut self, macro_expanded: bool) -> Option<Stmt> {
3858+
self.parse_stmt_without_recovery(macro_expanded).unwrap_or_else(|mut e| {
38643859
e.emit();
38653860
self.recover_stmt_(SemiColonMode::Break);
38663861
None
38673862
})
38683863
}
38693864

3870-
fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option<Stmt>> {
3865+
fn parse_stmt_without_recovery(&mut self, macro_expanded: bool) -> PResult<'a, Option<Stmt>> {
38713866
maybe_whole!(Some deref self, NtStmt);
38723867

38733868
let attrs = self.parse_outer_attributes()?;
@@ -3930,10 +3925,34 @@ impl<'a> Parser<'a> {
39303925

39313926
if id.name == keywords::Invalid.name() {
39323927
let mac = spanned(lo, hi, Mac_ { path: pth, tts: tts });
3928+
let node = if delim == token::Brace ||
3929+
self.token == token::Semi || self.token == token::Eof {
3930+
StmtKind::Mac(P((mac, style, attrs.into())))
3931+
}
3932+
// We used to incorrectly stop parsing macro-expanded statements here.
3933+
// If the next token will be an error anyway but could have parsed with the
3934+
// earlier behavior, stop parsing here and emit a warning to avoid breakage.
3935+
else if macro_expanded && self.token.can_begin_expr() && match self.token {
3936+
// These can continue an expression, so we can't stop parsing and warn.
3937+
token::OpenDelim(token::Paren) | token::OpenDelim(token::Bracket) |
3938+
token::BinOp(token::Minus) | token::BinOp(token::Star) |
3939+
token::BinOp(token::And) | token::BinOp(token::Or) |
3940+
token::AndAnd | token::OrOr |
3941+
token::DotDot | token::DotDotDot => false,
3942+
_ => true,
3943+
} {
3944+
self.warn_missing_semicolon();
3945+
StmtKind::Mac(P((mac, style, attrs.into())))
3946+
} else {
3947+
let e = self.mk_mac_expr(lo, hi, mac.node, ThinVec::new());
3948+
let e = self.parse_dot_or_call_expr_with(e, lo, attrs.into())?;
3949+
let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
3950+
StmtKind::Expr(e)
3951+
};
39333952
Stmt {
39343953
id: ast::DUMMY_NODE_ID,
3935-
node: StmtKind::Mac(P((mac, style, attrs.into()))),
39363954
span: mk_sp(lo, hi),
3955+
node: node,
39373956
}
39383957
} else {
39393958
// if it has a special ident, it's definitely an item
@@ -4061,49 +4080,12 @@ impl<'a> Parser<'a> {
40614080
}
40624081

40634082
/// Parse a statement, including the trailing semicolon.
4064-
/// This parses expression statements that begin with macros correctly (c.f. `parse_stmt`).
40654083
pub fn parse_full_stmt(&mut self, macro_expanded: bool) -> PResult<'a, Option<Stmt>> {
4066-
let mut stmt = match self.parse_stmt_() {
4084+
let mut stmt = match self.parse_stmt_(macro_expanded) {
40674085
Some(stmt) => stmt,
40684086
None => return Ok(None),
40694087
};
40704088

4071-
if let StmtKind::Mac(mac) = stmt.node {
4072-
if mac.1 != MacStmtStyle::NoBraces ||
4073-
self.token == token::Semi || self.token == token::Eof {
4074-
stmt.node = StmtKind::Mac(mac);
4075-
} else {
4076-
// We used to incorrectly stop parsing macro-expanded statements here.
4077-
// If the next token will be an error anyway but could have parsed with the
4078-
// earlier behavior, stop parsing here and emit a warning to avoid breakage.
4079-
if macro_expanded && self.token.can_begin_expr() && match self.token {
4080-
// These tokens can continue an expression, so we can't stop parsing and warn.
4081-
token::OpenDelim(token::Paren) | token::OpenDelim(token::Bracket) |
4082-
token::BinOp(token::Minus) | token::BinOp(token::Star) |
4083-
token::BinOp(token::And) | token::BinOp(token::Or) |
4084-
token::AndAnd | token::OrOr |
4085-
token::DotDot | token::DotDotDot => false,
4086-
_ => true,
4087-
} {
4088-
self.warn_missing_semicolon();
4089-
stmt.node = StmtKind::Mac(mac);
4090-
return Ok(Some(stmt));
4091-
}
4092-
4093-
let (mac, _style, attrs) = mac.unwrap();
4094-
let e = self.mk_mac_expr(stmt.span.lo, stmt.span.hi, mac.node, ThinVec::new());
4095-
let e = self.parse_dot_or_call_expr_with(e, stmt.span.lo, attrs)?;
4096-
let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
4097-
stmt.node = StmtKind::Expr(e);
4098-
}
4099-
}
4100-
4101-
stmt = self.handle_trailing_semicolon(stmt, macro_expanded)?;
4102-
Ok(Some(stmt))
4103-
}
4104-
4105-
fn handle_trailing_semicolon(&mut self, mut stmt: Stmt, macro_expanded: bool)
4106-
-> PResult<'a, Stmt> {
41074089
match stmt.node {
41084090
StmtKind::Expr(ref expr) if self.token != token::Eof => {
41094091
// expression without semicolon
@@ -4133,7 +4115,7 @@ impl<'a> Parser<'a> {
41334115
}
41344116

41354117
stmt.span.hi = self.last_span.hi;
4136-
Ok(stmt)
4118+
Ok(Some(stmt))
41374119
}
41384120

41394121
fn warn_missing_semicolon(&self) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
13+
#[rustc_error]
14+
fn main() { //~ ERROR compilation successful
15+
macro_rules! m { ($s:stmt;) => { $s } }
16+
m!(vec![].push(0););
17+
}

0 commit comments

Comments
 (0)