1use proc_macro::TokenStream;
2use proc_macro_error2::{abort, abort_call_site};
3use quote::quote;
4use syn::{parse_macro_input, Attribute, ItemFn, ReturnType, Type};
5
6pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
7 if !args.is_empty() {
8 abort_call_site!("`#[defmt::panic_handler]` attribute takes no arguments");
9 }
10
11 let fun: ItemFn = parse_macro_input!(item as ItemFn);
12
13 validate(&fun);
14
15 codegen(&fun)
16}
17
18fn validate(fun: &ItemFn) {
19 let is_divergent: bool = match &fun.sig.output {
20 ReturnType::Default => false,
21 ReturnType::Type(_, ty: &Box) => matches!(&**ty, Type::Never(_)),
22 };
23
24 if fun.sig.constness.is_some()
25 || fun.sig.asyncness.is_some()
26 || fun.sig.unsafety.is_some()
27 || fun.sig.abi.is_some()
28 || !fun.sig.generics.params.is_empty()
29 || fun.sig.generics.where_clause.is_some()
30 || fun.sig.variadic.is_some()
31 || !fun.sig.inputs.is_empty()
32 || !is_divergent
33 {
34 abort!(fun.sig.ident, "function must have signature `fn() -> !`");
35 }
36
37 check_for_attribute_conflicts(attr_name:"panic_handler", &fun.attrs, &["export_name", "no_mangle"]);
38}
39
40/// Checks if any attribute in `attrs_to_check` is in `reject_list` and returns a compiler error if there's a match
41///
42/// The compiler error will indicate that the attribute conflicts with `attr_name`
43fn check_for_attribute_conflicts(
44 attr_name: &str,
45 attrs_to_check: &[Attribute],
46 reject_list: &[&str],
47) {
48 for attr: &Attribute in attrs_to_check {
49 if let Some(ident: &Ident) = attr.path().get_ident() {
50 let ident: String = ident.to_string();
51
52 if reject_list.contains(&ident.as_str()) {
53 abort!(
54 attr,
55 "`#[{}]` attribute cannot be used together with `#[{}]`",
56 attr_name,
57 ident
58 )
59 }
60 }
61 }
62}
63
64fn codegen(fun: &ItemFn) -> TokenStream {
65 let attrs: &Vec = &fun.attrs;
66 let block: &Box = &fun.block;
67 let ident: &Ident = &fun.sig.ident;
68
69 quoteTokenStream!(
70 #(#attrs)*
71 #[export_name = "_defmt_panic"]
72 #[inline(never)]
73 fn #ident() -> ! {
74 #block
75 }
76 )
77 .into()
78}
79