partial_bind/
lib.rs

1/// A proc-macro to bind some arguments of a function to a closure.
2/// Which is known as partial function application.
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5use quote::quote;
6use syn::{ExprCall, ExprPath, Path, PathSegment};
7
8/// Binds some arguments of a function to a closure.
9/// The arguments to be bound are denoted by `_` (underscore) in the function signature.
10///
11/// # Example:
12/// ```rust
13/// fn foo(a: i32, b: i32, c: i32, d: i32) { a + b + c + d }
14/// let bar = bind(foo(1, _, 3, _));
15/// assert_eq!(bar(2, 4), foo(1, 2, 3, 4));
16///
17/// let baz = bind(foo(_, 2, _, 4));
18/// assert_eq!(baz(1, 3), foo(1, 2, 3, 4));
19/// ```
20#[proc_macro]
21pub fn bind(input: TokenStream) -> TokenStream {
22    let input: proc_macro2::TokenStream = input.into();
23    let mut call: ExprCall = syn::parse2(input).unwrap();
24
25    // Token![_] => "_n"
26    // Expr => Expr,
27    let placeholders = call
28        .args
29        .pairs_mut()
30        .filter_map(|pair| {
31            let value = pair.into_value();
32            matches!(value, syn::Expr::Infer(_)).then_some(value)
33        })
34        .enumerate()
35        .map(|(i, underscore)| {
36            // Ident => Expr
37            let ident = format!("__{}", i);
38            let ident = Ident::new(&ident, Span::call_site());
39            let segment = PathSegment::from(ident);
40            let path = Path::from(segment);
41            let expr_path = ExprPath {
42                attrs: Vec::new(),
43                qself: None,
44                path,
45            };
46            *underscore = expr_path.clone().into();
47            expr_path
48        })
49        .collect::<Vec<_>>();
50
51    quote! {
52        |#(#placeholders),*| { #call }
53    }
54    .into()
55}