cortex_m_rt_macros/
lib.rs

1//! Internal implementation details of `cortex-m-rt`.
2//!
3//! Do not use this crate directly.
4
5extern crate proc_macro;
6
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::quote;
10use std::iter;
11use std::{collections::HashSet, fmt::Display};
12use syn::{
13    parse::{self, Parse},
14    parse_macro_input,
15    spanned::Spanned,
16    AttrStyle, Attribute, FnArg, Ident, Item, ItemFn, ItemStatic, ReturnType, Stmt, Type,
17    Visibility,
18};
19
20#[proc_macro_attribute]
21pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
22    let mut f = parse_macro_input!(input as ItemFn);
23
24    // check the function signature
25    let valid_signature = f.sig.constness.is_none()
26        && f.vis == Visibility::Inherited
27        && f.sig.abi.is_none()
28        && f.sig.inputs.is_empty()
29        && f.sig.generics.params.is_empty()
30        && f.sig.generics.where_clause.is_none()
31        && f.sig.variadic.is_none()
32        && match f.sig.output {
33            ReturnType::Default => false,
34            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
35        };
36
37    if !valid_signature {
38        return parse::Error::new(
39            f.span(),
40            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
41        )
42        .to_compile_error()
43        .into();
44    }
45
46    if !args.is_empty() {
47        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
48            .to_compile_error()
49            .into();
50    }
51
52    // XXX should we blacklist other attributes?
53    let (statics, stmts) = match extract_static_muts(f.block.stmts) {
54        Err(e) => return e.to_compile_error().into(),
55        Ok(x) => x,
56    };
57
58    f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
59    f.sig.inputs.extend(statics.iter().map(|statik| {
60        let ident = &statik.ident;
61        let ty = &statik.ty;
62        let attrs = &statik.attrs;
63
64        // Note that we use an explicit `'static` lifetime for the entry point arguments. This makes
65        // it more flexible, and is sound here, since the entry will not be called again, ever.
66        syn::parse::<FnArg>(
67            quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
68        )
69        .unwrap()
70    }));
71    f.block.stmts = stmts;
72
73    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
74    let ident = &f.sig.ident;
75
76    let resource_args = statics
77        .iter()
78        .map(|statik| {
79            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
80            let ident = &statik.ident;
81            let ty = &statik.ty;
82            let expr = &statik.expr;
83            quote! {
84                #(#cfgs)*
85                {
86                    #(#attrs)*
87                    static mut #ident: #ty = #expr;
88                    unsafe { &mut #ident }
89                }
90            }
91        })
92        .collect::<Vec<_>>();
93
94    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
95        return error;
96    }
97
98    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
99
100    quote!(
101        #(#cfgs)*
102        #(#attrs)*
103        #[doc(hidden)]
104        #[export_name = "main"]
105        pub unsafe extern "C" fn #tramp_ident() {
106            #[allow(static_mut_refs)]
107            #ident(
108                #(#resource_args),*
109            )
110        }
111
112        #f
113    )
114    .into()
115}
116
117#[derive(Debug, PartialEq)]
118enum Exception {
119    DefaultHandler,
120    HardFault(HardFaultArgs),
121    NonMaskableInt,
122    Other,
123}
124
125impl Display for Exception {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Exception::DefaultHandler => write!(f, "`DefaultHandler`"),
129            Exception::HardFault(_) => write!(f, "`HardFault` handler"),
130            Exception::NonMaskableInt => write!(f, "`NonMaskableInt` handler"),
131            Exception::Other => write!(f, "Other exception handler"),
132        }
133    }
134}
135
136#[derive(Debug, PartialEq)]
137struct HardFaultArgs {
138    trampoline: bool,
139}
140
141impl Default for HardFaultArgs {
142    fn default() -> Self {
143        Self { trampoline: true }
144    }
145}
146
147impl Parse for HardFaultArgs {
148    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
149        let mut items = Vec::new();
150        // Read a list of `ident = value,`
151        loop {
152            if input.is_empty() {
153                break;
154            }
155
156            let name = input.parse::<Ident>()?;
157            input.parse::<syn::Token!(=)>()?;
158            let value = input.parse::<syn::Lit>()?;
159
160            items.push((name, value));
161
162            if input.is_empty() {
163                break;
164            }
165
166            input.parse::<syn::Token!(,)>()?;
167        }
168
169        let mut args = Self::default();
170
171        for (name, value) in items {
172            match name.to_string().as_str() {
173                "trampoline" => match value {
174                    syn::Lit::Bool(val) => {
175                        args.trampoline = val.value();
176                    }
177                    _ => {
178                        return Err(syn::Error::new_spanned(
179                            value,
180                            "Not a valid value. `trampoline` takes a boolean literal",
181                        ))
182                    }
183                },
184                _ => {
185                    return Err(syn::Error::new_spanned(name, "Not a valid argument name"));
186                }
187            }
188        }
189
190        Ok(args)
191    }
192}
193
194#[proc_macro_attribute]
195pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
196    let mut f = parse_macro_input!(input as ItemFn);
197
198    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
199        return error;
200    }
201
202    let fspan = f.span();
203    let ident = f.sig.ident.clone();
204
205    let ident_s = ident.to_string();
206    let exn = match &*ident_s {
207        "DefaultHandler" => {
208            if !args.is_empty() {
209                return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
210                    .to_compile_error()
211                    .into();
212            }
213            Exception::DefaultHandler
214        }
215        "HardFault" => Exception::HardFault(parse_macro_input!(args)),
216        "NonMaskableInt" => {
217            if !args.is_empty() {
218                return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
219                    .to_compile_error()
220                    .into();
221            }
222            Exception::NonMaskableInt
223        }
224        // NOTE that at this point we don't check if the exception is available on the target (e.g.
225        // MemoryManagement is not available on Cortex-M0)
226        "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
227        | "DebugMonitor" | "PendSV" | "SysTick" => {
228            if !args.is_empty() {
229                return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
230                    .to_compile_error()
231                    .into();
232            }
233
234            Exception::Other
235        }
236        _ => {
237            return parse::Error::new(ident.span(), "This is not a valid exception name")
238                .to_compile_error()
239                .into();
240        }
241    };
242
243    if f.sig.unsafety.is_none() {
244        match exn {
245            Exception::DefaultHandler | Exception::HardFault(_) | Exception::NonMaskableInt => {
246                // These are unsafe to define.
247                let name = format!("{}", exn);
248                return parse::Error::new(ident.span(), format_args!("defining a {} is unsafe and requires an `unsafe fn` (see the cortex-m-rt docs)", name))
249                    .to_compile_error()
250                    .into();
251            }
252            Exception::Other => {}
253        }
254    }
255
256    // Emit a reference to the `Exception` variant corresponding to our exception.
257    // This will fail compilation when the target doesn't have that exception.
258    let assertion = match exn {
259        Exception::Other => {
260            quote! {
261                const _: () = {
262                    let _ = ::cortex_m_rt::Exception::#ident;
263                };
264            }
265        }
266        _ => quote!(),
267    };
268
269    let handler = match exn {
270        Exception::DefaultHandler => {
271            let valid_signature = f.sig.constness.is_none()
272                && f.vis == Visibility::Inherited
273                && f.sig.abi.is_none()
274                && f.sig.inputs.len() == 1
275                && f.sig.generics.params.is_empty()
276                && f.sig.generics.where_clause.is_none()
277                && f.sig.variadic.is_none()
278                && match f.sig.output {
279                    ReturnType::Default => true,
280                    ReturnType::Type(_, ref ty) => match **ty {
281                        Type::Tuple(ref tuple) => tuple.elems.is_empty(),
282                        Type::Never(..) => true,
283                        _ => false,
284                    },
285                };
286
287            if !valid_signature {
288                return parse::Error::new(
289                    fspan,
290                    "`DefaultHandler` must have signature `unsafe fn(i16) [-> !]`",
291                )
292                .to_compile_error()
293                .into();
294            }
295
296            f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
297            let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
298            let ident = &f.sig.ident;
299
300            let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
301
302            quote!(
303                #(#cfgs)*
304                #(#attrs)*
305                #[doc(hidden)]
306                #[export_name = #ident_s]
307                pub unsafe extern "C" fn #tramp_ident() {
308                    extern crate core;
309
310                    const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
311
312                    let irqn = unsafe { (core::ptr::read_volatile(SCB_ICSR) & 0x1FF) as i16 - 16 };
313
314                    #ident(irqn)
315                }
316
317                #f
318            )
319        }
320        Exception::HardFault(args) => {
321            let valid_signature = f.sig.constness.is_none()
322                && f.vis == Visibility::Inherited
323                && f.sig.abi.is_none()
324                && if args.trampoline {
325                    f.sig.inputs.len() == 1
326                        && match &f.sig.inputs[0] {
327                            FnArg::Typed(arg) => match arg.ty.as_ref() {
328                                Type::Reference(r) => {
329                                    r.lifetime.is_none() && r.mutability.is_none()
330                                }
331                                _ => false,
332                            },
333                            _ => false,
334                        }
335                } else {
336                    f.sig.inputs.is_empty()
337                }
338                && f.sig.generics.params.is_empty()
339                && f.sig.generics.where_clause.is_none()
340                && f.sig.variadic.is_none()
341                && match f.sig.output {
342                    ReturnType::Default => false,
343                    ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
344                };
345
346            if !valid_signature {
347                return parse::Error::new(
348                    fspan,
349                    if args.trampoline {
350                        "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`"
351                    } else {
352                        "`HardFault` handler must have signature `unsafe fn() -> !`"
353                    },
354                )
355                .to_compile_error()
356                .into();
357            }
358
359            f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
360            let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
361
362            if args.trampoline {
363                let ident = &f.sig.ident;
364
365                let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
366
367                quote!(
368                    #(#cfgs)*
369                    #(#attrs)*
370                    #[doc(hidden)]
371                    #[export_name = "_HardFault"]
372                    // Only emit link_section when building for embedded targets,
373                    // because some hosted platforms (used to check the build)
374                    // cannot handle the long link section names.
375                    #[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
376                    unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) {
377                        #ident(frame)
378                    }
379
380                    #f
381
382                    // HardFault exceptions are bounced through this trampoline which grabs the stack pointer at
383                    // the time of the exception and passes it to the user's HardFault handler in r0.
384                    // Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP.
385                    core::arch::global_asm!(
386                        ".cfi_sections .debug_frame
387                        .section .HardFaultTrampoline, \"ax\"
388                        .global HardFault
389                        .type HardFault,%function
390                        .thumb_func
391                        .cfi_startproc
392                        HardFault:",
393                           "mov r0, lr
394                            movs r1, #4
395                            tst r0, r1
396                            bne 0f
397                            mrs r0, MSP
398                            b _HardFault
399                        0:
400                            mrs r0, PSP
401                            b _HardFault",
402                        ".cfi_endproc
403                        .size HardFault, . - HardFault",
404                    );
405                )
406            } else {
407                quote!(
408                    #[doc(hidden)]
409                    #[export_name = "_HardFault"]
410                    unsafe extern "C" fn #tramp_ident() {
411                        // This trampoline has no function except making the compiler diagnostics better.
412                    }
413
414                    #[export_name = "HardFault"]
415                    // Only emit link_section when building for embedded targets,
416                    // because some hosted platforms (used to check the build)
417                    // cannot handle the long link section names.
418                    #[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
419                    #f
420                )
421            }
422        }
423        Exception::NonMaskableInt | Exception::Other => {
424            let valid_signature = f.sig.constness.is_none()
425                && f.vis == Visibility::Inherited
426                && f.sig.abi.is_none()
427                && f.sig.inputs.is_empty()
428                && f.sig.generics.params.is_empty()
429                && f.sig.generics.where_clause.is_none()
430                && f.sig.variadic.is_none()
431                && match f.sig.output {
432                    ReturnType::Default => true,
433                    ReturnType::Type(_, ref ty) => match **ty {
434                        Type::Tuple(ref tuple) => tuple.elems.is_empty(),
435                        Type::Never(..) => true,
436                        _ => false,
437                    },
438                };
439
440            if !valid_signature {
441                return parse::Error::new(
442                    fspan,
443                    "`#[exception]` handlers other than `DefaultHandler` and `HardFault` must have \
444                     signature `[unsafe] fn() [-> !]`",
445                )
446                .to_compile_error()
447                .into();
448            }
449
450            let (statics, stmts) = match extract_static_muts(f.block.stmts) {
451                Err(e) => return e.to_compile_error().into(),
452                Ok(x) => x,
453            };
454
455            f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
456            f.sig.inputs.extend(statics.iter().map(|statik| {
457                let ident = &statik.ident;
458                let ty = &statik.ty;
459                let attrs = &statik.attrs;
460                syn::parse::<FnArg>(
461                    quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into(),
462                )
463                .unwrap()
464            }));
465            f.block.stmts = iter::once(
466                syn::parse2(quote! {{
467                    // check that this exception actually exists
468                    ::cortex_m_rt::exception::#ident;
469                }})
470                .unwrap(),
471            )
472            .chain(stmts)
473            .collect();
474
475            let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
476            let ident = &f.sig.ident;
477
478            let resource_args = statics
479                .iter()
480                .map(|statik| {
481                    let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
482                    let ident = &statik.ident;
483                    let ty = &statik.ty;
484                    let expr = &statik.expr;
485                    quote! {
486                        #(#cfgs)*
487                        {
488                            #(#attrs)*
489                            static mut #ident: #ty = #expr;
490                            unsafe { &mut #ident }
491                        }
492                    }
493                })
494                .collect::<Vec<_>>();
495
496            let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
497
498            quote!(
499                #(#cfgs)*
500                #(#attrs)*
501                #[doc(hidden)]
502                #[export_name = #ident_s]
503                pub unsafe extern "C" fn #tramp_ident() {
504                    #[allow(static_mut_refs)]
505                    #ident(
506                        #(#resource_args),*
507                    )
508                }
509
510                #f
511            )
512        }
513    };
514
515    quote!(
516        #assertion
517        #handler
518    )
519    .into()
520}
521
522#[proc_macro_attribute]
523pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
524    let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
525
526    if !args.is_empty() {
527        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
528            .to_compile_error()
529            .into();
530    }
531
532    let fspan = f.span();
533    let ident = f.sig.ident.clone();
534    let ident_s = ident.to_string();
535
536    // XXX should we blacklist other attributes?
537
538    let valid_signature = f.sig.constness.is_none()
539        && f.vis == Visibility::Inherited
540        && f.sig.abi.is_none()
541        && f.sig.inputs.is_empty()
542        && f.sig.generics.params.is_empty()
543        && f.sig.generics.where_clause.is_none()
544        && f.sig.variadic.is_none()
545        && match f.sig.output {
546            ReturnType::Default => true,
547            ReturnType::Type(_, ref ty) => match **ty {
548                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
549                Type::Never(..) => true,
550                _ => false,
551            },
552        };
553
554    if !valid_signature {
555        return parse::Error::new(
556            fspan,
557            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
558        )
559        .to_compile_error()
560        .into();
561    }
562
563    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
564        Err(e) => return e.to_compile_error().into(),
565        Ok(x) => x,
566    };
567
568    f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
569    f.sig.inputs.extend(statics.iter().map(|statik| {
570        let ident = &statik.ident;
571        let ty = &statik.ty;
572        let attrs = &statik.attrs;
573        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
574            .unwrap()
575    }));
576    f.block.stmts = iter::once(
577        syn::parse2(quote! {{
578            // Check that this interrupt actually exists
579            interrupt::#ident;
580        }})
581        .unwrap(),
582    )
583    .chain(stmts)
584    .collect();
585
586    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
587    let ident = &f.sig.ident;
588
589    let resource_args = statics
590        .iter()
591        .map(|statik| {
592            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
593            let ident = &statik.ident;
594            let ty = &statik.ty;
595            let expr = &statik.expr;
596            quote! {
597                #(#cfgs)*
598                {
599                    #(#attrs)*
600                    static mut #ident: #ty = #expr;
601                    unsafe { &mut #ident }
602                }
603            }
604        })
605        .collect::<Vec<_>>();
606
607    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
608        return error;
609    }
610
611    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
612
613    quote!(
614        #(#cfgs)*
615        #(#attrs)*
616        #[doc(hidden)]
617        #[export_name = #ident_s]
618        pub unsafe extern "C" fn #tramp_ident() {
619            #[allow(static_mut_refs)]
620            #ident(
621                #(#resource_args),*
622            )
623        }
624
625        #f
626    )
627    .into()
628}
629
630#[proc_macro_attribute]
631pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
632    let f = parse_macro_input!(input as ItemFn);
633
634    // check the function signature
635    let valid_signature = f.sig.constness.is_none()
636        && f.vis == Visibility::Inherited
637        && f.sig.unsafety.is_some()
638        && f.sig.abi.is_none()
639        && f.sig.inputs.is_empty()
640        && f.sig.generics.params.is_empty()
641        && f.sig.generics.where_clause.is_none()
642        && f.sig.variadic.is_none()
643        && match f.sig.output {
644            ReturnType::Default => true,
645            ReturnType::Type(_, ref ty) => match **ty {
646                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
647                _ => false,
648            },
649        };
650
651    if !valid_signature {
652        return parse::Error::new(
653            f.span(),
654            "`#[pre_init]` function must have signature `unsafe fn()`",
655        )
656        .to_compile_error()
657        .into();
658    }
659
660    if !args.is_empty() {
661        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
662            .to_compile_error()
663            .into();
664    }
665
666    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
667        return error;
668    }
669
670    // XXX should we blacklist other attributes?
671    let attrs = f.attrs;
672    let ident = f.sig.ident;
673    let block = f.block;
674
675    quote!(
676        #[export_name = "__pre_init"]
677        #[allow(missing_docs)]  // we make a private fn public, which can trigger this lint
678        #(#attrs)*
679        pub unsafe fn #ident() #block
680    )
681    .into()
682}
683
684/// Extracts `static mut` vars from the beginning of the given statements
685fn extract_static_muts(
686    stmts: impl IntoIterator<Item = Stmt>,
687) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
688    let mut istmts = stmts.into_iter();
689
690    let mut seen = HashSet::new();
691    let mut statics = vec![];
692    let mut stmts = vec![];
693    for stmt in istmts.by_ref() {
694        match stmt {
695            Stmt::Item(Item::Static(var)) => match var.mutability {
696                syn::StaticMutability::Mut(_) => {
697                    if seen.contains(&var.ident) {
698                        return Err(parse::Error::new(
699                            var.ident.span(),
700                            format!("the name `{}` is defined multiple times", var.ident),
701                        ));
702                    }
703
704                    seen.insert(var.ident.clone());
705                    statics.push(var);
706                }
707                _ => stmts.push(Stmt::Item(Item::Static(var))),
708            },
709            _ => {
710                stmts.push(stmt);
711                break;
712            }
713        }
714    }
715
716    stmts.extend(istmts);
717
718    Ok((statics, stmts))
719}
720
721fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
722    let mut cfgs = vec![];
723    let mut not_cfgs = vec![];
724
725    for attr in attrs {
726        if eq(&attr, "cfg") {
727            cfgs.push(attr);
728        } else {
729            not_cfgs.push(attr);
730        }
731    }
732
733    (cfgs, not_cfgs)
734}
735
736enum WhiteListCaller {
737    Entry,
738    Exception,
739    Interrupt,
740    PreInit,
741}
742
743fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
744    let whitelist = &[
745        "doc",
746        "link_section",
747        "cfg",
748        "allow",
749        "warn",
750        "deny",
751        "forbid",
752        "cold",
753        "naked",
754        "expect",
755    ];
756
757    'o: for attr in attrs {
758        for val in whitelist {
759            if eq(attr, val) {
760                continue 'o;
761            }
762        }
763
764        let err_str = match caller {
765            WhiteListCaller::Entry => "this attribute is not allowed on a cortex-m-rt entry point",
766            WhiteListCaller::Exception => {
767                "this attribute is not allowed on an exception handler controlled by cortex-m-rt"
768            }
769            WhiteListCaller::Interrupt => {
770                "this attribute is not allowed on an interrupt handler controlled by cortex-m-rt"
771            }
772            WhiteListCaller::PreInit => {
773                "this attribute is not allowed on a pre-init controlled by cortex-m-rt"
774            }
775        };
776
777        return Err(parse::Error::new(attr.span(), err_str)
778            .to_compile_error()
779            .into());
780    }
781
782    Ok(())
783}
784
785/// Returns `true` if `attr.path` matches `name`
786fn eq(attr: &Attribute, name: &str) -> bool {
787    attr.style == AttrStyle::Outer && attr.path().is_ident(name)
788}