1use std::fmt::Display;
2use std::str::FromStr;
3
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::punctuated::Punctuated;
7use syn::spanned::Spanned;
8use syn::{parse_macro_input, Expr, Item, Meta, Token};
9
10#[allow(clippy::test_attr_in_doctest)]
11#[proc_macro_attribute]
45pub fn fickle(attr: TokenStream, item: TokenStream) -> TokenStream {
46 let mut n_retries: Option<usize> = None;
48 let mut n_passes: Option<usize> = None;
49 let attrinp = parse_macro_input!(attr with Punctuated<Meta, Token![,]>::parse_terminated);
50 let attrspan = attrinp.span();
51 for a in attrinp {
52 match a {
53 Meta::NameValue(nv) => {
54 if nv.path.is_ident("retries") {
55 match get_int(nv.value) {
56 Ok(r) => n_retries = Some(r),
57 Err(err) => return err.into_compile_error().into(),
58 }
59 } else if nv.path.is_ident("passes") {
60 match get_int(nv.value) {
61 Ok(r) => n_passes = Some(r),
62 Err(err) => return err.into_compile_error().into(),
63 }
64 } else {
65 return syn::Error::new(nv.path.span(), "Unrecognized argument")
66 .into_compile_error()
67 .into();
68 }
69 }
70 _ => {
71 return syn::Error::new(a.span(), "Not a name value (`name = value`)")
72 .into_compile_error()
73 .into();
74 }
75 }
76 }
77
78 let fickle_obj = match (n_retries, n_passes) {
79 (Some(retries), Some(passes)) => {
80 if passes > retries + 1 {
81 return syn::Error::new(
82 attrspan,
83 format!(
84 "Requested a minimum of {} passes with a maximum of {} runs",
85 passes,
86 retries + 1
87 ),
88 )
89 .into_compile_error()
90 .into();
91 }
92 quote! {
93 let fickle = fickle::Fickle::new(#retries, #passes).unwrap();
94 }
95 }
96 (Some(retries), None) => quote! {
97 let fickle = fickle::Fickle::new_retries(#retries).unwrap();
98 },
99 (None, Some(passes)) => {
100 if passes > 2 {
101 return syn::Error::new(
102 attrspan,
103 format!(
104 "Requested a minimum of {} passes with a maximum of {} runs",
105 passes, 2
106 ),
107 )
108 .into_compile_error()
109 .into();
110 }
111 quote! {
112 let fickle = fickle::Fickle::new_passes(#passes).unwrap();
113 }
114 }
115 (None, None) => quote! {
116 let fickle = fickle::Fickle::default();
117 },
118 };
119
120 let iteminput = parse_macro_input!(item as Item);
122 let testfn = match iteminput {
123 Item::Fn(func) => func,
124 _ => {
125 return syn::Error::new(
126 iteminput.span(),
127 "Expected #[fickle] to annotate a function",
128 )
129 .into_compile_error()
130 .into();
131 }
132 };
133 let attrs = testfn.attrs;
134 let vis = testfn.vis;
135 let sig = testfn.sig;
136 let stmts = testfn.block.stmts;
137
138 let func_body = match &sig.output {
139 syn::ReturnType::Default => {
140 quote! {
142 {
143 #fickle_obj
144 let block: fn() -> () = || {
145 #(#stmts)*
146 };
147 fickle.run(block).unwrap();
148 }
149 }
150 }
151 syn::ReturnType::Type(_rarrow, typ) => match **typ {
152 syn::Type::Path(_) => {
153 quote! {
155 {
156 #fickle_obj
157 let block: fn() -> #typ = || {
158 #(#stmts)*
159 };
160 match fickle.run(block) {
161 Ok(res) => Ok(res),
162 Err(fails) => panic!("{}", fails)
163 }
164 }
165 }
166 }
167 _ => {
168 return syn::Error::new(typ.span(), "Not able to handle return type")
169 .into_compile_error()
170 .into();
171 }
172 },
173 };
174
175 let expanded = quote! {
176 #(#attrs)*
177 #vis #sig
178 #func_body
179 };
180 expanded.into()
181}
182
183fn get_int<T>(expr: Expr) -> syn::Result<T>
184where
185 T: FromStr,
186 <T as FromStr>::Err: Display,
187{
188 match expr {
189 Expr::Lit(lit) => match lit.lit {
190 syn::Lit::Int(litint) => match litint.base10_parse() {
191 Ok(r) => Ok(r),
192 Err(_) => Err(syn::Error::new(litint.span(), "Could not parse to a usize")),
193 },
194 _ => Err(syn::Error::new(lit.lit.span(), "Not an integer")),
195 },
196 _ => Err(syn::Error::new(expr.span(), "Not an literal")),
197 }
198}