anodized_core/
lib.rs

1#![cfg(not(doctest))]
2#![doc = include_str!("../README.md")]
3
4use proc_macro2::Span;
5use quote::{ToTokens, quote};
6use syn::{
7    Attribute, Block, Expr, ExprClosure, Ident, Meta, Pat, Token,
8    parse::{Parse, ParseStream, Result},
9    parse_quote,
10    punctuated::Punctuated,
11    spanned::Spanned,
12};
13
14/// A spec specifies the intended behavior of a function or method.
15#[derive(Debug)]
16pub struct Spec {
17    /// Preconditions: conditions that must hold when the function is called.
18    pub requires: Vec<Condition>,
19    /// Invariants: conditions that must hold both when the function is called and when it returns.
20    pub maintains: Vec<Condition>,
21    /// Postconditions: conditions that must hold when the function returns.
22    pub ensures: Vec<ConditionClosure>,
23}
24
25/// A condition represented by a `bool`-valued expression.
26#[derive(Debug)]
27pub struct Condition {
28    /// The expression.
29    pub expr: Expr,
30    /// **Static analyzers can safely ignore this field.**
31    ///
32    /// Build configuration filter to decide whether to add runtime checks.
33    /// Passed to a `cfg!()` guard in the instrumented function.
34    pub cfg: Option<Meta>,
35}
36
37/// A condition represented by a `bool`-valued closure.
38#[derive(Debug)]
39pub struct ConditionClosure {
40    /// The closure.
41    pub closure: ExprClosure,
42    /// **Static analyzers can safely ignore this field.**
43    ///
44    /// Build configuration filter to decide whether to add runtime checks.
45    /// Passed to a `cfg!()` guard in the instrumented function.
46    pub cfg: Option<Meta>,
47}
48
49impl Parse for Spec {
50    fn parse(input: ParseStream) -> Result<Self> {
51        let args = Punctuated::<SpecArg, Token![,]>::parse_terminated(input)?;
52
53        let mut last_arg_order: Option<ArgOrder> = None;
54        let mut requires: Vec<Condition> = vec![];
55        let mut maintains: Vec<Condition> = vec![];
56        let mut binds_pattern: Option<Pat> = None;
57        let mut ensures_exprs: Vec<Condition> = vec![];
58
59        for arg in args {
60            let current_arg_order = arg.get_order();
61            if let Some(last_order) = last_arg_order {
62                if current_arg_order < last_order {
63                    return Err(syn::Error::new(
64                        arg.get_keyword_span(),
65                        "parameters are out of order: their order must be `requires`, `maintains`, `binds`, `ensures`",
66                    ));
67                }
68            }
69            last_arg_order = Some(current_arg_order);
70
71            match arg {
72                SpecArg::Requires { cfg, expr, .. } => {
73                    if let Expr::Array(conditions) = expr {
74                        requires.extend(conditions.elems.into_iter().map(|expr| Condition {
75                            expr,
76                            cfg: cfg.clone(),
77                        }));
78                    } else {
79                        requires.push(Condition { expr, cfg });
80                    }
81                }
82                SpecArg::Maintains { cfg, expr, .. } => {
83                    if let Expr::Array(conditions) = expr {
84                        maintains.extend(conditions.elems.into_iter().map(|expr| Condition {
85                            expr,
86                            cfg: cfg.clone(),
87                        }));
88                    } else {
89                        maintains.push(Condition { expr, cfg });
90                    }
91                }
92                SpecArg::Binds { keyword, pattern } => {
93                    if binds_pattern.is_some() {
94                        return Err(syn::Error::new(
95                            keyword.span(),
96                            "multiple `binds` parameters are not allowed",
97                        ));
98                    }
99                    binds_pattern = Some(pattern);
100                }
101                SpecArg::Ensures { cfg, expr, .. } => {
102                    if let Expr::Array(conditions) = expr {
103                        ensures_exprs.extend(conditions.elems.into_iter().map(|expr| Condition {
104                            expr,
105                            cfg: cfg.clone(),
106                        }));
107                    } else {
108                        ensures_exprs.push(Condition { expr, cfg });
109                    }
110                }
111            }
112        }
113
114        let default_closure_pattern = binds_pattern
115            .as_ref()
116            .map(|p| p.to_token_stream())
117            .unwrap_or_else(|| quote! { output });
118
119        let ensures = ensures_exprs
120            .into_iter()
121            .map(|condition| {
122                let closure: ExprClosure = if let Expr::Closure(closure) = condition.expr {
123                    closure
124                } else {
125                    // Convert "naked" postcondition to closure
126                    let inner_condition = condition.expr;
127                    parse_quote! { |#default_closure_pattern| #inner_condition }
128                };
129                Ok(ConditionClosure {
130                    closure,
131                    cfg: condition.cfg,
132                })
133            })
134            .collect::<Result<Vec<ConditionClosure>>>()?;
135
136        Ok(Spec {
137            requires,
138            maintains,
139            ensures,
140        })
141    }
142}
143
144#[derive(PartialEq, PartialOrd, Clone, Copy, Debug)]
145enum ArgOrder {
146    Requires,
147    Maintains,
148    Binds,
149    Ensures,
150}
151
152/// An intermediate enum to help parse either a condition or a `binds` setting.
153enum SpecArg {
154    Requires {
155        keyword: kw::requires,
156        cfg: Option<Meta>,
157        expr: Expr,
158    },
159    Ensures {
160        keyword: kw::ensures,
161        cfg: Option<Meta>,
162        expr: Expr,
163    },
164    Maintains {
165        keyword: kw::maintains,
166        cfg: Option<Meta>,
167        expr: Expr,
168    },
169    Binds {
170        keyword: kw::binds,
171        pattern: Pat,
172    },
173}
174
175impl SpecArg {
176    fn get_order(&self) -> ArgOrder {
177        match self {
178            SpecArg::Requires { .. } => ArgOrder::Requires,
179            SpecArg::Maintains { .. } => ArgOrder::Maintains,
180            SpecArg::Binds { .. } => ArgOrder::Binds,
181            SpecArg::Ensures { .. } => ArgOrder::Ensures,
182        }
183    }
184
185    fn get_keyword_span(&self) -> Span {
186        match self {
187            SpecArg::Requires { keyword, .. } => keyword.span,
188            SpecArg::Ensures { keyword, .. } => keyword.span,
189            SpecArg::Maintains { keyword, .. } => keyword.span,
190            SpecArg::Binds { keyword, .. } => keyword.span,
191        }
192    }
193}
194
195impl Parse for SpecArg {
196    fn parse(input: ParseStream) -> Result<Self> {
197        let attrs = input.call(Attribute::parse_outer)?;
198        let cfg = parse_cfg_attribute(&attrs)?;
199
200        let lookahead = input.lookahead1();
201        if lookahead.peek(kw::binds) {
202            if cfg.is_some() {
203                return Err(syn::Error::new(
204                    attrs[0].span(),
205                    "`cfg` attribute is not supported on `binds`",
206                ));
207            }
208
209            // Parse `binds: <pattern>`
210            let keyword = input.parse::<kw::binds>()?;
211            input.parse::<Token![:]>()?;
212            Ok(SpecArg::Binds {
213                keyword,
214                pattern: Pat::parse_single(input)?,
215            })
216        } else if lookahead.peek(kw::requires) {
217            // Parse `requires: <conditions>`
218            let keyword = input.parse::<kw::requires>()?;
219            input.parse::<Token![:]>()?;
220            Ok(SpecArg::Requires {
221                keyword,
222                cfg,
223                expr: input.parse()?,
224            })
225        } else if lookahead.peek(kw::maintains) {
226            // Parse `maintains: <conditions>`
227            let keyword = input.parse::<kw::maintains>()?;
228            input.parse::<Token![:]>()?;
229            Ok(SpecArg::Maintains {
230                keyword,
231                cfg,
232                expr: input.parse()?,
233            })
234        } else if lookahead.peek(kw::ensures) {
235            // Parse `ensures: <conditions>`
236            let keyword = input.parse::<kw::ensures>()?;
237            input.parse::<Token![:]>()?;
238            Ok(SpecArg::Ensures {
239                keyword,
240                cfg,
241                expr: input.parse()?,
242            })
243        } else {
244            Err(lookahead.error())
245        }
246    }
247}
248
249fn parse_cfg_attribute(attrs: &[Attribute]) -> Result<Option<Meta>> {
250    let mut cfg_attrs: Vec<Meta> = vec![];
251
252    for attr in attrs {
253        if attr.path().is_ident("cfg") {
254            cfg_attrs.push(attr.parse_args()?);
255        } else {
256            return Err(syn::Error::new(
257                attr.span(),
258                "unsupported attribute; only `cfg` is allowed",
259            ));
260        }
261    }
262
263    if cfg_attrs.len() > 1 {
264        return Err(syn::Error::new(
265            cfg_attrs[1].span(),
266            "multiple `cfg` attributes are not supported",
267        ));
268    }
269
270    Ok(cfg_attrs.pop())
271}
272
273/// Custom keywords for parsing. This allows us to use `requires`, `ensures`, etc.,
274/// as if they were built-in Rust keywords during parsing.
275mod kw {
276    syn::custom_keyword!(requires);
277    syn::custom_keyword!(maintains);
278    syn::custom_keyword!(binds);
279    syn::custom_keyword!(ensures);
280}
281
282/// Takes the spec and the original body and returns a new instrumented function body.
283pub fn instrument_fn_body(spec: &Spec, original_body: &Block, is_async: bool) -> Result<Block> {
284    // The identifier for the return value binding. It's hygienic to prevent collisions.
285    let binding_ident = Ident::new("__anodized_output", Span::mixed_site());
286
287    // --- Generate Precondition Checks ---
288    let preconditions = spec
289        .requires
290        .iter()
291        .map(|condition| {
292            let expr = &condition.expr;
293            let msg = format!("Precondition failed: {}", expr.to_token_stream());
294            let assert = quote! { assert!(#expr, #msg); };
295            if let Some(cfg) = &condition.cfg {
296                quote! { if cfg!(#cfg) { #assert } }
297            } else {
298                assert
299            }
300        })
301        .chain(spec.maintains.iter().map(|condition| {
302            let expr = &condition.expr;
303            let msg = format!("Pre-invariant failed: {}", expr.to_token_stream());
304            let assert = quote! { assert!(#expr, #msg); };
305            if let Some(cfg) = &condition.cfg {
306                quote! { if cfg!(#cfg) { #assert } }
307            } else {
308                assert
309            }
310        }));
311
312    // --- Generate Postcondition Checks ---
313    let postconditions = spec
314        .maintains
315        .iter()
316        .map(|condition| {
317            let expr = &condition.expr;
318            let msg = format!("Post-invariant failed: {}", expr.to_token_stream());
319            let assert = quote! { assert!(#expr, #msg); };
320            if let Some(cfg) = &condition.cfg {
321                quote! { if cfg!(#cfg) { #assert } }
322            } else {
323                assert
324            }
325        })
326        .chain(spec.ensures.iter().map(|condition_closure| {
327            let closure = &condition_closure.closure;
328            let msg = format!("Postcondition failed: {}", closure.to_token_stream());
329            let assert = quote! { assert!((#closure)(#binding_ident), #msg); };
330            if let Some(cfg) = &condition_closure.cfg {
331                quote! { if cfg!(#cfg) { #assert } }
332            } else {
333                assert
334            }
335        }));
336
337    // --- Construct the New Body ---
338    let body_expr = if is_async {
339        quote! { async #original_body.await }
340    } else {
341        quote! { #original_body }
342    };
343
344    Ok(parse_quote! {
345        {
346            #(#preconditions)*
347            let #binding_ident = #body_expr;
348            #(#postconditions)*
349            #binding_ident
350        }
351    })
352}
353
354#[cfg(test)]
355mod test_parse_spec;
356
357#[cfg(test)]
358mod test_instrument_fn;
359
360#[cfg(test)]
361mod test_util;