use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{FnArg, ItemFn, ReturnType, Type, Visibility, parse_macro_input, spanned::Spanned};
pub fn irq_handler(args: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
if !args.is_empty() {
return syn::Error::new(
Span::call_site(),
"`#[irq_handler]` does not accept any arguments",
)
.to_compile_error()
.into();
}
if let Some(err) = validate_signature(&f) {
return err.to_compile_error().into();
}
let input_arg = match validate_args(&f.sig.inputs) {
Ok(arg) => arg,
Err(err) => return err.to_compile_error().into(),
};
if let Some(err) = validate_return_type(&f.sig.output) {
return err.to_compile_error().into();
}
let attrs = f.attrs;
let unsafety = f.sig.unsafety;
let stmts = f.block.stmts;
let expanded = quote! {
#[unsafe(no_mangle)]
#(#attrs)*
pub #unsafety extern "Rust" fn _someboot_handle_irq(#input_arg) {
#(#stmts)*
}
};
expanded.into()
}
fn validate_signature(f: &ItemFn) -> Option<syn::Error> {
if f.sig.constness.is_some() {
return Some(syn::Error::new(
f.span(),
"`#[irq_handler]` function cannot be const",
));
}
if f.sig.asyncness.is_some() {
return Some(syn::Error::new(
f.span(),
"`#[irq_handler]` function cannot be async",
));
}
if !f.sig.generics.params.is_empty() || f.sig.generics.where_clause.is_some() {
return Some(syn::Error::new(
f.sig.generics.span(),
"`#[irq_handler]` function cannot have generic parameters",
));
}
if f.sig.variadic.is_some() {
return Some(syn::Error::new(
f.sig.variadic.span(),
"`#[irq_handler]` function cannot be variadic",
));
}
if !matches!(f.vis, Visibility::Inherited) {
return Some(syn::Error::new(
f.span(),
"`#[irq_handler]` function should not have explicit visibility",
));
}
None
}
fn validate_args(
inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
) -> Result<&FnArg, syn::Error> {
match inputs.len() {
0 => Err(syn::Error::new(
inputs.span(),
"`#[irq_handler]` function must have exactly one `IrqId` parameter",
)),
1 => {
match inputs.first().unwrap() {
FnArg::Typed(arg) => {
if !is_irq_id_type(&arg.ty) {
return Err(syn::Error::new(
arg.ty.span(),
"`#[irq_handler]` argument type must be `someboot::irq::IrqId`",
));
}
Ok(inputs.first().unwrap())
}
FnArg::Receiver(receiver) => Err(syn::Error::new(
receiver.span(),
"`#[irq_handler]` function cannot have `self` parameter",
)),
}
}
_ => Err(syn::Error::new(
inputs.span(),
"`#[irq_handler]` function must have exactly one `IrqId` parameter",
)),
}
}
fn is_irq_id_type(ty: &Type) -> bool {
match ty {
Type::Path(path) => {
let type_str = quote!(#path).to_string();
type_str.ends_with("IrqId") || type_str.contains("irq::IrqId")
}
_ => false,
}
}
fn validate_return_type(ret: &ReturnType) -> Option<syn::Error> {
match ret {
ReturnType::Default => None,
ReturnType::Type(_, ty) => Some(syn::Error::new(
ty.span(),
"`#[irq_handler]` function must not have a return type",
)),
}
}