Skip to main content

miniextendr_macros/
cfg_gates.rs

1//! Resolve `cfg` / `cfg_attr` on the members of a `#[miniextendr]` item before
2//! anything is generated from it.
3//!
4//! An attribute macro receives its item unconfigured: rustc has not evaluated
5//! the gates on the item's methods, trait items, parameters, variants or fields.
6//! Forwarding a member's gate to some generated items is not enough, because
7//! item-wide analysis (constructor inference, S7 properties, name collisions,
8//! factor levels, R formals) and the generated R text must see the same members
9//! the compiled item has. So the macro re-enters itself on two gated copies of
10//! the item, one per value of the first member gate it finds. rustc strips the
11//! inactive copy before expanding it, so each pass resolves one gate and
12//! configurations are never enumerated. The item stays whole: splitting an impl
13//! into separately registered blocks would change constructor inference and the
14//! ownership of S7 properties.
15//!
16//! Gates on the item itself need no re-entry: rustc evaluates them when they
17//! precede `#[miniextendr]`, and the expansion forwards them otherwise.
18
19use proc_macro2::TokenStream;
20use quote::quote;
21use syn::{Attribute, Item, Meta, Token, punctuated::Punctuated};
22
23/// Re-enter `#[miniextendr(attr)]` on both resolutions of the item's first
24/// member gate, or return `None` when no member carries one.
25pub(crate) fn select(attr: &TokenStream, item: &Item) -> syn::Result<Option<TokenStream>> {
26    for member in 0..member_count(item) {
27        let Some(attrs) = member_attrs(item, member) else {
28            continue;
29        };
30        let Some(position) = attrs
31            .iter()
32            .position(|gate| gate.path().is_ident("cfg") || gate.path().is_ident("cfg_attr"))
33        else {
34            continue;
35        };
36        let gate = &attrs[position];
37        let mut enabled = item.clone();
38        let mut disabled = item.clone();
39        let enabled_attrs = member_attrs_mut(&mut enabled, member);
40        enabled_attrs.remove(position);
41        let predicate = if gate.path().is_ident("cfg") {
42            remove_member(&mut disabled, member);
43            gate.parse_args::<Meta>()?
44        } else {
45            let mut args = gate
46                .parse_args_with(Punctuated::<Meta, ::syn::token::CommaToken![,]>::parse_terminated)?
47                .into_iter();
48            let predicate = args
49                .next()
50                .ok_or_else(|| syn::Error::new_spanned(gate, "expected a cfg_attr predicate"))?;
51            enabled_attrs.splice(
52                position..position,
53                args.map(|meta| -> Attribute { ::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::__private::push_pound(&mut _s);
        ::quote::__private::push_group(&mut _s,
            ::quote::__private::Delimiter::Bracket,
            {
                let mut _s = ::quote::__private::TokenStream::new();
                ::quote::ToTokens::to_tokens(&meta, &mut _s);
                _s
            });
        _s
    })syn::parse_quote!(#[#meta]) }),
54            );
55            member_attrs_mut(&mut disabled, member).remove(position);
56            predicate
57        };
58        return Ok(Some({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "cfg");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&predicate, &mut _s);
                    _s
                });
            _s
        });
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&attr, &mut _s);
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&enabled, &mut _s);
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "cfg");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "not");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::ToTokens::to_tokens(&predicate, &mut _s);
                            _s
                        });
                    _s
                });
            _s
        });
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&attr, &mut _s);
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&disabled, &mut _s);
    _s
}quote! {
59            #[cfg(#predicate)]
60            #[::miniextendr_api::miniextendr(#attr)]
61            #enabled
62
63            #[cfg(not(#predicate))]
64            #[::miniextendr_api::miniextendr(#attr)]
65            #disabled
66        }));
67    }
68    Ok(None)
69}
70
71/// Number of gateable members: function parameters, impl and trait items, enum
72/// variants, struct fields.
73fn member_count(item: &Item) -> usize {
74    match item {
75        Item::Fn(item) => item.sig.inputs.len(),
76        Item::Impl(item) => item.items.len(),
77        Item::Trait(item) => item.items.len(),
78        Item::Enum(item) => item.variants.len(),
79        Item::Struct(item) => item.fields.len(),
80        _ => 0,
81    }
82}
83
84fn member_attrs(item: &Item, member: usize) -> Option<&Vec<Attribute>> {
85    match item {
86        Item::Fn(item) => Some(match &item.sig.inputs[member] {
87            syn::FnArg::Receiver(receiver) => &receiver.attrs,
88            syn::FnArg::Typed(typed) => &typed.attrs,
89        }),
90        Item::Impl(item) => match &item.items[member] {
91            syn::ImplItem::Fn(member) => Some(&member.attrs),
92            syn::ImplItem::Const(member) => Some(&member.attrs),
93            syn::ImplItem::Type(member) => Some(&member.attrs),
94            syn::ImplItem::Macro(member) => Some(&member.attrs),
95            _ => None,
96        },
97        Item::Trait(item) => match &item.items[member] {
98            syn::TraitItem::Fn(member) => Some(&member.attrs),
99            syn::TraitItem::Const(member) => Some(&member.attrs),
100            syn::TraitItem::Type(member) => Some(&member.attrs),
101            syn::TraitItem::Macro(member) => Some(&member.attrs),
102            _ => None,
103        },
104        Item::Enum(item) => Some(&item.variants[member].attrs),
105        Item::Struct(item) => item.fields.iter().nth(member).map(|field| &field.attrs),
106        _ => None,
107    }
108}
109
110/// Only called for a member `member_attrs` returned.
111fn member_attrs_mut(item: &mut Item, member: usize) -> &mut Vec<Attribute> {
112    match item {
113        Item::Fn(item) => match &mut item.sig.inputs[member] {
114            syn::FnArg::Receiver(receiver) => &mut receiver.attrs,
115            syn::FnArg::Typed(typed) => &mut typed.attrs,
116        },
117        Item::Impl(item) => match &mut item.items[member] {
118            syn::ImplItem::Fn(member) => &mut member.attrs,
119            syn::ImplItem::Const(member) => &mut member.attrs,
120            syn::ImplItem::Type(member) => &mut member.attrs,
121            syn::ImplItem::Macro(member) => &mut member.attrs,
122            _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
123        },
124        Item::Trait(item) => match &mut item.items[member] {
125            syn::TraitItem::Fn(member) => &mut member.attrs,
126            syn::TraitItem::Const(member) => &mut member.attrs,
127            syn::TraitItem::Type(member) => &mut member.attrs,
128            syn::TraitItem::Macro(member) => &mut member.attrs,
129            _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
130        },
131        Item::Enum(item) => &mut item.variants[member].attrs,
132        Item::Struct(item) => &mut item.fields.iter_mut().nth(member).unwrap().attrs,
133        _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
134    }
135}
136
137fn remove_member(item: &mut Item, member: usize) {
138    match item {
139        Item::Fn(item) => remove_at(&mut item.sig.inputs, member),
140        Item::Impl(item) => {
141            item.items.remove(member);
142        }
143        Item::Trait(item) => {
144            item.items.remove(member);
145        }
146        Item::Enum(item) => remove_at(&mut item.variants, member),
147        Item::Struct(item) => match &mut item.fields {
148            syn::Fields::Named(fields) => remove_at(&mut fields.named, member),
149            syn::Fields::Unnamed(fields) => remove_at(&mut fields.unnamed, member),
150            syn::Fields::Unit => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
151        },
152        _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
153    }
154}
155
156fn remove_at<T, P: Default>(list: &mut Punctuated<T, P>, index: usize) {
157    *list = std::mem::take(list)
158        .into_iter()
159        .enumerate()
160        .filter_map(|(position, member)| (position != index).then_some(member))
161        .collect();
162}