Skip to content

Commit 8cc6f34

Browse files
committed
Auto merge of #119427 - dtolnay:maccall, r=compiler-errors
Fix, document, and test parser and pretty-printer edge cases related to braced macro calls _Review note: this is a deceptively small PR because it comes with 145 lines of docs and 196 lines of tests, and only 25 lines of compiler code changed. However, I recommend reviewing it 1 commit at a time because much of the effect of the code changes is non-local i.e. affecting code that is not visible in the final state of the PR. I have paid attention that reviewing the PR one commit at a time is as easy as I can make it. All of the code you need to know about is touched in those commits, even if some of those changes disappear by the end of the stack._ This is a follow-up to #119105. One case that is not relevant to `-Zunpretty=expanded`, but which came up as I'm porting #119105 and #118726 into `syn`'s printer and `prettyplease`'s printer where it **is** relevant, and is also relevant to rustc's `stringify!`, is statement boundaries in the vicinity of braced macro calls. Rustc's AST pretty-printer produces invalid syntax for statements that begin with a braced macro call: ```rust macro_rules! stringify_item { ($i:item) => { stringify!($i) }; } macro_rules! repro { ($e:expr) => { stringify_item!(fn main() { $e + 1; }) }; } fn main() { println!("{}", repro!(m! {})); } ``` **Before this PR:** output is not valid Rust syntax. ```console fn main() { m! {} + 1; } ``` ```console error: leading `+` is not supported --> <anon>:1:19 | 1 | fn main() { m! {} + 1; } | ^ unexpected `+` | help: try removing the `+` | 1 - fn main() { m! {} + 1; } 1 + fn main() { m! {} 1; } | ``` **After this PR:** valid syntax. ```console fn main() { (m! {}) + 1; } ```
2 parents ee97564 + 78c8dc1 commit 8cc6f34

File tree

12 files changed

+517
-57
lines changed

12 files changed

+517
-57
lines changed

compiler/rustc_ast/src/util/classify.rs

+76-22
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,88 @@
1-
//! Routines the parser uses to classify AST nodes
2-
3-
// Predicates on exprs and stmts that the pretty-printer and parser use
1+
//! Routines the parser and pretty-printer use to classify AST nodes.
42
3+
use crate::ast::ExprKind::*;
54
use crate::{ast, token::Delimiter};
65

7-
/// Does this expression require a semicolon to be treated
8-
/// as a statement? The negation of this: 'can this expression
9-
/// be used as a statement without a semicolon' -- is used
10-
/// as an early-bail-out in the parser so that, for instance,
11-
/// if true {...} else {...}
12-
/// |x| 5
13-
/// isn't parsed as (if true {...} else {...} | x) | 5
14-
pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
15-
!matches!(
6+
/// This classification determines whether various syntactic positions break out
7+
/// of parsing the current expression (true) or continue parsing more of the
8+
/// same expression (false).
9+
///
10+
/// For example, it's relevant in the parsing of match arms:
11+
///
12+
/// ```ignore (illustrative)
13+
/// match ... {
14+
/// // Is this calling $e as a function, or is it the start of a new arm
15+
/// // with a tuple pattern?
16+
/// _ => $e (
17+
/// ^ )
18+
///
19+
/// // Is this an Index operation, or new arm with a slice pattern?
20+
/// _ => $e [
21+
/// ^ ]
22+
///
23+
/// // Is this a binary operator, or leading vert in a new arm? Same for
24+
/// // other punctuation which can either be a binary operator in
25+
/// // expression or unary operator in pattern, such as `&` and `-`.
26+
/// _ => $e |
27+
/// ^
28+
/// }
29+
/// ```
30+
///
31+
/// If $e is something like `{}` or `if … {}`, then terminate the current
32+
/// arm and parse a new arm.
33+
///
34+
/// If $e is something like `path::to` or `(…)`, continue parsing the same
35+
/// arm.
36+
///
37+
/// *Almost* the same classification is used as an early bail-out for parsing
38+
/// statements. See `expr_requires_semi_to_be_stmt`.
39+
pub fn expr_is_complete(e: &ast::Expr) -> bool {
40+
matches!(
1641
e.kind,
17-
ast::ExprKind::If(..)
18-
| ast::ExprKind::Match(..)
19-
| ast::ExprKind::Block(..)
20-
| ast::ExprKind::While(..)
21-
| ast::ExprKind::Loop(..)
22-
| ast::ExprKind::ForLoop { .. }
23-
| ast::ExprKind::TryBlock(..)
24-
| ast::ExprKind::ConstBlock(..)
42+
If(..)
43+
| Match(..)
44+
| Block(..)
45+
| While(..)
46+
| Loop(..)
47+
| ForLoop { .. }
48+
| TryBlock(..)
49+
| ConstBlock(..)
2550
)
2651
}
2752

53+
/// Does this expression require a semicolon to be treated as a statement?
54+
///
55+
/// The negation of this: "can this expression be used as a statement without a
56+
/// semicolon" -- is used as an early bail-out when parsing statements so that,
57+
/// for instance,
58+
///
59+
/// ```ignore (illustrative)
60+
/// if true {...} else {...}
61+
/// |x| 5
62+
/// ```
63+
///
64+
/// isn't parsed as `(if true {...} else {...} | x) | 5`.
65+
///
66+
/// Surprising special case: even though braced macro calls like `m! {}`
67+
/// normally do not introduce a boundary when found at the head of a match arm,
68+
/// they do terminate the parsing of a statement.
69+
///
70+
/// ```ignore (illustrative)
71+
/// match ... {
72+
/// _ => m! {} (), // macro that expands to a function, which is then called
73+
/// }
74+
///
75+
/// let _ = { m! {} () }; // macro call followed by unit
76+
/// ```
77+
pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
78+
match &e.kind {
79+
MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,
80+
_ => !expr_is_complete(e),
81+
}
82+
}
83+
2884
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
2985
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
30-
use ast::ExprKind::*;
31-
3286
loop {
3387
match &expr.kind {
3488
AddrOf(_, _, e)

compiler/rustc_ast_pretty/src/pprust/state/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ impl<'a> State<'a> {
780780
}
781781
_ => {
782782
self.end(); // Close the ibox for the pattern.
783-
self.print_expr(body, FixupContext::new_stmt());
783+
self.print_expr(body, FixupContext::new_match_arm());
784784
self.word(",");
785785
}
786786
}

compiler/rustc_ast_pretty/src/pprust/state/fixup.rs

+52-5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,38 @@ pub(crate) struct FixupContext {
4949
/// No parentheses required.
5050
leftmost_subexpression_in_stmt: bool,
5151

52+
/// Print expression such that it can be parsed as a match arm.
53+
///
54+
/// This is almost equivalent to `stmt`, but the grammar diverges a tiny bit
55+
/// between statements and match arms when it comes to braced macro calls.
56+
/// Macro calls with brace delimiter terminate a statement without a
57+
/// semicolon, but do not terminate a match-arm without comma.
58+
///
59+
/// ```ignore (illustrative)
60+
/// m! {} - 1; // two statements: a macro call followed by -1 literal
61+
///
62+
/// match () {
63+
/// _ => m! {} - 1, // binary subtraction operator
64+
/// }
65+
/// ```
66+
match_arm: bool,
67+
68+
/// This is almost equivalent to `leftmost_subexpression_in_stmt`, other
69+
/// than for braced macro calls.
70+
///
71+
/// If we have `m! {} - 1` as an expression, the leftmost subexpression
72+
/// `m! {}` will need to be parenthesized in the statement case but not the
73+
/// match-arm case.
74+
///
75+
/// ```ignore (illustrative)
76+
/// (m! {}) - 1; // subexpression needs parens
77+
///
78+
/// match () {
79+
/// _ => m! {} - 1, // no parens
80+
/// }
81+
/// ```
82+
leftmost_subexpression_in_match_arm: bool,
83+
5284
/// This is the difference between:
5385
///
5486
/// ```ignore (illustrative)
@@ -68,6 +100,8 @@ impl Default for FixupContext {
68100
FixupContext {
69101
stmt: false,
70102
leftmost_subexpression_in_stmt: false,
103+
match_arm: false,
104+
leftmost_subexpression_in_match_arm: false,
71105
parenthesize_exterior_struct_lit: false,
72106
}
73107
}
@@ -76,13 +110,16 @@ impl Default for FixupContext {
76110
impl FixupContext {
77111
/// Create the initial fixup for printing an expression in statement
78112
/// position.
79-
///
80-
/// This is currently also used for printing an expression as a match-arm,
81-
/// but this is incorrect and leads to over-parenthesizing.
82113
pub fn new_stmt() -> Self {
83114
FixupContext { stmt: true, ..FixupContext::default() }
84115
}
85116

117+
/// Create the initial fixup for printing an expression as the right-hand
118+
/// side of a match arm.
119+
pub fn new_match_arm() -> Self {
120+
FixupContext { match_arm: true, ..FixupContext::default() }
121+
}
122+
86123
/// Create the initial fixup for printing an expression as the "condition"
87124
/// of an `if` or `while`. There are a few other positions which are
88125
/// grammatically equivalent and also use this, such as the iterator
@@ -106,6 +143,9 @@ impl FixupContext {
106143
FixupContext {
107144
stmt: false,
108145
leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt,
146+
match_arm: false,
147+
leftmost_subexpression_in_match_arm: self.match_arm
148+
|| self.leftmost_subexpression_in_match_arm,
109149
..self
110150
}
111151
}
@@ -119,7 +159,13 @@ impl FixupContext {
119159
/// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or
120160
/// `$a.f($b)`.
121161
pub fn subsequent_subexpression(self) -> Self {
122-
FixupContext { stmt: false, leftmost_subexpression_in_stmt: false, ..self }
162+
FixupContext {
163+
stmt: false,
164+
leftmost_subexpression_in_stmt: false,
165+
match_arm: false,
166+
leftmost_subexpression_in_match_arm: false,
167+
..self
168+
}
123169
}
124170

125171
/// Determine whether parentheses are needed around the given expression to
@@ -128,7 +174,8 @@ impl FixupContext {
128174
/// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has
129175
/// examples.
130176
pub fn would_cause_statement_boundary(self, expr: &Expr) -> bool {
131-
self.leftmost_subexpression_in_stmt && !classify::expr_requires_semi_to_be_stmt(expr)
177+
(self.leftmost_subexpression_in_stmt && !classify::expr_requires_semi_to_be_stmt(expr))
178+
|| (self.leftmost_subexpression_in_match_arm && classify::expr_is_complete(expr))
132179
}
133180

134181
/// Determine whether parentheses are needed around the given `let`

compiler/rustc_lint/src/unused.rs

+27
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,33 @@ trait UnusedDelimLint {
677677
}
678678

679679
// Check if LHS needs parens to prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`.
680+
//
681+
// FIXME: https://github.com/rust-lang/rust/issues/119426
682+
// The syntax tree in this code is from after macro expansion, so the
683+
// current implementation has both false negatives and false positives
684+
// related to expressions containing macros.
685+
//
686+
// macro_rules! m1 {
687+
// () => {
688+
// 1
689+
// };
690+
// }
691+
//
692+
// fn f1() -> u8 {
693+
// // Lint says parens are not needed, but they are.
694+
// (m1! {} + 1)
695+
// }
696+
//
697+
// macro_rules! m2 {
698+
// () => {
699+
// loop { break 1; }
700+
// };
701+
// }
702+
//
703+
// fn f2() -> u8 {
704+
// // Lint says parens are needed, but they are not.
705+
// (m2!() + 1)
706+
// }
680707
{
681708
let mut innermost = inner;
682709
loop {

compiler/rustc_parse/src/parser/expr.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,7 @@ impl<'a> Parser<'a> {
497497

498498
/// Checks if this expression is a successfully parsed statement.
499499
fn expr_is_complete(&self, e: &Expr) -> bool {
500-
self.restrictions.contains(Restrictions::STMT_EXPR)
501-
&& !classify::expr_requires_semi_to_be_stmt(e)
500+
self.restrictions.contains(Restrictions::STMT_EXPR) && classify::expr_is_complete(e)
502501
}
503502

504503
/// Parses `x..y`, `x..=y`, and `x..`/`x..=`.
@@ -2691,8 +2690,33 @@ impl<'a> Parser<'a> {
26912690
let first_tok_span = self.token.span;
26922691
match self.parse_expr() {
26932692
Ok(cond)
2694-
// If it's not a free-standing expression, and is followed by a block,
2695-
// then it's very likely the condition to an `else if`.
2693+
// Try to guess the difference between a "condition-like" vs
2694+
// "statement-like" expression.
2695+
//
2696+
// We are seeing the following code, in which $cond is neither
2697+
// ExprKind::Block nor ExprKind::If (the 2 cases wherein this
2698+
// would be valid syntax).
2699+
//
2700+
// if ... {
2701+
// } else $cond
2702+
//
2703+
// If $cond is "condition-like" such as ExprKind::Binary, we
2704+
// want to suggest inserting `if`.
2705+
//
2706+
// if ... {
2707+
// } else if a == b {
2708+
// ^^
2709+
// }
2710+
//
2711+
// If $cond is "statement-like" such as ExprKind::While then we
2712+
// want to suggest wrapping in braces.
2713+
//
2714+
// if ... {
2715+
// } else {
2716+
// ^
2717+
// while true {}
2718+
// }
2719+
// ^
26962720
if self.check(&TokenKind::OpenDelim(Delimiter::Brace))
26972721
&& classify::expr_requires_semi_to_be_stmt(&cond) =>
26982722
{
@@ -3136,7 +3160,7 @@ impl<'a> Parser<'a> {
31363160
err
31373161
})?;
31383162

3139-
let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
3163+
let require_comma = !classify::expr_is_complete(&expr)
31403164
&& this.token != token::CloseDelim(Delimiter::Brace);
31413165

31423166
if !require_comma {

tests/ui/lint/lint-unnecessary-parens.fixed

+31
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 {
4646
macro_rules! baz {
4747
($($foo:expr),+) => {
4848
($($foo),*)
49+
};
50+
}
51+
52+
macro_rules! unit {
53+
() => {
54+
()
55+
};
56+
}
57+
58+
struct One;
59+
60+
impl std::ops::Sub<One> for () {
61+
type Output = i32;
62+
fn sub(self, _: One) -> Self::Output {
63+
-1
64+
}
65+
}
66+
67+
impl std::ops::Neg for One {
68+
type Output = i32;
69+
fn neg(self) -> Self::Output {
70+
-1
4971
}
5072
}
5173

@@ -94,4 +116,13 @@ fn main() {
94116

95117
let _a = baz!(3, 4);
96118
let _b = baz!(3);
119+
120+
let _ = {
121+
unit!() - One //~ ERROR unnecessary parentheses around block return value
122+
} + {
123+
unit![] - One //~ ERROR unnecessary parentheses around block return value
124+
} + {
125+
// FIXME: false positive. This parenthesis is required.
126+
unit! {} - One //~ ERROR unnecessary parentheses around block return value
127+
};
97128
}

tests/ui/lint/lint-unnecessary-parens.rs

+31
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 {
4646
macro_rules! baz {
4747
($($foo:expr),+) => {
4848
($($foo),*)
49+
};
50+
}
51+
52+
macro_rules! unit {
53+
() => {
54+
()
55+
};
56+
}
57+
58+
struct One;
59+
60+
impl std::ops::Sub<One> for () {
61+
type Output = i32;
62+
fn sub(self, _: One) -> Self::Output {
63+
-1
64+
}
65+
}
66+
67+
impl std::ops::Neg for One {
68+
type Output = i32;
69+
fn neg(self) -> Self::Output {
70+
-1
4971
}
5072
}
5173

@@ -94,4 +116,13 @@ fn main() {
94116

95117
let _a = baz!(3, 4);
96118
let _b = baz!(3);
119+
120+
let _ = {
121+
(unit!() - One) //~ ERROR unnecessary parentheses around block return value
122+
} + {
123+
(unit![] - One) //~ ERROR unnecessary parentheses around block return value
124+
} + {
125+
// FIXME: false positive. This parenthesis is required.
126+
(unit! {} - One) //~ ERROR unnecessary parentheses around block return value
127+
};
97128
}

0 commit comments

Comments
 (0)