├── .gitignore ├── optargs-macro ├── .gitignore ├── README.md ├── Cargo.toml └── src │ ├── lib.rs │ ├── optstruct.rs │ └── optfn.rs ├── examples ├── optfn_multiple_dyns.rs ├── optstruct_demo.rs ├── optstruct_reference.rs ├── optfn_demo.rs └── optfn_reference.rs ├── Cargo.toml ├── notes └── ROADMAP.md ├── src └── lib.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode 4 | -------------------------------------------------------------------------------- /optargs-macro/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /optargs-macro/README.md: -------------------------------------------------------------------------------- 1 | # OptArgs-Macro 2 | 3 | This crate powers the OptArgs crate which lets you create macro_rules for big functions and structs. 4 | -------------------------------------------------------------------------------- /examples/optfn_multiple_dyns.rs: -------------------------------------------------------------------------------- 1 | #[optargs::optfn] 2 | fn blah2(a: Option<&dyn Iterator>) {} 3 | 4 | fn main() { 5 | blah2! { 6 | a: &(0..10) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/optstruct_demo.rs: -------------------------------------------------------------------------------- 1 | #[derive(optargs::OptStruct)] 2 | struct Example { 3 | a: i32, 4 | b: Option, 5 | } 6 | 7 | fn main() { 8 | let ex = Example! { 9 | a: 10, 10 | b: "asd".into() 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "optargs" 3 | version = "0.1.2" 4 | authors = ["Jonathan Kelley "] 5 | edition = "2018" 6 | description = "Easily create macros for functions with optional arguments" 7 | keywords = ["optional", "function", "arguments", "macro", "const"] 8 | license = "MIT OR Apache-2.0" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | optargs-macro = { path = "./optargs-macro", version = "0.1.1" } 14 | 15 | [profile.dev] 16 | debug = 0 17 | 18 | [profile.release] 19 | incremental = true 20 | debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger. 21 | -------------------------------------------------------------------------------- /optargs-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "optargs-macro" 3 | version = "0.1.2" 4 | authors = ["Jonathan Kelley "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "Macros for the optargs crate" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | 15 | [dependencies] 16 | proc-macro2 = "1.0.6" 17 | quote = "1.0" 18 | syn = { version = "1.0.11", features = ["full", "extra-traits"] } 19 | 20 | 21 | [profile.dev] 22 | debug = 0 23 | 24 | [profile.release] 25 | incremental = true 26 | debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger. 27 | -------------------------------------------------------------------------------- /optargs-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::ToTokens; 3 | 4 | mod optfn; 5 | mod optstruct; 6 | 7 | #[proc_macro_attribute] 8 | pub fn optfn(_attr: TokenStream, s: TokenStream) -> TokenStream { 9 | match syn::parse::(s) { 10 | Err(e) => e.to_compile_error().into(), 11 | Ok(s) => s.to_token_stream().into(), 12 | } 13 | } 14 | 15 | #[proc_macro_derive(OptStruct, attributes(builder))] 16 | pub fn optstruct(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 17 | match syn::parse::(input) { 18 | Err(e) => e.to_compile_error().into(), 19 | Ok(s) => s.to_token_stream().into(), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /notes/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Future 2 | - positional parameters (potential conflict with naming) 3 | - argument forwarding (*args, **kwargs) 4 | - allow arbitrary ordering of parameters (potential conflict with named + positional params ) 5 | 6 | # v1 7 | - [x] optional functions with named parameters with `fn!()` syntax 8 | - [x] optional structs with `Struct!{}` syntax 9 | - [x] use of const generics over the typed-builder crate 10 | - [ ] builder structs with `Struct::builder().build()` syntax 11 | 12 | n.b.: Place some restrictions on ordering and naming requirements to potentially lift them in the future in a non-breaking fashion. 13 | 14 | For structs: 15 | ```rust 16 | // enables the builder syntadx 17 | #[derive(optbuilder)] 18 | struct Example {} 19 | 20 | // enables both the builder syntax and macro caller syntax 21 | #[derive(optbuilder, optmacro)] 22 | struct Example {} 23 | ``` 24 | 25 | For functions: 26 | ```rust 27 | #[optfn] 28 | fn example() { } 29 | ``` 30 | -------------------------------------------------------------------------------- /examples/optstruct_reference.rs: -------------------------------------------------------------------------------- 1 | //! poc for struct builder 2 | 3 | struct Example { 4 | a: i32, 5 | b: Option<&'static str>, 6 | } 7 | impl Example { 8 | fn build(mut self) -> bool { 9 | false 10 | } 11 | } 12 | 13 | #[doc(hidden)] 14 | #[macro_export] 15 | macro_rules! Example { 16 | ($($key:ident $(: $value:expr)? ), *) => {{ 17 | let mut inners = (None, None); 18 | { $( Example!(@setter_helper inners $key $key $($value)? ) )* } 19 | Example { 20 | a: inners.0.unwrap(), 21 | b: inners.1 22 | } 23 | }}; 24 | (@setter_helper $src:ident a $key:ident) => { 25 | $src.0 = Some($key); 26 | }; 27 | 28 | (@setter_helper $src:ident a $key:ident $value:expr) => { 29 | $src.0 = Some($value); 30 | }; 31 | 32 | (@setter_helper $src:ident b $key:ident) => { 33 | $src.1 = Some($key); 34 | }; 35 | 36 | (@setter_helper $src:ident b $key:ident $value:expr) => { 37 | $src.1 = Some($value); 38 | }; 39 | } 40 | 41 | fn main() { 42 | let p = Example! { a: 10 }; 43 | } 44 | -------------------------------------------------------------------------------- /examples/optfn_demo.rs: -------------------------------------------------------------------------------- 1 | #[optargs::optfn] 2 | fn go_gme( 3 | price: f32, 4 | to_the_moon: Option, 5 | rocket_ships: Option, 6 | doges: Option, 7 | tendies: Option, 8 | ) { 9 | println!( 10 | " 11 | GME: {} 12 | ---- 13 | Destination: - {} 14 | Velocity: - {} 15 | Passengers: - {} 16 | Menu: - {} 17 | ", 18 | price, 19 | to_the_moon.map(|_| "🌓").unwrap_or(""), 20 | rocket_ships 21 | .map(|f| (0..f).map(|_| "🚀").collect()) 22 | .unwrap_or("".to_string()), 23 | doges 24 | .map(|f| (0..f).map(|_| "🐶").collect()) 25 | .unwrap_or("".to_string()), 26 | tendies.map(|_| "🍗").unwrap_or("") 27 | ); 28 | } 29 | 30 | fn main() { 31 | go_gme!( 32 | price: 10.0, 33 | to_the_moon: true, 34 | rocket_ships: 10, 35 | doges: 7, 36 | tendies: true 37 | ); 38 | 39 | // pass it from the environment 40 | let doges = 8; 41 | go_gme!(price: 10.0, doges, tendies: true); 42 | 43 | // order doesn't matter since they're named! 44 | go_gme!(price: 10.0, tendies: true, doges); 45 | 46 | // this works too! 47 | let price = 10.0; 48 | go_gme!(price); 49 | 50 | // but this doesn't since we need name for the builder 51 | // This might eventually work, but it's currently a limited until positional args are implemented completely 52 | // go_gme!(10.0); 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use optargs_macro; 2 | 3 | /// Optional arguments for functions! 4 | /// Add optfn on top of any function and then you can call the funtion with optional arguments. 5 | /// 6 | /// Note that this still obeys traditional macro_rules, so you can only use the macro *after* declaration or import it from "crate". 7 | /// 8 | /// 9 | /// ```ignore 10 | /// #[optargs::optfn] 11 | /// pub fn plot( 12 | /// x: Vec, 13 | /// y: Option>, 14 | /// title: Option<&str>, 15 | /// xlabel: Option<&str>, 16 | /// ylabel: Option<&str>, 17 | /// legend: Option 18 | /// ) {} 19 | /// 20 | /// // can now call it with optional arguments 21 | /// plot!( 22 | /// x: vec![1,2,3], 23 | /// y: vec![1,2,3], 24 | /// title: "Awesome plot", 25 | /// xlabel: "x axis", 26 | /// ylabel: "y axis" 27 | /// ); 28 | /// ``` 29 | pub use optargs_macro::optfn; 30 | 31 | /// Flexible struct builder with optional arguments 32 | /// Derive OptStruct for your structs and then call the Struct's name as a macro to build it, eliding optionals. 33 | /// 34 | /// Note that this still obeys traditional macro_rules, so you can only use the macro *after* declaration or import it from "crate". 35 | /// 36 | /// ```rust 37 | /// #[derive(optargs::OptStruct)] 38 | /// pub struct Scatter { 39 | /// x: Vec, 40 | /// y: Option>, 41 | /// title: Option<&str>, 42 | /// xlabel: Option<&str>, 43 | /// ylabel: Option<&str>, 44 | /// legend: Option 45 | /// } 46 | /// 47 | /// let plot = Scatter!{ 48 | /// x: vec![1,2,3], 49 | /// legend: true 50 | /// }; 51 | /// ``` 52 | pub use optargs_macro::OptStruct; 53 | -------------------------------------------------------------------------------- /examples/optfn_reference.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how the optfn proc macro expands your function with a macro_rules 2 | //! The goal here is to demistify what gets generated by the macro. 3 | 4 | /// How you would go about using optfn 5 | #[optargs::optfn] 6 | fn example(a: i32, b: Option<&str>) {} 7 | 8 | /// A rough translation of what opftn generates 9 | #[doc(hidden)] 10 | #[macro_export] 11 | macro_rules! example_src { 12 | ($($key:ident $(: $value:expr)? ), *) => { 13 | { 14 | #[allow(unused_mut)] 15 | let mut inners: (Option, Option<&str>) = (None, None); 16 | { $( example_src!(@setter_helper inners $key $key $($value)? ); )* } 17 | 18 | // Validate 19 | struct Validator {} 20 | impl Validator { fn validate(self) {} } 21 | impl Validator { fn a(self) -> Validator { unsafe {std::mem::transmute(self)} } } 22 | impl Validator { fn b(self) -> Validator { self }} 23 | 24 | #[allow(unused_mut)] 25 | let mut validator = Validator:: {}; 26 | validator $(.$key())* .validate(); 27 | example(inners.0.unwrap(), inners.1) 28 | } 29 | }; 30 | 31 | (@setter_helper $src:ident a $key:ident) => { 32 | $src.0 = Some($key); 33 | }; 34 | 35 | (@setter_helper $src:ident a $key:ident $value:expr) => { 36 | $src.0 = Some($value); 37 | }; 38 | 39 | (@setter_helper $src:ident b $key:ident) => { 40 | $src.1 = Some($key); 41 | }; 42 | 43 | (@setter_helper $src:ident b $key:ident $value:expr) => { 44 | $src.1 = Some($value); 45 | }; 46 | } 47 | 48 | fn main() { 49 | example!(a: 10, b: "asd"); 50 | 51 | example_src!(a: 10, b: "asd"); 52 | example_src!(a: 10); 53 | 54 | let a = 10; 55 | example!(a); 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OptArgs: Optional arguments for Rust functions 2 | 3 | Enable optional arguments for any function: 4 | 5 | ```rust 6 | #[optargs::optfn] 7 | pub fn plot( 8 | x: Vec, 9 | y: Option>, 10 | title: Option<&str>, 11 | xlabel: Option<&str>, 12 | ylabel: Option<&str>, 13 | legend: Option 14 | ) {} 15 | 16 | // can now call it with optional arguments 17 | plot!( 18 | x: vec![1,2,3], 19 | y: vec![1,2,3], 20 | title: "Awesome plot", 21 | xlabel: "x axis", 22 | ylabel: "y axis" 23 | ); 24 | ``` 25 | 26 | ...or struct: 27 | 28 | ```rust 29 | #[derive(optargs::OptStruct)] 30 | pub struct Scatter { 31 | x: Vec, 32 | y: Option>, 33 | title: Option<&str>, 34 | xlabel: Option<&str>, 35 | ylabel: Option<&str>, 36 | legend: Option 37 | } 38 | impl Scatter { 39 | fn plot(self) {/* custom impl that references self */} 40 | } 41 | 42 | // Call it 43 | Scatter!{ x: vec![1,2,3], y: vec![1,2,3] }.plot() 44 | ``` 45 | 46 | This crate is especially useful for cleaning up builder-heavy codebases and making library APIs more ergonomic. It also integrates well with Rust-Analyzer and doesn't generate heavy compile times. 47 | 48 | --- 49 | 50 | This crate adds two macros to make it easy to add optional arguments to functions. 51 | - `#[optargs]` - derive a `macro_rules` to call a function with optional arguments. 52 | - `#[derive(OptStruct)]` - derive a typed-builder builder for a struct with optional fields. 53 | 54 | This crate takes advantage of the recent const_generics in Rust stable (1.51), so our MSRV is 1.51. 55 | 56 | Of note: 57 | - All optional arguments will default to none. We currently don't provide a custom default (accepting PRs if anyone wants!). 58 | - All optional arguments must come *after* required arguments. 59 | - Unnamed positional arguments *must* be in the correct position. 60 | - All arguments *can* be required, but now you get to name them. 61 | - Traditional macro_rules scoping applies IE you can't use the macro before function declaration. However, they are exported with macro_export, so you can use them anywhere with `crate::$MACRO`. Currently, there's no way to disable this, so you can't have two functions with the same name. If this becomes a problem, we'll gladly accept a PR. 62 | 63 | ## How it works: 64 | OptArgs uses const generics to ensure compile-time correctness. I've taken the liberty of expanding and humanizing the macros in the reference examples. 65 | 66 | In essence, we encode the state of required parameters into a ZST with const parameters. When each required parameter is added, we flip the const parameter from false to true. Only when all the required parameters are entered, then can we proceed with calling the original function. 67 | 68 | ```rust 69 | struct Validator {} 70 | impl Validator { fn validate(self) {} } 71 | impl Validator { fn b(self) -> Validator { self }} 72 | // Not to worry about the unsafe, Validator is a ZST and has no place in memory. 73 | // All this validation code will be removed anyways. 74 | impl Validator { fn a(self) -> Validator { unsafe {std::mem::transmute(self)} } } 75 | ``` 76 | 77 | 78 | ## License 79 | 80 | Licensed under either of 81 | 82 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 83 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 84 | 85 | at your option. 86 | 87 | ### Contribution 88 | 89 | Unless you explicitly state otherwise, any contribution intentionally submitted 90 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 91 | additional terms or conditions. 92 | -------------------------------------------------------------------------------- /optargs-macro/src/optstruct.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::{quote, ToTokens, TokenStreamExt}; 5 | use syn::parse::{Parse, ParseStream}; 6 | use syn::{DeriveInput, Error, GenericArgument, Ident, Path, PathArguments, Result, Type}; 7 | 8 | type BuilderField = (Ident, Box); 9 | 10 | pub struct OptStruct { 11 | name: Ident, 12 | required_args: Vec, 13 | optional_args: Vec, 14 | } 15 | 16 | impl Parse for OptStruct { 17 | /* 18 | 19 | We care about: 20 | - all the fields 21 | - that all "optional" fields come after required/positional fields 22 | - the original 23 | - fields that are required 24 | - fields that are optional 25 | */ 26 | 27 | fn parse(input: ParseStream) -> Result { 28 | let input: DeriveInput = input.parse()?; 29 | 30 | let data = match &input.data { 31 | syn::Data::Struct(a) => Ok(a), 32 | syn::Data::Enum(_) | syn::Data::Union(_) => Err(syn::Error::new( 33 | input.ident.span(), 34 | "Only structs can be created with the optional pattern.", 35 | )), 36 | }?; 37 | 38 | let name = input.ident.clone(); 39 | 40 | let mut parsing_optionals = false; 41 | let (mut required_args, mut optional_args) = (Vec::new(), Vec::new()); 42 | 43 | for field in &data.fields { 44 | let syn::Field { ident, ty, .. } = field; 45 | 46 | let ident = ident.clone().ok_or(Error::new_spanned( 47 | &name, 48 | "Non-optional values must be placed before optionals", 49 | ))?; 50 | 51 | // todo: allow attrs like #[default] to be placed on fields that don't have an obvious option 52 | let is_optional = match ty { 53 | Type::Path(p) => { 54 | if let Some(arg) = p.path.segments.first() { 55 | arg.ident.to_string() == "Option" 56 | } else { 57 | false 58 | } 59 | } 60 | _ => false, 61 | }; 62 | 63 | match (is_optional, parsing_optionals) { 64 | (true, _) => { 65 | optional_args.push((ident.clone(), extract_type_from_option(ty)?.clone())); 66 | parsing_optionals = true; 67 | } 68 | (false, false) => required_args.push((ident.clone(), Box::new(ty.clone()))), 69 | (false, true) => { 70 | return Err(Error::new_spanned( 71 | &name, 72 | "Non-optional values must be placed before optionals", 73 | )); 74 | } 75 | }; 76 | } 77 | 78 | Ok(Self { 79 | name, 80 | optional_args, 81 | required_args, 82 | }) 83 | } 84 | } 85 | 86 | impl ToTokens for OptStruct { 87 | fn to_tokens(&self, tokens: &mut TokenStream2) { 88 | let OptStruct { 89 | required_args, 90 | optional_args, 91 | name, 92 | .. 93 | } = self; 94 | 95 | let helper_defs = required_args 96 | .iter() 97 | .chain(optional_args.iter()) 98 | .enumerate() 99 | .map(|(id, (arg, _ty))| { 100 | let id = syn::Index::from(id); 101 | quote! { 102 | (@setter_helper $src:ident #arg $key:ident) => { 103 | $src.#id = Some($key); 104 | }; 105 | (@setter_helper $src:ident #arg $key:ident $value:expr) => { 106 | $src.#id = Some($value); 107 | }; 108 | } 109 | }); 110 | 111 | let inners_body = required_args 112 | .iter() 113 | .chain(optional_args.iter()) 114 | .map(|_| quote! {None,}); 115 | 116 | let call_body = required_args 117 | .iter() 118 | .map(|f| (true, f)) 119 | .chain(optional_args.iter().map(|f| (false, f))) 120 | .enumerate() 121 | .map(|(id, (required, (name, _ty)))| { 122 | let id = syn::Index::from(id); 123 | match required { 124 | true => quote! { 125 | #name: inners.#id.unwrap(), 126 | }, 127 | false => quote! { 128 | #name: inners.#id, 129 | }, 130 | } 131 | }); 132 | 133 | let validator = 134 | GenericGenerator::new(required_args.len()).generate(required_args, optional_args); 135 | 136 | ToTokens::to_tokens( 137 | "e! { 138 | 139 | #[doc(hidden)] 140 | #[macro_export] 141 | macro_rules! #name { 142 | ($($key:ident $(: $value:expr)? ), * $(,)?) => { 143 | { 144 | #[allow(unused_mut)] 145 | let mut inners = (#( #inners_body )*); 146 | { $( #name! (@setter_helper inners $key $key $($value)? ); )* } 147 | #validator 148 | 149 | #[allow(unused_mut)] 150 | let mut validator = Validator::builder(); 151 | validator $(.$key())* .build(); 152 | 153 | #name{ 154 | #( #call_body )* 155 | } 156 | } 157 | }; 158 | #( #helper_defs )* 159 | } 160 | 161 | }, 162 | tokens, 163 | ); 164 | } 165 | } 166 | 167 | // https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn 168 | fn extract_type_from_option(ty: &Type) -> Result> { 169 | // todo: allow other option types (probably generated by macro) 170 | fn path_is_option(path: &Path) -> bool { 171 | path.leading_colon.is_none() 172 | && path.segments.len() == 1 173 | && path.segments.iter().next().unwrap().ident == "Option" 174 | } 175 | 176 | match ty { 177 | Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => { 178 | // Get the first segment of the path (there is only one, in fact: "Option"): 179 | 180 | let type_params = &typepath.path.segments.iter().next().unwrap().arguments; 181 | 182 | // It should have only on angle-bracketed param (""): 183 | let generic_arg = match type_params { 184 | PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(), 185 | _ => panic!("TODO: error handling"), 186 | }; 187 | 188 | // This argument must be a type: 189 | match generic_arg { 190 | GenericArgument::Type(ty) => Ok(Box::new(ty.clone())), 191 | _ => panic!("TODO: error handling"), 192 | } 193 | } 194 | _ => panic!("TODO: error handling"), 195 | } 196 | } 197 | 198 | /* 199 | This struct lets us generate the correct const generics form depending on the arguments. 200 | --- 201 | So we can turn this function: 202 | 203 | fn blah(a: u32, b: Option){} 204 | 205 | into 206 | 207 | impl Builder { 208 | ^^^^^^^ -- this gets generated from a method 209 | fn a(self) -> Builder { 210 | ^^^^^^ -- this gets generated from a method 211 | ... 212 | } 213 | } 214 | -- 215 | It's important to keep the original generics, and add any lifetimes for fields that start with &'_os. 216 | To do this, we always generate a borrowed lifetime and let the builder automatically add in the '_os lifetime 217 | */ 218 | struct GenericGenerator { 219 | num_args: usize, 220 | } 221 | 222 | impl GenericGenerator { 223 | fn new(num_args: usize) -> Self { 224 | Self { num_args } 225 | } 226 | 227 | // generate the generics for an all-generic const 228 | // used in the struct position 229 | /* 230 | pub struct ExampleBuilder { 231 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this bit gets generated 232 | a: Option, 233 | b: Option<&'_o str>, 234 | } 235 | */ 236 | fn gen_all_generic(&self, exclude: usize) -> TokenStream2 { 237 | let mut inner = quote! {}; 238 | for id in 0..self.num_args { 239 | let idref = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap(); 240 | if id != exclude { 241 | inner.append_all(quote! { const #idref: bool, }); 242 | } 243 | } 244 | quote! { <#inner> } 245 | } 246 | 247 | // generate all the const generics with the same marker 248 | /* 249 | impl ExampleBuilder { 250 | ^^^^^^^^^^^^^^^^^^^^ generate this part 251 | fn call(self) #ret { 252 | #inner(#callerargs) 253 | } 254 | } 255 | */ 256 | fn gen_all(&self, marker: bool) -> TokenStream2 { 257 | let mut inner = quote! {}; 258 | for _ in 0..self.num_args { 259 | inner.append_all(quote! { #marker, }); 260 | } 261 | quote! { <#inner> } 262 | } 263 | 264 | // generate all as generic, except for a single position with the marker 265 | /* 266 | impl ExampleBuilder { 267 | ^^^^^^^^^^^^^^^ gen this 268 | fn call(self, val: #ty) -> ExampleBuilder { 269 | ... 270 | } 271 | } 272 | */ 273 | fn gen_positional(&self, position: usize, marker: bool) -> TokenStream2 { 274 | // 275 | let mut inner = quote! {}; 276 | for id in 0..self.num_args { 277 | if id == position { 278 | inner.append_all(quote! { #marker, }); 279 | } else { 280 | let mtok = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap(); 281 | inner.append_all(quote! { #mtok, }); 282 | } 283 | } 284 | 285 | quote! { <#inner> } 286 | } 287 | 288 | fn generate( 289 | &self, 290 | required_args: &Vec, 291 | optional_args: &Vec, 292 | ) -> TokenStream2 { 293 | let impl_generics = self.gen_all_generic(usize::MAX); 294 | let ty_gen = self.gen_all(false); 295 | let builder_builder = quote! { 296 | #[derive(Default)] 297 | struct Validator #impl_generics; 298 | impl Validator #ty_gen { 299 | fn builder() -> Validator #ty_gen { Validator::default() } 300 | } 301 | }; 302 | 303 | let mut builders = TokenStream2::new(); 304 | for (id, (name, _ty)) in required_args.iter().enumerate() { 305 | let impl_generics = self.gen_all_generic(id); 306 | let ty_gen_in = self.gen_positional(id, false); 307 | let ty_gen_out = self.gen_positional(id, true); 308 | builders.append_all(quote! { 309 | impl #impl_generics Validator #ty_gen_in { 310 | fn #name(self) -> Validator #ty_gen_out { unsafe {::core::mem::transmute(self)} } 311 | } 312 | }) 313 | } 314 | 315 | let impl_generics = self.gen_all_generic(usize::MAX); 316 | let ty_gen = self.gen_positional(usize::MAX, false); 317 | for (name, _ty) in optional_args { 318 | builders.append_all(quote! { 319 | impl #impl_generics Validator #ty_gen { 320 | #[allow(unused)] 321 | fn #name(self) -> Validator #ty_gen { self } 322 | } 323 | }) 324 | } 325 | 326 | let ty_gen = self.gen_all(true); 327 | let caller = quote! { 328 | impl Validator #ty_gen { 329 | fn build(self) {} 330 | } 331 | }; 332 | 333 | quote! { 334 | #builder_builder 335 | #builders 336 | #caller 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /optargs-macro/src/optfn.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::{quote, ToTokens, TokenStreamExt}; 5 | use syn::parse::{Parse, ParseStream}; 6 | use syn::{Error, FnArg, GenericArgument, Ident, ItemFn, Path, PathArguments, Result, Type}; 7 | 8 | type BuilderField = (Ident, Box); 9 | 10 | pub struct OptFn { 11 | original: ItemFn, 12 | required_args: Vec, 13 | optional_args: Vec, 14 | name: Ident, 15 | } 16 | 17 | impl Parse for OptFn { 18 | /* 19 | 20 | We care about: 21 | - all the fields 22 | - that all "optional" fields come after required/positional fields 23 | - the original 24 | - fields that are required 25 | - fields that are optional 26 | */ 27 | 28 | fn parse(input: ParseStream) -> Result { 29 | let orig: ItemFn = input.parse()?; 30 | 31 | // start by parsing positionals 32 | // optionals must come after positionals 33 | let mut parsing_optionals = false; 34 | let (mut required_args, mut optional_args) = (Vec::new(), Vec::new()); 35 | 36 | for arg in orig.sig.inputs.clone() { 37 | match arg { 38 | FnArg::Typed(arg) => Ok(arg), 39 | FnArg::Receiver(r) => Err(Error::new_spanned(r, "optfn cannot be used on methods")), 40 | } 41 | .and_then(|f| match (&f).pat.as_ref() { 42 | syn::Pat::Ident(iden) => Ok((iden.clone(), f)), 43 | other => Err(Error::new_spanned(other, "optfn cannot struct fields")), 44 | }) 45 | .map(|(name, pat)| { 46 | let is_optional = match pat.ty.as_ref() { 47 | Type::Path(p) => { 48 | if let Some(arg) = p.path.segments.first() { 49 | arg.ident.to_string() == "Option" 50 | } else { 51 | false 52 | } 53 | } 54 | _ => false, 55 | }; 56 | (name, pat, is_optional) 57 | }) 58 | .and_then(|(name, pat, is_optional)| { 59 | match (is_optional, parsing_optionals) { 60 | (false, false) => { 61 | required_args.push((name.ident, pat.ty)); 62 | Ok(()) 63 | } 64 | (false, true) => Err(Error::new_spanned( 65 | name, 66 | "Non-optional values must be placed before optionals", 67 | )), 68 | (true, _) => { 69 | optional_args.push((name.ident, extract_type_from_option(pat.ty)?)); 70 | parsing_optionals = true; 71 | Ok(()) 72 | } 73 | } 74 | })?; 75 | } 76 | 77 | Ok(Self { 78 | name: orig.sig.ident.clone(), 79 | original: orig, 80 | required_args, 81 | optional_args, 82 | }) 83 | } 84 | } 85 | 86 | impl ToTokens for OptFn { 87 | fn to_tokens(&self, tokens: &mut TokenStream2) { 88 | let OptFn { 89 | original, 90 | required_args, 91 | optional_args, 92 | name, 93 | .. 94 | } = self; 95 | 96 | let helper_defs = required_args 97 | .iter() 98 | .chain(optional_args.iter()) 99 | .enumerate() 100 | .map(|(id, (arg, _ty))| { 101 | let id = syn::Index::from(id); 102 | quote! { 103 | (@setter_helper $src:ident #arg $key:ident) => { 104 | $src.#id = ::core::option::Option::Some($key); 105 | }; 106 | (@setter_helper $src:ident #arg $key:ident $value:expr) => { 107 | $src.#id = ::core::option::Option::Some($value); 108 | }; 109 | } 110 | }); 111 | 112 | let inners_body = required_args 113 | .iter() 114 | .chain(optional_args.iter()) 115 | .map(|_| quote! {::core::option::Option::None,}); 116 | 117 | let call_body = required_args 118 | .iter() 119 | .map(|f| (true, f)) 120 | .chain(optional_args.iter().map(|f| (false, f))) 121 | .enumerate() 122 | .map(|(id, (required, (_, ty)))| { 123 | let id = syn::Index::from(id); 124 | match required { 125 | true => quote! {inners.#id.unwrap() as #ty,}, 126 | false => quote! { inners.#id, }, 127 | } 128 | }); 129 | 130 | let validator = 131 | GenericGenerator::new(required_args.len()).generate(required_args, optional_args); 132 | 133 | let ty_expanse = required_args 134 | .iter() 135 | .chain(optional_args.iter()) 136 | .map(|(_, ty)| quote! { ::core::option::Option<#ty>, }); 137 | 138 | ToTokens::to_tokens( 139 | "e! { 140 | #original 141 | 142 | #[doc(hidden)] 143 | #[macro_export] 144 | macro_rules! #name { 145 | ($($key:ident $(: $value:expr)? ), * $(,)?) => { 146 | { 147 | #[allow(unused_mut)] 148 | let mut inners: (#( #ty_expanse)*) = (#( #inners_body )*); 149 | { $( #name! (@setter_helper inners $key $key $($value)? ); )* } 150 | #validator 151 | 152 | #[allow(unused_mut)] 153 | let mut validator = Validator::builder(); 154 | validator $(.$key())* .build(); 155 | #name(#( #call_body )*) 156 | } 157 | }; 158 | #( #helper_defs )* 159 | } 160 | 161 | }, 162 | tokens, 163 | ); 164 | } 165 | } 166 | 167 | // https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn 168 | fn extract_type_from_option(ty: Box) -> Result> { 169 | // todo: allow other option types (probably generated by macro) 170 | fn path_is_option(path: &Path) -> bool { 171 | path.leading_colon.is_none() 172 | && path.segments.len() == 1 173 | && path.segments.iter().next().unwrap().ident == "Option" 174 | } 175 | 176 | match ty.as_ref() { 177 | Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => { 178 | // Get the first segment of the path (there is only one, in fact: "Option"): 179 | 180 | let type_params = &typepath.path.segments.iter().next().unwrap().arguments; 181 | 182 | // It should have only on angle-bracketed param (""): 183 | let generic_arg = match type_params { 184 | PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(), 185 | _ => panic!("TODO: error handling"), 186 | }; 187 | 188 | // This argument must be a type: 189 | match generic_arg { 190 | GenericArgument::Type(ty) => Ok(Box::new(ty.clone())), 191 | _ => panic!("TODO: error handling"), 192 | } 193 | } 194 | _ => panic!("TODO: error handling"), 195 | } 196 | } 197 | 198 | /* 199 | This struct lets us generate the correct const generics form depending on the arguments. 200 | --- 201 | So we can turn this function: 202 | 203 | fn blah(a: u32, b: Option){} 204 | 205 | into 206 | 207 | impl Builder { 208 | ^^^^^^^ -- this gets generated from a method 209 | fn a(self) -> Builder { 210 | ^^^^^^ -- this gets generated from a method 211 | ... 212 | } 213 | } 214 | -- 215 | It's important to keep the original generics, and add any lifetimes for fields that start with &'_os. 216 | To do this, we always generate a borrowed lifetime and let the builder automatically add in the '_os lifetime 217 | */ 218 | struct GenericGenerator { 219 | num_args: usize, 220 | } 221 | 222 | impl GenericGenerator { 223 | fn new(num_args: usize) -> Self { 224 | Self { num_args } 225 | } 226 | 227 | // generate the generics for an all-generic const 228 | // used in the struct position 229 | /* 230 | pub struct ExampleBuilder { 231 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this bit gets generated 232 | a: Option, 233 | b: Option<&'_o str>, 234 | } 235 | */ 236 | fn gen_all_generic(&self, exclude: usize) -> TokenStream2 { 237 | let mut inner = quote! {}; 238 | for id in 0..self.num_args { 239 | let idref = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap(); 240 | if id != exclude { 241 | inner.append_all(quote! { const #idref: bool, }); 242 | } 243 | } 244 | quote! { <#inner> } 245 | } 246 | 247 | // generate all the const generics with the same marker 248 | /* 249 | impl ExampleBuilder { 250 | ^^^^^^^^^^^^^^^^^^^^ generate this part 251 | fn call(self) #ret { 252 | #inner(#callerargs) 253 | } 254 | } 255 | */ 256 | fn gen_all(&self, marker: bool) -> TokenStream2 { 257 | let mut inner = quote! {}; 258 | for _ in 0..self.num_args { 259 | inner.append_all(quote! { #marker, }); 260 | } 261 | quote! { <#inner> } 262 | } 263 | 264 | // generate all as generic, except for a single position with the marker 265 | /* 266 | impl ExampleBuilder { 267 | ^^^^^^^^^^^^^^^ gen this 268 | fn call(self, val: #ty) -> ExampleBuilder { 269 | ... 270 | } 271 | } 272 | */ 273 | fn gen_positional(&self, position: usize, marker: bool) -> TokenStream2 { 274 | // 275 | let mut inner = quote! {}; 276 | for id in 0..self.num_args { 277 | if id == position { 278 | inner.append_all(quote! { #marker, }); 279 | } else { 280 | let mtok = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap(); 281 | inner.append_all(quote! { #mtok, }); 282 | } 283 | } 284 | 285 | quote! { <#inner> } 286 | } 287 | 288 | fn generate( 289 | &self, 290 | required_args: &Vec, 291 | optional_args: &Vec, 292 | ) -> TokenStream2 { 293 | let impl_generics = self.gen_all_generic(usize::MAX); 294 | let ty_gen = self.gen_all(false); 295 | let builder_builder = quote! { 296 | #[derive(Default)] 297 | struct Validator #impl_generics; 298 | impl Validator #ty_gen { 299 | fn builder() -> Validator #ty_gen { Validator::default() } 300 | } 301 | }; 302 | 303 | let mut builders = TokenStream2::new(); 304 | for (id, (name, _ty)) in required_args.iter().enumerate() { 305 | let impl_generics = self.gen_all_generic(id); 306 | let ty_gen_in = self.gen_positional(id, false); 307 | let ty_gen_out = self.gen_positional(id, true); 308 | builders.append_all(quote! { 309 | impl #impl_generics Validator #ty_gen_in { 310 | fn #name(self) -> Validator #ty_gen_out { unsafe {::core::mem::transmute(self)} } 311 | } 312 | }) 313 | } 314 | 315 | let impl_generics = self.gen_all_generic(usize::MAX); 316 | let ty_gen = self.gen_positional(usize::MAX, false); 317 | for (name, _ty) in optional_args { 318 | builders.append_all(quote! { 319 | impl #impl_generics Validator #ty_gen { 320 | #[allow(unused)] 321 | fn #name(self) -> Validator #ty_gen { self } 322 | } 323 | }) 324 | } 325 | 326 | let ty_gen = self.gen_all(true); 327 | let caller = quote! { 328 | impl Validator #ty_gen { 329 | fn build(self) {} 330 | } 331 | }; 332 | 333 | quote! { 334 | #builder_builder 335 | #builders 336 | #caller 337 | } 338 | } 339 | } 340 | --------------------------------------------------------------------------------