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