1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{
4 Attribute, Block, FnArg, Ident, ItemConst, ItemFn, ItemImpl, ItemTrait, Result, ReturnType,
5 Signature, parse_quote,
6};
7
8use crate::{DataSpec, Spec};
9
10pub mod data;
11pub mod fns;
12pub mod impls;
13pub mod loops;
14pub mod traits;
15
16#[derive(Debug, Clone)]
17pub enum Mode {
18 ChangeNothing,
20 InjectChecks(CheckSettings),
22 EmbedSpecs,
24}
25
26#[derive(Debug, Clone)]
27pub struct CheckSettings {
28 pub does_print: bool,
30 pub does_panic: Option<PanicSettings>,
32}
33
34#[derive(Debug, Clone)]
35pub struct PanicSettings {
36 pub has_try_fn: bool,
38}
39
40impl Mode {
41 pub fn changes_anything(&self) -> bool {
42 !matches!(self, Mode::ChangeNothing)
43 }
44
45 pub fn emits_try_fn(&self) -> bool {
46 if let Self::InjectChecks(check_settings) = self
47 && let Some(panic_settings) = &check_settings.does_panic
48 {
49 panic_settings.has_try_fn
50 } else {
51 false
52 }
53 }
54
55 pub fn with_try_fn(&self, value: bool) -> Self {
56 match self {
57 Mode::ChangeNothing => Mode::ChangeNothing,
58 Mode::InjectChecks(check_settings) => {
59 let mut check_settings = check_settings.clone();
60 if let Some(panic_settings) = &mut check_settings.does_panic {
61 panic_settings.has_try_fn = value;
62 };
63 Mode::InjectChecks(check_settings)
64 }
65 Mode::EmbedSpecs => Mode::EmbedSpecs,
66 }
67 }
68
69 pub fn instrument_item_fn(&self, spec: Spec, mut item_fn: ItemFn) -> Result<TokenStream> {
70 let mut tokens = TokenStream::new();
71
72 if item_fn.sig.ident.to_string().starts_with("__anodized_") {
73 return Err(syn::Error::new_spanned(
74 item_fn.sig.ident,
75 r#"An item with the `__anodized_` prefix is internal. Do not implement it directly.
76Instead, you likely need to place a `#[spec]` attribute on an enclosing trait or impl block."#,
77 ));
78 }
79
80 if let Self::EmbedSpecs = self {
81 let attrs: [Attribute; 2] = [
83 parse_quote!(#[doc(hidden)]),
84 parse_quote!(#[allow(warnings)]),
85 ];
86
87 let spec_qualifiers_const: ItemConst = Self::build_qualifier_const_item(
88 &attrs,
89 "__anodized_fn_qualifiers",
90 spec.qualifiers,
91 &item_fn.sig.ident,
92 );
93 let spec_requires_fn = ItemFn {
94 attrs: attrs.to_vec(),
95 vis: syn::Visibility::Inherited,
96 sig: Self::build_precondition_fn_sig("__anodized_fn_requires", &item_fn.sig),
97 block: Box::new(Self::build_precondition_fn_body(
98 &spec.requires,
99 &spec.maintains,
100 )),
101 };
102 let spec_ensures_fn = ItemFn {
103 attrs: attrs.to_vec(),
104 vis: syn::Visibility::Inherited,
105 sig: Self::build_postcondition_fn_sig("__anodized_fn_ensures", &item_fn.sig),
106 block: Box::new(Self::build_postcondition_fn_body(
107 &spec.maintains,
108 &spec.captures,
109 &spec.ensures,
110 )?),
111 };
112
113 spec_qualifiers_const.to_tokens(&mut tokens);
114 spec_requires_fn.to_tokens(&mut tokens);
115 spec_ensures_fn.to_tokens(&mut tokens);
116 }
117
118 self.instrument_fn(&spec, &item_fn.sig, &mut item_fn.block)?;
120
121 if let Self::InjectChecks(check_settings) = self
122 && let Some(ref panic_settings) = check_settings.does_panic
123 && panic_settings.has_try_fn
124 {
125 let mut wrapper_fn = item_fn.clone();
127 let mangled_ident =
128 Self::build_try_fn_wrapper(false, &mut wrapper_fn.sig, wrapper_fn.block.as_mut());
129 wrapper_fn.to_tokens(&mut tokens);
130
131 item_fn.sig.ident = mangled_ident;
133 item_fn.sig.output = match item_fn.sig.output {
134 ReturnType::Default => parse_quote!(-> ::anodized::result::Result<()>),
135 ReturnType::Type(ra, ty) => {
136 parse_quote!(#ra ::anodized::result::Result<#ty>)
137 }
138 };
139 item_fn.attrs = vec![parse_quote!(#[doc(hidden)]), parse_quote!(#[inline])];
140 }
141
142 item_fn.to_tokens(&mut tokens);
143 Ok(tokens)
144 }
145
146 fn build_try_fn_wrapper(is_impl: bool, sig: &mut Signature, body: &mut Block) -> Ident {
147 let mangled_ident = fns::make_try_fn_ident(&sig.ident);
148
149 Self::build_wrapper_fn_signature(sig);
150
151 let args = sig.inputs.iter().map(|arg| match arg {
152 FnArg::Receiver(receiver) => receiver.self_token.to_token_stream(),
153 FnArg::Typed(pat_type) => pat_type.pat.to_token_stream(),
154 });
155
156 let maybe_self = match is_impl {
157 true => quote!(Self::),
158 false => quote!(),
159 };
160
161 let maybe_await = match &sig.asyncness {
162 Some(_) => quote!(.await),
163 None => quote!(),
164 };
165
166 *body = parse_quote! {
167 {
168 match #maybe_self #mangled_ident(#(#args),*) #maybe_await {
169 ::anodized::result::Result::Ok(output) => output,
170 ::anodized::result::Result::Err(
171 ::anodized::result::Error::Pre(errors)
172 ) => panic!("precondition failed:{errors}"),
173 ::anodized::result::Result::Err(
174 ::anodized::result::Error::Post(_, errors)
175 ) => panic!("postcondition failed:{errors}"),
176 }
177 }
178 };
179
180 mangled_ident
181 }
182
183 fn build_wrapper_fn_signature(sig: &mut Signature) {
184 use syn::spanned::Spanned;
185 for (i, arg) in sig.inputs.iter_mut().enumerate() {
186 match arg {
187 FnArg::Receiver(_) => {}
188 FnArg::Typed(pat_type) => {
189 let name = Ident::new(&format!("input_{i}"), pat_type.span());
190 pat_type.pat = parse_quote!(#name);
191 }
192 }
193 }
194 }
195
196 pub fn instrument_item_impl(&self, spec: DataSpec, item_impl: ItemImpl) -> Result<TokenStream> {
197 let new_impl = self.instrument_impl(spec, item_impl)?;
198 Ok(new_impl.to_token_stream())
199 }
200
201 pub fn instrument_item_trait(
202 &self,
203 spec: DataSpec,
204 item_trait: ItemTrait,
205 ) -> Result<TokenStream> {
206 let new_trait = self.instrument_trait(spec, item_trait)?;
207 Ok(new_trait.to_token_stream())
208 }
209
210 pub fn instrument_item_trait_impl(
211 &self,
212 spec: DataSpec,
213 item_impl: ItemImpl,
214 ) -> Result<TokenStream> {
215 let new_trait_impl = self.instrument_trait_impl(spec, item_impl)?;
216 Ok(new_trait_impl.to_token_stream())
217 }
218}
219
220#[cfg(test)]
221impl Mode {
222 pub(crate) const DEFAULT: Self = Mode::InjectChecks(CheckSettings::DEFAULT);
223}
224
225#[cfg(test)]
226impl CheckSettings {
227 pub(crate) const DEFAULT: Self = Self {
228 does_print: false,
229 does_panic: None,
230 };
231
232 pub(crate) const PRINT: Self = Self {
233 does_print: true,
234 does_panic: None,
235 };
236
237 pub(crate) const PRINT_AND_PANIC: Self = Self {
238 does_print: true,
239 does_panic: Some(PanicSettings { has_try_fn: false }),
240 };
241
242 pub(crate) const PRINT_AND_TRY: Self = Self {
243 does_print: true,
244 does_panic: Some(PanicSettings { has_try_fn: true }),
245 };
246}
247
248pub fn make_item_error<T: ToTokens>(tokens: &T, item_descr: &str) -> syn::Error {
250 let msg = format!(
251 r#"The #[spec] attribute doesn't yet support this item: {}.
252If this is a problem for your use case, please open a feature
253request at https://github.com/anodized-rs/anodized/issues/new"#,
254 item_descr
255 );
256 syn::Error::new_spanned(tokens, msg)
257}
258
259fn find_spec_attr(attrs: Vec<Attribute>) -> syn::Result<(Option<Attribute>, Vec<Attribute>)> {
263 let mut spec_attr = None;
264 let mut other_attrs = Vec::new();
265
266 for attr in attrs {
267 if attr.path().is_ident("spec") {
268 if spec_attr.is_some() {
269 return Err(syn::Error::new_spanned(
270 attr,
271 "multiple `#[spec]` attributes on a single item are not supported",
272 ));
273 }
274 spec_attr = Some(attr);
275 } else {
276 other_attrs.push(attr);
277 }
278 }
279
280 Ok((spec_attr, other_attrs))
281}