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}