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#[derive(Debug)]
16pub struct Spec {
17 pub requires: Vec<Condition>,
19 pub maintains: Vec<Condition>,
21 pub ensures: Vec<ConditionClosure>,
23}
24
25#[derive(Debug)]
27pub struct Condition {
28 pub expr: Expr,
30 pub cfg: Option<Meta>,
35}
36
37#[derive(Debug)]
39pub struct ConditionClosure {
40 pub closure: ExprClosure,
42 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 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
152enum 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 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 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 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 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
273mod kw {
276 syn::custom_keyword!(requires);
277 syn::custom_keyword!(maintains);
278 syn::custom_keyword!(binds);
279 syn::custom_keyword!(ensures);
280}
281
282pub fn instrument_fn_body(spec: &Spec, original_body: &Block, is_async: bool) -> Result<Block> {
284 let binding_ident = Ident::new("__anodized_output", Span::mixed_site());
286
287 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 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 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;