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 https://github.com/rust-lang/rust/pull/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; } ```
This commit is contained in:
commit
8cc6f34653
12 changed files with 517 additions and 57 deletions
|
@ -1,34 +1,88 @@
|
||||||
//! Routines the parser uses to classify AST nodes
|
//! Routines the parser and pretty-printer use to classify AST nodes.
|
||||||
|
|
||||||
// Predicates on exprs and stmts that the pretty-printer and parser use
|
|
||||||
|
|
||||||
|
use crate::ast::ExprKind::*;
|
||||||
use crate::{ast, token::Delimiter};
|
use crate::{ast, token::Delimiter};
|
||||||
|
|
||||||
/// Does this expression require a semicolon to be treated
|
/// This classification determines whether various syntactic positions break out
|
||||||
/// as a statement? The negation of this: 'can this expression
|
/// of parsing the current expression (true) or continue parsing more of the
|
||||||
/// be used as a statement without a semicolon' -- is used
|
/// same expression (false).
|
||||||
/// as an early-bail-out in the parser so that, for instance,
|
///
|
||||||
|
/// For example, it's relevant in the parsing of match arms:
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// match ... {
|
||||||
|
/// // Is this calling $e as a function, or is it the start of a new arm
|
||||||
|
/// // with a tuple pattern?
|
||||||
|
/// _ => $e (
|
||||||
|
/// ^ )
|
||||||
|
///
|
||||||
|
/// // Is this an Index operation, or new arm with a slice pattern?
|
||||||
|
/// _ => $e [
|
||||||
|
/// ^ ]
|
||||||
|
///
|
||||||
|
/// // Is this a binary operator, or leading vert in a new arm? Same for
|
||||||
|
/// // other punctuation which can either be a binary operator in
|
||||||
|
/// // expression or unary operator in pattern, such as `&` and `-`.
|
||||||
|
/// _ => $e |
|
||||||
|
/// ^
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If $e is something like `{}` or `if … {}`, then terminate the current
|
||||||
|
/// arm and parse a new arm.
|
||||||
|
///
|
||||||
|
/// If $e is something like `path::to` or `(…)`, continue parsing the same
|
||||||
|
/// arm.
|
||||||
|
///
|
||||||
|
/// *Almost* the same classification is used as an early bail-out for parsing
|
||||||
|
/// statements. See `expr_requires_semi_to_be_stmt`.
|
||||||
|
pub fn expr_is_complete(e: &ast::Expr) -> bool {
|
||||||
|
matches!(
|
||||||
|
e.kind,
|
||||||
|
If(..)
|
||||||
|
| Match(..)
|
||||||
|
| Block(..)
|
||||||
|
| While(..)
|
||||||
|
| Loop(..)
|
||||||
|
| ForLoop { .. }
|
||||||
|
| TryBlock(..)
|
||||||
|
| ConstBlock(..)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this expression require a semicolon to be treated as a statement?
|
||||||
|
///
|
||||||
|
/// The negation of this: "can this expression be used as a statement without a
|
||||||
|
/// semicolon" -- is used as an early bail-out when parsing statements so that,
|
||||||
|
/// for instance,
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
/// if true {...} else {...}
|
/// if true {...} else {...}
|
||||||
/// |x| 5
|
/// |x| 5
|
||||||
/// isn't parsed as (if true {...} else {...} | x) | 5
|
/// ```
|
||||||
|
///
|
||||||
|
/// isn't parsed as `(if true {...} else {...} | x) | 5`.
|
||||||
|
///
|
||||||
|
/// Surprising special case: even though braced macro calls like `m! {}`
|
||||||
|
/// normally do not introduce a boundary when found at the head of a match arm,
|
||||||
|
/// they do terminate the parsing of a statement.
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// match ... {
|
||||||
|
/// _ => m! {} (), // macro that expands to a function, which is then called
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let _ = { m! {} () }; // macro call followed by unit
|
||||||
|
/// ```
|
||||||
pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
|
pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
|
||||||
!matches!(
|
match &e.kind {
|
||||||
e.kind,
|
MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,
|
||||||
ast::ExprKind::If(..)
|
_ => !expr_is_complete(e),
|
||||||
| ast::ExprKind::Match(..)
|
}
|
||||||
| ast::ExprKind::Block(..)
|
|
||||||
| ast::ExprKind::While(..)
|
|
||||||
| ast::ExprKind::Loop(..)
|
|
||||||
| ast::ExprKind::ForLoop { .. }
|
|
||||||
| ast::ExprKind::TryBlock(..)
|
|
||||||
| ast::ExprKind::ConstBlock(..)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
|
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
|
||||||
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
|
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
|
||||||
use ast::ExprKind::*;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match &expr.kind {
|
match &expr.kind {
|
||||||
AddrOf(_, _, e)
|
AddrOf(_, _, e)
|
||||||
|
|
|
@ -780,7 +780,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.end(); // Close the ibox for the pattern.
|
self.end(); // Close the ibox for the pattern.
|
||||||
self.print_expr(body, FixupContext::new_stmt());
|
self.print_expr(body, FixupContext::new_match_arm());
|
||||||
self.word(",");
|
self.word(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,38 @@ pub(crate) struct FixupContext {
|
||||||
/// No parentheses required.
|
/// No parentheses required.
|
||||||
leftmost_subexpression_in_stmt: bool,
|
leftmost_subexpression_in_stmt: bool,
|
||||||
|
|
||||||
|
/// Print expression such that it can be parsed as a match arm.
|
||||||
|
///
|
||||||
|
/// This is almost equivalent to `stmt`, but the grammar diverges a tiny bit
|
||||||
|
/// between statements and match arms when it comes to braced macro calls.
|
||||||
|
/// Macro calls with brace delimiter terminate a statement without a
|
||||||
|
/// semicolon, but do not terminate a match-arm without comma.
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// m! {} - 1; // two statements: a macro call followed by -1 literal
|
||||||
|
///
|
||||||
|
/// match () {
|
||||||
|
/// _ => m! {} - 1, // binary subtraction operator
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
match_arm: bool,
|
||||||
|
|
||||||
|
/// This is almost equivalent to `leftmost_subexpression_in_stmt`, other
|
||||||
|
/// than for braced macro calls.
|
||||||
|
///
|
||||||
|
/// If we have `m! {} - 1` as an expression, the leftmost subexpression
|
||||||
|
/// `m! {}` will need to be parenthesized in the statement case but not the
|
||||||
|
/// match-arm case.
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// (m! {}) - 1; // subexpression needs parens
|
||||||
|
///
|
||||||
|
/// match () {
|
||||||
|
/// _ => m! {} - 1, // no parens
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
leftmost_subexpression_in_match_arm: bool,
|
||||||
|
|
||||||
/// This is the difference between:
|
/// This is the difference between:
|
||||||
///
|
///
|
||||||
/// ```ignore (illustrative)
|
/// ```ignore (illustrative)
|
||||||
|
@ -68,6 +100,8 @@ impl Default for FixupContext {
|
||||||
FixupContext {
|
FixupContext {
|
||||||
stmt: false,
|
stmt: false,
|
||||||
leftmost_subexpression_in_stmt: false,
|
leftmost_subexpression_in_stmt: false,
|
||||||
|
match_arm: false,
|
||||||
|
leftmost_subexpression_in_match_arm: false,
|
||||||
parenthesize_exterior_struct_lit: false,
|
parenthesize_exterior_struct_lit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,13 +110,16 @@ impl Default for FixupContext {
|
||||||
impl FixupContext {
|
impl FixupContext {
|
||||||
/// Create the initial fixup for printing an expression in statement
|
/// Create the initial fixup for printing an expression in statement
|
||||||
/// position.
|
/// position.
|
||||||
///
|
|
||||||
/// This is currently also used for printing an expression as a match-arm,
|
|
||||||
/// but this is incorrect and leads to over-parenthesizing.
|
|
||||||
pub fn new_stmt() -> Self {
|
pub fn new_stmt() -> Self {
|
||||||
FixupContext { stmt: true, ..FixupContext::default() }
|
FixupContext { stmt: true, ..FixupContext::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the initial fixup for printing an expression as the right-hand
|
||||||
|
/// side of a match arm.
|
||||||
|
pub fn new_match_arm() -> Self {
|
||||||
|
FixupContext { match_arm: true, ..FixupContext::default() }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the initial fixup for printing an expression as the "condition"
|
/// Create the initial fixup for printing an expression as the "condition"
|
||||||
/// of an `if` or `while`. There are a few other positions which are
|
/// of an `if` or `while`. There are a few other positions which are
|
||||||
/// grammatically equivalent and also use this, such as the iterator
|
/// grammatically equivalent and also use this, such as the iterator
|
||||||
|
@ -106,6 +143,9 @@ impl FixupContext {
|
||||||
FixupContext {
|
FixupContext {
|
||||||
stmt: false,
|
stmt: false,
|
||||||
leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt,
|
leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt,
|
||||||
|
match_arm: false,
|
||||||
|
leftmost_subexpression_in_match_arm: self.match_arm
|
||||||
|
|| self.leftmost_subexpression_in_match_arm,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +159,13 @@ impl FixupContext {
|
||||||
/// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or
|
/// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or
|
||||||
/// `$a.f($b)`.
|
/// `$a.f($b)`.
|
||||||
pub fn subsequent_subexpression(self) -> Self {
|
pub fn subsequent_subexpression(self) -> Self {
|
||||||
FixupContext { stmt: false, leftmost_subexpression_in_stmt: false, ..self }
|
FixupContext {
|
||||||
|
stmt: false,
|
||||||
|
leftmost_subexpression_in_stmt: false,
|
||||||
|
match_arm: false,
|
||||||
|
leftmost_subexpression_in_match_arm: false,
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether parentheses are needed around the given expression to
|
/// Determine whether parentheses are needed around the given expression to
|
||||||
|
@ -128,7 +174,8 @@ impl FixupContext {
|
||||||
/// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has
|
/// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has
|
||||||
/// examples.
|
/// examples.
|
||||||
pub fn would_cause_statement_boundary(self, expr: &Expr) -> bool {
|
pub fn would_cause_statement_boundary(self, expr: &Expr) -> bool {
|
||||||
self.leftmost_subexpression_in_stmt && !classify::expr_requires_semi_to_be_stmt(expr)
|
(self.leftmost_subexpression_in_stmt && !classify::expr_requires_semi_to_be_stmt(expr))
|
||||||
|
|| (self.leftmost_subexpression_in_match_arm && classify::expr_is_complete(expr))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether parentheses are needed around the given `let`
|
/// Determine whether parentheses are needed around the given `let`
|
||||||
|
|
|
@ -677,6 +677,33 @@ trait UnusedDelimLint {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if LHS needs parens to prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`.
|
// Check if LHS needs parens to prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`.
|
||||||
|
//
|
||||||
|
// FIXME: https://github.com/rust-lang/rust/issues/119426
|
||||||
|
// The syntax tree in this code is from after macro expansion, so the
|
||||||
|
// current implementation has both false negatives and false positives
|
||||||
|
// related to expressions containing macros.
|
||||||
|
//
|
||||||
|
// macro_rules! m1 {
|
||||||
|
// () => {
|
||||||
|
// 1
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn f1() -> u8 {
|
||||||
|
// // Lint says parens are not needed, but they are.
|
||||||
|
// (m1! {} + 1)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// macro_rules! m2 {
|
||||||
|
// () => {
|
||||||
|
// loop { break 1; }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn f2() -> u8 {
|
||||||
|
// // Lint says parens are needed, but they are not.
|
||||||
|
// (m2!() + 1)
|
||||||
|
// }
|
||||||
{
|
{
|
||||||
let mut innermost = inner;
|
let mut innermost = inner;
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -497,8 +497,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
/// Checks if this expression is a successfully parsed statement.
|
/// Checks if this expression is a successfully parsed statement.
|
||||||
fn expr_is_complete(&self, e: &Expr) -> bool {
|
fn expr_is_complete(&self, e: &Expr) -> bool {
|
||||||
self.restrictions.contains(Restrictions::STMT_EXPR)
|
self.restrictions.contains(Restrictions::STMT_EXPR) && classify::expr_is_complete(e)
|
||||||
&& !classify::expr_requires_semi_to_be_stmt(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses `x..y`, `x..=y`, and `x..`/`x..=`.
|
/// Parses `x..y`, `x..=y`, and `x..`/`x..=`.
|
||||||
|
@ -2691,8 +2690,33 @@ impl<'a> Parser<'a> {
|
||||||
let first_tok_span = self.token.span;
|
let first_tok_span = self.token.span;
|
||||||
match self.parse_expr() {
|
match self.parse_expr() {
|
||||||
Ok(cond)
|
Ok(cond)
|
||||||
// If it's not a free-standing expression, and is followed by a block,
|
// Try to guess the difference between a "condition-like" vs
|
||||||
// then it's very likely the condition to an `else if`.
|
// "statement-like" expression.
|
||||||
|
//
|
||||||
|
// We are seeing the following code, in which $cond is neither
|
||||||
|
// ExprKind::Block nor ExprKind::If (the 2 cases wherein this
|
||||||
|
// would be valid syntax).
|
||||||
|
//
|
||||||
|
// if ... {
|
||||||
|
// } else $cond
|
||||||
|
//
|
||||||
|
// If $cond is "condition-like" such as ExprKind::Binary, we
|
||||||
|
// want to suggest inserting `if`.
|
||||||
|
//
|
||||||
|
// if ... {
|
||||||
|
// } else if a == b {
|
||||||
|
// ^^
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If $cond is "statement-like" such as ExprKind::While then we
|
||||||
|
// want to suggest wrapping in braces.
|
||||||
|
//
|
||||||
|
// if ... {
|
||||||
|
// } else {
|
||||||
|
// ^
|
||||||
|
// while true {}
|
||||||
|
// }
|
||||||
|
// ^
|
||||||
if self.check(&TokenKind::OpenDelim(Delimiter::Brace))
|
if self.check(&TokenKind::OpenDelim(Delimiter::Brace))
|
||||||
&& classify::expr_requires_semi_to_be_stmt(&cond) =>
|
&& classify::expr_requires_semi_to_be_stmt(&cond) =>
|
||||||
{
|
{
|
||||||
|
@ -3136,7 +3160,7 @@ impl<'a> Parser<'a> {
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let require_comma = classify::expr_requires_semi_to_be_stmt(&expr)
|
let require_comma = !classify::expr_is_complete(&expr)
|
||||||
&& this.token != token::CloseDelim(Delimiter::Brace);
|
&& this.token != token::CloseDelim(Delimiter::Brace);
|
||||||
|
|
||||||
if !require_comma {
|
if !require_comma {
|
||||||
|
|
|
@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 {
|
||||||
macro_rules! baz {
|
macro_rules! baz {
|
||||||
($($foo:expr),+) => {
|
($($foo:expr),+) => {
|
||||||
($($foo),*)
|
($($foo),*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unit {
|
||||||
|
() => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct One;
|
||||||
|
|
||||||
|
impl std::ops::Sub<One> for () {
|
||||||
|
type Output = i32;
|
||||||
|
fn sub(self, _: One) -> Self::Output {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Neg for One {
|
||||||
|
type Output = i32;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
-1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,4 +116,13 @@ fn main() {
|
||||||
|
|
||||||
let _a = baz!(3, 4);
|
let _a = baz!(3, 4);
|
||||||
let _b = baz!(3);
|
let _b = baz!(3);
|
||||||
|
|
||||||
|
let _ = {
|
||||||
|
unit!() - One //~ ERROR unnecessary parentheses around block return value
|
||||||
|
} + {
|
||||||
|
unit![] - One //~ ERROR unnecessary parentheses around block return value
|
||||||
|
} + {
|
||||||
|
// FIXME: false positive. This parenthesis is required.
|
||||||
|
unit! {} - One //~ ERROR unnecessary parentheses around block return value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 {
|
||||||
macro_rules! baz {
|
macro_rules! baz {
|
||||||
($($foo:expr),+) => {
|
($($foo:expr),+) => {
|
||||||
($($foo),*)
|
($($foo),*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unit {
|
||||||
|
() => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct One;
|
||||||
|
|
||||||
|
impl std::ops::Sub<One> for () {
|
||||||
|
type Output = i32;
|
||||||
|
fn sub(self, _: One) -> Self::Output {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Neg for One {
|
||||||
|
type Output = i32;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
-1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,4 +116,13 @@ fn main() {
|
||||||
|
|
||||||
let _a = baz!(3, 4);
|
let _a = baz!(3, 4);
|
||||||
let _b = baz!(3);
|
let _b = baz!(3);
|
||||||
|
|
||||||
|
let _ = {
|
||||||
|
(unit!() - One) //~ ERROR unnecessary parentheses around block return value
|
||||||
|
} + {
|
||||||
|
(unit![] - One) //~ ERROR unnecessary parentheses around block return value
|
||||||
|
} + {
|
||||||
|
// FIXME: false positive. This parenthesis is required.
|
||||||
|
(unit! {} - One) //~ ERROR unnecessary parentheses around block return value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ LL + return 1;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around assigned value
|
error: unnecessary parentheses around assigned value
|
||||||
--> $DIR/lint-unnecessary-parens.rs:52:31
|
--> $DIR/lint-unnecessary-parens.rs:74:31
|
||||||
|
|
|
|
||||||
LL | pub const CONST_ITEM: usize = (10);
|
LL | pub const CONST_ITEM: usize = (10);
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -136,7 +136,7 @@ LL + pub const CONST_ITEM: usize = 10;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around assigned value
|
error: unnecessary parentheses around assigned value
|
||||||
--> $DIR/lint-unnecessary-parens.rs:53:33
|
--> $DIR/lint-unnecessary-parens.rs:75:33
|
||||||
|
|
|
|
||||||
LL | pub static STATIC_ITEM: usize = (10);
|
LL | pub static STATIC_ITEM: usize = (10);
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -148,7 +148,7 @@ LL + pub static STATIC_ITEM: usize = 10;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around function argument
|
error: unnecessary parentheses around function argument
|
||||||
--> $DIR/lint-unnecessary-parens.rs:57:9
|
--> $DIR/lint-unnecessary-parens.rs:79:9
|
||||||
|
|
|
|
||||||
LL | bar((true));
|
LL | bar((true));
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -160,7 +160,7 @@ LL + bar(true);
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around `if` condition
|
error: unnecessary parentheses around `if` condition
|
||||||
--> $DIR/lint-unnecessary-parens.rs:59:8
|
--> $DIR/lint-unnecessary-parens.rs:81:8
|
||||||
|
|
|
|
||||||
LL | if (true) {}
|
LL | if (true) {}
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -172,7 +172,7 @@ LL + if true {}
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around `while` condition
|
error: unnecessary parentheses around `while` condition
|
||||||
--> $DIR/lint-unnecessary-parens.rs:60:11
|
--> $DIR/lint-unnecessary-parens.rs:82:11
|
||||||
|
|
|
|
||||||
LL | while (true) {}
|
LL | while (true) {}
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -184,7 +184,7 @@ LL + while true {}
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around `match` scrutinee expression
|
error: unnecessary parentheses around `match` scrutinee expression
|
||||||
--> $DIR/lint-unnecessary-parens.rs:61:11
|
--> $DIR/lint-unnecessary-parens.rs:83:11
|
||||||
|
|
|
|
||||||
LL | match (true) {
|
LL | match (true) {
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -196,7 +196,7 @@ LL + match true {
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around `let` scrutinee expression
|
error: unnecessary parentheses around `let` scrutinee expression
|
||||||
--> $DIR/lint-unnecessary-parens.rs:64:16
|
--> $DIR/lint-unnecessary-parens.rs:86:16
|
||||||
|
|
|
|
||||||
LL | if let 1 = (1) {}
|
LL | if let 1 = (1) {}
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -208,7 +208,7 @@ LL + if let 1 = 1 {}
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around `let` scrutinee expression
|
error: unnecessary parentheses around `let` scrutinee expression
|
||||||
--> $DIR/lint-unnecessary-parens.rs:65:19
|
--> $DIR/lint-unnecessary-parens.rs:87:19
|
||||||
|
|
|
|
||||||
LL | while let 1 = (2) {}
|
LL | while let 1 = (2) {}
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -220,7 +220,7 @@ LL + while let 1 = 2 {}
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around method argument
|
error: unnecessary parentheses around method argument
|
||||||
--> $DIR/lint-unnecessary-parens.rs:81:24
|
--> $DIR/lint-unnecessary-parens.rs:103:24
|
||||||
|
|
|
|
||||||
LL | X { y: false }.foo((true));
|
LL | X { y: false }.foo((true));
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -232,7 +232,7 @@ LL + X { y: false }.foo(true);
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around assigned value
|
error: unnecessary parentheses around assigned value
|
||||||
--> $DIR/lint-unnecessary-parens.rs:83:18
|
--> $DIR/lint-unnecessary-parens.rs:105:18
|
||||||
|
|
|
|
||||||
LL | let mut _a = (0);
|
LL | let mut _a = (0);
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -244,7 +244,7 @@ LL + let mut _a = 0;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around assigned value
|
error: unnecessary parentheses around assigned value
|
||||||
--> $DIR/lint-unnecessary-parens.rs:84:10
|
--> $DIR/lint-unnecessary-parens.rs:106:10
|
||||||
|
|
|
|
||||||
LL | _a = (0);
|
LL | _a = (0);
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -256,7 +256,7 @@ LL + _a = 0;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around assigned value
|
error: unnecessary parentheses around assigned value
|
||||||
--> $DIR/lint-unnecessary-parens.rs:85:11
|
--> $DIR/lint-unnecessary-parens.rs:107:11
|
||||||
|
|
|
|
||||||
LL | _a += (1);
|
LL | _a += (1);
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -268,7 +268,7 @@ LL + _a += 1;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:87:8
|
--> $DIR/lint-unnecessary-parens.rs:109:8
|
||||||
|
|
|
|
||||||
LL | let(mut _a) = 3;
|
LL | let(mut _a) = 3;
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -280,7 +280,7 @@ LL + let mut _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:88:9
|
--> $DIR/lint-unnecessary-parens.rs:110:9
|
||||||
|
|
|
|
||||||
LL | let (mut _a) = 3;
|
LL | let (mut _a) = 3;
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -292,7 +292,7 @@ LL + let mut _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:89:8
|
--> $DIR/lint-unnecessary-parens.rs:111:8
|
||||||
|
|
|
|
||||||
LL | let( mut _a) = 3;
|
LL | let( mut _a) = 3;
|
||||||
| ^^ ^
|
| ^^ ^
|
||||||
|
@ -304,7 +304,7 @@ LL + let mut _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:91:8
|
--> $DIR/lint-unnecessary-parens.rs:113:8
|
||||||
|
|
|
|
||||||
LL | let(_a) = 3;
|
LL | let(_a) = 3;
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -316,7 +316,7 @@ LL + let _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:92:9
|
--> $DIR/lint-unnecessary-parens.rs:114:9
|
||||||
|
|
|
|
||||||
LL | let (_a) = 3;
|
LL | let (_a) = 3;
|
||||||
| ^ ^
|
| ^ ^
|
||||||
|
@ -328,7 +328,7 @@ LL + let _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: unnecessary parentheses around pattern
|
error: unnecessary parentheses around pattern
|
||||||
--> $DIR/lint-unnecessary-parens.rs:93:8
|
--> $DIR/lint-unnecessary-parens.rs:115:8
|
||||||
|
|
|
|
||||||
LL | let( _a) = 3;
|
LL | let( _a) = 3;
|
||||||
| ^^ ^
|
| ^^ ^
|
||||||
|
@ -339,5 +339,41 @@ LL - let( _a) = 3;
|
||||||
LL + let _a = 3;
|
LL + let _a = 3;
|
||||||
|
|
|
|
||||||
|
|
||||||
error: aborting due to 28 previous errors
|
error: unnecessary parentheses around block return value
|
||||||
|
--> $DIR/lint-unnecessary-parens.rs:121:9
|
||||||
|
|
|
||||||
|
LL | (unit!() - One)
|
||||||
|
| ^ ^
|
||||||
|
|
|
||||||
|
help: remove these parentheses
|
||||||
|
|
|
||||||
|
LL - (unit!() - One)
|
||||||
|
LL + unit!() - One
|
||||||
|
|
|
||||||
|
|
||||||
|
error: unnecessary parentheses around block return value
|
||||||
|
--> $DIR/lint-unnecessary-parens.rs:123:9
|
||||||
|
|
|
||||||
|
LL | (unit![] - One)
|
||||||
|
| ^ ^
|
||||||
|
|
|
||||||
|
help: remove these parentheses
|
||||||
|
|
|
||||||
|
LL - (unit![] - One)
|
||||||
|
LL + unit![] - One
|
||||||
|
|
|
||||||
|
|
||||||
|
error: unnecessary parentheses around block return value
|
||||||
|
--> $DIR/lint-unnecessary-parens.rs:126:9
|
||||||
|
|
|
||||||
|
LL | (unit! {} - One)
|
||||||
|
| ^ ^
|
||||||
|
|
|
||||||
|
help: remove these parentheses
|
||||||
|
|
|
||||||
|
LL - (unit! {} - One)
|
||||||
|
LL + unit! {} - One
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to 31 previous errors
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,21 @@ fn test_expr() {
|
||||||
"match () { _ => ({ 1 }) - 1, }",
|
"match () { _ => ({ 1 }) - 1, }",
|
||||||
"match () { _ => { 1 } - 1 }",
|
"match () { _ => { 1 } - 1 }",
|
||||||
);
|
);
|
||||||
|
c2_match_arm!(
|
||||||
|
[ m!() - 1 ],
|
||||||
|
"match () { _ => m!() - 1, }",
|
||||||
|
"match () { _ => m!() - 1 }",
|
||||||
|
);
|
||||||
|
c2_match_arm!(
|
||||||
|
[ m![] - 1 ],
|
||||||
|
"match () { _ => m![] - 1, }",
|
||||||
|
"match () { _ => m![] - 1 }",
|
||||||
|
);
|
||||||
|
c2_match_arm!(
|
||||||
|
[ m! {} - 1 ],
|
||||||
|
"match () { _ => m! {} - 1, }",
|
||||||
|
"match () { _ => m! {} - 1 }",
|
||||||
|
);
|
||||||
|
|
||||||
// ExprKind::Closure
|
// ExprKind::Closure
|
||||||
c1!(expr, [ || {} ], "|| {}");
|
c1!(expr, [ || {} ], "|| {}");
|
||||||
|
@ -720,6 +735,21 @@ fn test_stmt() {
|
||||||
"(loop { break 1; }) - 1;",
|
"(loop { break 1; }) - 1;",
|
||||||
"loop { break 1; } - 1",
|
"loop { break 1; } - 1",
|
||||||
);
|
);
|
||||||
|
c2_minus_one!(
|
||||||
|
[ m!() ],
|
||||||
|
"m!() - 1;",
|
||||||
|
"m!() - 1"
|
||||||
|
);
|
||||||
|
c2_minus_one!(
|
||||||
|
[ m![] ],
|
||||||
|
"m![] - 1;",
|
||||||
|
"m![] - 1"
|
||||||
|
);
|
||||||
|
c2_minus_one!(
|
||||||
|
[ m! {} ],
|
||||||
|
"(m! {}) - 1;",
|
||||||
|
"m! {} - 1"
|
||||||
|
);
|
||||||
|
|
||||||
// StmtKind::Empty
|
// StmtKind::Empty
|
||||||
c1!(stmt, [ ; ], ";");
|
c1!(stmt, [ ; ], ";");
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
macro_rules! falsy {
|
||||||
|
() => { false };
|
||||||
|
}
|
||||||
|
|
||||||
fn foo() {
|
fn foo() {
|
||||||
if true {
|
if true {
|
||||||
} else false {
|
} else false {
|
||||||
|
@ -25,6 +29,32 @@ fn foo4() {
|
||||||
{}
|
{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn foo5() {
|
||||||
|
if true {
|
||||||
|
} else falsy!() {
|
||||||
|
//~^ ERROR expected `{`, found `falsy`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo6() {
|
||||||
|
if true {
|
||||||
|
} else falsy!();
|
||||||
|
//~^ ERROR expected `{`, found `falsy`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo7() {
|
||||||
|
if true {
|
||||||
|
} else falsy! {} {
|
||||||
|
//~^ ERROR expected `{`, found `falsy`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo8() {
|
||||||
|
if true {
|
||||||
|
} else falsy! {};
|
||||||
|
//~^ ERROR expected `{`, found `falsy`
|
||||||
|
}
|
||||||
|
|
||||||
fn falsy() -> bool {
|
fn falsy() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: expected `{`, found keyword `false`
|
error: expected `{`, found keyword `false`
|
||||||
--> $DIR/else-no-if.rs:3:12
|
--> $DIR/else-no-if.rs:7:12
|
||||||
|
|
|
|
||||||
LL | } else false {
|
LL | } else false {
|
||||||
| ---- ^^^^^
|
| ---- ^^^^^
|
||||||
|
@ -12,7 +12,7 @@ LL | } else if false {
|
||||||
| ++
|
| ++
|
||||||
|
|
||||||
error: expected `{`, found `falsy`
|
error: expected `{`, found `falsy`
|
||||||
--> $DIR/else-no-if.rs:10:12
|
--> $DIR/else-no-if.rs:14:12
|
||||||
|
|
|
|
||||||
LL | } else falsy() {
|
LL | } else falsy() {
|
||||||
| ---- ^^^^^
|
| ---- ^^^^^
|
||||||
|
@ -25,7 +25,7 @@ LL | } else if falsy() {
|
||||||
| ++
|
| ++
|
||||||
|
|
||||||
error: expected `{`, found `falsy`
|
error: expected `{`, found `falsy`
|
||||||
--> $DIR/else-no-if.rs:17:12
|
--> $DIR/else-no-if.rs:21:12
|
||||||
|
|
|
|
||||||
LL | } else falsy();
|
LL | } else falsy();
|
||||||
| ^^^^^ expected `{`
|
| ^^^^^ expected `{`
|
||||||
|
@ -36,7 +36,7 @@ LL | } else { falsy() };
|
||||||
| + +
|
| + +
|
||||||
|
|
||||||
error: expected `{`, found keyword `loop`
|
error: expected `{`, found keyword `loop`
|
||||||
--> $DIR/else-no-if.rs:23:12
|
--> $DIR/else-no-if.rs:27:12
|
||||||
|
|
|
|
||||||
LL | } else loop{}
|
LL | } else loop{}
|
||||||
| ^^^^ expected `{`
|
| ^^^^ expected `{`
|
||||||
|
@ -46,5 +46,51 @@ help: try placing this code inside a block
|
||||||
LL | } else { loop{} }
|
LL | } else { loop{} }
|
||||||
| + +
|
| + +
|
||||||
|
|
||||||
error: aborting due to 4 previous errors
|
error: expected `{`, found `falsy`
|
||||||
|
--> $DIR/else-no-if.rs:34:12
|
||||||
|
|
|
||||||
|
LL | } else falsy!() {
|
||||||
|
| ---- ^^^^^
|
||||||
|
| |
|
||||||
|
| expected an `if` or a block after this `else`
|
||||||
|
|
|
||||||
|
help: add an `if` if this is the condition of a chained `else if` statement
|
||||||
|
|
|
||||||
|
LL | } else if falsy!() {
|
||||||
|
| ++
|
||||||
|
|
||||||
|
error: expected `{`, found `falsy`
|
||||||
|
--> $DIR/else-no-if.rs:41:12
|
||||||
|
|
|
||||||
|
LL | } else falsy!();
|
||||||
|
| ^^^^^ expected `{`
|
||||||
|
|
|
||||||
|
help: try placing this code inside a block
|
||||||
|
|
|
||||||
|
LL | } else { falsy!() };
|
||||||
|
| + +
|
||||||
|
|
||||||
|
error: expected `{`, found `falsy`
|
||||||
|
--> $DIR/else-no-if.rs:47:12
|
||||||
|
|
|
||||||
|
LL | } else falsy! {} {
|
||||||
|
| ^^^^^ expected `{`
|
||||||
|
|
|
||||||
|
help: try placing this code inside a block
|
||||||
|
|
|
||||||
|
LL | } else { falsy! {} } {
|
||||||
|
| + +
|
||||||
|
|
||||||
|
error: expected `{`, found `falsy`
|
||||||
|
--> $DIR/else-no-if.rs:54:12
|
||||||
|
|
|
||||||
|
LL | } else falsy! {};
|
||||||
|
| ^^^^^ expected `{`
|
||||||
|
|
|
||||||
|
help: try placing this code inside a block
|
||||||
|
|
|
||||||
|
LL | } else { falsy! {} };
|
||||||
|
| + +
|
||||||
|
|
||||||
|
error: aborting due to 8 previous errors
|
||||||
|
|
||||||
|
|
104
tests/ui/parser/macro/statement-boundaries.rs
Normal file
104
tests/ui/parser/macro/statement-boundaries.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
//@ run-pass
|
||||||
|
//@ edition:2021
|
||||||
|
|
||||||
|
// This is a test of several uses of rustc_ast::util::classify::expr_requires_semi_to_be_stmt
|
||||||
|
// by the Rust parser, which relates to the insertion of statement boundaries
|
||||||
|
// after certain kinds of expressions if they appear at the head of a statement.
|
||||||
|
|
||||||
|
#![allow(unused_braces, unused_unsafe)]
|
||||||
|
|
||||||
|
macro_rules! unit {
|
||||||
|
() => {
|
||||||
|
{ () }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct X;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = X;
|
||||||
|
|
||||||
|
// There is a statement boundary before `|x| x`, so it's a closure.
|
||||||
|
let _: fn(X) -> X = { if true {} |x| x };
|
||||||
|
let _: fn(X) -> X = { if true {} else {} |x| x };
|
||||||
|
let _: fn(X) -> X = { match () { () => {} } |x| x };
|
||||||
|
let _: fn(X) -> X = { { () } |x| x };
|
||||||
|
let _: fn(X) -> X = { unsafe {} |x| x };
|
||||||
|
let _: fn(X) -> X = { while false {} |x| x };
|
||||||
|
let _: fn(X) -> X = { loop { break; } |x| x };
|
||||||
|
let _: fn(X) -> X = { for _ in 0..0 {} |x| x };
|
||||||
|
let _: fn(X) -> X = { const {} |x| x };
|
||||||
|
let _: fn(X) -> X = { unit! {} |x| x };
|
||||||
|
|
||||||
|
// No statement boundary, so `|x| x` is 2× BitOr operation.
|
||||||
|
() = { "" |x| x };
|
||||||
|
() = { ("") |x| x };
|
||||||
|
() = { [""] |x| x };
|
||||||
|
() = { unit!() |x| x };
|
||||||
|
() = { unit![] |x| x };
|
||||||
|
|
||||||
|
// All the same cases, but as a match arm.
|
||||||
|
() = match x {
|
||||||
|
// Statement boundary before `| X`, which becomes a new arm with leading vert.
|
||||||
|
X if false => if true {} | X if false => {}
|
||||||
|
X if false => if true {} else {} | X if false => {}
|
||||||
|
X if false => match () { () => {} } | X if false => {}
|
||||||
|
X if false => { () } | X if false => {}
|
||||||
|
X if false => unsafe {} | X if false => {}
|
||||||
|
X if false => while false {} | X if false => {}
|
||||||
|
X if false => loop { break; } | X if false => {}
|
||||||
|
X if false => for _ in 0..0 {} | X if false => {}
|
||||||
|
X if false => const {} | X if false => {}
|
||||||
|
|
||||||
|
// No statement boundary, so `| X` is BitOr.
|
||||||
|
X if false => "" | X,
|
||||||
|
X if false => ("") | X,
|
||||||
|
X if false => [""] | X,
|
||||||
|
X if false => unit! {} | X, // !! inconsistent with braced mac call in statement position
|
||||||
|
X if false => unit!() | X,
|
||||||
|
X if false => unit![] | X,
|
||||||
|
|
||||||
|
X => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test how the statement boundary logic interacts with macro metavariables /
|
||||||
|
// "invisible delimiters".
|
||||||
|
macro_rules! assert_statement_boundary {
|
||||||
|
($expr:expr) => {
|
||||||
|
let _: fn(X) -> X = { $expr |x| x };
|
||||||
|
|
||||||
|
() = match X {
|
||||||
|
X if false => $expr | X if false => {}
|
||||||
|
X => {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! assert_no_statement_boundary {
|
||||||
|
($expr:expr) => {
|
||||||
|
() = { $expr |x| x };
|
||||||
|
|
||||||
|
() = match x {
|
||||||
|
X if false => $expr | X,
|
||||||
|
X => {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
assert_statement_boundary!(if true {});
|
||||||
|
assert_no_statement_boundary!("");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::BitOr<X> for () {
|
||||||
|
type Output = ();
|
||||||
|
fn bitor(self, _: X) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::BitOr<X> for &str {
|
||||||
|
type Output = ();
|
||||||
|
fn bitor(self, _: X) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> std::ops::BitOr<X> for [T; N] {
|
||||||
|
type Output = ();
|
||||||
|
fn bitor(self, _: X) {}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue