Skip to main content

let_else/
lib.rs

1use quote::quote;
2use syn::parse::discouraged::Speculative;
3
4
5/// A procedural macro that enhance the `let-else` syntax in Rust.
6///
7/// # Example
8///
9/// ```rs, no_run
10/// fn foo(value: Result<i32, String>) {
11///   let_else!(Ok(value) = value else as Err(err) {
12///     eprintln!("Error: {}", err);
13///     return;
14///   });
15/// }
16/// ```
17#[proc_macro]
18pub fn let_else(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19  enum RestPat {
20    Pattern(syn::Pat),
21    Ident(syn::Ident),
22  }
23
24  impl syn::parse::Parse for RestPat {
25    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
26      if let Err(_) = input.parse::<syn::Token![as]>() {
27        return Ok(Self::Pattern(syn::parse_quote! { _ }));
28      }
29
30      let fork = input.fork();
31      if let Ok(pattern) = fork.call(syn::Pat::parse_multi_with_leading_vert) {
32        input.advance_to(&fork);
33        return Ok(Self::Pattern(pattern));
34      }
35
36      if let Ok(ident) = input.parse() {
37        return Ok(Self::Ident(ident));
38      }
39
40      Err(input.error("Expected a pattern or an identifier after `as`"))
41    }
42  }
43
44  impl quote::ToTokens for RestPat {
45    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
46      match self {
47        RestPat::Pattern(pat) => pat.to_tokens(tokens),
48        RestPat::Ident(ident) => ident.to_tokens(tokens),
49      }
50    }
51  }
52
53  struct Input {
54    pattern:  syn::Pat,
55    _sym_eq:  syn::Token![=],
56    expr:     syn::Expr,
57    _kw_else: syn::Token![else],
58    rest:     RestPat,
59    block:    syn::Block,
60  }
61
62  impl syn::parse::Parse for Input {
63    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
64      Ok(Self {
65        pattern:  input.call(syn::Pat::parse_single)?,
66        _sym_eq:  input.parse()?,
67        expr:     input.parse()?,
68        _kw_else: input.parse()?,
69        rest:     input.parse()?,
70        block:    input.parse()?,
71      })
72    }
73  }
74
75  let Input { pattern, expr, rest, block, .. } = syn::parse_macro_input!(input as Input);
76
77  quote! {
78    #[doc(hidden)]
79    let __let_else_expr__ = #expr;
80    let #pattern = __let_else_expr__ else {
81      match __let_else_expr__ {
82        #[allow(unused, dead_code)]
83        #pattern => unsafe { std::hint::unreachable_unchecked() },
84        #rest => #block,
85      }
86    };
87  }.into()
88}