1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! This is an implementation of a `try-let` similar to the one proposed in
//! [RFC #1303](https://github.com/rust-lang/rfcs/pull/1303), as a proc macro.
//!
//! > _NOTE:_ Proc macros in statement position are currently unstable, meaning this
//! > macro will not work on stable rust until
//! > [PR #68717](https://github.com/rust-lang/rust/pull/68717) is merged.
//!
//! # Usage
//!
//! try-let is implemented using a proc macro instead, as parsing the pattern
//! expression in the way which try-let needs to is not possible with a
//! `macro_rules!` macro.
//!
//! This plugin currently requires enabling `#![feature(proc_macro_hygiene)]`, like
//! so:
//!
//! ```rust
//! #![feature(proc_macro_hygiene)]
//! use try_let::try_let;
//! ```
//!
//! The actual use is fairly similar to a `let` expression:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # use try_let::try_let;
//! # fn main() -> Result<(), &'static str> {
//! # let foo = Some(());
//! try_let!(Some(x) = foo else {
//!     return Err("Shoot! There was a problem!")
//! });
//! # Ok(())
//! # }
//! ```
//!
//! The expression after else must diverge (e.g. via `return`, `continue`, `break`
//! or `panic!`).
//!
//! This also handles more complex types than `Some` and `None`:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # use try_let::try_let;
//! # fn main() {
//! enum E {
//!     A(i32, i32, i32, i32, Option<i32>, Result<(), i32>),
//!     B,
//! }
//!
//! let foo = E::A(0, 21, 10, 34, Some(5), Err(32));
//!
//! try_let!(E::A(a, 21, c, 34, Some(e), Err(f)) = foo else {
//!     unreachable!()
//! });
//! // a, c, e, and f are all bound here.
//! assert_eq!(a, 0);
//! assert_eq!(c, 10);
//! assert_eq!(e, 5);
//! assert_eq!(f, 32);
//! # }
//! ```
//!
//! # Why
//!
//! This provides a simple way to avoid the rightward-shift of logic which performs
//! a large number of faillible pattern matches in rust. This allows the main logic
//! flow to continue without increasing the indent level, while handling errors with
//! diverging logic.
//!
//! # How
//!
//! a `try_let!()` invocation expands to the following:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # use try_let::try_let;
//! # fn main() -> Result<(), &'static str> {
//! # let foo = Some(10);
//! try_let!(Some(x) = foo else {
//!     return Err("Shoot! There was a problem!");
//! });
//! // ... becomes ...
//! let (x,) = match foo {
//!     Some(x) => (x,),
//!     _ => {
//!         return Err("Shoot! There was a problem!");
//!     }
//! };
//! # Ok(())
//! # }
//! ```
//!
//! ## A note on `None` and empty enum variants
//!
//! A question which some people will be asking now is how are enum variants like
//! `None` handled?
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # use try_let::try_let;
//! # fn main() {
//! # let foo = Some(10);
//! try_let!(None = foo else {
//!     return;
//! });
//! // ... becomes ...
//! let () = match foo {
//!     None => (),
//!     _ => {
//!         return;
//!     }
//! };
//! # }
//! ```
//!
//! `None` isn't mistaken for a binding variable by try-let because of the dirty
//! little trick which try-let uses to function: which is that it is powered by
//! rust's style conventions. There is no way for the parser (which is all that the
//! syntax extension has access to) to determine whether a lone identifier in a
//! pattern is an empty enum variant like `None` or a variable binding like `x`.
//! This is determined later in the compiler, too late for this extension to use
//! that information.
//!
//! Instead, the extension checks the first character of the identifier. If it is an
//! ASCII capital, we assume it is a empty enum variant, and otherwise we assume it
//! is a variable binding.

use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::visit::{self, Visit};
use syn::{parse_macro_input, Block, Expr, Ident, Pat, PatIdent, Result, Token};

struct TryLet {
    pat: Pat,
    _eq_token: Token![=],
    expr: Expr,
    _else_token: Token![else],
    fallback: Block,
}

impl Parse for TryLet {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(TryLet {
            pat: input.parse()?,
            _eq_token: input.parse()?,
            expr: input.parse()?,
            _else_token: input.parse()?,
            fallback: input.parse()?,
        })
    }
}

struct Visitor<'a> {
    bindings: &'a mut Vec<Ident>,
}

impl<'a> Visit<'_> for Visitor<'a> {
    fn visit_pat_ident(&mut self, id: &PatIdent) {
        // If the identifier starts with an uppercase letter, assume that it's a
        // constant, unit struct, or a unit enum variant. This isn't a very
        // accurate system for this type of check, but is unfortunately the best
        // we can do from within a proc macro.
        if id
            .ident
            .to_string()
            .chars()
            .next()
            .unwrap()
            .is_ascii_uppercase()
        {
            return;
        }
        self.bindings.push(id.ident.clone());

        // Visit any nested expressions (e.g. using `id @ pat`)
        visit::visit_pat_ident(self, id);
    }
}

/// The whole point
///
/// See the module-level documentation for details.
#[proc_macro]
pub fn try_let(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let TryLet {
        pat,
        expr,
        fallback,
        ..
    } = parse_macro_input!(input as TryLet);

    let mut bindings = Vec::new();
    Visitor {
        bindings: &mut bindings,
    }
    .visit_pat(&pat);

    // NOTE: This doesn't use `mixed_site`, however it also doesn't introduce
    // any new identifiers not from the call-site.
    let output = quote!(
        let (#(#bindings,)*) = match (#expr) {
            #pat => (#(#bindings,)*),
            _ => {
                #fallback;
            }
        };
    );
    return output.into();
}