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}