1mod 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
43pub fn announce(attr: TokenStream, item: TokenStream) -> TokenStream {
68 try_announce(attr, item).unwrap_or_else(|e| e.to_compile_error())
69}
70
71pub 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
93pub(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
126pub fn define_repeater(input: TokenStream) -> TokenStream {
140 try_define_repeater(input).unwrap_or_else(|e| e.to_compile_error())
141}
142
143pub 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
169pub fn derive_announce(input: TokenStream) -> TokenStream {
184 try_derive_announce(input).unwrap_or_else(|e| e.to_compile_error())
185}
186
187pub 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
216pub 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
233fn 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 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 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
285pub(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
298struct Dummy {
302 args: options::CaptureArgs,
303 item: Option<TokenStream>,
304}
305
306fn 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
370fn 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 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#[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 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 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 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 let out = norm(&announce(ts("layout = single"), ts(item)));
610 assert!(out.contains("#[__announce("), "{}", out);
611 assert!(out.contains("layout=single"), "{}", out);
612 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 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 assert!(
638 out.starts_with(
639 "a::check!{@__announce_attrs[a::check!,b::check!,::l::announce::__private::finish!]{[",
640 ),
641 "{}",
642 out
643 );
644 assert!(
646 out.contains(";attr(crate=::l::announce,a::check,b::check(x),meta=m::info){pubstructS{puba:u8}}]}}"),
647 "{}",
648 out
649 );
650 assert!(
652 !out.contains("Capture") && out.matches("pubstructS").count() == 1,
653 "{}",
654 out
655 );
656 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 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 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 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 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 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 try_finish(ts(
749 "@__announce_attrs [ x::y! ] { [7u64; attr () { struct S; }] }",
750 ))
751 .unwrap();
752 let e = err(ts("struct S;"));
754 assert!(e.contains("cannot be called by hand"), "{}", e);
755 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 assert!(
795 !out.contains("#[announce") && out.contains("pubstructS(#[my_lib]pubu8);"),
796 "{}",
797 out
798 );
799 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 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 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 let (_, item) = scan_dummy(derive_input(DUMMY_ARGS, "")).unwrap();
887 assert!(item.is_none());
888 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 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 assert!(!out.starts_with("pubstruct"), "{}", out);
922 assert!(capture(derive_input(DUMMY_ARGS, "")).unwrap().is_empty());
924 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 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 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 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 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 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}