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}