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}