esp_hal_procmacros/
interrupt.rs

1use darling::{FromMeta, ast::NestedMeta};
2use proc_macro::{Span, TokenStream};
3use proc_macro_crate::{FoundCrate, crate_name};
4use proc_macro2::Ident;
5use syn::{
6    AttrStyle,
7    Attribute,
8    ItemFn,
9    ReturnType,
10    Type,
11    parse::Error as SynError,
12    spanned::Spanned,
13};
14
15pub enum WhiteListCaller {
16    Interrupt,
17}
18
19pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
20    #[derive(Debug, FromMeta)]
21    struct MacroArgs {
22        priority: Option<syn::Expr>,
23    }
24
25    let mut f: ItemFn = syn::parse(input).expect("`#[handler]` must be applied to a function");
26    let original_span = f.span();
27
28    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
29        Ok(v) => v,
30        Err(e) => {
31            return TokenStream::from(darling::Error::from(e).write_errors());
32        }
33    };
34
35    let args = match MacroArgs::from_list(&attr_args) {
36        Ok(v) => v,
37        Err(e) => {
38            return TokenStream::from(e.write_errors());
39        }
40    };
41
42    let root = Ident::new(
43        match crate_name("esp-hal") {
44            Ok(FoundCrate::Name(ref name)) => name,
45            _ => "crate",
46        },
47        Span::call_site().into(),
48    );
49
50    let priority = match args.priority {
51        Some(priority) => {
52            quote::quote!( #priority )
53        }
54        _ => {
55            quote::quote! { #root::interrupt::Priority::min() }
56        }
57    };
58
59    // XXX should we blacklist other attributes?
60
61    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
62        return error;
63    }
64
65    let valid_signature = f.sig.constness.is_none()
66        && f.sig.abi.is_none()
67        && f.sig.generics.params.is_empty()
68        && f.sig.generics.where_clause.is_none()
69        && f.sig.variadic.is_none()
70        && match f.sig.output {
71            ReturnType::Default => true,
72            ReturnType::Type(_, ref ty) => match **ty {
73                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
74                Type::Never(..) => true,
75                _ => false,
76            },
77        }
78        && f.sig.inputs.len() <= 1;
79
80    if !valid_signature {
81        return SynError::new(
82            f.span(),
83            "`#[handler]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`",
84        )
85        .to_compile_error()
86        .into();
87    }
88
89    f.sig.abi = syn::parse_quote_spanned!(original_span => extern "C");
90    let orig = f.sig.ident;
91    let vis = f.vis.clone();
92    f.sig.ident = Ident::new(
93        &format!("__esp_hal_internal_{orig}"),
94        proc_macro2::Span::call_site(),
95    );
96    let new = f.sig.ident.clone();
97
98    quote::quote_spanned!(original_span =>
99        #f
100
101        const _: () = {
102            core::assert!(
103            match #priority {
104                #root::interrupt::Priority::None => false,
105                _ => true,
106            },
107            "Priority::None is not supported");
108        };
109
110        #[allow(non_upper_case_globals)]
111        #vis const #orig: #root::interrupt::InterruptHandler = #root::interrupt::InterruptHandler::new(#new, #priority);
112    )
113    .into()
114}
115
116pub fn check_attr_whitelist(
117    attrs: &[Attribute],
118    caller: WhiteListCaller,
119) -> Result<(), TokenStream> {
120    let whitelist = &[
121        "doc",
122        "link_section",
123        "cfg",
124        "allow",
125        "warn",
126        "deny",
127        "forbid",
128        "cold",
129        "ram",
130        "inline",
131    ];
132
133    'o: for attr in attrs {
134        for val in whitelist {
135            if eq(attr, val) {
136                continue 'o;
137            }
138        }
139
140        let err_str = match caller {
141            WhiteListCaller::Interrupt => {
142                "this attribute is not allowed on an interrupt handler controlled by esp-hal"
143            }
144        };
145
146        return Err(SynError::new(attr.span(), err_str)
147            .to_compile_error()
148            .into());
149    }
150
151    Ok(())
152}
153
154/// Returns `true` if `attr.path` matches `name`
155fn eq(attr: &Attribute, name: &str) -> bool {
156    attr.style == AttrStyle::Outer && attr.path().is_ident(name)
157}