use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Attribute, Block, Expr, Fields, ImplItem, Item, parse_macro_input, parse_quote};
#[proc_macro_attribute]
pub fn make_noop(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new(
Span::call_site(),
"`make_noop` takes no arguments; use `#[noop_returns(EXPR)]` on a \
function or method to customize the return value",
)
.to_compile_error()
.into();
}
let input = parse_macro_input!(item as Item);
let result: syn::Result<proc_macro2::TokenStream> = match input {
Item::Fn(mut func) => {
*func.block = noop_block(&func.sig.output, None);
Ok(quote! { #func })
}
Item::Impl(mut item_impl) => (|| {
for impl_item in &mut item_impl.items {
if let ImplItem::Fn(method) = impl_item {
let override_expr = take_noop_returns(&mut method.attrs)?;
method.block = noop_block(&method.sig.output, override_expr.as_ref());
}
}
Ok(quote! { #item_impl })
})(),
other => Err(syn::Error::new_spanned(
&other,
"`make_noop` can only be applied to a function, a method, or an impl block",
)),
};
match result {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn noop_returns(attr: TokenStream, item: TokenStream) -> TokenStream {
let return_expr = parse_macro_input!(attr as Expr);
let input = parse_macro_input!(item as Item);
match input {
Item::Fn(mut func) => {
*func.block = noop_block(&func.sig.output, Some(&return_expr));
quote! { #func }.into()
}
Item::Impl(mut item_impl) => {
let result: syn::Result<proc_macro2::TokenStream> = (|| {
for impl_item in &mut item_impl.items {
if let ImplItem::Fn(method) = impl_item {
let override_expr = take_noop_returns(&mut method.attrs)?;
let expr = override_expr.as_ref().unwrap_or(&return_expr);
method.block = noop_block(&method.sig.output, Some(expr));
}
}
Ok(quote! { #item_impl })
})();
match result {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
other => {
let err = syn::Error::new_spanned(
&other,
"`noop_returns` can only be applied to a function, a method, or an impl block",
)
.to_compile_error();
quote! { #err #other }.into()
}
}
}
#[proc_macro_attribute]
pub fn make_unit(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new(Span::call_site(), "`make_unit` takes no arguments")
.to_compile_error()
.into();
}
let input = parse_macro_input!(item as Item);
match input {
Item::Struct(mut item_struct) => {
item_struct.fields = Fields::Unit;
item_struct.semi_token = Some(Default::default());
quote! { #item_struct }.into()
}
other => syn::Error::new_spanned(&other, "`make_unit` can only be applied to a struct")
.to_compile_error()
.into(),
}
}
fn take_noop_returns(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Expr>> {
attrs
.extract_if(.., |attr| attr.path().is_ident("noop_returns"))
.last()
.map(|attr| attr.parse_args::<Expr>())
.transpose()
}
fn noop_block(output: &syn::ReturnType, return_expr: Option<&Expr>) -> Block {
match output {
syn::ReturnType::Default => parse_quote!({}),
syn::ReturnType::Type(..) => match return_expr {
Some(expr) => parse_quote!({ #expr }),
None => parse_quote!({ ::core::default::Default::default() }),
},
}
}