Skip to main content

let_else/
lib.rs

1use quote::quote;
2use syn::parse::discouraged::Speculative;
3
4
5/// A procedural macro that extends `let-else` with explicit else bindings.
6///
7/// # Syntax
8///
9/// ```rs
10/// let_else!(PAT = EXPR else as REST { /* else block */ });
11/// let_else!(PAT = EXPR else match { /* match arms */ });
12/// ```
13///
14/// `REST` can be a pattern (including `|`-separated patterns) or a single
15/// identifier. When `match` is used, provide full match arms for the else
16/// branch.
17///
18/// # Examples
19///
20/// Simple else binding:
21///
22/// ```rs, no_run
23/// fn foo(value: Result<i32, String>) {
24///   let_else!(Ok(value) = value else as Err(err) {
25///     eprintln!("Error: {}", err);
26///     return;
27///   });
28/// }
29/// ```
30///
31/// Complete else match:
32///
33/// ```rs, no_run
34/// fn bar(value: Result<i32, String>) {
35///   let_else!(Ok(value) = value else match {
36///     Err(err) => {
37///       eprintln!("Error: {}", err);
38///       return;
39///     }
40///   });
41/// }
42/// ```
43///
44/// # Notes
45///
46/// - The expression is evaluated once and reused in the else branch.
47/// - The macro generates an unreachable arm for the success pattern.
48#[proc_macro]
49pub fn let_else(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
50  let Input { pattern, expr, rest, .. } = syn::parse_macro_input!(input as Input);
51
52  let match_inner = match rest {
53    Rest::Simple(SimpleRest { bound, block }) => quote! { #bound => #block, },
54    Rest::Complete(CompleteRest { arms, .. }) => quote! { #(#arms)* },
55  };
56
57  quote! {
58    #[doc(hidden)]
59    let __let_else_expr__ = #expr;
60    let #pattern = __let_else_expr__ else {
61      match __let_else_expr__ {
62        #[allow(unused, dead_code)]
63        #pattern => unsafe { std::hint::unreachable_unchecked() },
64        #match_inner
65      }
66    };
67  }.into()
68}
69
70
71struct Input {
72  pattern:  syn::Pat,
73  _sym_eq:  syn::Token![=],
74  expr:     syn::Expr,
75  _kw_else: syn::Token![else],
76  rest:     Rest,
77}
78
79impl syn::parse::Parse for Input {
80  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
81    Ok(Self {
82      pattern:  input.call(syn::Pat::parse_single)?,
83      _sym_eq:  input.parse()?,
84      expr:     input.parse()?,
85      _kw_else: input.parse()?,
86      rest:     input.parse()?,
87    })
88  }
89}
90
91
92enum Rest {
93  Simple(SimpleRest),
94  Complete(CompleteRest),
95}
96
97impl syn::parse::Parse for Rest {
98  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
99    let fork = input.fork();
100    if let Ok(simple) = fork.parse() {
101      input.advance_to(&fork);
102      return Ok(Self::Simple(simple));
103    }
104
105    let fork = input.fork();
106    if let Ok(complete) = fork.parse() {
107      input.advance_to(&fork);
108      return Ok(Self::Complete(complete));
109    }
110
111    Err(input.error("Expected `as` or `match` after `else`"))
112  }
113}
114
115
116struct SimpleRest {
117  bound: RestBound,
118  block: syn::Block,
119}
120
121impl syn::parse::Parse for SimpleRest {
122  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
123    Ok(Self {
124      bound: input.parse()?,
125      block: input.parse()?,
126    })
127  }
128}
129
130
131enum RestBound {
132  Pattern(syn::Pat),
133  Ident(syn::Ident),
134}
135
136impl syn::parse::Parse for RestBound {
137  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
138    if let Err(_) = input.parse::<syn::Token![as]>() {
139      return Ok(Self::Pattern(syn::parse_quote! { _ }));
140    }
141
142    let fork = input.fork();
143    if let Ok(pattern) = fork.call(syn::Pat::parse_multi_with_leading_vert) {
144      input.advance_to(&fork);
145      return Ok(Self::Pattern(pattern));
146    }
147
148    if let Ok(ident) = input.parse() {
149      return Ok(Self::Ident(ident));
150    }
151
152    Err(input.error("Expected a pattern or an identifier after `as`"))
153  }
154}
155
156impl quote::ToTokens for RestBound {
157  fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
158    match self {
159      RestBound::Pattern(pat) => pat.to_tokens(tokens),
160      RestBound::Ident(ident) => ident.to_tokens(tokens),
161    }
162  }
163}
164
165
166struct CompleteRest {
167  _kw_match: syn::Token![match],
168  arms:      Vec<syn::Arm>,
169}
170
171impl syn::parse::Parse for CompleteRest {
172  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
173    Ok(Self {
174      _kw_match: input.parse()?,
175      arms: {
176        let content;
177        syn::braced!(content in input);
178
179        let mut arms = Vec::new();
180        while !content.is_empty() {
181          arms.push(content.parse()?);
182        }
183
184        arms
185      },
186    })
187  }
188}