Skip to main content

announce_core/
options.rs

1use proc_macro2::{Ident, TokenStream};
2use syn::parse::{Parse, ParseStream};
3use syn::{Error, LitBool, Path, Result, Token};
4use template_quote::{quote, ToTokens};
5
6/// One check or `meta` entry of the attribute: `path` or `path(args)`.
7///
8/// As a check (`#[announce(my_lib::check(args))]`) it runs at the definition,
9/// as `path! { (args), ITEM }`. As a `meta` entry it is only listed in `META`.
10#[derive(Clone)]
11pub struct Check {
12    /// The macro path.
13    pub path: Path,
14    /// The tokens inside the parentheses, if there were any.
15    pub args: Option<TokenStream>,
16}
17
18impl ToTokens for Check {
19    fn to_tokens(&self, tokens: &mut TokenStream) {
20        let p = &self.path;
21        match &self.args {
22            Some(a) => tokens.extend(quote! { #p(#a) }),
23            None => tokens.extend(quote! { #p }),
24        }
25    }
26}
27
28/// How the generated trampoline holds the item (`layout = ..`, advanced).
29///
30/// The default, [`Layout::Auto`], is the right choice almost always.
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum Layout {
34    /// [`Layout::Direct`] for a small item, [`Layout::Single`] for a large one
35    /// (more than [`SINGLE_COPY_THRESHOLD`](crate::SINGLE_COPY_THRESHOLD)
36    /// tokens with its `META`).
37    Auto,
38    /// The item is held once, so the macro's metadata is about the size of one
39    /// item, and one call takes three expansions (two through the derive route)
40    /// instead of one.
41    Single,
42    /// One expansion per call; the item is copied into each of the macro's
43    /// three output arms.
44    Direct,
45}
46
47/// The parsed arguments of `#[announce(..)]` or of the `#[announce(..)]`
48/// helper attribute of `#[derive(Announce)]`.
49///
50/// Parse it from the attribute's tokens with [`syn::parse2`]; the grammar and
51/// the meaning of every option are those of the `#[announce]` attribute
52/// (`announce::announce`). Build one in code with [`Options::default`] and
53/// the public fields. Turn it back into attribute arguments with
54/// [`ToTokens`] (`opts.to_token_stream()`), for example to hand it to
55/// [`announce()`](crate::announce).
56///
57/// The most useful field for a library is `krate`: the path to the `announce`
58/// crate that the generated code uses (see [`Options::krate()`]). Without it the
59/// path is read from the `Cargo.toml` of the crate being compiled, which only
60/// works when that crate depends on `announce` itself.
61#[derive(Clone)]
62#[non_exhaustive]
63pub struct Options {
64    /// `macro_name = Name`: name of the emitted macro. Default: the item's name.
65    pub macro_name: Option<Ident>,
66    /// `mock = path`: the item is a stand-in for the item at `path`.
67    pub mock: Option<Path>,
68    /// `repeater = path`: emit the type-leak table against this repeater trait.
69    pub repeater: Option<Path>,
70    /// type-leak: primitives resolve everywhere (default `true`).
71    pub allow_primitive: bool,
72    /// type-leak: std prelude names resolve everywhere (default `false`).
73    pub allow_std_prelude: bool,
74    /// `crate = path`: path to the `announce` crate in generated code. When it
75    /// is `None`, [`Options::krate()`] looks the crate up in `Cargo.toml`.
76    pub krate: Option<Path>,
77    /// `emit_item = false`: do not re-emit the item (for derives, whose input
78    /// already exists). Default `true`.
79    pub emit_item: bool,
80    /// `layout = auto | single | direct`: how the trampoline holds the item.
81    /// Default `auto`: by the size of the item (advanced).
82    pub layout: Layout,
83    /// `docs = true`: keep the doc attributes (`#[doc = ".."]`, so `///` and
84    /// `//!` too) in the item that callbacks and checks receive. Default
85    /// `false`: they are removed there, which makes the trampoline smaller.
86    /// The item that is compiled keeps its docs either way.
87    pub docs: bool,
88    /// Set by `#[derive(Announce)]`: the item is emitted by the user and always
89    /// gets the `Announce` impl. Not an option.
90    pub(crate) derived: bool,
91    /// The option keys that were written (for derive-specific errors).
92    pub(crate) given: Vec<String>,
93    /// Checks: bare `path` / `path(args)` entries. They run at the definition.
94    pub checks: Vec<Check>,
95    /// `meta = path(args)`: listed in META like a check, but never invoked.
96    pub metas: Vec<Check>,
97}
98
99impl Default for Options {
100    fn default() -> Self {
101        Options {
102            macro_name: None,
103            mock: None,
104            repeater: None,
105            allow_primitive: true,
106            allow_std_prelude: false,
107            krate: None,
108            emit_item: true,
109            layout: Layout::Auto,
110            docs: false,
111            derived: false,
112            given: Vec::new(),
113            checks: Vec::new(),
114            metas: Vec::new(),
115        }
116    }
117}
118
119impl Options {
120    /// The path to the `announce` crate that the generated code uses.
121    ///
122    /// `crate = ..` (the `krate` field) wins. Otherwise it is
123    /// the name that the `Cargo.toml` of the crate being compiled
124    /// (`CARGO_MANIFEST_DIR`) gives to its `announce` dependency, renames
125    /// included, else `::announce`.
126    pub fn krate(&self) -> Path {
127        self.krate
128            .clone()
129            .unwrap_or_else(crate::cratepath::default_crate_path)
130    }
131}
132
133impl ToTokens for Options {
134    fn to_tokens(&self, tokens: &mut TokenStream) {
135        let mut parts: Vec<TokenStream> = Vec::new();
136        if let Some(k) = &self.krate {
137            parts.push(quote! { crate = #k });
138        }
139        if let Some(n) = &self.macro_name {
140            parts.push(quote! { macro_name = #n });
141        }
142        if let Some(m) = &self.mock {
143            parts.push(quote! { mock = #m });
144        }
145        if let Some(r) = &self.repeater {
146            parts.push(quote! { repeater = #r });
147        }
148        if !self.allow_primitive {
149            parts.push(quote! { allow_primitive = false });
150        }
151        if self.allow_std_prelude {
152            parts.push(quote! { allow_std_prelude = true });
153        }
154        if !self.emit_item {
155            parts.push(quote! { emit_item = false });
156        }
157        match self.layout {
158            Layout::Auto => {}
159            Layout::Single => parts.push(quote! { layout = single }),
160            Layout::Direct => parts.push(quote! { layout = direct }),
161        }
162        if self.docs {
163            parts.push(quote! { docs = true });
164        }
165        for m in &self.metas {
166            parts.push(quote! { meta = #m });
167        }
168        for c in &self.checks {
169            parts.push(quote! { #c });
170        }
171        tokens.extend(quote! { #(#parts),* });
172    }
173}
174
175impl Parse for Options {
176    fn parse(input: ParseStream) -> Result<Self> {
177        let mut opts = Options::default();
178        while !input.is_empty() {
179            if input.peek(Token![crate]) && input.peek2(Token![=]) {
180                input.parse::<Token![crate]>()?;
181                input.parse::<Token![=]>()?;
182                opts.krate = Some(Path::parse_mod_style(input)?);
183            } else if input.peek(syn::Ident) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
184                let key: Ident = input.parse()?;
185                input.parse::<Token![=]>()?;
186                opts.given.push(key.to_string());
187                match key.to_string().as_str() {
188                    "macro_name" => opts.macro_name = Some(input.parse()?),
189                    "export_name" | "marker" => {
190                        return Err(Error::new(
191                            key.span(),
192                            if key == "export_name" {
193                                "`export_name` was removed: the hidden macro name is generated; \
194                                 use `macro_name = ..` to choose the public name"
195                            } else {
196                                "`marker` was removed: the `Announce` impl follows the item \
197                                 (none with `emit_item = false` or `mock`); use `#[derive(Announce)]` \
198                                 to announce a struct, enum or union and implement it"
199                            },
200                        ))
201                    }
202                    "mock" => opts.mock = Some(Path::parse_mod_style(input)?),
203                    "repeater" => {
204                        let p = Path::parse_mod_style(input)?;
205                        crate::leak::check_repeater(&p)?;
206                        opts.repeater = Some(p);
207                    }
208                    "signature_only" | "transform" => {
209                        return Err(Error::new(
210                            key.span(),
211                            format!(
212                                "`{}` is not a definition-side option: transformers are chosen by the caller, \
213                                 e.g. `F! {{ [F!, ::announce::signature_only!, callback!] .. }}`",
214                                key
215                            ),
216                        ))
217                    }
218                    "emit_item" => opts.emit_item = input.parse::<LitBool>()?.value,
219                    "layout" => {
220                        let v: Ident = input.parse()?;
221                        opts.layout = match v.to_string().as_str() {
222                            "auto" => Layout::Auto,
223                            "single" => Layout::Single,
224                            "direct" => Layout::Direct,
225                            _ => {
226                                return Err(Error::new(
227                                    v.span(),
228                                    "`layout` is `auto`, `single` or `direct`",
229                                ))
230                            }
231                        };
232                    }
233                    "docs" => opts.docs = input.parse::<LitBool>()?.value,
234                    "allow_primitive" => opts.allow_primitive = input.parse::<LitBool>()?.value,
235                    "allow_std_prelude" => opts.allow_std_prelude = input.parse::<LitBool>()?.value,
236                    "meta" => {
237                        let path = Path::parse_mod_style(input)?;
238                        let args = if input.peek(syn::token::Paren) {
239                            let content;
240                            syn::parenthesized!(content in input);
241                            Some(content.parse::<TokenStream>()?)
242                        } else {
243                            None
244                        };
245                        opts.metas.push(Check { path, args });
246                    }
247                    _ => {
248                        return Err(Error::new(
249                            key.span(),
250                            "unknown announce option; expected one of `macro_name`, \
251                             `mock`, `repeater`, `allow_primitive`, `allow_std_prelude`, `crate`, `emit_item`, `layout`, `docs`, `meta` (a bare path is a check)",
252                        ))
253                    }
254                }
255            } else {
256                let path = Path::parse_mod_style(input)?;
257                let args = if input.peek(syn::token::Paren) {
258                    let content;
259                    syn::parenthesized!(content in input);
260                    Some(content.parse::<TokenStream>()?)
261                } else {
262                    None
263                };
264                opts.checks.push(Check { path, args });
265            }
266            if input.is_empty() {
267                break;
268            }
269            input.parse::<Token![,]>()?;
270        }
271        Ok(opts)
272    }
273}
274
275/// Arguments of the hidden `#[__announce(RANDOM; [OWNED;] OPTIONS)]` helper
276/// attribute that `#[announce]` puts on the capture dummy. `OWNED` is a bracketed,
277/// comma-separated list of the attribute names that the checks own; it is left
278/// out when there are none.
279pub struct CaptureArgs {
280    pub random: u64,
281    pub owned: crate::helper::OwnedAttrs,
282    pub opts: Options,
283}
284
285impl Parse for CaptureArgs {
286    fn parse(input: ParseStream) -> Result<Self> {
287        let lit: syn::LitInt = input.parse()?;
288        input.parse::<Token![;]>()?;
289        let owned = if input.peek(syn::token::Bracket) {
290            let content;
291            syn::bracketed!(content in input);
292            let names: syn::punctuated::Punctuated<Ident, Token![,]> =
293                syn::punctuated::Punctuated::parse_terminated(&content)?;
294            input.parse::<Token![;]>()?;
295            crate::helper::OwnedAttrs::from_idents(names)
296        } else {
297            crate::helper::OwnedAttrs::new()
298        };
299        Ok(CaptureArgs {
300            random: lit.base10_parse()?,
301            owned,
302            opts: input.parse()?,
303        })
304    }
305}