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}