Skip to main content

announce_core/
lib.rs

1//! The implementation behind `#[announce]` and `#[derive(Announce)]`, as plain
2//! functions, and the helpers for callbacks and checks. Do not depend on this
3//! crate: go through [`announce`](https://docs.rs/announce), which re-exports
4//! the public API as `announce::machinery` and `announce::helper`.
5//!
6//! A proc-macro crate cannot export functions, so the code behind the macros
7//! lives here, in an ordinary library. `announce-macros` is a thin wrapper
8//! around it. The crate name and layout are not a stable interface.
9//!
10//! The API of the generation is [`announce()`], [`derive_announce()`] and
11//! [`Options`] (`announce::machinery`). See their docs, and
12//! `announce::machinery` for how a library uses them. [`helper`] is what
13//! callbacks and checks use (`announce::helper`): the parser of the tokens a
14//! trampoline sends, and the macros [`callback!`] and [`transformer!`].
15
16mod cratepath;
17mod fastpath;
18mod finish;
19mod generate;
20pub mod helper;
21mod leak;
22mod options;
23mod scan;
24mod timing;
25
26use crate::helper::OwnedAttrs;
27use proc_macro2::{Ident, Span, TokenStream};
28use std::sync::atomic::{AtomicU64, Ordering};
29use template_quote::{quote, ToTokens};
30
31pub use finish::{finish, try_finish};
32pub use generate::SINGLE_COPY_THRESHOLD;
33pub use options::{Check, Layout, Options};
34
35fn random_u64() -> u64 {
36    use std::hash::{BuildHasher, Hasher};
37    static COUNTER: AtomicU64 = AtomicU64::new(0);
38    let mut h = std::collections::hash_map::RandomState::new().build_hasher();
39    h.write_u64(COUNTER.fetch_add(1, Ordering::Relaxed));
40    h.finish()
41}
42
43/// Does what `#[announce(attr)]` does to `item`.
44///
45/// `attr` holds the tokens inside the parentheses of the attribute
46/// (`macro_name = Name, my_lib::check`), and `item` the item the attribute is
47/// on. The result replaces the item. It keeps the item as written (unless
48/// `emit_item = false` or `mock`) and adds the code that emits the
49/// trampoline `macro_rules!` and the other generated items. The item that the
50/// trampoline holds, and the checks get, has its `#[doc = ..]` attributes
51/// removed unless `docs = true`; the emitted item keeps them.
52///
53/// With checks (`my_lib::check`) the result is not the item yet: it is the
54/// call of the first check, with the query that asks every check which
55/// attributes it owns. The last element of the query is the hidden macro
56/// `finish!` of the `announce` crate (`<crate path>::__private::finish!`, see
57/// [`finish()`]), which expands to what is described above, without the
58/// attributes the checks own. See [`helper::attrs`] for the protocol.
59///
60/// A returned error is a syntax or type-context error of the same kind as
61/// `#[announce]` reports. Here it comes back as a `compile_error!` invocation
62/// in the returned tokens, so a proc macro can return the result as it is.
63/// Use [`try_announce`] to get the [`syn::Error`] instead.
64///
65/// The generated code names the `announce` crate. See [`Options::krate()`] and
66/// the `crate = ..` option.
67pub fn announce(attr: TokenStream, item: TokenStream) -> TokenStream {
68    try_announce(attr, item).unwrap_or_else(|e| e.to_compile_error())
69}
70
71/// [`announce()`] with the error returned as a [`syn::Error`].
72///
73/// The item is not parsed here: only its kind and name are read (`scan`), to
74/// refuse what is not supported at once. Its syntax is validated by the parse of
75/// the `Capture` derive that the result contains.
76pub fn try_announce(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
77    let mut t = timing::Timer::new("attribute");
78    let opts: Options = syn::parse2(attr.clone())?;
79    t.step("options");
80    let head = scan::head(&item)?;
81    t.subject(|| format!("{:?} {}", head.kind, head.ident));
82    t.step("scan");
83    let random = random_u64();
84    let out = if opts.checks.is_empty() {
85        capture_output(&opts, &attr, item, random, &OwnedAttrs::new())
86    } else {
87        finish::query(&opts, finish::Mode::Attribute, &attr, &item, random)
88    };
89    t.step("emit");
90    Ok(out)
91}
92
93/// What `#[announce]` expands to once the attribute names that its checks own
94/// are known (`owned`; empty without checks): the item and the capture dummy.
95///
96/// The derive sees the copy with every nested cfg
97/// evaluated in this crate, then `remove` deletes the dummy. Without owned
98/// attributes the item is emitted here, as written. With owned attributes it
99/// is the derive that emits it, from its cfg-evaluated copy and without them.
100pub(crate) fn capture_output(
101    opts: &Options,
102    attr: &TokenStream,
103    item: TokenStream,
104    random: u64,
105    owned: &OwnedAttrs,
106) -> TokenStream {
107    let krate = opts.krate();
108    let cap = Ident::new(
109        &format!("__AnnounceCap_{}", generate::hex(random)),
110        Span::call_site(),
111    );
112    let random_lit = syn::LitInt::new(&format!("{}u64", random), Span::call_site());
113    let emit_item = opts.mock.is_none() && opts.emit_item && owned.is_empty();
114    let names = owned.names();
115    let owned_arg = (!owned.is_empty()).then(|| quote! { [ #(#names),* ] ; });
116    quote! {
117        #(if emit_item) { #item }
118        #[derive(#krate::__private::Capture)]
119        #[__announce(#random_lit; #owned_arg #attr)]
120        #[#krate::__private::remove]
121        #[allow(dead_code, non_camel_case_types)]
122        enum #cap { V = { #item 0 } }
123    }
124}
125
126/// Does what `define_repeater!($vis $ident)` does: the definition of the
127/// repeater trait, as type-leak writes it (`type_leak::repeater_definition`).
128///
129/// ```
130/// let out = announce_core::define_repeater("pub Repeater".parse().unwrap());
131/// let out = out.to_string();
132/// assert!(out.contains("pub trait Repeater"), "{}", out);
133/// ```
134///
135/// `$vis` may be empty (a private trait), `pub`, `pub(crate)`, .. The output
136/// is the trait, whose const parameters with defaults need Rust 1.59 in the
137/// crate that expands it. An error comes back as a `compile_error!`
138/// invocation; see [`try_define_repeater`] for a [`syn::Error`].
139pub fn define_repeater(input: TokenStream) -> TokenStream {
140    try_define_repeater(input).unwrap_or_else(|e| e.to_compile_error())
141}
142
143/// [`define_repeater()`] with the error returned as a [`syn::Error`].
144pub fn try_define_repeater(input: TokenStream) -> syn::Result<TokenStream> {
145    use syn::parse::Parser;
146    const USAGE: &str = "announce: expected `define_repeater!(<visibility> <Name>)`, \
147                         e.g. `define_repeater!(pub Repeater)`";
148    let (vis, ident) = (|input: syn::parse::ParseStream| {
149        let vis: syn::Visibility = input.parse()?;
150        let ident: Ident = input
151            .parse()
152            .map_err(|e: syn::Error| syn::Error::new(e.span(), USAGE))?;
153        if !input.is_empty() {
154            return Err(input.error(USAGE));
155        }
156        Ok((vis, ident))
157    })
158    .parse2(input)?;
159    let mut definition = type_leak::repeater_definition(&vis, &ident);
160    definition.attrs.push(syn::parse_quote! {
161        #[doc = "The repeater of `#[announce(repeater = ..)]`: it carries the types of \
162                 announced items, which do not resolve in other crates, to the macros \
163                 that copy them. Defined by `announce::define_repeater!`; do not \
164                 implement it or write its paths yourself."]
165    });
166    Ok(quote! { #definition })
167}
168
169/// Does what `#[derive(Announce)]` does to its input.
170///
171/// `input` is the struct, enum or union, including its attributes. Options come
172/// from its `#[announce(..)]` helper attribute, which is removed from the
173/// announced copy; `mock` and `emit_item` are errors. The result is what the
174/// derive emits: the trampoline and the `Announce` impl. The item itself is not
175/// part of it, because a derive does not emit its input.
176///
177/// A library whose own derive macro calls this must list `announce` among the
178/// derive's helper attributes (`#[proc_macro_derive(Mine, attributes(announce))]`)
179/// if its users are to write options, or use [`derive_announce_with`].
180///
181/// Errors come back as a `compile_error!` invocation; see [`announce()`] and
182/// [`try_derive_announce`].
183pub fn derive_announce(input: TokenStream) -> TokenStream {
184    try_derive_announce(input).unwrap_or_else(|e| e.to_compile_error())
185}
186
187/// [`derive_announce()`] with the error returned as a [`syn::Error`].
188pub fn try_derive_announce(input: TokenStream) -> syn::Result<TokenStream> {
189    let mut t = timing::Timer::new("derive");
190    let mut item: syn::Item = syn::parse2(input)?;
191    t.step("parse");
192    let attrs = derive_attrs(&mut item)?;
193    let mut opts = Options::default();
194    let mut seen = false;
195    for a in attrs.iter().filter(|a| a.path().is_ident("announce")) {
196        if seen {
197            return Err(syn::Error::new_spanned(
198                a,
199                "announce: only one `#[announce(..)]` helper attribute is allowed",
200            ));
201        }
202        seen = true;
203        if let syn::Meta::List(l) = &a.meta {
204            opts = syn::parse2(l.tokens.clone())?;
205        }
206    }
207    for key in ["mock", "emit_item"] {
208        if opts.given.iter().any(|g| g == key) {
209            return Err(derive_option_error(key));
210        }
211    }
212    t.step("options");
213    finish_derive(item, opts, t)
214}
215
216/// Like [`derive_announce()`], with the options given in code instead of a
217/// helper attribute. Any `#[announce(..)]` attribute on the input is removed
218/// and ignored.
219///
220/// This is for a derive macro of your own that decides the options itself, for
221/// example `crate` (see [`Options::krate()`]). `opts.mock` must be `None`;
222/// `emit_item` is ignored, because the derive input is already emitted by the
223/// user.
224pub fn derive_announce_with(opts: Options, input: TokenStream) -> syn::Result<TokenStream> {
225    let mut item: syn::Item = syn::parse2(input)?;
226    derive_attrs(&mut item)?;
227    if opts.mock.is_some() {
228        return Err(derive_option_error("mock"));
229    }
230    finish_derive(item, opts, timing::Timer::new("derive"))
231}
232
233/// The attributes of a struct, enum or union; error for any other item.
234fn derive_attrs(item: &mut syn::Item) -> syn::Result<&mut Vec<syn::Attribute>> {
235    match item {
236        syn::Item::Struct(s) => Ok(&mut s.attrs),
237        syn::Item::Enum(e) => Ok(&mut e.attrs),
238        syn::Item::Union(u) => Ok(&mut u.attrs),
239        other => Err(syn::Error::new_spanned(
240            other,
241            "`#[derive(Announce)]` supports struct, enum and union items",
242        )),
243    }
244}
245
246fn derive_option_error(key: &str) -> syn::Error {
247    syn::Error::new(
248        Span::call_site(),
249        format!(
250            "`{}` does not apply to `#[derive(Announce)]`: the item is emitted as written",
251            key
252        ),
253    )
254}
255
256fn finish_derive(
257    mut item: syn::Item,
258    mut opts: Options,
259    mut t: timing::Timer,
260) -> syn::Result<TokenStream> {
261    derive_attrs(&mut item)?.retain(|a| !a.path().is_ident("announce"));
262    // The derive input is already cfg-evaluated by rustc.
263    opts.emit_item = false;
264    opts.derived = true;
265    if opts.checks.is_empty() {
266        t.subject(|| {
267            generate::ident_vis(&item).map_or_else(|_| String::new(), |(i, _)| i.to_string())
268        });
269        return generate::generate_timed(item, &opts, random_u64(), &OwnedAttrs::new(), &mut t);
270    }
271    // The same query as `#[announce]` sends. A derive cannot remove the
272    // attributes from its input, so a library that owns attributes declares them
273    // on its own derive (`attributes(..)`); the query only lists them in META.
274    let (attr, item) = (opts.to_token_stream(), item.to_token_stream());
275    let random = random_u64();
276    Ok(finish::query(
277        &opts,
278        finish::Mode::Derive,
279        &attr,
280        &item,
281        random,
282    ))
283}
284
285/// The end of the derive's query: [`finish_derive`] with the owned names known.
286pub(crate) fn finish_derive_now(
287    item: syn::Item,
288    mut opts: Options,
289    random: u64,
290    owned: &OwnedAttrs,
291    t: &mut timing::Timer,
292) -> syn::Result<TokenStream> {
293    opts.emit_item = false;
294    opts.derived = true;
295    generate::generate_timed(item, &opts, random, owned, t)
296}
297
298/// What the `Capture` derive reads from its input, the capture dummy:
299/// the arguments of `#[__announce(..)]` and the tokens of the item, if it is
300/// still there (it is gone when it was `#[cfg]`'d out).
301struct Dummy {
302    args: options::CaptureArgs,
303    item: Option<TokenStream>,
304}
305
306/// Reads the dummy `#[__announce(ARGS)] .. enum Cap { V = { ITEM 0 } }` from its
307/// top-level tokens, without parsing it. `None` if it has another shape.
308fn scan_dummy(input: TokenStream) -> Option<(TokenStream, Option<TokenStream>)> {
309    use proc_macro2::{Delimiter, TokenTree};
310    let mut it = input.into_iter().peekable();
311    let mut args = None;
312    while matches!(it.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '#') {
313        it.next();
314        let group = match it.next()? {
315            TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => g,
316            _ => return None,
317        };
318        let mut inner = group.stream().into_iter();
319        if matches!(inner.next(), Some(TokenTree::Ident(i)) if i == "__announce") {
320            match inner.next() {
321                Some(TokenTree::Group(a))
322                    if a.delimiter() == Delimiter::Parenthesis && inner.next().is_none() =>
323                {
324                    args = Some(a.stream())
325                }
326                _ => return None,
327            }
328        }
329    }
330    if !matches!(it.next()?, TokenTree::Ident(i) if i == "enum") {
331        return None;
332    }
333    if !matches!(it.next()?, TokenTree::Ident(_)) {
334        return None;
335    }
336    let body = match it.next()? {
337        TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => g,
338        _ => return None,
339    };
340    if it.next().is_some() {
341        return None;
342    }
343    let mut variant = body.stream().into_iter();
344    if !matches!(variant.next()?, TokenTree::Ident(_)) {
345        return None;
346    }
347    if !matches!(variant.next()?, TokenTree::Punct(p) if p.as_char() == '=') {
348        return None;
349    }
350    let block = match variant.next()? {
351        TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => g,
352        _ => return None,
353    };
354    if variant.next().is_some() {
355        return None;
356    }
357    let mut tts: Vec<TokenTree> = block.stream().into_iter().collect();
358    match tts.pop()? {
359        TokenTree::Literal(l) if l.to_string() == "0" => {}
360        _ => return None,
361    }
362    let item = if tts.is_empty() {
363        None
364    } else {
365        Some(tts.into_iter().collect())
366    };
367    Some((args?, item))
368}
369
370/// The same with `syn`, for a dummy of another shape than [`scan_dummy`] knows.
371fn read_dummy_with_syn(input: TokenStream) -> syn::Result<Dummy> {
372    let di: syn::DeriveInput = syn::parse2(input)?;
373    let attr = di
374        .attrs
375        .iter()
376        .find(|a| a.path().is_ident("__announce"))
377        .ok_or_else(|| {
378            syn::Error::new_spanned(
379                &di.ident,
380                "announce: missing #[__announce] on capture dummy",
381            )
382        })?;
383    let args: options::CaptureArgs = attr.parse_args()?;
384    let expr = match &di.data {
385        syn::Data::Enum(e) => e
386            .variants
387            .first()
388            .and_then(|v| v.discriminant.as_ref())
389            .map(|d| &d.1),
390        _ => None,
391    };
392    let stmts = match expr {
393        Some(syn::Expr::Block(b)) => &b.block.stmts,
394        _ => {
395            return Err(syn::Error::new_spanned(
396                &di.ident,
397                "announce: malformed capture dummy",
398            ))
399        }
400    };
401    // The copy is gone when the item itself was `#[cfg]`'d out.
402    let item = stmts.iter().find_map(|s| match s {
403        syn::Stmt::Item(i) => Some(i.to_token_stream()),
404        _ => None,
405    });
406    Ok(Dummy { args, item })
407}
408
409fn read_dummy(input: TokenStream) -> syn::Result<Dummy> {
410    match scan_dummy(input.clone()) {
411        Some((args, item)) => Ok(Dummy {
412            args: syn::parse2(args)?,
413            item,
414        }),
415        None => read_dummy_with_syn(input),
416    }
417}
418
419/// The body of the hidden `Capture` derive that `#[announce]` expands to.
420/// Not part of the API.
421///
422/// The item is parsed here, once: everything else is done
423/// on that tree and on the tokens it prints.
424#[doc(hidden)]
425pub fn capture(input: TokenStream) -> syn::Result<TokenStream> {
426    let mut t = timing::Timer::new("capture");
427    let dummy = read_dummy(input)?;
428    t.step("read");
429    let tokens = match dummy.item {
430        Some(tokens) => tokens,
431        None => return Ok(TokenStream::new()),
432    };
433    let args = dummy.args;
434    let item: syn::Item = syn::parse2(tokens.clone())?;
435    t.subject(|| generate::ident_vis(&item).map_or_else(|_| String::new(), |(i, _)| i.to_string()));
436    t.step("parse");
437    let prep = generate::prepare(item, Some(tokens), &args.opts, &args.owned);
438    t.step("strip");
439    Ok(
440        match generate::emit(&prep, &args.opts, args.random, &args.owned, &mut t) {
441            Ok(generated) => generated,
442            Err(e) => {
443                let mut out = e.to_compile_error();
444                out.extend(generate::real_item(&prep, &args.opts, &args.owned));
445                out
446            }
447        },
448    )
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use template_quote::ToTokens;
455
456    fn norm(ts: &TokenStream) -> String {
457        ts.to_string().split_whitespace().collect()
458    }
459
460    fn ts(s: &str) -> TokenStream {
461        s.parse().unwrap()
462    }
463
464    #[test]
465    fn options_round_trip() {
466        let src = "crate = ::l::announce, macro_name = N, mock = a::B, repeater = crate::R, \
467                   allow_primitive = false, allow_std_prelude = true, emit_item = false, \
468                   layout = single, docs = true, meta = m::info(1, 2), meta = m::bare, \
469                   c::check(x), c::bare";
470        let opts: Options = syn::parse2(ts(src)).unwrap();
471        let printed = opts.to_token_stream();
472        let again: Options = syn::parse2(printed.clone()).unwrap();
473        assert_eq!(norm(&printed), norm(&again.to_token_stream()));
474        assert_eq!(
475            norm(&printed),
476            "crate=::l::announce,macro_name=N,mock=a::B,repeater=crate::R,\
477             allow_primitive=false,allow_std_prelude=true,emit_item=false,layout=single,docs=true,\
478             meta=m::info(1,2),meta=m::bare,c::check(x),c::bare"
479        );
480        for (src, want) in [("layout = auto", ""), ("layout = direct", "layout=direct")] {
481            let o: Options = syn::parse2(ts(src)).unwrap();
482            assert_eq!(norm(&o.to_token_stream()), want);
483        }
484        let e = syn::parse2::<Options>(ts("layout = fast")).err().unwrap();
485        assert!(
486            e.to_string().contains("`auto`, `single` or `direct`"),
487            "{}",
488            e
489        );
490    }
491
492    #[test]
493    fn define_repeater_is_type_leaks_definition() {
494        // The tokens are `type_leak::repeater_definition()`'s, with the docs on top.
495        for (input, vis) in [
496            ("pub Repeater", "pub"),
497            ("pub(crate) Repeater", "pub(crate)"),
498            ("Repeater", ""),
499        ] {
500            let out = try_define_repeater(ts(input)).unwrap();
501            let def = type_leak::repeater_definition(
502                &syn::parse_str::<syn::Visibility>(vis).unwrap(),
503                &Ident::new("Repeater", Span::call_site()),
504            );
505            let expected = norm(&def.to_token_stream());
506            let got = norm(&out);
507            assert!(got.ends_with(&expected), "{}\n{}", got, expected);
508            assert!(got.starts_with("#[doc="), "{}", got);
509        }
510    }
511
512    #[test]
513    fn define_repeater_reports_a_bad_input() {
514        for bad in ["", "pub", "pub Repeater extra", "pub 1", "Rep, pub"] {
515            let e = try_define_repeater(ts(bad)).err();
516            assert!(e.is_some(), "{:?} was accepted", bad);
517        }
518        let out = norm(&define_repeater(ts("pub")));
519        assert!(
520            out.starts_with("::core::compile_error!")
521                && out.contains("expected`define_repeater!(<visibility><Name>)`"),
522            "{}",
523            out
524        );
525    }
526
527    #[test]
528    fn default_options_print_nothing() {
529        assert!(Options::default().to_token_stream().is_empty());
530    }
531
532    #[test]
533    fn announce_names_the_given_crate() {
534        let out = announce(
535            ts("crate = ::l::announce"),
536            ts("pub struct S { pub a: u8 }"),
537        );
538        let out = norm(&out);
539        assert!(out.contains("pubstructS{puba:u8}"), "{}", out);
540        assert!(
541            out.contains("derive(::l::announce::__private::Capture)"),
542            "{}",
543            out
544        );
545        assert!(
546            out.contains("#[::l::announce::__private::remove]"),
547            "{}",
548            out
549        );
550    }
551
552    #[test]
553    fn announce_through_options_equals_the_attribute() {
554        let mut opts: Options = syn::parse2(ts("macro_name = N")).unwrap();
555        opts.krate = Some(syn::parse_quote!(::l::announce));
556        let item = ts("pub struct S;");
557        let a = norm(&announce(opts.to_token_stream(), item.clone()));
558        let b = norm(&announce(ts("crate = ::l::announce, macro_name = N"), item));
559        // Only the random numbers differ; the attribute arguments are the same.
560        let args = |s: &str| {
561            s.split("u64;")
562                .nth(1)
563                .unwrap()
564                .split(")]")
565                .next()
566                .unwrap()
567                .to_string()
568        };
569        assert_eq!(args(&a), "crate=::l::announce,macro_name=N");
570        assert_eq!(args(&a), args(&b));
571    }
572
573    #[test]
574    fn errors_are_compile_error_tokens_or_syn_errors() {
575        let bad = ts("fn f() {}");
576        let out = announce(TokenStream::new(), bad.clone());
577        assert!(out.to_string().contains("compile_error"), "{}", out);
578        let e = try_announce(TokenStream::new(), bad).err().unwrap();
579        assert!(e.to_string().contains("supports struct, enum"), "{}", e);
580        let e = try_announce(ts("nope = 1"), ts("struct S;")).err().unwrap();
581        assert!(e.to_string().contains("unknown announce option"), "{}", e);
582    }
583
584    #[test]
585    fn derive_announce_uses_the_helper_attribute() {
586        let out = derive_announce(ts(
587            "#[announce(crate = ::l::announce, macro_name = N)] pub struct S(pub u8);",
588        ));
589        let out = norm(&out);
590        assert!(out.contains("macro_rules!"), "{}", out);
591        assert!(out.contains("::l::announce::Announce"), "{}", out);
592        // The helper attribute is not part of the announced copy.
593        assert!(!out.contains("#[announce"), "{}", out);
594    }
595
596    #[test]
597    fn layout_is_an_option_of_the_attribute_and_of_the_derive() {
598        let item = "pub struct S { pub a: u8 }";
599        let derive = |attr: &str| {
600            norm(&derive_announce(ts(&format!(
601                "#[announce({})] {}",
602                attr, item
603            ))))
604        };
605        assert!(!derive("").contains("__announce_emit"));
606        assert!(!derive("layout = direct").contains("__announce_emit"));
607        assert!(derive("layout = single").contains("__announce_emit"));
608        // The attribute hands the option to the capture derive.
609        let out = norm(&announce(ts("layout = single"), ts(item)));
610        assert!(out.contains("#[__announce("), "{}", out);
611        assert!(out.contains("layout=single"), "{}", out);
612        // From code.
613        let opts = Options {
614            layout: Layout::Single,
615            ..Options::default()
616        };
617        let out = derive_announce_with(opts, ts(item)).unwrap();
618        assert!(norm(&out).contains("__announce_emit"));
619    }
620
621    /// The state group of a query for `S`, as `query` writes it, to feed `finish`.
622    fn finish_input(mode: &str, attr: &str, item: &str, answers: &str) -> TokenStream {
623        ts(&format!(
624            "@__announce_attrs [ ::l::announce::__private::finish! ] {{ [7u64; {} ({}) {{ {} }}] {} }}",
625            mode, attr, item, answers
626        ))
627    }
628
629    #[test]
630    fn checks_send_the_query_before_anything_is_emitted() {
631        let out = norm(&announce(
632            ts("crate = ::l::announce, a::check, b::check(x), meta = m::info"),
633            ts("pub struct S { pub a: u8 }"),
634        ));
635        // The first check is called, and heads the list: all the checks and
636        // `finish!` follow, like a continuation list.
637        assert!(
638            out.starts_with(
639                "a::check!{@__announce_attrs[a::check!,b::check!,::l::announce::__private::finish!]{[",
640            ),
641            "{}",
642            out
643        );
644        // The state: a random number, the mode, the attribute and the item.
645        assert!(
646            out.contains(";attr(crate=::l::announce,a::check,b::check(x),meta=m::info){pubstructS{puba:u8}}]}}"),
647            "{}",
648            out
649        );
650        // Nothing else yet: no item, no capture dummy.
651        assert!(
652            !out.contains("Capture") && out.matches("pubstructS").count() == 1,
653            "{}",
654            out
655        );
656        // Without checks there is no query.
657        let out = norm(&announce(
658            ts("crate = ::l::announce, meta = m::info"),
659            ts("pub struct S;"),
660        ));
661        assert!(
662            !out.contains("__announce_attrs") && out.contains("Capture"),
663            "{}",
664            out
665        );
666    }
667
668    #[test]
669    fn finish_without_owned_attributes_emits_what_announce_emits_without_checks() {
670        let item = "pub struct S { pub a: u8 }";
671        let out = norm(&finish(finish_input(
672            "attr",
673            "crate=::l::announce,a::check",
674            item,
675            "",
676        )));
677        assert!(
678            out.starts_with("pubstructS{puba:u8}#[derive(::l::announce::__private::Capture)]"),
679            "{}",
680            out
681        );
682        assert!(
683            out.contains("#[__announce(7u64;crate=::l::announce,a::check)]"),
684            "{}",
685            out
686        );
687    }
688
689    #[test]
690    fn finish_with_owned_attributes_leaves_the_item_to_the_capture_derive() {
691        let item = "pub struct S { #[my_lib(skip)] pub a: u8 }";
692        let out = norm(&finish(finish_input(
693            "attr",
694            "crate=::l::announce,a::check",
695            item,
696            "attrs(my_lib) attrs(other, my_lib)",
697        )));
698        // The item is not emitted here (it would have an attribute no macro knows),
699        // only in the dummy that the derive reads, with the owned names in front
700        // of the options.
701        assert!(
702            out.starts_with("#[derive(::l::announce::__private::Capture)]"),
703            "{}",
704            out
705        );
706        assert!(
707            out.contains("#[__announce(7u64;[my_lib,other];crate=::l::announce,a::check)]"),
708            "{}",
709            out
710        );
711        assert_eq!(out.matches("#[my_lib(skip)]").count(), 1, "{}", out);
712    }
713
714    #[test]
715    fn finish_reports_a_bad_query() {
716        let item = "struct S;";
717        let attr = "a::check";
718        let err = |ts: TokenStream| try_finish(ts).err().unwrap().to_string();
719        // A name that rustc uses.
720        let e = err(finish_input("attr", attr, item, "attrs(doc)"));
721        assert!(
722            e.contains("`doc` cannot be an owned attribute name"),
723            "{}",
724            e
725        );
726        // A check that changed the state.
727        let e = err(finish_input("attr", attr, item, "other(x)"));
728        assert!(e.contains("was changed by a check"), "{}", e);
729        let e = err(finish_input("attr", attr, item, "attrs[x]"));
730        assert!(e.contains("was changed by a check"), "{}", e);
731        let e = err(finish_input("weird", attr, item, ""));
732        assert!(e.contains("was changed by a check"), "{}", e);
733        // A list that was not forwarded to the end: a check kept its own head ...
734        let e = err(ts(
735            "@__announce_attrs [ x!, f::finish! ] { [7u64; attr (a) { struct S; }] }",
736        ));
737        assert!(e.contains("elements left in its list"), "{}", e);
738        // ... or dropped more than that.
739        let e = err(ts(
740            "@__announce_attrs [] { [7u64; attr (a) { struct S; }] }",
741        ));
742        assert!(e.contains("with an empty list"), "{}", e);
743        let e = err(ts(
744            "@__announce_attrs [ #[derive(A)] ] { [7u64; attr (a) { struct S; }] }",
745        ));
746        assert!(e.contains("elements left in its list"), "{}", e);
747        // The list `[ finish! ]`, whatever the path of the macro, is the end.
748        try_finish(ts(
749            "@__announce_attrs [ x::y! ] { [7u64; attr () { struct S; }] }",
750        ))
751        .unwrap();
752        // Called by hand.
753        let e = err(ts("struct S;"));
754        assert!(e.contains("cannot be called by hand"), "{}", e);
755        // The item is parsed again, with its error.
756        assert!(try_finish(finish_input("attr", attr, "fn f() {}", "")).is_err());
757    }
758
759    #[test]
760    fn capture_arguments_read_the_owned_names() {
761        let with: options::CaptureArgs =
762            syn::parse2(ts("5u64; [a, b]; crate = ::l::announce, c::check")).unwrap();
763        assert_eq!(with.random, 5);
764        assert_eq!(
765            with.owned
766                .names()
767                .iter()
768                .map(|n| n.to_string())
769                .collect::<Vec<_>>(),
770            ["a", "b"]
771        );
772        assert_eq!(
773            norm(&with.opts.to_token_stream()),
774            "crate=::l::announce,c::check"
775        );
776        let without: options::CaptureArgs = syn::parse2(ts("5u64; c::check")).unwrap();
777        assert!(without.owned.is_empty());
778        assert_eq!(norm(&without.opts.to_token_stream()), "c::check");
779    }
780
781    #[test]
782    fn derive_with_checks_sends_the_same_query() {
783        let out = norm(&derive_announce(ts(
784            "#[announce(crate = ::l::announce, macro_name = N, a::check)] pub struct S(#[my_lib] pub u8);",
785        )));
786        assert!(
787            out.starts_with(
788                "a::check!{@__announce_attrs[a::check!,::l::announce::__private::finish!]{["
789            ) && out.contains(";derive(crate=::l::announce,macro_name=N,emit_item=false,a::check)"),
790            "{}",
791            out
792        );
793        // The helper attribute is not part of the item; the derive's own attributes are.
794        assert!(
795            !out.contains("#[announce") && out.contains("pubstructS(#[my_lib]pubu8);"),
796            "{}",
797            out
798        );
799        // The end of the query: the trampoline, the check and the `Announce` impl,
800        // with the owned names in META; the item is not emitted again.
801        let end = norm(&finish(finish_input(
802            "derive",
803            "crate=::l::announce,macro_name=N,emit_item=false,a::check",
804            "pub struct S(#[my_lib] pub u8);",
805            "attrs(my_lib)",
806        )));
807        assert!(end.contains("macro_rules!__announce_S_"), "{}", end);
808        assert!(
809            end.contains("a::check!{(),pubstructS(#[my_lib]pubu8);}"),
810            "{}",
811            end
812        );
813        assert!(
814            end.contains("[a::check,attrs(my_lib)]{pubstructS(#[my_lib]pubu8);}"),
815            "{}",
816            end
817        );
818        assert!(end.contains("::l::announce::AnnounceforS{}"), "{}", end);
819        assert!(!end.starts_with("pubstructS"), "{}", end);
820    }
821
822    #[test]
823    fn derive_rejects_mock_and_emit_item() {
824        for src in [
825            "#[announce(mock = a::B)] struct S;",
826            "#[announce(emit_item = true)] struct S;",
827        ] {
828            let e = try_derive_announce(ts(src)).err().unwrap();
829            assert!(e.to_string().contains("does not apply"), "{}", e);
830        }
831        let opts = Options {
832            mock: Some(syn::parse_quote!(a::B)),
833            ..Options::default()
834        };
835        let e = derive_announce_with(opts, ts("struct S;")).err().unwrap();
836        assert!(e.to_string().contains("`mock` does not apply"), "{}", e);
837        let e = try_derive_announce(ts("fn f() {}")).err().unwrap();
838        assert!(
839            e.to_string().contains("supports struct, enum and union"),
840            "{}",
841            e
842        );
843    }
844
845    #[test]
846    fn derive_with_options_ignores_the_helper_attribute() {
847        let opts = Options {
848            krate: Some(syn::parse_quote!(::l::announce)),
849            ..Options::default()
850        };
851        let out =
852            derive_announce_with(opts, ts("#[announce(macro_name = Other)] struct S;")).unwrap();
853        let out = norm(&out);
854        assert!(out.contains("::l::announce::Announce"), "{}", out);
855        assert!(!out.contains("Other"), "{}", out);
856    }
857
858    // ---- the capture dummy, read without a parse ----------------------------
859
860    const DUMMY_ARGS: &str = "7u64; crate = ::l::announce";
861
862    fn dummy(args: &str, body: &str) -> TokenStream {
863        ts(&format!(
864            "#[derive(::l::announce::__private::Capture)] #[__announce({})] \
865             #[::l::announce::__private::remove] #[allow(dead_code, non_camel_case_types)] \
866             enum __AnnounceCap_0 {{ V = {{ {} 0 }} }}",
867            args, body
868        ))
869    }
870
871    /// `#[derive(..)]` is not part of what a derive macro receives.
872    fn derive_input(args: &str, body: &str) -> TokenStream {
873        let mut it = dummy(args, body).into_iter();
874        it.next();
875        it.next();
876        it.collect()
877    }
878
879    #[test]
880    fn the_dummy_is_read_from_its_top_level_tokens() {
881        let (args, item) =
882            scan_dummy(derive_input(DUMMY_ARGS, "pub struct S { pub a: u8 }")).unwrap();
883        assert_eq!(norm(&args), "7u64;crate=::l::announce");
884        assert_eq!(norm(&item.unwrap()), "pubstructS{puba:u8}");
885        // An item that was `#[cfg]`'d out leaves the block with the `0` only.
886        let (_, item) = scan_dummy(derive_input(DUMMY_ARGS, "")).unwrap();
887        assert!(item.is_none());
888        // Other shapes are not read (the code then parses the dummy).
889        for bad in [
890            "struct S;",
891            "enum E { V = 1 }",
892            "enum E { V = { 1 } }",
893            "enum E { V = { S } }",
894            "#[__announce] enum E { V = { 0 } }",
895            "#[__announce(1u64;)] enum E { V = { S 1 } }",
896        ] {
897            assert!(scan_dummy(ts(bad)).is_none(), "{}", bad);
898        }
899    }
900
901    #[test]
902    fn a_dummy_of_another_shape_is_read_with_syn() {
903        // `pub`: the scan does not know it, `syn::DeriveInput` does.
904        let input = ts(
905            "#[__announce(7u64; crate = ::l::announce)] pub enum Cap { V = { pub struct S; 0 } }",
906        );
907        assert!(scan_dummy(input.clone()).is_none());
908        let d = read_dummy(input).unwrap();
909        assert_eq!(d.args.random, 7);
910        assert_eq!(norm(&d.item.unwrap()), "pubstructS;");
911        let e = read_dummy(ts("pub enum Cap { V = { 0 } }")).err().unwrap();
912        assert!(e.to_string().contains("missing #[__announce]"), "{}", e);
913    }
914
915    #[test]
916    fn capture_emits_what_the_derive_emits() {
917        let out = norm(&capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: u8 }")).unwrap());
918        assert!(out.contains("macro_rules!__announce_S_"), "{}", out);
919        assert!(out.contains("::l::announce::AnnounceforS{}"), "{}", out);
920        // The item itself is not part of it: `#[announce]` emitted it.
921        assert!(!out.starts_with("pubstruct"), "{}", out);
922        // Gone with its `#[cfg]`.
923        assert!(capture(derive_input(DUMMY_ARGS, "")).unwrap().is_empty());
924        // Not a valid item: the error of the parse.
925        assert!(capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: }")).is_err());
926    }
927
928    #[test]
929    fn capture_keeps_the_real_item_next_to_its_error() {
930        // With owned attributes the derive emits the item, so an error in the type
931        // context is followed by the item (without the owned attributes and the cfgs).
932        let args = "7u64; [my_lib]; crate = ::l::announce";
933        let out = norm(
934            &capture(derive_input(
935                args,
936                "pub struct S { #[my_lib] #[cfg(a)] pub a: Local, pub b: u8 }",
937            ))
938            .unwrap(),
939        );
940        assert!(out.starts_with("::core::compile_error!"), "{}", out);
941        assert!(out.ends_with("pubstructS{puba:Local,pubb:u8}"), "{}", out);
942        // Without owned names `#[announce]` emitted it: the error only.
943        let out =
944            norm(&capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: Local }")).unwrap());
945        assert!(out.starts_with("::core::compile_error!"), "{}", out);
946        assert!(!out.contains("pubstructS"), "{}", out);
947    }
948
949    #[test]
950    fn the_attribute_reads_no_more_than_the_head_of_the_item() {
951        // The syntax of the rest is the business of the derive: an item with an
952        // error in it is passed on, to be refused there.
953        let out = norm(&announce(TokenStream::new(), ts("pub struct S { pub a: }")));
954        assert!(
955            out.contains("Capture") && !out.contains("compile_error"),
956            "{}",
957            out
958        );
959        // But what is not a supported item is refused at once, with the same errors.
960        for (src, msg) in [
961            ("pub fn f() {}", "supports struct, enum"),
962            ("mod m;", "only inline modules"),
963            ("impl S {}", "supports struct, enum"),
964        ] {
965            let e = try_announce(TokenStream::new(), ts(src)).err().unwrap();
966            assert!(e.to_string().contains(msg), "{}: {}", src, e);
967        }
968        // The kinds, with all their qualifiers.
969        for src in [
970            "struct S;",
971            "pub(crate) enum E {}",
972            "union U { a: u8 }",
973            "pub unsafe trait T {}",
974            "type A = u8;",
975            "mod m { }",
976        ] {
977            assert!(try_announce(TokenStream::new(), ts(src)).is_ok(), "{}", src);
978        }
979    }
980}