#[cfg(test)]
mod tests;
use crate::{
Capture, PostCondition, PreCondition, Spec,
instrument::{Config, build_assert, build_eprint},
qualifiers::FnQualifiers,
};
use proc_macro2::Span;
use quote::{ToTokens, quote};
use syn::{
Attribute, Block, Expr, Ident, Pat, PatIdent, Path, ReturnType, Signature, Stmt, Type,
parse::{Parse, Result},
parse_quote,
};
impl Config {
pub fn instrument_fn(&self, spec: &Spec, sig: &Signature, body: &mut Block) -> syn::Result<()> {
self.instrument_loops_in_fn_body(body)?;
let is_async = sig.asyncness.is_some();
let return_type = match &sig.output {
syn::ReturnType::Default => syn::parse_quote!(()),
syn::ReturnType::Type(_, ty) => ty.as_ref().clone(),
};
let new_body = self.instrument_fn_body(spec, body, is_async, &return_type)?;
*body = new_body;
Ok(())
}
pub fn build_spec_fn_sig(prefix: &str, sig: &Signature) -> Signature {
Signature {
constness: sig.constness,
asyncness: sig.asyncness,
unsafety: sig.unsafety,
abi: sig.abi.clone(),
fn_token: sig.fn_token,
ident: syn::Ident::new(&format!("{prefix}_{}", sig.ident), sig.ident.span()),
generics: sig.generics.clone(),
paren_token: sig.paren_token,
inputs: sig.inputs.clone(),
variadic: sig.variadic.clone(),
output: syn::ReturnType::Default,
}
}
pub fn build_qualifier_const_item<SomeConstItem: Parse>(
attrs: &[Attribute],
prefix: &str,
qualifiers: FnQualifiers,
fn_ident: &Ident,
) -> SomeConstItem {
let qualifier_bits = qualifiers.bits();
let name: Ident = syn::Ident::new(&format!("{}_{}", prefix, fn_ident), fn_ident.span());
parse_quote! {
#(#attrs)*
const #name: u32 = #qualifier_bits;
}
}
pub fn build_qualifier_check_stmt(
fn_ident: &Ident,
impl_type: &Type,
trait_path: &Path,
) -> Stmt {
let impl_const_name = Ident::new(
&format!("__anodized_fn_qualifiers_{}", fn_ident),
fn_ident.span(),
);
let trait_const_name = Ident::new(
&format!("__anodized_fn_qualifiers_trait_{}", fn_ident),
fn_ident.span(),
);
let message = format!(
"the qualifiers on the impl `{}::{fn_ident}` cannot be weaker than the qualifiers on the trait `{}::{fn_ident}`",
impl_type.to_token_stream(),
trait_path.to_token_stream(),
);
parse_quote! {
const {
assert!(
Self::#impl_const_name == Self::#trait_const_name | Self::#impl_const_name,
#message,
);
};
}
}
pub fn build_precondition_fn_body(conditions: &[PreCondition]) -> Block {
let statements = conditions.iter().map(|condition| -> Stmt {
let closure = &condition.closure;
parse_quote! { let _ = #closure; }
});
parse_quote! {
{
#(#statements)*
}
}
}
pub fn build_poscondition_fn_body(
captures: &[Capture],
conditions: &[PostCondition],
return_type: &ReturnType,
) -> Result<Block> {
let aliases = captures.iter().map(|capture| &capture.pat);
let capture_exprs = captures.iter().map(|capture| -> Expr {
let expr = &capture.expr;
parse_quote! { (|| #expr)() }
});
let mut statements = vec![];
for condition in conditions {
let closure = &condition.closure;
let output_binder = match closure.inputs.first() {
Some(output_binder) if closure.inputs.len() == 1 => output_binder,
_ => {
return Err(syn::Error::new_spanned(
&closure.inputs,
"Postcondition closure must have exactly one parameter.",
));
}
};
let statement: Stmt = if let Pat::Type(_) = output_binder {
parse_quote! { let _ = #closure; }
} else {
let body = &closure.body;
match &return_type {
ReturnType::Default => {
parse_quote! { let _ = |#output_binder: &()| #body; }
}
ReturnType::Type(_, ty) => {
parse_quote! { let _ = |#output_binder: &#ty| #body; }
}
}
};
statements.push(statement);
}
Ok(parse_quote! {
{
let (#(#aliases),*) = (#(#capture_exprs),*);
#(#statements)*
}
})
}
fn instrument_fn_body(
&self,
spec: &Spec,
original_body: &Block,
is_async: bool,
return_type: &syn::Type,
) -> Result<Block> {
let build_check = match (self.emit_print, self.emit_panic) {
(true, true) => build_assert,
(true, false) => build_eprint,
(false, true) => build_assert,
(false, false) => return Ok(original_body.clone()),
};
let output_ident = Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new("__anodized_output", Span::mixed_site()),
subpat: None,
});
let precondition_checks = spec
.requires
.iter()
.map(|condition| {
let closure = condition.closure.to_token_stream();
let expr = quote! { (#closure)() };
let repr = condition.closure.body.to_token_stream();
build_check(
condition.cfg.as_ref(),
&expr,
"Precondition failed: {}",
&repr,
)
})
.chain(spec.maintains.iter().map(|condition| {
let closure = condition.closure.to_token_stream();
let expr = quote! { (#closure)() };
let repr = condition.closure.body.to_token_stream();
build_check(
condition.cfg.as_ref(),
&expr,
"Pre-invariant failed: {}",
&repr,
)
}));
let aliases = spec
.captures
.iter()
.map(|cb| &cb.pat)
.chain(std::iter::once(&output_ident));
let capture_exprs = spec.captures.iter().map(|cb| {
let expr = &cb.expr;
quote! { (|| #expr)() }
});
let types = spec
.captures
.iter()
.map(|_| quote! { _ })
.chain(std::iter::once(quote! { #return_type }));
let body_expr = if is_async {
quote! { (async || #original_body)().await }
} else {
quote! { (|| #original_body)() }
};
let exprs = capture_exprs.chain(std::iter::once(body_expr));
let body_and_captures = quote! {
let (#(#aliases),*): (#(#types),*) = (#(#exprs),*);
};
let postcondition_checks = spec
.maintains
.iter()
.map(|condition| {
let closure = condition.closure.to_token_stream();
let expr = quote! { (#closure)() };
let repr = condition.closure.body.to_token_stream();
build_check(
condition.cfg.as_ref(),
&expr,
"Post-invariant failed: {}",
&repr,
)
})
.chain(spec.ensures.iter().map(|postcondition| {
let closure = annotate_postcondition_closure_argument(
postcondition.closure.clone(),
return_type.clone(),
);
let expr = quote! { (#closure)(&#output_ident) };
build_check(
postcondition.cfg.as_ref(),
&expr,
"Postcondition failed: {}",
&postcondition.closure.to_token_stream(),
)
}));
Ok(parse_quote! {
{
#(#precondition_checks)*
#body_and_captures
#(#postcondition_checks)*
#output_ident
}
})
}
}
fn annotate_postcondition_closure_argument(
mut closure: syn::ExprClosure,
return_type: syn::Type,
) -> syn::ExprClosure {
if let Some(first_input) = closure.inputs.first_mut() {
let pattern = first_input.clone();
*first_input = syn::Pat::Type(syn::PatType {
attrs: vec![],
pat: Box::new(pattern),
colon_token: Default::default(),
ty: Box::new(syn::Type::Reference(syn::TypeReference {
and_token: Default::default(),
lifetime: None,
mutability: None,
elem: Box::new(return_type),
})),
});
}
closure
}