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:
bors 2024-05-12 04:18:20 +00:00
commit 8cc6f34653
12 changed files with 517 additions and 57 deletions

View File

@ -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)

View File

@ -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(",");
}
}

View File

@ -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`

View File

@ -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 {

View File

@ -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 {

View File

@ -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<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 _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
};
}

View File

@ -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<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 _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
};
}

View File

@ -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

View File

@ -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, [ ; ], ";");

View File

@ -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
}

View File

@ -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

View 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) {}
}