diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index f21a9cabb81be..f6e9e1a87c4bb 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -1,34 +1,88 @@ -//! Routines the parser uses to classify AST nodes - -// Predicates on exprs and stmts that the pretty-printer and parser use +//! Routines the parser and pretty-printer use to classify AST nodes. +use crate::ast::ExprKind::*; use crate::{ast, token::Delimiter}; -/// 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 in the parser so that, for instance, -/// if true {...} else {...} -/// |x| 5 -/// isn't parsed as (if true {...} else {...} | x) | 5 -pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { - !matches!( +/// This classification determines whether various syntactic positions break out +/// of parsing the current expression (true) or continue parsing more of the +/// same expression (false). +/// +/// 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, - ast::ExprKind::If(..) - | ast::ExprKind::Match(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::While(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::ForLoop { .. } - | ast::ExprKind::TryBlock(..) - | ast::ExprKind::ConstBlock(..) + 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 {...} +/// |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 { + match &e.kind { + MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace, + _ => !expr_is_complete(e), + } +} + /// If an expression ends with `}`, returns the innermost expression ending in the `}` pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> { - use ast::ExprKind::*; - loop { match &expr.kind { AddrOf(_, _, e) diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 93400c67949d9..1e117c46b6e29 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -780,7 +780,7 @@ impl<'a> State<'a> { } _ => { 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(","); } } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/fixup.rs b/compiler/rustc_ast_pretty/src/pprust/state/fixup.rs index d21cb82f83b28..86d4796e9ce3b 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/fixup.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/fixup.rs @@ -49,6 +49,38 @@ pub(crate) struct FixupContext { /// No parentheses required. 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: /// /// ```ignore (illustrative) @@ -68,6 +100,8 @@ impl Default for FixupContext { FixupContext { stmt: false, leftmost_subexpression_in_stmt: false, + match_arm: false, + leftmost_subexpression_in_match_arm: false, parenthesize_exterior_struct_lit: false, } } @@ -76,13 +110,16 @@ impl Default for FixupContext { impl FixupContext { /// Create the initial fixup for printing an expression in statement /// 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 { 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" /// of an `if` or `while`. There are a few other positions which are /// grammatically equivalent and also use this, such as the iterator @@ -106,6 +143,9 @@ impl FixupContext { FixupContext { stmt: false, 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 } } @@ -119,7 +159,13 @@ impl FixupContext { /// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or /// `$a.f($b)`. 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 @@ -128,7 +174,8 @@ impl FixupContext { /// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has /// examples. 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` diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 2b147e052ae0f..8866b2be07847 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -677,6 +677,33 @@ trait UnusedDelimLint { } // 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; loop { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 577003e94fb28..441aa5b0806da 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -497,8 +497,7 @@ impl<'a> Parser<'a> { /// Checks if this expression is a successfully parsed statement. fn expr_is_complete(&self, e: &Expr) -> bool { - self.restrictions.contains(Restrictions::STMT_EXPR) - && !classify::expr_requires_semi_to_be_stmt(e) + self.restrictions.contains(Restrictions::STMT_EXPR) && classify::expr_is_complete(e) } /// Parses `x..y`, `x..=y`, and `x..`/`x..=`. @@ -2691,8 +2690,33 @@ impl<'a> Parser<'a> { let first_tok_span = self.token.span; match self.parse_expr() { Ok(cond) - // If it's not a free-standing expression, and is followed by a block, - // then it's very likely the condition to an `else if`. + // Try to guess the difference between a "condition-like" vs + // "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)) && classify::expr_requires_semi_to_be_stmt(&cond) => { @@ -3136,7 +3160,7 @@ impl<'a> Parser<'a> { 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); if !require_comma { diff --git a/tests/ui/lint/lint-unnecessary-parens.fixed b/tests/ui/lint/lint-unnecessary-parens.fixed index 973bbd70f257f..760897c5143f1 100644 --- a/tests/ui/lint/lint-unnecessary-parens.fixed +++ b/tests/ui/lint/lint-unnecessary-parens.fixed @@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 { macro_rules! baz { ($($foo:expr),+) => { ($($foo),*) + }; +} + +macro_rules! unit { + () => { + () + }; +} + +struct One; + +impl std::ops::Sub 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 _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 + }; } diff --git a/tests/ui/lint/lint-unnecessary-parens.rs b/tests/ui/lint/lint-unnecessary-parens.rs index 40cd61fcc2c0e..7cbaac8ae5403 100644 --- a/tests/ui/lint/lint-unnecessary-parens.rs +++ b/tests/ui/lint/lint-unnecessary-parens.rs @@ -46,6 +46,28 @@ pub fn parens_with_keyword(e: &[()]) -> i32 { macro_rules! baz { ($($foo:expr),+) => { ($($foo),*) + }; +} + +macro_rules! unit { + () => { + () + }; +} + +struct One; + +impl std::ops::Sub 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 _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 + }; } diff --git a/tests/ui/lint/lint-unnecessary-parens.stderr b/tests/ui/lint/lint-unnecessary-parens.stderr index ba7a78b8da1a9..755dd5fc3094b 100644 --- a/tests/ui/lint/lint-unnecessary-parens.stderr +++ b/tests/ui/lint/lint-unnecessary-parens.stderr @@ -124,7 +124,7 @@ LL + return 1; | 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); | ^ ^ @@ -136,7 +136,7 @@ LL + pub const CONST_ITEM: usize = 10; | 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); | ^ ^ @@ -148,7 +148,7 @@ LL + pub static STATIC_ITEM: usize = 10; | error: unnecessary parentheses around function argument - --> $DIR/lint-unnecessary-parens.rs:57:9 + --> $DIR/lint-unnecessary-parens.rs:79:9 | LL | bar((true)); | ^ ^ @@ -160,7 +160,7 @@ LL + bar(true); | error: unnecessary parentheses around `if` condition - --> $DIR/lint-unnecessary-parens.rs:59:8 + --> $DIR/lint-unnecessary-parens.rs:81:8 | LL | if (true) {} | ^ ^ @@ -172,7 +172,7 @@ LL + if true {} | error: unnecessary parentheses around `while` condition - --> $DIR/lint-unnecessary-parens.rs:60:11 + --> $DIR/lint-unnecessary-parens.rs:82:11 | LL | while (true) {} | ^ ^ @@ -184,7 +184,7 @@ LL + while true {} | error: unnecessary parentheses around `match` scrutinee expression - --> $DIR/lint-unnecessary-parens.rs:61:11 + --> $DIR/lint-unnecessary-parens.rs:83:11 | LL | match (true) { | ^ ^ @@ -196,7 +196,7 @@ LL + match true { | 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) {} | ^ ^ @@ -208,7 +208,7 @@ LL + if let 1 = 1 {} | 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) {} | ^ ^ @@ -220,7 +220,7 @@ LL + while let 1 = 2 {} | 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)); | ^ ^ @@ -232,7 +232,7 @@ LL + X { y: false }.foo(true); | 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); | ^ ^ @@ -244,7 +244,7 @@ LL + let mut _a = 0; | error: unnecessary parentheses around assigned value - --> $DIR/lint-unnecessary-parens.rs:84:10 + --> $DIR/lint-unnecessary-parens.rs:106:10 | LL | _a = (0); | ^ ^ @@ -256,7 +256,7 @@ LL + _a = 0; | error: unnecessary parentheses around assigned value - --> $DIR/lint-unnecessary-parens.rs:85:11 + --> $DIR/lint-unnecessary-parens.rs:107:11 | LL | _a += (1); | ^ ^ @@ -268,7 +268,7 @@ LL + _a += 1; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:87:8 + --> $DIR/lint-unnecessary-parens.rs:109:8 | LL | let(mut _a) = 3; | ^ ^ @@ -280,7 +280,7 @@ LL + let mut _a = 3; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:88:9 + --> $DIR/lint-unnecessary-parens.rs:110:9 | LL | let (mut _a) = 3; | ^ ^ @@ -292,7 +292,7 @@ LL + let mut _a = 3; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:89:8 + --> $DIR/lint-unnecessary-parens.rs:111:8 | LL | let( mut _a) = 3; | ^^ ^ @@ -304,7 +304,7 @@ LL + let mut _a = 3; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:91:8 + --> $DIR/lint-unnecessary-parens.rs:113:8 | LL | let(_a) = 3; | ^ ^ @@ -316,7 +316,7 @@ LL + let _a = 3; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:92:9 + --> $DIR/lint-unnecessary-parens.rs:114:9 | LL | let (_a) = 3; | ^ ^ @@ -328,7 +328,7 @@ LL + let _a = 3; | error: unnecessary parentheses around pattern - --> $DIR/lint-unnecessary-parens.rs:93:8 + --> $DIR/lint-unnecessary-parens.rs:115:8 | LL | let( _a) = 3; | ^^ ^ @@ -339,5 +339,41 @@ 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 diff --git a/tests/ui/macros/stringify.rs b/tests/ui/macros/stringify.rs index 492bd2450b1bf..472cb4d417be8 100644 --- a/tests/ui/macros/stringify.rs +++ b/tests/ui/macros/stringify.rs @@ -213,6 +213,21 @@ fn test_expr() { "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 c1!(expr, [ || {} ], "|| {}"); @@ -720,6 +735,21 @@ fn test_stmt() { "(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 c1!(stmt, [ ; ], ";"); diff --git a/tests/ui/parser/else-no-if.rs b/tests/ui/parser/else-no-if.rs index f0b40ecde6660..ad5262cd2cc93 100644 --- a/tests/ui/parser/else-no-if.rs +++ b/tests/ui/parser/else-no-if.rs @@ -1,3 +1,7 @@ +macro_rules! falsy { + () => { false }; +} + fn foo() { if true { } 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 { false } diff --git a/tests/ui/parser/else-no-if.stderr b/tests/ui/parser/else-no-if.stderr index b9c1a75276c1f..2e3e8f6b50e95 100644 --- a/tests/ui/parser/else-no-if.stderr +++ b/tests/ui/parser/else-no-if.stderr @@ -1,5 +1,5 @@ error: expected `{`, found keyword `false` - --> $DIR/else-no-if.rs:3:12 + --> $DIR/else-no-if.rs:7:12 | LL | } else false { | ---- ^^^^^ @@ -12,7 +12,7 @@ LL | } else if false { | ++ error: expected `{`, found `falsy` - --> $DIR/else-no-if.rs:10:12 + --> $DIR/else-no-if.rs:14:12 | LL | } else falsy() { | ---- ^^^^^ @@ -25,7 +25,7 @@ LL | } else if falsy() { | ++ error: expected `{`, found `falsy` - --> $DIR/else-no-if.rs:17:12 + --> $DIR/else-no-if.rs:21:12 | LL | } else falsy(); | ^^^^^ expected `{` @@ -36,7 +36,7 @@ LL | } else { falsy() }; | + + error: expected `{`, found keyword `loop` - --> $DIR/else-no-if.rs:23:12 + --> $DIR/else-no-if.rs:27:12 | LL | } else loop{} | ^^^^ expected `{` @@ -46,5 +46,51 @@ help: try placing this code inside a block 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 diff --git a/tests/ui/parser/macro/statement-boundaries.rs b/tests/ui/parser/macro/statement-boundaries.rs new file mode 100644 index 0000000000000..67a6aa30f0c5e --- /dev/null +++ b/tests/ui/parser/macro/statement-boundaries.rs @@ -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 for () { + type Output = (); + fn bitor(self, _: X) {} +} + +impl std::ops::BitOr for &str { + type Output = (); + fn bitor(self, _: X) {} +} + +impl std::ops::BitOr for [T; N] { + type Output = (); + fn bitor(self, _: X) {} +}