use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{FnArg, ImplItem, ImplItemFn, Item, ItemFn, ItemImpl};
use crate::implementation::{emit_error, ContractMode, ContractType, FuncWithContracts};
pub(crate) fn invariant(mode: ContractMode, attr: TokenStream, toks: TokenStream) -> TokenStream {
let name = mode.name().unwrap().to_string() + "invariant";
let item: Item = match syn::parse2(toks.clone()) {
Ok(item) => item,
Err(err) => return emit_error(err, toks),
};
match item {
Item::Fn(fn_) => invariant_fn(mode, attr, fn_),
Item::Impl(impl_) => invariant_impl(mode, attr, impl_),
item => {
let error = syn::Error::new_spanned(
&item,
format!(
"the #[{}] attribute only works on functions and impl blocks",
name
),
);
emit_error(error, item)
}
}
}
fn invariant_fn(mode: ContractMode, attr: TokenStream, func: ItemFn) -> TokenStream {
let ty = ContractType::Invariant;
let f = FuncWithContracts::new_with_initial_contract(func, ty, mode, attr);
f.generate()
}
fn invariant_impl(
mode: ContractMode,
invariant: TokenStream,
mut impl_def: ItemImpl,
) -> TokenStream {
let name = match mode.name() {
Some(n) => n.to_string() + "invariant",
None => {
return quote::quote!( #impl_def );
}
};
let invariant_ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
fn method_uses_self(method: &ImplItemFn) -> bool {
let inputs = &method.sig.inputs;
if !inputs.is_empty() {
matches!(inputs[0], FnArg::Receiver(_))
} else {
false
}
}
for item in &mut impl_def.items {
if let ImplItem::Fn(method) = item {
if !method_uses_self(method) {
continue;
}
let method_toks = quote::quote! {
#[#invariant_ident(#invariant)]
#method
};
let met: ImplItemFn = syn::parse_quote!(#method_toks);
*method = met;
}
}
impl_def.into_token_stream()
}