contracts_try/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! A crate implementing ["Design by Contract"][dbc] via procedural macros.
6//!
7//! This crate is heavily inspired by the [`libhoare`] compiler plugin.
8//!
9//! The main use of this crate is to annotate functions and methods using
10//! "contracts" in the form of [*pre-conditions* (`requires`)][precond],
11//! [*post-conditions* (`ensures`)][postcond] and [*invariants*][invariant].
12//!
13//! Each "contract" annotation that is violated will cause an assertion failure.
14//!
15//! The attributes use "function call form" and can contain 1 or more conditions
16//! to check.
17//! If the last argument to an attribute is a string constant it will be
18//! inserted into the assertion message.
19//!
20//! ## Example
21//!
22//! ```rust
23//! # use contracts_try::*;
24//! #[requires(x > 0, "x must be in the valid input range")]
25//! #[ensures(ret.is_some() -> ret.unwrap() * ret.unwrap() == x)]
26//! fn integer_sqrt(x: u64) -> Option<u64> {
27//!    // ...
28//! # unimplemented!()
29//! }
30//! ```
31//!
32//! ```rust
33//! # use std::collections::HashSet;
34//! # use contracts_try::*;
35//! pub struct Library {
36//!     available: HashSet<String>,
37//!     lent: HashSet<String>,
38//! }
39//!
40//! impl Library {
41//!     fn book_exists(&self, book_id: &str) -> bool {
42//!         self.available.contains(book_id)
43//!             || self.lent.contains(book_id)
44//!     }
45//!
46//!     #[debug_requires(!self.book_exists(book_id), "Book IDs are unique")]
47//!     #[debug_ensures(self.available.contains(book_id), "Book now available")]
48//!     #[ensures(self.available.len() == old(self.available.len()) + 1)]
49//!     #[ensures(self.lent.len() == old(self.lent.len()), "No lent change")]
50//!     pub fn add_book(&mut self, book_id: &str) {
51//!         self.available.insert(book_id.to_string());
52//!     }
53//!
54//!     #[debug_requires(self.book_exists(book_id))]
55//!     #[ensures(ret -> self.available.len() == old(self.available.len()) - 1)]
56//!     #[ensures(ret -> self.lent.len() == old(self.lent.len()) + 1)]
57//!     #[debug_ensures(ret -> self.lent.contains(book_id))]
58//!     #[debug_ensures(!ret -> self.lent.contains(book_id), "Book already lent")]
59//!     pub fn lend(&mut self, book_id: &str) -> bool {
60//!         if self.available.contains(book_id) {
61//!             self.available.remove(book_id);
62//!             self.lent.insert(book_id.to_string());
63//!             true
64//!         } else {
65//!             false
66//!         }
67//!     }
68//!
69//!     #[debug_requires(self.lent.contains(book_id), "Can't return a non-lent book")]
70//!     #[ensures(self.lent.len() == old(self.lent.len()) - 1)]
71//!     #[ensures(self.available.len() == old(self.available.len()) + 1)]
72//!     #[debug_ensures(!self.lent.contains(book_id))]
73//!     #[debug_ensures(self.available.contains(book_id), "Book available again")]
74//!     pub fn return_book(&mut self, book_id: &str) {
75//!         self.lent.remove(book_id);
76//!         self.available.insert(book_id.to_string());
77//!     }
78//! }
79//! ```
80//!
81//! ## Attributes
82//!
83//! This crate exposes the `requires`, `ensures` and `invariant` attributes.
84//!
85//! - `requires` are checked before a function/method is executed.
86//! - `ensures` are checked after a function/method ran to completion
87//! - `invariant`s are checked both before *and* after a function/method ran.
88//!
89//! Additionally, all those attributes have versions with different "modes". See
90//! [the Modes section](#modes) below.
91//!
92//! For `trait`s and trait `impl`s the `contract_trait` attribute can be used.
93//!
94//! ## Pseudo-functions and operators
95//!
96//! ### `old()` function
97//!
98//! One unique feature that this crate provides is an `old()` pseudo-function which
99//! allows to perform checks using a value of a parameter before the function call
100//! happened. This is only available in `ensures` attributes.
101//!
102//! ```rust
103//! # use contracts_try::*;
104//! #[ensures(*x == old(*x) + 1, "after the call `x` was incremented")]
105//! fn incr(x: &mut usize) {
106//!     *x += 1;
107//! }
108//! ```
109//!
110//! ### `->` operator
111//!
112//! For more complex functions it can be useful to express behaviour using logical
113//! implication. Because Rust does not feature an operator for implication, this
114//! crate adds this operator for use in attributes.
115//!
116//! ```rust
117//! # use contracts_try::*;
118//! #[ensures(person_name.is_some() -> ret.contains(person_name.unwrap()))]
119//! fn geeting(person_name: Option<&str>) -> String {
120//!     let mut s = String::from("Hello");
121//!     if let Some(name) = person_name {
122//!         s.push(' ');
123//!         s.push_str(name);
124//!     }
125//!     s.push('!');
126//!     s
127//! }
128//! ```
129//!
130//! This operator is right-associative.
131//!
132//! **Note**: Because of the design of `syn`, it is tricky to add custom operators
133//! to be parsed, so this crate performs a rewrite of the `TokenStream` instead.
134//! The rewrite works by separating the expression into a part that's left of the
135//! `->` operator and the rest on the right side. This means that
136//! `if a -> b { c } else { d }` will not generate the expected code.
137//! Explicit grouping using parenthesis or curly-brackets can be used to avoid this.
138//!
139//! ## Modes
140//!
141//! All the attributes (requires, ensures, invariant) have `debug_*` and `test_*` versions.
142//!
143//! - `debug_requires`/`debug_ensures`/`debug_invariant` use `debug_assert!`
144//!   internally rather than `assert!`
145//! - `test_requires`/`test_ensures`/`test_invariant` guard the `assert!` with an
146//!   `if cfg!(test)`.
147//!   This should mostly be used for stating equivalence to "slow but obviously
148//!   correct" alternative implementations or checks.
149//!   
150//!   For example, a merge-sort implementation might look like this
151//!   ```rust
152//!   # use contracts_try::*;
153//!   # fn is_sorted<T>(x: T) -> bool { true }
154//!   #[test_ensures(is_sorted(input))]
155//!   fn merge_sort<T: Ord + Copy>(input: &mut [T]) {
156//!       // ...
157//!   }
158//!   ```
159//!
160//! ## Feature flags
161//!
162//! Following feature flags are available:
163//!  - `disable_contracts` - disables all checks and assertions.
164//!  - `override_debug` - changes all contracts (except `test_` ones) into
165//!    `debug_*` versions
166//!  - `override_log` - changes all contracts (except `test_` ones) into a
167//!    `log::error!()` call if the condition is violated.
168//!    No abortion happens.
169//! - `mirai_assertions` - instead of regular assert! style macros, emit macros
170//!   used by the [MIRAI] static analyzer.
171//!
172//! [dbc]: https://en.wikipedia.org/wiki/Design_by_contract
173//! [`libhoare`]: https://github.com/nrc/libhoare
174//! [precond]: attr.requires.html
175//! [postcond]: attr.ensures.html
176//! [invariant]: attr.invariant.html
177//! [MIRAI]: https://github.com/facebookexperimental/MIRAI
178
179extern crate proc_macro;
180
181mod implementation;
182
183use implementation::ContractMode;
184use proc_macro::TokenStream;
185
186/// Pre-conditions are checked before the function body is run.
187///
188/// ## Example
189///
190/// ```rust
191/// # use contracts_try::*;
192/// #[requires(elems.len() >= 1)]
193/// fn max<T: Ord + Copy>(elems: &[T]) -> T {
194///    // ...
195/// # unimplemented!()
196/// }
197/// ```
198#[proc_macro_attribute]
199pub fn requires(attr: TokenStream, toks: TokenStream) -> TokenStream {
200    if cfg!(feature = "disable_contracts") {
201        return toks;
202    }
203
204    let attr = attr.into();
205    let toks = toks.into();
206    implementation::requires(ContractMode::Always, attr, toks).into()
207}
208
209/// Same as [`requires`], but uses `debug_assert!`.
210///
211/// [`requires`]: attr.requires.html
212#[proc_macro_attribute]
213pub fn debug_requires(attr: TokenStream, toks: TokenStream) -> TokenStream {
214    if cfg!(feature = "disable_contracts") {
215        return toks;
216    }
217
218    let attr = attr.into();
219    let toks = toks.into();
220    implementation::requires(ContractMode::Debug, attr, toks).into()
221}
222
223/// Same as [`requires`], but is only enabled in `#[cfg(test)]` environments.
224///
225/// [`requires`]: attr.requires.html
226#[proc_macro_attribute]
227pub fn test_requires(attr: TokenStream, toks: TokenStream) -> TokenStream {
228    if cfg!(feature = "disable_contracts") {
229        return toks;
230    }
231
232    let attr = attr.into();
233    let toks = toks.into();
234    implementation::requires(ContractMode::Test, attr, toks).into()
235}
236
237/// Post-conditions are checked after the function body is run.
238///
239/// The result of the function call is accessible in conditions using the `ret`
240/// identifier.
241///
242/// A "pseudo-function" named `old` can be used to evaluate expressions in a
243/// context *prior* to function execution.
244/// This function takes only a single argument and the result of it will be
245/// stored in a variable before the function is called. Because of this,
246/// handling references might require special care.
247///
248/// ## Examples
249///
250/// ```rust
251/// # use contracts_try::*;
252/// #[ensures(ret > x)]
253/// fn incr(x: usize) -> usize {
254///     x + 1
255/// }
256/// ```
257///
258/// ```rust
259/// # use contracts_try::*;
260/// #[ensures(*x == old(*x) + 1, "x is incremented")]
261/// fn incr(x: &mut usize) {
262///     *x += 1;
263/// }
264/// ```
265#[proc_macro_attribute]
266pub fn ensures(attr: TokenStream, toks: TokenStream) -> TokenStream {
267    if cfg!(feature = "disable_contracts") {
268        return toks;
269    }
270
271    let attr = attr.into();
272    let toks = toks.into();
273    implementation::ensures(ContractMode::Always, attr, toks).into()
274}
275
276/// Same as [`ensures`], but uses `debug_assert!`.
277///
278/// [`ensures`]: attr.ensures.html
279#[proc_macro_attribute]
280pub fn debug_ensures(attr: TokenStream, toks: TokenStream) -> TokenStream {
281    if cfg!(feature = "disable_contracts") {
282        return toks;
283    }
284
285    let attr = attr.into();
286    let toks = toks.into();
287    implementation::ensures(ContractMode::Debug, attr, toks).into()
288}
289
290/// Same as [`ensures`], but is only enabled in `#[cfg(test)]` environments.
291///
292/// [`ensures`]: attr.ensures.html
293#[proc_macro_attribute]
294pub fn test_ensures(attr: TokenStream, toks: TokenStream) -> TokenStream {
295    if cfg!(feature = "disable_contracts") {
296        return toks;
297    }
298
299    let attr = attr.into();
300    let toks = toks.into();
301    implementation::ensures(ContractMode::Test, attr, toks).into()
302}
303
304/// Invariants are conditions that have to be maintained at the "interface
305/// boundaries".
306///
307/// Invariants can be supplied to functions (and "methods"), as well as on
308/// `impl` blocks.
309///
310/// When applied to an `impl`-block all methods taking `self` (either by value
311/// or reference) will be checked for the invariant.
312///
313/// ## Example
314///
315/// On a function:
316///
317/// ```rust
318/// # use contracts_try::*;
319/// /// Update `num` to the next bigger even number.
320/// #[invariant(*num % 2 == 0)]
321/// fn advance_even(num: &mut usize) {
322///     *num += 2;
323/// }
324/// ```
325///
326/// On an `impl`-block:
327///
328/// ```rust
329/// # use contracts_try::*;
330/// struct EvenAdder {
331///     count: usize,
332/// }
333///
334/// #[invariant(self.count % 2 == 0)]
335/// impl EvenAdder {
336///     pub fn tell(&self) -> usize {
337///         self.count
338///     }
339///
340///     pub fn advance(&mut self) {
341///         self.count += 2;
342///     }
343/// }
344/// ```
345#[proc_macro_attribute]
346pub fn invariant(attr: TokenStream, toks: TokenStream) -> TokenStream {
347    if cfg!(feature = "disable_contracts") {
348        return toks;
349    }
350
351    // Invariant attributes might apply to `impl` blocks as well, where the same
352    // level is simply replicated on all methods.
353    // Function expansions will resolve the actual mode themselves, so the
354    // actual "raw" mode is passed here
355    //
356    // TODO: update comment when implemented for traits
357    let attr = attr.into();
358    let toks = toks.into();
359    let mode = ContractMode::Always;
360    implementation::invariant(mode, attr, toks).into()
361}
362
363/// Same as [`invariant`], but uses `debug_assert!`.
364///
365/// [`invariant`]: attr.invariant.html
366#[proc_macro_attribute]
367pub fn debug_invariant(attr: TokenStream, toks: TokenStream) -> TokenStream {
368    if cfg!(feature = "disable_contracts") {
369        return toks;
370    }
371
372    let mode = ContractMode::Debug;
373    let attr = attr.into();
374    let toks = toks.into();
375    implementation::invariant(mode, attr, toks).into()
376}
377
378/// Same as [`invariant`], but is only enabled in `#[cfg(test)]` environments.
379///
380/// [`invariant`]: attr.invariant.html
381#[proc_macro_attribute]
382pub fn test_invariant(attr: TokenStream, toks: TokenStream) -> TokenStream {
383    if cfg!(feature = "disable_contracts") {
384        return toks;
385    }
386
387    let mode = ContractMode::Test;
388    let attr = attr.into();
389    let toks = toks.into();
390    implementation::invariant(mode, attr, toks).into()
391}
392
393/// A "contract_trait" is a trait which ensures all implementors respect all
394/// provided contracts.
395///
396/// When this attribute is applied to a `trait` definition, the trait gets
397/// modified so that all invocations of methods are checked.
398///
399/// When this attribute is applied to an `impl Trait for Type` item, the
400/// implementation gets modified so it matches the trait definition.
401///
402/// **When the `#[contract_trait]` is not applied to either the trait or an
403/// `impl` it will cause compile errors**.
404///
405/// ## Example
406///
407/// ```rust
408/// # use contracts_try::*;
409/// #[contract_trait]
410/// trait MyRandom {
411///     #[requires(min < max)]
412///     #[ensures(min <= ret, ret <= max)]
413///     fn gen(min: f64, max: f64) -> f64;
414/// }
415///
416/// // Not a very useful random number generator, but a valid one!
417/// struct AlwaysMax;
418///
419/// #[contract_trait]
420/// impl MyRandom for AlwaysMax {
421///     fn gen(min: f64, max: f64) -> f64 {
422///         max
423///     }
424/// }
425/// ```
426#[proc_macro_attribute]
427pub fn contract_trait(attrs: TokenStream, toks: TokenStream) -> TokenStream {
428    if cfg!(feature = "disable_contracts") {
429        return toks;
430    }
431
432    let attrs: proc_macro2::TokenStream = attrs.into();
433    let toks: proc_macro2::TokenStream = toks.into();
434
435    let item: syn::Item = syn::parse_quote!(#toks);
436
437    let tts = match item {
438        syn::Item::Trait(trait_) => implementation::contract_trait_item_trait(attrs, trait_),
439        syn::Item::Impl(impl_) => {
440            assert!(
441                impl_.trait_.is_some(),
442                "#[contract_trait] can only be applied to `trait` and `impl ... for` items"
443            );
444            implementation::contract_trait_item_impl(attrs, impl_)
445        }
446        _ => panic!("#[contract_trait] can only be applied to `trait` and `impl ... for` items"),
447    };
448
449    tts.into()
450}