Skip to main content

anodized_core/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro2::Span;
4use syn::{Error, Expr, Meta, Pat};
5
6use crate::qualifiers::FnQualifiers;
7
8pub mod annotate;
9pub mod instrument;
10pub mod qualifiers;
11
12#[cfg(test)]
13mod test_util;
14
15/// Specifies the intended behavior of a function or method: `fn`.
16#[derive(Debug)]
17// TODO: Rename to `FnSpec` to reduce ambiguity.
18pub struct Spec {
19    /// Qualifiers that constrain the behavior of the computation.
20    pub qualifiers: FnQualifiers,
21    /// Preconditions: conditions that must hold when the function is called.
22    pub requires: Vec<Condition>,
23    /// Invariants: conditions that must hold both when the function is called and when it returns.
24    pub maintains: Vec<Condition>,
25    /// Captures: expressions to snapshot at function entry for use in postconditions.
26    pub captures: Vec<Capture>,
27    /// Postconditions: conditions that must hold when the function returns.
28    pub ensures: Vec<PostCondition>,
29    /// The span in the source code, from which this spec was parsed.
30    span: Span,
31}
32
33impl Spec {
34    /// Empty spec that contains no elements.
35    pub fn empty() -> Self {
36        Self {
37            qualifiers: FnQualifiers::empty(),
38            requires: vec![],
39            maintains: vec![],
40            captures: vec![],
41            ensures: vec![],
42            span: Span::call_site(),
43        }
44    }
45
46    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
47    pub fn is_empty(&self) -> bool {
48        self.qualifiers.is_empty()
49            && self.requires.is_empty()
50            && self.maintains.is_empty()
51            && self.ensures.is_empty()
52            && self.captures.is_empty()
53    }
54
55    /// Construct an error from the whole spec.
56    pub fn spec_err(&self, message: &str) -> Error {
57        Error::new::<&str>(self.span, message)
58    }
59}
60
61/// Specifies the intended behavior of a data type: `struct` or `enum`.
62#[derive(Debug)]
63pub struct DataSpec {
64    /// Invariants: conditions that must hold for all instances of the data type.
65    pub maintains: Vec<Condition>,
66    /// The span in the source code, from which this spec was parsed.
67    span: Span,
68}
69
70impl DataSpec {
71    /// Empty spec that contains no elements.
72    pub fn empty() -> Self {
73        Self {
74            maintains: vec![],
75            span: Span::call_site(),
76        }
77    }
78
79    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
80    pub fn is_empty(&self) -> bool {
81        self.maintains.is_empty()
82    }
83
84    /// Construct an error from the whole spec.
85    pub fn spec_err(&self, message: &str) -> Error {
86        Error::new::<&str>(self.span, message)
87    }
88}
89
90/// Specifies the intended behavior of a loop: `while` or `for`.
91#[derive(Debug)]
92pub struct LoopSpec {
93    /// Loop invariants: conditions that must hold both before and after the loop's body runs.
94    pub maintains: Vec<Condition>,
95    /// Loop variant: an expression that decreases with each run of the loop's body.
96    pub decreases: Option<LoopVariant>,
97    /// The span in the source code, from which this spec was parsed.
98    span: Span,
99}
100
101impl LoopSpec {
102    /// Empty spec that contains no elements.
103    pub fn empty() -> Self {
104        Self {
105            maintains: vec![],
106            decreases: None,
107            span: Span::call_site(),
108        }
109    }
110
111    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
112    pub fn is_empty(&self) -> bool {
113        self.maintains.is_empty() && self.decreases.is_none()
114    }
115
116    /// Construct an error from the whole spec.
117    pub fn spec_err(&self, message: &str) -> Error {
118        Error::new::<&str>(self.span, message)
119    }
120}
121
122/// A condition represented by a `bool`-valued expression.
123#[derive(Debug)]
124pub struct Condition {
125    /// The expression that validates the condition, e.g. `value > 42`.
126    pub expr: Expr,
127    /// **Static analyzers can safely ignore this field.**
128    ///
129    /// Build configuration filter to decide whether to add runtime checks.
130    /// Passed to a `cfg!()` guard in the instrumented function.
131    pub cfg: Option<Meta>,
132}
133
134/// A postcondition represented by a pattern to bind the output and a `bool`-valued expression.
135#[derive(Debug)]
136pub struct PostCondition {
137    /// The pattern to bind/destructure the function's output, e.g. `ref answer`.
138    pub pat: Option<Pat>,
139    /// The expression that validates the postcondition, e.g. `answer == "forty-two"`.
140    pub expr: Expr,
141    /// **Static analyzers can safely ignore this field.**
142    ///
143    /// Build configuration filter to decide whether to add runtime checks.
144    /// Passed to a `cfg!()` guard in the instrumented function.
145    pub cfg: Option<Meta>,
146}
147
148/// Captures an expression's value at function entry.
149#[derive(Debug)]
150pub struct Capture {
151    /// The pattern to bind/destructure the captured value.
152    pub pat: Pat,
153    /// The expression to capture.
154    pub expr: Expr,
155}
156
157/// Decreases with each run of a loop's body.
158#[derive(Debug)]
159pub struct LoopVariant {
160    /// The expression that defines the variant.
161    pub expr: Expr,
162    /// **Static analyzers can safely ignore this field.**
163    ///
164    /// Build configuration filter to decide whether to add runtime checks.
165    /// Passed to a `cfg!()` guard in the instrumented code.
166    pub cfg: Option<Meta>,
167}