Skip to main content

anodized_core/instrument/
mod.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{Attribute, ItemConst, ItemFn, ItemImpl, ItemTrait, Meta, Result, parse_quote};
4
5use crate::{DataSpec, Spec};
6
7pub mod data;
8pub mod fns;
9pub mod loops;
10pub mod traits;
11
12pub struct Config {
13    pub embed_spec: bool,
14    pub emit_print: bool,
15    pub emit_panic: bool,
16}
17
18impl Config {
19    pub fn emit_anything(&self) -> bool {
20        self.emit_print || self.emit_panic
21    }
22
23    pub fn instrument_item_fn(&self, spec: Spec, mut item_fn: ItemFn) -> Result<TokenStream> {
24        let mut tokens = TokenStream::new();
25
26        if self.embed_spec {
27            let attrs: [Attribute; 2] = [
28                parse_quote!(#[doc(hidden)]),
29                parse_quote!(#[allow(warnings)]),
30            ];
31
32            // Embed `spec` elements as `__anodized_fn_*` items.
33            let spec_qualifiers_const: ItemConst = Self::build_qualifier_const_item(
34                &attrs,
35                "__anodized_fn_qualifiers",
36                spec.qualifiers,
37                &item_fn.sig.ident,
38            );
39            let spec_requires_fn = ItemFn {
40                attrs: attrs.to_vec(),
41                vis: syn::Visibility::Inherited,
42                sig: Self::build_spec_fn_sig("__anodized_fn_requires", &item_fn.sig),
43                block: Box::new(Self::build_precondition_fn_body(&spec.requires)),
44            };
45            let spec_maintains_fn = ItemFn {
46                attrs: attrs.to_vec(),
47                vis: syn::Visibility::Inherited,
48                sig: Self::build_spec_fn_sig("__anodized_fn_maintains", &item_fn.sig),
49                block: Box::new(Self::build_precondition_fn_body(&spec.maintains)),
50            };
51            let spec_ensures_fn = ItemFn {
52                attrs: attrs.to_vec(),
53                vis: syn::Visibility::Inherited,
54                sig: Self::build_spec_fn_sig("__anodized_fn_ensures", &item_fn.sig),
55                block: Box::new(Self::build_poscondition_fn_body(
56                    &spec.captures,
57                    &spec.ensures,
58                    &item_fn.sig.output,
59                )?),
60            };
61
62            spec_qualifiers_const.to_tokens(&mut tokens);
63            spec_requires_fn.to_tokens(&mut tokens);
64            spec_maintains_fn.to_tokens(&mut tokens);
65            spec_ensures_fn.to_tokens(&mut tokens);
66        }
67
68        // Instrument function body.
69        self.instrument_fn(&spec, &item_fn.sig, &mut item_fn.block)?;
70        item_fn.to_tokens(&mut tokens);
71
72        Ok(tokens)
73    }
74
75    pub fn instrument_item_trait(
76        &self,
77        spec: DataSpec,
78        item_trait: ItemTrait,
79    ) -> Result<TokenStream> {
80        let new_trait = self.instrument_trait(spec, item_trait)?;
81        Ok(new_trait.to_token_stream())
82    }
83
84    pub fn instrument_item_trait_impl(
85        &self,
86        spec: DataSpec,
87        item_impl: ItemImpl,
88    ) -> Result<TokenStream> {
89        let new_trait_impl = self.instrument_trait_impl(spec, item_impl)?;
90        Ok(new_trait_impl.to_token_stream())
91    }
92}
93
94#[cfg(test)]
95impl Config {
96    pub(crate) const DEFAULT: Config = Config {
97        embed_spec: true,
98        emit_print: false,
99        emit_panic: false,
100    };
101
102    pub(crate) const PRINT: Config = Config {
103        embed_spec: true,
104        emit_print: true,
105        emit_panic: false,
106    };
107
108    pub(crate) const PANIC: Config = Config {
109        embed_spec: true,
110        emit_print: true,
111        emit_panic: true,
112    };
113}
114
115/// Make an error message to say that some item is unsupported.
116pub fn make_item_error<T: ToTokens>(tokens: &T, item_descr: &str) -> syn::Error {
117    let msg = format!(
118        r#"The #[spec] attribute doesn't yet support this item: {}.
119If this is a problem for your use case, please open a feature
120request at https://github.com/mkovaxx/anodized/issues/new"#,
121        item_descr
122    );
123    syn::Error::new_spanned(tokens, msg)
124}
125
126/// Finds the `[spec]` attrib in an attribute list.
127///
128/// Returns the spec [Attribute] and the remaining attributes.
129fn find_spec_attr(attrs: Vec<Attribute>) -> syn::Result<(Option<Attribute>, Vec<Attribute>)> {
130    let mut spec_attr = None;
131    let mut other_attrs = Vec::new();
132
133    for attr in attrs {
134        if attr.path().is_ident("spec") {
135            if spec_attr.is_some() {
136                return Err(syn::Error::new_spanned(
137                    attr,
138                    "multiple `#[spec]` attributes on a single item are not supported",
139                ));
140            }
141            spec_attr = Some(attr);
142        } else {
143            other_attrs.push(attr);
144        }
145    }
146
147    Ok((spec_attr, other_attrs))
148}
149
150fn build_assert(
151    cfg: Option<&Meta>,
152    expr: &TokenStream,
153    message: &str,
154    repr: &TokenStream,
155) -> TokenStream {
156    let repr_str = repr.to_string();
157    let check = quote! { assert!(#expr, #message, #repr_str); };
158    guard_check(cfg, check)
159}
160
161fn build_eprint(
162    cfg: Option<&Meta>,
163    expr: &TokenStream,
164    message: &str,
165    repr: &TokenStream,
166) -> TokenStream {
167    let repr_str = repr.to_string();
168    let check = quote! {
169        if !(#expr) {
170            eprintln!(#message, #repr_str);
171        }
172    };
173    guard_check(cfg, check)
174}
175
176fn guard_check(cfg: Option<&Meta>, check: TokenStream) -> TokenStream {
177    if let Some(cfg) = cfg {
178        quote! { if cfg!(#cfg) { #check } }
179    } else {
180        check
181    }
182}