Skip to main content

announce_core/helper/
transformer.rs

1//! Transformers: list elements that rewrite the newest entry and forward.
2//!
3//! The caller places a transformer right after the definition it rewrites:
4//!
5//! ```text
6//! Partly! { [Partly!, my_lib::public_fields!, cb!] REST }
7//! ```
8//!
9//! `public_fields!` rewrites the newest entry (item and/or META) and forwards
10//! to `cb!`. The definer writes a plain `#[announce]`.
11
12use crate::helper::{Entry, Input};
13use proc_macro2::TokenStream;
14use syn::{Error, Item, Result};
15use template_quote::quote;
16
17impl Input {
18    /// The newest entry, the one a transformer rewrites.
19    pub fn newest(&self) -> Option<&Entry> {
20        self.entries.first()
21    }
22
23    /// For a transformer: forwards to the next element with the newest entry
24    /// replaced by `entry`. Errors if there is no entry or no next element.
25    pub fn forward_transformed(&self, entry: Entry) -> Result<TokenStream> {
26        if self.entries.is_empty() {
27            return Err(Error::new(
28                proc_macro2::Span::call_site(),
29                "announce: a transformer must come after the definition it transforms, \
30                 e.g. `[F!, transformer!, callback!]`",
31            ));
32        }
33        let mut next = self.clone();
34        next.entries[0] = entry;
35        next.forward().ok_or_else(|| {
36            Error::new(
37                proc_macro2::Span::call_site(),
38                "announce: a transformer cannot be the last element of the continuation list",
39            )
40        })
41    }
42}
43
44/// Runs a transformer: the body of a `#[proc_macro]` that gets the newest entry,
45/// rewrites it and forwards to the next list element.
46///
47/// - `input` is the input of the proc macro: the tokens that the trampoline before it
48///   sends, parsed as an [`Input`].
49/// - `f` is the transformer itself. It gets the newest [`Entry`] by value and returns
50///   the rewritten one, or a `syn::Error`.
51///
52/// The result forwards the rewritten entry to the next element of the list
53/// ([`Input::forward_transformed`]). A `syn::Error` (the input does not parse, there is
54/// no entry, the transformer is the last element of the list, or `f` fails) becomes a
55/// `compile_error!`, so the result can be returned from the proc macro as it is.
56///
57/// In a proc-macro crate this is a `#[proc_macro]` function. The example is run as a
58/// doctest, which cannot define a proc macro: it has no attribute, and `proc_macro`
59/// stands for `proc_macro2`.
60///
61/// ```
62/// # use announce_core as announce;
63/// # use proc_macro2 as proc_macro;
64/// # use template_quote as quote;
65/// /// Drops non-`pub` fields and lists the kept ones in META.
66/// pub fn public_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
67///     announce::helper::transformer(input.into(), |mut entry| {
68///         // `entry` is an `announce::helper::Entry`.
69///         let mut item: syn::ItemStruct = syn::parse2(entry.item.clone())?;
70///         if let syn::Fields::Named(fields) = &mut item.fields {
71///             let all = std::mem::take(&mut fields.named);
72///             fields.named = all
73///                 .into_iter()
74///                 .filter(|f| matches!(f.vis, syn::Visibility::Public(_)))
75///                 .collect();
76///             let kept = fields.named.iter().filter_map(|f| f.ident.as_ref());
77///             entry.meta.push(syn::parse_quote!(fields(#(#kept),*)));
78///         }
79///         entry.item = quote::quote!(#item);
80///         Ok(entry)
81///     })
82///     .into()
83/// }
84/// #
85/// # let out = public_fields(syn::parse_quote! {
86/// #     [t!, cb!] a::S! => [] { struct S { pub a: u8, b: u8, pub c: u8 } } REST
87/// # });
88/// # let out = out.to_string();
89/// # assert!(out.starts_with("cb !"), "{}", out);
90/// # assert!(out.contains("struct S { pub a : u8 , pub c : u8 }"), "{}", out);
91/// # assert!(out.contains("fields (a , c)"), "{}", out);
92/// ```
93///
94/// The function can be tested with `proc_macro2` streams, because it does not use the
95/// `proc_macro` crate:
96///
97/// ```
98/// # use announce_core as announce;
99/// use announce::helper::transformer;
100/// use proc_macro2::TokenStream;
101///
102/// let run = |input: TokenStream| {
103///     transformer(input, |mut entry| {
104///         entry.meta.push(syn::parse_quote!(seen));
105///         Ok(entry)
106///     })
107///     .to_string()
108/// };
109/// // The next element gets the rewritten entry.
110/// let out = run(syn::parse_quote! { [t!, cb!] a::S! => [] { struct S; } REST });
111/// assert!(out.starts_with("cb !") && out.contains("[seen]"), "{}", out);
112/// // Nothing to transform, and nothing to forward to.
113/// assert!(run(syn::parse_quote! { [t!, cb!] REST }).contains("compile_error"));
114/// assert!(run(syn::parse_quote! { [t!] a::S! => [] { struct S; } REST }).contains("compile_error"));
115/// ```
116///
117/// The macro `transformer!` (`announce::helper::transformer! { pub fn name(entry) { .. } }`)
118/// does the same and defines the `#[proc_macro]` function too. It is kept for
119/// compatibility; write the function above instead.
120pub fn transformer(input: TokenStream, f: impl FnOnce(Entry) -> Result<Entry>) -> TokenStream {
121    let run = || -> Result<TokenStream> {
122        let input: Input = syn::parse2(input)?;
123        let newest = input.newest().cloned().ok_or_else(|| {
124            Error::new(
125                proc_macro2::Span::call_site(),
126                "announce: a transformer must come after the definition it transforms",
127            )
128        })?;
129        input.forward_transformed(f(newest)?)
130    };
131    run().unwrap_or_else(|e| e.to_compile_error())
132}
133
134/// Defines a transformer: the same as the function [`transformer()`], in the form
135/// of a macro.
136///
137/// **Kept for compatibility; prefer the function [`transformer()`]**, called from a
138/// `#[proc_macro]` function you write yourself. This macro is a thin wrapper that
139/// defines that function: the body gets the newest entry as an [`Entry`] (as `entry`,
140/// by value, mutable) and returns `syn::Result<Entry>`.
141///
142/// ```ignore
143/// announce::helper::transformer! {
144///     /// Drops non-`pub` fields and lists the kept ones in META.
145///     pub fn public_fields(entry) {
146///         let mut item: syn::ItemStruct = syn::parse2(entry.item.clone())?;
147///         // ..
148///         entry.item = quote::quote!(#item);
149///         entry.meta.push(syn::parse_quote!(fields(a, c)));
150///         Ok(entry)
151///     }
152/// }
153/// ```
154///
155/// is the same as
156///
157/// ```
158/// # use announce_core as announce;
159/// # use proc_macro2 as proc_macro; // a doctest cannot define a `#[proc_macro]`
160/// # use template_quote as quote;
161/// /// Drops non-`pub` fields and lists the kept ones in META.
162/// pub fn public_fields(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
163///     announce::helper::transformer(ts.into(), |mut entry| {
164///         let mut item: syn::ItemStruct = syn::parse2(entry.item.clone())?;
165///         // ..
166///         entry.item = quote::quote!(#item);
167///         entry.meta.push(syn::parse_quote!(fields(a, c)));
168///         Ok(entry)
169///     })
170///     .into()
171/// }
172/// #
173/// # let out = public_fields(syn::parse_quote! { [t!, cb!] a::S! => [] { struct S; } REST });
174/// # let out = out.to_string();
175/// # assert!(out.starts_with("cb !") && out.contains("fields (a , c)"), "{}", out);
176/// ```
177#[doc(hidden)]
178#[macro_export]
179macro_rules! transformer {
180    ($(#[$m:meta])* $vis:vis fn $name:ident ($entry:ident) $body:block) => {
181        $(#[$m])*
182        #[proc_macro]
183        $vis fn $name(__announce_ts: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
184            ::core::convert::Into::into($crate::helper::transformer(
185                ::core::convert::Into::into(__announce_ts),
186                |__announce_entry: $crate::helper::Entry|
187                 -> $crate::helper::__private::syn::Result<$crate::helper::Entry> {
188                    #[allow(unused_mut)]
189                    let mut $entry: $crate::helper::Entry = __announce_entry;
190                    $body
191                },
192            ))
193        }
194    };
195}
196
197/// Strips an item down to its signatures. This is what the
198/// [`signature_only!`] transformer does, and custom transformers can use it too.
199/// That macro lists the rules: function bodies are replaced with
200/// `{ ::core::unimplemented!() }`, except in a `const fn` and in a fn that returns
201/// `impl Trait`.
202///
203/// [`signature_only!`]: https://docs.rs/announce/latest/announce/macro.signature_only.html
204pub fn signature_only(item: &mut Item) {
205    fn returns_impl(sig: &syn::Signature) -> bool {
206        fn has_impl(ts: TokenStream) -> bool {
207            ts.into_iter().any(|tt| match tt {
208                proc_macro2::TokenTree::Ident(i) => i == "impl",
209                proc_macro2::TokenTree::Group(g) => has_impl(g.stream()),
210                _ => false,
211            })
212        }
213        match &sig.output {
214            syn::ReturnType::Default => false,
215            syn::ReturnType::Type(_, ty) => has_impl(quote!(#ty)),
216        }
217    }
218    fn placeholder() -> syn::Block {
219        syn::parse_quote!({ ::core::unimplemented!() })
220    }
221    match item {
222        Item::Trait(t) => {
223            for ti in &mut t.items {
224                if let syn::TraitItem::Fn(f) = ti {
225                    if f.default.is_some() && !returns_impl(&f.sig) {
226                        f.default = Some(placeholder());
227                    }
228                }
229            }
230        }
231        Item::Impl(i) => {
232            for ii in &mut i.items {
233                if let syn::ImplItem::Fn(f) = ii {
234                    if !returns_impl(&f.sig) && f.sig.constness.is_none() {
235                        f.block = placeholder();
236                    }
237                }
238            }
239        }
240        Item::Fn(f) => {
241            // `const fn` bodies are kept: a re-emitted `const` may call them.
242            if !returns_impl(&f.sig) && f.sig.constness.is_none() {
243                *f.block = placeholder();
244            }
245        }
246        Item::Mod(m) => {
247            if let Some((_, items)) = &mut m.content {
248                for i in items {
249                    signature_only(i);
250                }
251            }
252        }
253        _ => {}
254    }
255}