default_args/lib.rs
1//! Enables default arguments in rust by macro in zero cost.
2//!
3//! Just wrap function with `default_args!` and macro with name of function
4//! would be automatically generated to be used with default argument.
5//!
6//! See below for usage
7//!
8//! ```
9//! # extern crate default_args;
10//! use default_args::default_args;
11//!
12//! // this would make a macro named `foo`
13//! // and original function named `foo_`
14//! default_args! {
15//! fn foo(important_arg: u32, optional: u32 = 100) -> String {
16//! format!("{}, {}", important_arg, optional)
17//! }
18//! }
19//!
20//! // in other codes ...
21//! assert_eq!(foo!(1), "1, 100"); // foo(1, 100)
22//! assert_eq!(foo!(1, 3), "1, 3"); // foo(1, 3)
23//! assert_eq!(foo!(1, optional = 10), "1, 10"); // foo(1, 10)
24//!
25//! // let's make another one
26//! default_args! {
27//! #[inline]
28//! pub async unsafe extern "C" fn bar<S1, S2, S3>(a: S1, b: S2 = "b", c: S3 = "c") -> String
29//! where
30//! S1: AsRef<str>,
31//! S2: AsRef<str>,
32//! S3: AsRef<str>,
33//! {
34//! format!("{}, {}, {}", a.as_ref(), b.as_ref(), c.as_ref())
35//! }
36//! // that was long signature!
37//! }
38//!
39//! # tokio_test::block_on(async {
40//! // in other codes ...
41//! assert_eq!(unsafe { bar!("a") }.await, "a, b, c");
42//! assert_eq!(unsafe { bar!("a", "d") }.await, "a, d, c");
43//! // you can even mix named & unnamed argument in optional arguments
44//! assert_eq!(unsafe { bar!("a", "d", c = "e") }.await, "a, d, e");
45//! assert_eq!(unsafe { bar!("a", c = "e") }.await, "a, b, e");
46//! # });
47//! ```
48//!
49//! # More Features
50//!
51//! ## Export
52//!
53//! Add export in the front of the function and the macro would be exported.
54//! *(add pub to export function with macro)*
55//!
56//! ```
57//! # extern crate default_args;
58//! # use default_args::default_args;
59//! #
60//! default_args! {
61//! export pub fn foo() {}
62//! }
63//! ```
64//!
65//! Above macro will expand as below
66//!
67//! ```
68//! pub fn foo_() {}
69//!
70//! #[macro_export]
71//! macro_rules! foo { () => {}; }
72//! ```
73//!
74//! ## Path of function
75//!
76//! Macro just call the function in name, so you should import both macro and the function to use it.
77//! By writing the path of this function, you can just only import the macro.
78//! *(path should start with `crate`)*
79//!
80//! ```ignore
81//! # extern crate default_args;
82//! #
83//! #[macro_use]
84//! pub mod foo {
85//! # use default_args::default_args;
86//! default_args! {
87//! pub fn crate::foo::bar() {}
88//! }
89//! }
90//!
91//! // then it would create `bar!()`
92//! bar!();
93//! ```
94//!
95//! Above macro would expand as below
96//!
97//! ```
98//! pub mod foo {
99//! pub fn bar_() {}
100//!
101//! macro_rules! bar_ {
102//! () => {
103//! $crate::foo::bar_()
104//! };
105//! }
106//! }
107//! ```
108//!
109//! ## *Why do we have to write module?*
110//!
111//! > `std::module_path!` can resolve the module path of the function where it is declared.
112//! > However, it can be resolved in runtime, not compile-time.
113//! > I couldn't find a way to get module path in compile-time.
114
115use proc_macro::TokenStream;
116use proc_macro2::Ident;
117use quote::{format_ident, quote, ToTokens, TokenStreamExt};
118use syn::parse::{Parse, ParseStream};
119use syn::punctuated::Punctuated;
120use syn::spanned::Spanned;
121use syn::{
122 parenthesized, parse_macro_input, token, Abi, Attribute, Block, Expr, FnArg, Generics, PatType,
123 ReturnType, Token, Visibility,
124};
125
126/// Structure for arguments
127///
128/// This contains arguments of function and default values like: `a: u32, b: u32 = 0`
129struct Args {
130 parsed: Punctuated<PatType, Token![,]>,
131 required: usize,
132 optional: Vec<(PatType, Expr)>,
133}
134
135impl Parse for Args {
136 /// Parse function for `Args`
137 ///
138 /// ## Errors
139 ///
140 /// - when self is the argument of the function: `self in default_args! is not support in this version`
141 /// - when required argument came after any optional argument: `required argument cannot come after optional argument`
142 fn parse(input: ParseStream) -> syn::Result<Self> {
143 let mut args = Punctuated::new();
144 let mut has_optional = false;
145 let mut required = 0;
146 let mut optional = Vec::new();
147
148 while !input.is_empty() {
149 let fn_arg = input.parse::<FnArg>()?;
150
151 let pat = match fn_arg {
152 FnArg::Receiver(r) => {
153 return Err(syn::Error::new(
154 r.span(),
155 "self in default_args! is not support in this version",
156 ));
157 }
158 FnArg::Typed(pat) => pat,
159 };
160
161 if input.parse::<Option<Token![=]>>()?.is_some() {
162 has_optional = true;
163 optional.push((pat.clone(), input.parse()?));
164 } else if has_optional {
165 return Err(syn::Error::new(
166 pat.span(),
167 "required argument cannot come after optional argument",
168 ));
169 } else {
170 required += 1;
171 }
172
173 args.push_value(pat);
174
175 if input.is_empty() {
176 break;
177 }
178
179 args.push_punct(input.parse()?);
180 }
181
182 Ok(Args {
183 parsed: args,
184 required,
185 optional,
186 })
187 }
188}
189
190impl ToTokens for Args {
191 /// This function changes to normal signature of function which is `self.parsed`
192 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
193 self.parsed.to_tokens(tokens)
194 }
195}
196
197/// Module for export keyword
198///
199/// export keyword would make macro export (by adding `#[macro_export]`
200mod export {
201 use syn::custom_keyword;
202
203 custom_keyword!(export);
204}
205
206/// Structure for Default Argument function
207///
208/// This contains the signature of function like
209/// `#[hello] export pub const async unsafe extern "C" fn crate::foo::bar<T>(a: T, b: u32 = 0) -> String where T: Display { format!("{}, {}", a, b) }`
210struct DefaultArgs {
211 attrs: Vec<Attribute>,
212 export: Option<export::export>,
213 vis: Visibility,
214 constness: Option<Token![const]>,
215 asyncness: Option<Token![async]>,
216 unsafety: Option<Token![unsafe]>,
217 abi: Option<Abi>,
218 fn_token: Token![fn],
219 crate_path: Option<(Token![crate], Token![::])>,
220 fn_path: Punctuated<Ident, Token![::]>,
221 fn_name: Ident,
222 generics: Generics,
223 paren_token: token::Paren,
224 args: Args,
225 ret: ReturnType,
226 body: Block,
227}
228
229impl Parse for DefaultArgs {
230 /// Parse function for `DefaultArgs`
231 ///
232 /// ## Errors
233 ///
234 /// - when path don't start with `crate`: `path should start with crate`
235 fn parse(input: ParseStream) -> syn::Result<Self> {
236 let attrs = input.call(Attribute::parse_outer)?;
237 let export = input.parse()?;
238 let vis = input.parse()?;
239 let constness = input.parse()?;
240 let asyncness = input.parse()?;
241 let unsafety = input.parse()?;
242 let abi = input.parse()?;
243 let fn_token = input.parse()?;
244
245 let mut fn_path: Punctuated<Ident, Token![::]> = Punctuated::new();
246 let crate_token = input.parse::<Option<Token![crate]>>()?;
247 let crate_path = if let Some(token) = crate_token {
248 let crate_colon_token = input.parse::<Token![::]>()?;
249 Some((token, crate_colon_token))
250 } else {
251 None
252 };
253
254 loop {
255 fn_path.push_value(input.parse()?);
256 if input.peek(Token![::]) {
257 fn_path.push_punct(input.parse()?);
258 } else {
259 break;
260 }
261 }
262
263 if crate_path.is_none() && fn_path.len() > 1 {
264 return Err(syn::Error::new(
265 fn_path.first().unwrap().span(),
266 "path should start with crate",
267 ));
268 }
269 let fn_name = fn_path.pop().unwrap().into_value();
270
271 let mut generics: Generics = input.parse()?;
272 let content;
273 let paren_token = parenthesized!(content in input);
274 let args = content.parse()?;
275 let ret = input.parse()?;
276 generics.where_clause = input.parse()?;
277 let body = input.parse()?;
278
279 Ok(DefaultArgs {
280 attrs,
281 export,
282 vis,
283 constness,
284 asyncness,
285 unsafety,
286 abi,
287 fn_token,
288 crate_path,
289 fn_path,
290 fn_name,
291 generics,
292 paren_token,
293 args,
294 ret,
295 body,
296 })
297 }
298}
299
300impl ToTokens for DefaultArgs {
301 /// This function changes to normal signature of function
302 /// It would not print `export` and change the name with under bar attached
303 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
304 for i in &self.attrs {
305 i.to_tokens(tokens);
306 }
307 self.vis.to_tokens(tokens);
308 self.constness.to_tokens(tokens);
309 self.asyncness.to_tokens(tokens);
310 self.unsafety.to_tokens(tokens);
311 self.abi.to_tokens(tokens);
312 self.fn_token.to_tokens(tokens);
313 format_ident!("{}_", &self.fn_name).to_tokens(tokens);
314 self.generics.lt_token.to_tokens(tokens);
315 self.generics.params.to_tokens(tokens);
316 self.generics.gt_token.to_tokens(tokens);
317 self.paren_token.surround(tokens, |tokens| {
318 self.args.to_tokens(tokens);
319 });
320 self.ret.to_tokens(tokens);
321 self.generics.where_clause.to_tokens(tokens);
322 self.body.to_tokens(tokens);
323 }
324}
325
326/// Make unnamed arguments in macro
327/// - `count`: how many arguments
328/// - `def`: if it would be used in macro definition (will add `expr`)
329fn unnamed_args(count: usize, def: bool) -> proc_macro2::TokenStream {
330 (0..count)
331 .map(|i| {
332 let item = format_ident!("u{}", i);
333 if def {
334 if i == 0 {
335 quote! { $#item:expr }
336 } else {
337 quote! { , $#item:expr }
338 }
339 } else if i == 0 {
340 quote! { $#item }
341 } else {
342 quote! { , $#item }
343 }
344 })
345 .collect()
346}
347
348/// Make named arguments in definition of macro
349/// - `front_comma`: if it needs a front comma
350/// - `input`: default args
351/// - `macro_index`: mapped index of argument in function from macro
352fn named_args_def(
353 front_comma: bool,
354 input: &DefaultArgs,
355 macro_index: &[usize],
356) -> proc_macro2::TokenStream {
357 macro_index
358 .iter()
359 .enumerate()
360 .map(|(j, i)| {
361 let item = format_ident!("n{}", i);
362 let pat = &input.args.optional[*i].0.pat;
363 if !front_comma && j == 0 {
364 quote! { #pat = $#item:expr }
365 } else {
366 quote! { , #pat = $#item:expr }
367 }
368 })
369 .collect()
370}
371
372/// Make names arguments in macro
373/// - `front_comma`: if it needs a front comma
374/// - `input`: default args
375/// - `offset`: offset of named argument
376/// - `func_index`: whether if the function argument is provided
377fn named_args(
378 front_comma: bool,
379 input: &DefaultArgs,
380 offset: usize,
381 func_index: &[bool],
382) -> proc_macro2::TokenStream {
383 func_index
384 .iter()
385 .enumerate()
386 .map(|(i, provided)| {
387 let inner = if *provided {
388 let item = format_ident!("n{}", i + offset);
389 quote! { $#item }
390 } else {
391 let item = &input.args.optional[i + offset].1;
392 quote! { ( #item ) }
393 };
394
395 if !front_comma && i == 0 {
396 quote! { #inner }
397 } else {
398 quote! { , #inner }
399 }
400 })
401 .collect()
402}
403
404/// Generate one arm of macro
405/// - `input`: default args
406/// - `unnamed_cnt`: unnamed argument count
407/// - `offset`: offset of named argument
408/// - `macro_index`: mapped index of argument in function from macro
409/// - `func_index`: whether if the function argument is provided
410fn generate(
411 input: &DefaultArgs,
412 unnamed_cnt: usize,
413 offset: usize,
414 macro_index: &[usize],
415 func_index: &[bool],
416) -> proc_macro2::TokenStream {
417 let fn_name = format_ident!("{}_", input.fn_name);
418
419 let unnamed_def = unnamed_args(unnamed_cnt, true);
420 let unnamed = unnamed_args(unnamed_cnt, false);
421
422 let named_def = named_args_def(unnamed_cnt != 0, input, macro_index);
423 let named = named_args(unnamed_cnt != 0, input, offset, func_index);
424
425 if input.crate_path.is_some() {
426 let fn_path = &input.fn_path;
427 quote! {
428 (#unnamed_def#named_def) => {
429 $crate::#fn_path#fn_name(#unnamed#named)
430 };
431 }
432 } else {
433 quote! {
434 (#unnamed_def#named_def) => {
435 #fn_name(#unnamed#named)
436 };
437 }
438 }
439}
440
441/// Generate macro arms recursively
442/// - `input`: default args
443/// - `unnamed_cnt`: unnamed argument count
444/// - `offset`: offset of named argument
445/// - `macro_index`: mapped index of argument in function from macro
446/// - `func_index`: whether if the function argument is provided
447/// - `stream`: token stream to append faster
448fn generate_recursive(
449 input: &DefaultArgs,
450 unnamed_cnt: usize,
451 offset: usize,
452 macro_index: &mut Vec<usize>,
453 func_index: &mut Vec<bool>,
454 stream: &mut proc_macro2::TokenStream,
455) {
456 stream.append_all(generate(
457 input,
458 unnamed_cnt,
459 offset,
460 macro_index,
461 func_index,
462 ));
463
464 for i in 0..func_index.len() {
465 if func_index[i] {
466 continue;
467 }
468
469 func_index[i] = true;
470 macro_index.push(i + offset);
471 generate_recursive(input, unnamed_cnt, offset, macro_index, func_index, stream);
472 macro_index.pop();
473 func_index[i] = false;
474 }
475}
476
477/// Generates all macro arms
478/// - `input`: default args
479fn generate_macro(input: &DefaultArgs) -> proc_macro2::TokenStream {
480 let mut stream = proc_macro2::TokenStream::new();
481
482 for i in 0..=input.args.optional.len() {
483 let mut macro_index = Vec::new();
484 let mut func_index = vec![false; input.args.optional.len() - i];
485 generate_recursive(
486 input,
487 input.args.required + i,
488 i,
489 &mut macro_index,
490 &mut func_index,
491 &mut stream,
492 );
493 }
494
495 stream
496}
497
498/// The main macro of this crate
499///
500/// This would generate the original function and the macro
501#[proc_macro]
502pub fn default_args(input: TokenStream) -> TokenStream {
503 let input = parse_macro_input!(input as DefaultArgs);
504
505 let name = &input.fn_name;
506 let export = if input.export.is_some() {
507 quote! { #[macro_export] }
508 } else {
509 quote! {}
510 };
511
512 let inner = generate_macro(&input);
513
514 let output = quote! {
515 #input
516
517 #export
518 macro_rules! #name {
519 #inner
520 }
521 };
522 output.into()
523}
524
525/// This is a test for compile failure
526/// This will check the error cases
527#[allow(dead_code)]
528mod compile_fail_test {
529 /// using `self` in argument is compile error for now
530 ///
531 /// error: `self in default_args! is not supported in this version`
532 ///
533 /// ```compile_fail
534 /// # extern crate default_args;
535 /// use default_args::default_args;
536 ///
537 /// struct A {}
538 ///
539 /// impl A {
540 /// default_args! {
541 /// fn foo(&self, a: usize, b: usize = 0) -> usize {
542 /// a + b
543 /// }
544 /// }
545 /// }
546 /// ```
547 fn using_self() {}
548
549 /// having required argument after optional argument is an error
550 ///
551 /// error: `required argument cannot come after optional argument`
552 ///
553 /// ```compile_fail
554 /// # extern crate default_args;
555 /// use default_args::default_args;
556 ///
557 /// default_args! {
558 /// fn foo(a: usize = 0, b: usize) -> usize {
559 /// a + b
560 /// }
561 /// }
562 /// ```
563 fn required_after_optional() {}
564
565 /// if path is used in function name, it should start with crate
566 ///
567 /// error: `path should start with crate`
568 ///
569 /// ```compile_fail
570 /// # extern crate default_args;
571 /// mod foo {
572 /// use default_args::default_args;
573 ///
574 /// default_args! {
575 /// fn foo::bar() {}
576 /// }
577 /// }
578 /// ```
579 fn path_not_starting_with_crate() {}
580}