├── .editorconfig ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── README.tpl ├── examples └── test.rs └── src ├── lib.rs └── parse.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.rs] 4 | indent_style = tab 5 | indent_size = 4 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spez" 3 | version = "0.1.2" 4 | authors = ["Mara Bos "] 5 | edition = "2021" 6 | license = "BSD-2-Clause" 7 | description = "Macro to specialize on the type of an expression" 8 | repository = "https://github.com/m-ou-se/spez" 9 | documentation = "https://docs.rs/spez" 10 | readme = "README.md" 11 | keywords = ["autoref", "autoderef", "specialization", "specialisation"] 12 | categories = ["rust-patterns", "development-tools", "development-tools::procedural-macro-helpers", "no-std"] 13 | 14 | [lib] 15 | proc_macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0.56" 19 | quote = "1.0.26" 20 | syn = { version = "2.0.15", features = ["full"] } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Mara Bos 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spez 2 | 3 | Macro to specialize on the type of an expression. 4 | 5 | This crate implements *auto(de)ref specialization*: 6 | A trick to do specialization in non-generic contexts on stable Rust. 7 | 8 | For the details of this technique, see: 9 | - [*Autoref-based stable specialization* by David Tolnay][autoref] 10 | - [*Generalized Autoref-Based Specialization* by Lukas Kalbertodt][autoderef] 11 | 12 | [autoref]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md 13 | [autoderef]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html 14 | 15 | ## What it can and cannot do 16 | 17 | The auto(de)ref technique—and therefore this macro—is useless in generic 18 | functions, as Rust resolves the specialization based on the bounds defined 19 | on the generic context, not based on the actual type when instantiated. 20 | (See [the example below](#in-a-generic-function) for a demonstration of 21 | this.) 22 | 23 | In non-generic contexts, it's also mostly useless, as you probably already 24 | know the exact type of all variables. 25 | 26 | The only place where using this can make sense is in the implementation of 27 | macros that need to have different behaviour depending on the type of a 28 | value passed to it. For example, a macro that prints the `Debug` output of 29 | a value, but falls back to a default when it doesn't implement `Debug`. 30 | (See [the example below](#in-a-macro) for a demonstration of 31 | that.) 32 | 33 | ## How to use it 34 | 35 | The basic syntax of the macro is: 36 | 37 | ``` 38 | spez! { 39 | for ; 40 | match { } 41 | [match { }] 42 | [...] 43 | } 44 | ``` 45 | 46 | The examples below show more details. 47 | 48 | ### Simple specialization 49 | 50 | In the most simple case, you use this macro to match specific types: 51 | 52 | ```rust 53 | let x = 0; 54 | spez! { 55 | for x; 56 | match i32 { 57 | println!("x is a 32-bit integer!"); 58 | } 59 | match &str { 60 | println!("x is a string slice!"); 61 | assert!(false); 62 | } 63 | } 64 | ``` 65 | 66 | ### Return types 67 | 68 | Values can be returned from the matches, but have to be explicitly 69 | specified for each `match`. They do not have to be the same for every 70 | `match`. 71 | 72 | ```rust 73 | let x = 0; 74 | let result = spez! { 75 | for x; 76 | match i32 -> &'static str { 77 | "x is a 32-bit integer!" 78 | } 79 | match &str -> i32 { 80 | 123 81 | } 82 | }; 83 | assert_eq!(result, "x is a 32-bit integer!"); 84 | ``` 85 | 86 | ### Generic matches 87 | 88 | Generic matches are also possible. Generic variables can be defined 89 | on the `match`, and a `where` clause can be added after the type. 90 | 91 | The matches are tried in order. The first matches get priority over later 92 | ones, even if later ones are perfect matches. 93 | 94 | ```rust 95 | let x = 123i32; 96 | let result = spez! { 97 | for x; 98 | match T where i8: From -> i32 { 99 | 0 100 | } 101 | match T -> i32 { 102 | 1 103 | } 104 | match i32 -> i32 { 105 | 2 106 | } 107 | }; 108 | assert_eq!(result, 1); 109 | ``` 110 | 111 | ## Consuming the input 112 | 113 | The input (after the `for`) is consumed and made available to the `match` 114 | bodies. 115 | 116 | (If you don't want to consume the input, take a reference and also prepend 117 | a `&` to the types you're matching.) 118 | 119 | ```rust 120 | let x = Box::new(123); 121 | let result = spez! { 122 | for x; 123 | match> T -> i32 { 124 | *x 125 | } 126 | match i32 -> i32 { 127 | x 128 | } 129 | }; 130 | assert_eq!(result, 123); 131 | ``` 132 | 133 | ## Expressions as input 134 | 135 | Not just variable names, but full expressions can be given as input. 136 | However, if you want to refer to them from the match bodies, you need to 137 | prepend `name =` to give the input a name. 138 | 139 | ```rust 140 | let result = spez! { 141 | for 1 + 1; 142 | match i32 -> i32 { 0 } 143 | match i64 -> i32 { 1 } 144 | }; 145 | assert_eq!(result, 0); 146 | ``` 147 | 148 | ```rust 149 | let result = spez! { 150 | for x = 1 + 1; 151 | match i32 -> i32 { x } 152 | match i64 -> i32 { 1 } 153 | }; 154 | assert_eq!(result, 2); 155 | ``` 156 | 157 | ## Capturing variables 158 | 159 | Unfortunately, you can't refer to variables of the scope around the `spez! {}` macro: 160 | 161 | ```compile_fail 162 | let a = 1; 163 | let result = spez! { 164 | for x = 1; 165 | match i32 { 166 | println!("{}", a); // ERROR 167 | } 168 | }; 169 | ``` 170 | 171 | ## In a generic function 172 | 173 | As mentioned above, the macro is of not much use in generic context, as the 174 | specialization is resolved based on the bounds rather than on the actual 175 | type in the instantiation of the generic function: 176 | 177 | ```rust 178 | fn f(v: T) -> &'static str { 179 | spez! { 180 | for v; 181 | match i32 -> &'static str { 182 | ":)" 183 | } 184 | match T -> &'static str { 185 | ":(" 186 | } 187 | match T -> &'static str { 188 | ":((" 189 | } 190 | } 191 | } 192 | assert_eq!(f(0i32), ":("); 193 | ``` 194 | 195 | ## In a macro 196 | 197 | This is a demonstration of a macro that prints the `Debug` output of a 198 | value, but falls back to `""` if it doesn't implement 199 | `Debug`. 200 | 201 | ```rust 202 | macro_rules! debug { 203 | ($e:expr) => { 204 | spez! { 205 | for x = $e; 206 | match T { 207 | println!("{:?}", x); 208 | } 209 | match T { 210 | println!("", std::any::type_name::()); 211 | } 212 | } 213 | } 214 | } 215 | debug!(123); 216 | debug!(NoDebugType); 217 | ``` 218 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | {{readme}} 4 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | use spez::spez; 2 | 3 | trait A {} 4 | trait B {} 5 | 6 | impl A for i32 {} 7 | impl B for i32 {} 8 | 9 | impl A for &str {} 10 | impl B for &str {} 11 | 12 | fn main() { 13 | let x = 0; 14 | //let x = [1, 2, 3]; 15 | //let x = [1.0, 2.0, 3.0]; 16 | //let x = &b"asdf"[..]; 17 | //let x = &(); 18 | //let x = String::new(); 19 | //let x = (); 20 | 21 | let result = spez! { 22 | for x; 23 | //for x[0]; 24 | //for x = x[0]; 25 | //for x = &x; 26 | match T where i32: From -> String { 27 | println!("A + B + Into"); 28 | format!("Test {}", i32::from(x)) 29 | } 30 | match [T; 3] where T: A -> i32 { 31 | println!("array of 3 things implementing A"); 32 | 9 33 | } 34 | match [T; 3] { 35 | println!("array of 3"); 36 | } 37 | match &[T] { 38 | println!("slice"); 39 | } 40 | match &T -> (i32, i32) { 41 | println!("reference"); 42 | (1, 2) 43 | } 44 | match i32 { 45 | println!("i32") 46 | } 47 | match String { 48 | println!("String") 49 | } 50 | }; 51 | 52 | println!("{:?}", result); 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Macro to specialize on the type of an expression. 2 | //! 3 | //! This crate implements *auto(de)ref specialization*: 4 | //! A trick to do specialization in non-generic contexts on stable Rust. 5 | //! 6 | //! For the details of this technique, see: 7 | //! - [*Autoref-based stable specialization* by David Tolnay][autoref] 8 | //! - [*Generalized Autoref-Based Specialization* by Lukas Kalbertodt][autoderef] 9 | //! 10 | //! [autoref]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md 11 | //! [autoderef]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html 12 | //! 13 | //! # What it can and cannot do 14 | //! 15 | //! The auto(de)ref technique—and therefore this macro—is useless in generic 16 | //! functions, as Rust resolves the specialization based on the bounds defined 17 | //! on the generic context, not based on the actual type when instantiated. 18 | //! (See [the example below](#in-a-generic-function) for a demonstration of 19 | //! this.) 20 | //! 21 | //! In non-generic contexts, it's also mostly useless, as you probably already 22 | //! know the exact type of all variables. 23 | //! 24 | //! The only place where using this can make sense is in the implementation of 25 | //! macros that need to have different behaviour depending on the type of a 26 | //! value passed to it. For example, a macro that prints the `Debug` output of 27 | //! a value, but falls back to a default when it doesn't implement `Debug`. 28 | //! (See [the example below](#in-a-macro) for a demonstration of 29 | //! that.) 30 | //! 31 | //! # How to use it 32 | //! 33 | //! The basic syntax of the macro is: 34 | //! 35 | //! ```text 36 | //! spez! { 37 | //! for ; 38 | //! match { } 39 | //! [match { }] 40 | //! [...] 41 | //! } 42 | //! ``` 43 | //! 44 | //! The examples below show more details. 45 | //! 46 | //! ## Simple specialization 47 | //! 48 | //! In the most simple case, you use this macro to match specific types: 49 | //! 50 | //! ``` 51 | //! # use spez::spez; 52 | //! let x = 0; 53 | //! spez! { 54 | //! for x; 55 | //! match i32 { 56 | //! println!("x is a 32-bit integer!"); 57 | //! } 58 | //! match &str { 59 | //! println!("x is a string slice!"); 60 | //! assert!(false); 61 | //! } 62 | //! } 63 | //! ``` 64 | //! 65 | //! ## Return types 66 | //! 67 | //! Values can be returned from the matches, but have to be explicitly 68 | //! specified for each `match`. They do not have to be the same for every 69 | //! `match`. 70 | //! 71 | //! ``` 72 | //! # use spez::spez; 73 | //! let x = 0; 74 | //! let result = spez! { 75 | //! for x; 76 | //! match i32 -> &'static str { 77 | //! "x is a 32-bit integer!" 78 | //! } 79 | //! match &str -> i32 { 80 | //! 123 81 | //! } 82 | //! }; 83 | //! assert_eq!(result, "x is a 32-bit integer!"); 84 | //! ``` 85 | //! 86 | //! ## Generic matches 87 | //! 88 | //! Generic matches are also possible. Generic variables can be defined 89 | //! on the `match`, and a `where` clause can be added after the type. 90 | //! 91 | //! The matches are tried in order. The first matches get priority over later 92 | //! ones, even if later ones are perfect matches. 93 | //! 94 | //! ``` 95 | //! # use spez::spez; 96 | //! let x = 123i32; 97 | //! let result = spez! { 98 | //! for x; 99 | //! match T where i8: From -> i32 { 100 | //! 0 101 | //! } 102 | //! match T -> i32 { 103 | //! 1 104 | //! } 105 | //! match i32 -> i32 { 106 | //! 2 107 | //! } 108 | //! }; 109 | //! assert_eq!(result, 1); 110 | //! ``` 111 | //! 112 | //! # Consuming the input 113 | //! 114 | //! The input (after the `for`) is consumed and made available to the `match` 115 | //! bodies. 116 | //! 117 | //! (If you don't want to consume the input, take a reference and also prepend 118 | //! a `&` to the types you're matching.) 119 | //! 120 | //! ``` 121 | //! # use spez::spez; 122 | //! # use core::ops::Deref; 123 | //! let x = Box::new(123); 124 | //! let result = spez! { 125 | //! for x; 126 | //! match> T -> i32 { 127 | //! *x 128 | //! } 129 | //! match i32 -> i32 { 130 | //! x 131 | //! } 132 | //! }; 133 | //! assert_eq!(result, 123); 134 | //! ``` 135 | //! 136 | //! # Expressions as input 137 | //! 138 | //! Not just variable names, but full expressions can be given as input. 139 | //! However, if you want to refer to them from the match bodies, you need to 140 | //! prepend `name =` to give the input a name. 141 | //! 142 | //! ``` 143 | //! # use spez::spez; 144 | //! let result = spez! { 145 | //! for 1 + 1; 146 | //! match i32 -> i32 { 0 } 147 | //! match i64 -> i32 { 1 } 148 | //! }; 149 | //! assert_eq!(result, 0); 150 | //! ``` 151 | //! 152 | //! ``` 153 | //! # use spez::spez; 154 | //! let result = spez! { 155 | //! for x = 1 + 1; 156 | //! match i32 -> i32 { x } 157 | //! match i64 -> i32 { 1 } 158 | //! }; 159 | //! assert_eq!(result, 2); 160 | //! ``` 161 | //! 162 | //! # Capturing variables 163 | //! 164 | //! Unfortunately, you can't refer to variables of the scope around the `spez! {}` macro: 165 | //! 166 | //! ```compile_fail 167 | //! let a = 1; 168 | //! let result = spez! { 169 | //! for x = 1; 170 | //! match i32 { 171 | //! println!("{}", a); // ERROR 172 | //! } 173 | //! }; 174 | //! ``` 175 | //! 176 | //! # In a generic function 177 | //! 178 | //! As mentioned above, the macro is of not much use in generic context, as the 179 | //! specialization is resolved based on the bounds rather than on the actual 180 | //! type in the instantiation of the generic function: 181 | //! 182 | //! ``` 183 | //! # use spez::spez; 184 | //! # use std::fmt::Debug; 185 | //! fn f(v: T) -> &'static str { 186 | //! spez! { 187 | //! for v; 188 | //! match i32 -> &'static str { 189 | //! ":)" 190 | //! } 191 | //! match T -> &'static str { 192 | //! ":(" 193 | //! } 194 | //! match T -> &'static str { 195 | //! ":((" 196 | //! } 197 | //! } 198 | //! } 199 | //! assert_eq!(f(0i32), ":("); 200 | //! ``` 201 | //! 202 | //! # In a macro 203 | //! 204 | //! This is a demonstration of a macro that prints the `Debug` output of a 205 | //! value, but falls back to `""` if it doesn't implement 206 | //! `Debug`. 207 | //! 208 | //! ``` 209 | //! # use spez::spez; 210 | //! # use std::fmt::Debug; 211 | //! macro_rules! debug { 212 | //! ($e:expr) => { 213 | //! spez! { 214 | //! for x = $e; 215 | //! match T { 216 | //! println!("{:?}", x); 217 | //! } 218 | //! match T { 219 | //! println!("", std::any::type_name::()); 220 | //! } 221 | //! } 222 | //! } 223 | //! } 224 | //! debug!(123); 225 | //! # struct NoDebugType; 226 | //! debug!(NoDebugType); 227 | //! ``` 228 | 229 | extern crate proc_macro; 230 | 231 | mod parse; 232 | 233 | use parse::Args; 234 | use proc_macro::TokenStream; 235 | use proc_macro2::Span; 236 | use proc_macro2::TokenStream as TokenStream2; 237 | use quote::quote; 238 | 239 | /// Specialize based on the type of an expression. 240 | /// 241 | /// See the [crate level documentation](index.html). 242 | #[proc_macro] 243 | pub fn spez(tokens: TokenStream) -> TokenStream { 244 | spez_impl(syn::parse_macro_input!(tokens)).into() 245 | } 246 | 247 | fn refs(n: usize) -> TokenStream2 { 248 | let mut refs = TokenStream2::new(); 249 | for _ in 0..n { 250 | refs.extend(quote![&]); 251 | } 252 | refs 253 | } 254 | 255 | fn spez_impl(args: Args) -> TokenStream2 { 256 | let mut traits = TokenStream2::new(); 257 | 258 | let param_def = match args.param { 259 | Some(param) => quote! { 260 | let #param = self.0.take().unwrap(); 261 | let _ = #param; // Suppress unused variable warning. 262 | }, 263 | None => quote! {}, 264 | }; 265 | 266 | let n_arms = args.arms.len(); 267 | 268 | for (i, arm) in args.arms.into_iter().enumerate() { 269 | let name = syn::Ident::new(&format!("Match{}", i + 1), Span::call_site()); 270 | let body = arm.body; 271 | let ty = arm.ty; 272 | let generics = &arm.generics; 273 | let where_clause = &arm.generics.where_clause; 274 | let refs = refs(n_arms - i - 1); 275 | let return_type = match arm.return_type { 276 | Some(return_type) => quote! { #return_type }, 277 | None => quote! { () }, 278 | }; 279 | 280 | traits.extend(quote! { 281 | trait #name { 282 | type Return; 283 | fn spez(&self) -> Self::Return; 284 | } 285 | impl #generics #name for #refs Match<#ty> #where_clause { 286 | type Return = #return_type; 287 | fn spez(&self) -> Self::Return { 288 | #param_def 289 | #body 290 | } 291 | } 292 | }); 293 | } 294 | 295 | let expr = args.expr; 296 | let refs = refs(n_arms); 297 | 298 | quote! { 299 | { 300 | struct Match(core::cell::Cell>); 301 | #traits 302 | (#refs Match(core::cell::Cell::new(Some(#expr)))).spez() 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::iter::once; 2 | use std::iter::FromIterator; 3 | use syn::punctuated::Punctuated; 4 | use syn::Token; 5 | 6 | pub struct Args { 7 | pub for_token: Token![for], 8 | pub param: Option, 9 | pub at_token: Option, 10 | pub expr: syn::Expr, 11 | pub semicolon_token: Token![;], 12 | pub arms: Vec, 13 | } 14 | 15 | pub struct Arm { 16 | pub match_token: Token![match], 17 | pub generics: syn::Generics, 18 | pub ty: syn::Type, 19 | pub arrow_token: Option]>, 20 | pub return_type: Option, 21 | pub body: syn::Block, 22 | } 23 | 24 | impl syn::parse::Parse for Args { 25 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 26 | let for_token = input.parse()?; 27 | let param; 28 | let at_token; 29 | let expr; 30 | if input.peek2(Token![=]) { 31 | param = Some(input.parse()?); 32 | at_token = Some(input.parse()?); 33 | expr = input.parse()?; 34 | } else if input.peek(syn::Ident) && input.peek2(Token![;]) { 35 | let ident: syn::Ident = input.parse()?; 36 | param = Some(ident.clone()); 37 | at_token = None; 38 | expr = ident_to_expr(ident); 39 | } else { 40 | param = None; 41 | at_token = None; 42 | expr = input.parse()?; 43 | } 44 | Ok(Self { 45 | for_token, 46 | param, 47 | at_token, 48 | expr, 49 | semicolon_token: input.parse()?, 50 | arms: { 51 | let mut arms = Vec::new(); 52 | while !input.is_empty() { 53 | arms.push(input.parse()?); 54 | } 55 | arms 56 | }, 57 | }) 58 | } 59 | } 60 | 61 | impl syn::parse::Parse for Arm { 62 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 63 | let match_token = input.parse()?; 64 | let generics = if input.peek(Token![<]) { 65 | input.parse()? 66 | } else { 67 | syn::Generics::default() 68 | }; 69 | let ty = input.parse()?; 70 | let where_clause: Option = input.parse()?; 71 | let (arrow_token, return_type) = if input.peek(Token![->]) { 72 | (Some(input.parse()?), Some(input.parse()?)) 73 | } else { 74 | (None, None) 75 | }; 76 | let body = input.parse()?; 77 | Ok(Self { 78 | match_token, 79 | generics: syn::Generics { 80 | where_clause, 81 | ..generics 82 | }, 83 | ty, 84 | arrow_token, 85 | return_type, 86 | body, 87 | }) 88 | } 89 | } 90 | 91 | fn ident_to_expr(ident: syn::Ident) -> syn::Expr { 92 | syn::Expr::Path(syn::ExprPath { 93 | attrs: Vec::new(), 94 | qself: None, 95 | path: syn::Path { 96 | leading_colon: None, 97 | segments: Punctuated::from_iter(once(syn::PathSegment { 98 | ident, 99 | arguments: syn::PathArguments::None, 100 | })), 101 | }, 102 | }) 103 | } 104 | --------------------------------------------------------------------------------