pub enum LetElseBodyMustDiverge {}
#[macro_export(local_inner_macros)]
macro_rules! __guard_output {
((($($imms:ident)*) ($($muts:ident)*)), [($($guard:tt)*) ($($pattern:tt)*) ($rhs:expr) ($diverge:expr)]) => {
__guard_impl!(@as_stmt
let ($($imms,)* $(mut $muts,)*) = { #[allow(unused_mut)]
match $rhs {
$($pattern)* => {
if $($guard)* { // move the guard inside to avoid "cannot bind by-move"
($($imms,)* $($muts,)*)
} else {
let _: $crate::LetElseBodyMustDiverge = $diverge;
}
},
_ => {
let _: $crate::LetElseBodyMustDiverge = $diverge;
},
} }
)
};
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! __guard_impl {
// 0. cast a series of token trees to a statement
(@as_stmt $s:stmt) => { $s };
// 1. output stage
(@collect () -> $($rest:tt)*) => {
__guard_output!($($rest)*)
};
// 2. identifier collection stage
// The pattern is scanned destructively. Anything that looks like a capture (including
// false positives, like un-namespaced/empty structs or enum variants) is copied into the
// appropriate identifier list. Irrelevant symbols are discarded. The scanning descends
// recursively into bracketed structures.
// unwrap brackets and prepend their contents to the pattern remainder, in case there are captures inside
(@collect (($($inside:tt)*) $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru)
};
(@collect ({$($inside:tt)*} $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru)
};
(@collect ([$($inside:tt)*] $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru)
};
// discard irrelevant symbols
(@collect (, $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
(@collect (.. $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
(@collect (@ $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
(@collect (_ $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
(@collect (& $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// ignore generic parameters
(@collect (:: <$($generic:tt),*> $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// a path can't be a capture, and a path can't end with ::, so the ident after :: is never a capture
(@collect (:: $pathend:ident $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// alternative patterns may be given with | as long as the same captures (including type) appear on each side
// due to this property, if we see a | we've already parsed all the captures and can simply stop
(@collect (| $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect () -> $idents, $thru) // discard the rest of the pattern, proceed to output stage
};
// an explicitly provided pattern guard replaces the default, if there was one
(@collect (if $($tail:tt)*) -> $idents:tt, [$guard:tt $($rest:tt)*]) => {
__guard_impl!(@collect () -> $idents, [($($tail)*) $($rest)*])
};
// throw away some identifiers that do not represent captures
// box destructuring
(@collect (box $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// an ident followed by a colon is the name of a structure member
(@collect ($id:ident: $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// paths do not represent captures
(@collect ($pathcomp:ident :: $pathend:ident $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> $idents, $thru)
};
// an ident followed by parentheses is the name of a tuple-like struct or enum variant
// (unwrap the parens to parse the contents)
(@collect ($id:ident ($($inside:tt)*) $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru)
};
// an ident followed by curly braces is the name of a struct or struct-like enum variant
// (unwrap the braces to parse the contents)
(@collect ($id:ident {$($inside:tt)*} $($tail:tt)*) -> $idents:tt, $thru:tt) => {
__guard_impl!(@collect ($($inside)* $($tail)*) -> $idents, $thru)
};
// actually identifier collection happens here!
// capture by mutable reference!
(@collect (ref mut $id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru)
};
// capture by immutable reference!
(@collect (ref $id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru)
};
// capture by move into mutable binding!
(@collect (mut $id:ident $($tail:tt)*) -> ($imms:tt ($($muts:ident)*)), $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> ($imms ($($muts)* $id)), $thru)
};
// capture by move into an immutable binding!
(@collect ($id:ident $($tail:tt)*) -> (($($imms:ident)*) $muts:tt), $thru:tt) => {
__guard_impl!(@collect ($($tail)*) -> (($($imms)* $id) $muts), $thru)
};
// 3. splitting (for new syntax)
// done with pattern (and it's LPED=X)
(@split (else { $($diverge:tt)* } = $($tail:tt)*) -> ($pat:tt $guard:tt)) => {
__guard_impl!(@collect $pat -> (() ()), [$guard $pat ($($tail)*) ({ $($diverge)* })])
};
// done with pattern (and it's LP=XED)
(@split (= $($tail:tt)*) -> ($pat:tt $guard:tt)) => {
__guard_impl!(@split expr ($($tail)*) -> ($pat $guard ()))
};
// found a guard in the pattern
(@split (if $($tail:tt)*) -> ($pat:tt $guard:tt)) => {
__guard_impl!(@split guard ($($tail)*) -> ($pat ()))
};
// done with guard (and it's LP=XED)
(@split guard (= $($tail:tt)*) -> ($pat:tt $guard:tt)) => {
__guard_impl!(@split expr ($($tail)*) -> ($pat $guard ()))
};
// done with guard (and it's LPED=X)
(@split guard (else { $($diverge:tt)* } = $($tail:tt)*) -> ($pat:tt $guard:tt)) => {
__guard_impl!(@collect $pat -> (() ()), [$guard $pat ($($tail)*) ({ $($diverge)* })])
};
// found a token in the guard
(@split guard ($head:tt $($tail:tt)*) -> ($pat:tt ($($guard:tt)*))) => {
__guard_impl!(@split guard ($($tail)*) -> ($pat ($($guard)* $head)))
};
// found a token in the pattern
(@split ($head:tt $($tail:tt)*) -> (($($pat:tt)*) $guard:tt)) => {
__guard_impl!(@split ($($tail)*) -> (($($pat)* $head) $guard))
};
// found an "else DIVERGE" in the expr
(@split expr (else { $($tail:tt)* }) -> ($pat:tt $guard:tt $expr:tt)) => {
__guard_impl!(@collect $pat -> (() ()), [$guard $pat $expr ({ $($tail)* })])
};
// found an else in the expr with more stuff after it
(@split expr (else { $($body:tt)* } $($tail:tt)*) -> ($pat:tt $guard:tt ($($expr:tt)*))) => {
__guard_impl!(@split expr ($($tail)*) -> ($pat $guard ($($expr)* else { $($body)* })))
};
// found another token in the expr
(@split expr ($head:tt $($tail:tt)*) -> ($pat:tt $guard:tt ($($expr:tt)*))) => {
__guard_impl!(@split expr ($($tail)*) -> ($pat $guard ($($expr)* $head)))
};
// 4. entry points
// old syntax
({ $($diverge:tt)* } unless $rhs:expr => $($pattern:tt)*) => {
__guard_impl!(@collect ($($pattern)*) -> (() ()), [(true) ($($pattern)*) ($rhs) ({$($diverge)*})])
// | | || | || | | |
// | | || | || | | ^ diverging expression
// | | || | || | ^ expression
// | | || | || ^ saved copy of pattern
// | | || | | ^ pattern guard
// | | || | ^ parameters that will be carried through to output stage
// | | || ^ identifiers bound to mutable captures
// | | |^ identifiers bound to immutable captures
// | | ^ identifiers found by the scan
// | ^ pattern to be destructively scanned for identifiers
// ^ proceed to identifier collection stage
// FIXME once #14252 is fixed, put "if true" in as the default guard to defeat E0008
};
// new syntax
(let $($tail:tt)*) => {
__guard_impl!(@split ($($tail)*) -> (() (true)))
// | | | |
// | | | ^ guard
// | | ^ pattern
// | ^ tail to be split into "PAT = EXPR else DIVERGE"
// ^ first pass will do the parsing
};
}
macro_rules! guard {
($($input:tt)*) => {
__guard_impl!($($input)*)
};
}
pub fn demo() {
guard!(let Some(_) = Some(1) else { panic!() });
guard!(let Option::Some(_) = Some(1) else { panic!() });
}
With this code:
Rustc compilles this code fine, but Rust-Analyzer thinks their is an error, but only for the secound line
version with `guard` definitions inlined (~200 loc), to make debugging easier
rust-analyzer version: rust-analyzer version: 02904e9 2022-02-14 nightly
rustc version: rustc 1.58.1 (db9d1b20b 2022-01-20)