convert_params/
lib.rs

1//! A macro to replace function parameters types and convert them to the original type using
2//! TryFrom.
3//!
4//! The original motivation for this crate was to use it with wasm-bindgen, so a function can .
5//!
6//! # Example
7//!
8//! ```
9//! #[macro_use]
10//! extern crate convert_params;
11//!
12//! struct Orig {}
13//!
14//! #[derive(Debug)]
15//! struct Foo {}
16//!
17//! impl TryFrom<Orig> for Foo {
18//!     type Error = &'static str;
19//!     fn try_from(_v: Orig) -> Result<Self, Self::Error> {
20//!         Ok(Foo {})
21//!     }
22//! }
23//!
24//! #[derive(Debug, PartialEq)]
25//! struct Error {
26//!     s: &'static str,
27//! }
28//!
29//! impl From<&'static str> for Error {
30//!     fn from(s: &'static str) -> Self {
31//!         Error { s }
32//!     }
33//! }
34//!
35//! #[convert_args(_value1: Orig, _value2: Orig)]
36//! fn example(i: u32, _value1: Foo, _value2: Foo) -> Result<(), Error> {
37//!     Ok(())
38//! }
39//!
40//! fn main() {
41//!     assert!(example(42, Orig {}, Orig {}).is_ok());
42//! }
43//! ```
44//!
45//! `example` to:
46//!
47//! ```ignore
48//! fn example(i: u32, _value1: Orig, _value2: Orig) -> Result<(), Error> {
49//!     let _value2 = {
50//!         let _value2: Orig = _value2.into();
51//!         <Foo as std::convert::TryFrom<_>>::try_from(_value2)?
52//!     };
53//!     let _value1 = {
54//!         let _value1: Orig = _value1.into();
55//!         <Foo as std::convert::TryFrom<_>>::try_from(_value1)?
56//!     };
57//!     Ok(())
58//! }
59//! ```
60
61use proc_macro::TokenStream;
62use quote::quote;
63use syn::{
64    parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated, FnArg, Ident,
65    ItemFn, Pat, Result as SynResult, Token, Type,
66};
67
68struct ConvertArgsItem {
69    arg_name: Ident,
70    new_type: Type,
71}
72
73struct ConvertArgs {
74    args: Punctuated<ConvertArgsItem, Token![,]>,
75}
76
77impl Parse for ConvertArgsItem {
78    fn parse(input: ParseStream) -> SynResult<Self> {
79        let arg_name = input.parse()?;
80        input.parse::<Token![:]>()?;
81        let new_type = input.parse()?;
82        Ok(ConvertArgsItem { arg_name, new_type })
83    }
84}
85
86impl Parse for ConvertArgs {
87    fn parse(input: ParseStream) -> SynResult<Self> {
88        let args = Punctuated::<ConvertArgsItem, Token![,]>::parse_terminated(input)?;
89        Ok(ConvertArgs {
90            args: args.into_iter().collect(),
91        })
92    }
93}
94
95#[proc_macro_attribute]
96pub fn convert_args(attr: TokenStream, item: TokenStream) -> TokenStream {
97    let ConvertArgs { args } = parse_macro_input!(attr);
98
99    let mut func = parse_macro_input!(item as ItemFn);
100    let inputs = &mut func.sig.inputs;
101    let block = &mut func.block;
102
103    for ConvertArgsItem { arg_name, new_type } in args.into_iter().rev() {
104        if let Some(FnArg::Typed(pat_type)) = inputs.iter_mut().find(|arg| {
105            if let FnArg::Typed(pat_type) = arg {
106                if let Pat::Ident(pat_ident) = &*pat_type.pat {
107                    return pat_ident.ident == arg_name;
108                }
109            }
110            false
111        }) {
112            let original_type = pat_type.ty.clone();
113            pat_type.ty = new_type.clone().into();
114
115            let stmt: syn::Stmt = syn::parse2(quote! {
116                let #arg_name = {
117                    let #arg_name: #new_type = #arg_name.into();
118                    <#original_type as std::convert::TryFrom<_>>::try_from(#arg_name)?
119                };
120            })
121            .unwrap();
122
123            block.stmts.insert(0, stmt);
124        } else {
125            panic!("No argument named {}", arg_name);
126        }
127    }
128
129    TokenStream::from(quote!(#func))
130}