1mod cratepath;
17mod fastpath;
18mod finish;
19mod generate;
20pub mod helper;
21mod leak;
22mod options;
23mod runner;
24mod scan;
25mod timing;
26
27use crate::helper::OwnedAttrs;
28use proc_macro2::{Ident, Span, TokenStream};
29use std::sync::atomic::{AtomicU64, Ordering};
30use template_quote::{quote, ToTokens};
31
32pub use finish::{finish, try_finish};
33pub use generate::SINGLE_COPY_THRESHOLD;
34pub use options::{Check, Layout, Options};
35
36fn random_u64() -> u64 {
37 use std::hash::{BuildHasher, Hasher};
38 static COUNTER: AtomicU64 = AtomicU64::new(0);
39 let mut h = std::collections::hash_map::RandomState::new().build_hasher();
40 h.write_u64(COUNTER.fetch_add(1, Ordering::Relaxed));
41 h.finish()
42}
43
44pub fn announce(attr: TokenStream, item: TokenStream) -> TokenStream {
81 try_announce(attr, item).unwrap_or_else(|e| e.to_compile_error())
82}
83
84pub fn try_announce(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
91 let mut t = timing::Timer::new("attribute");
92 let opts: Options = syn::parse2(attr)?;
93 t.step("options");
94 let head = scan::head(&item)?;
95 t.subject(|| format!("{:?} {}", head.kind, head.ident));
96 t.step("scan");
97 let attr = opts.to_token_stream();
98 let random = random_u64();
99 let out = if opts.checks.is_empty() {
102 announce_now(&opts, &attr, item, random, &OwnedAttrs::new(), &head)
103 } else {
104 finish::query(&opts, &attr, &item, random, &opts.checks)
105 };
106 t.step("emit");
107 Ok(out)
108}
109
110pub(crate) fn announce_now(
115 opts: &Options,
116 attr: &TokenStream,
117 item: TokenStream,
118 random: u64,
119 owned: &OwnedAttrs,
120 head: &scan::Head,
121) -> TokenStream {
122 if !runner::can_expand_after(&item, owned) {
125 return capture_output(opts, attr, item, random, owned, None);
126 }
127 runner::announce(opts, head, item, owned)
128}
129
130pub(crate) fn capture_output(
143 opts: &Options,
144 attr: &TokenStream,
145 item: TokenStream,
146 random: u64,
147 owned: &OwnedAttrs,
148 sentinel: Option<runner::Sentinel>,
149) -> TokenStream {
150 let krate = opts.krate();
151 let cap = Ident::new(
152 &format!("__AnnounceCap_{}", generate::hex(random)),
153 Span::call_site(),
154 );
155 let random_lit = syn::LitInt::new(&format!("{}u64", random), Span::call_site());
156 let emit_item = opts.mock.is_none() && opts.emit_item && owned.is_empty();
157 let names = owned.names();
158 let owned_arg = (!owned.is_empty()).then(|| quote! { [ #(#names),* ] ; });
159 let sentinel_arg = sentinel.map(|s| {
160 let word = Ident::new(s.capture_word(), Span::call_site());
161 quote! { #word ; }
162 });
163 quote! {
164 #(if emit_item) { #item }
165 #[derive(#krate::__private::Capture)]
166 #[__announce(#random_lit; #sentinel_arg #owned_arg #attr)]
167 #[#krate::__private::remove]
168 #[allow(dead_code, non_camel_case_types)]
169 enum #cap { V = { #item 0 } }
170 }
171}
172
173#[doc(hidden)]
178pub fn run_announce(args: TokenStream, item: TokenStream) -> TokenStream {
179 runner::run(args, item)
180}
181
182pub fn define_repeater(input: TokenStream) -> TokenStream {
196 try_define_repeater(input).unwrap_or_else(|e| e.to_compile_error())
197}
198
199pub fn try_define_repeater(input: TokenStream) -> syn::Result<TokenStream> {
201 use syn::parse::Parser;
202 const USAGE: &str = "announce: expected `define_repeater!(<visibility> <Name>)`, \
203 e.g. `define_repeater!(pub Repeater)`";
204 let (vis, ident) = (|input: syn::parse::ParseStream| {
205 let vis: syn::Visibility = input.parse()?;
206 let ident: Ident = input
207 .parse()
208 .map_err(|e: syn::Error| syn::Error::new(e.span(), USAGE))?;
209 if !input.is_empty() {
210 return Err(input.error(USAGE));
211 }
212 Ok((vis, ident))
213 })
214 .parse2(input)?;
215 let mut definition = type_leak::repeater_definition(&vis, &ident);
216 definition.attrs.push(syn::parse_quote! {
217 #[doc = "The repeater of `#[announce(repeater = ..)]`: it carries the types of \
218 announced items, which do not resolve in other crates, to the macros \
219 that copy them. Defined by `announce::define_repeater!`; do not \
220 implement it or write its paths yourself."]
221 });
222 Ok(quote! { #definition })
223}
224
225struct Dummy {
229 args: options::CaptureArgs,
230 item: Option<TokenStream>,
231}
232
233fn scan_dummy(input: TokenStream) -> Option<(TokenStream, Option<TokenStream>)> {
236 use proc_macro2::{Delimiter, TokenTree};
237 let mut it = input.into_iter().peekable();
238 let mut args = None;
239 while matches!(it.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '#') {
240 it.next();
241 let group = match it.next()? {
242 TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => g,
243 _ => return None,
244 };
245 let mut inner = group.stream().into_iter();
246 if matches!(inner.next(), Some(TokenTree::Ident(i)) if i == "__announce") {
247 match inner.next() {
248 Some(TokenTree::Group(a))
249 if a.delimiter() == Delimiter::Parenthesis && inner.next().is_none() =>
250 {
251 args = Some(a.stream())
252 }
253 _ => return None,
254 }
255 }
256 }
257 if !matches!(it.next()?, TokenTree::Ident(i) if i == "enum") {
258 return None;
259 }
260 if !matches!(it.next()?, TokenTree::Ident(_)) {
261 return None;
262 }
263 let body = match it.next()? {
264 TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => g,
265 _ => return None,
266 };
267 if it.next().is_some() {
268 return None;
269 }
270 let mut variant = body.stream().into_iter();
271 if !matches!(variant.next()?, TokenTree::Ident(_)) {
272 return None;
273 }
274 if !matches!(variant.next()?, TokenTree::Punct(p) if p.as_char() == '=') {
275 return None;
276 }
277 let block = match variant.next()? {
278 TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => g,
279 _ => return None,
280 };
281 if variant.next().is_some() {
282 return None;
283 }
284 let mut tts: Vec<TokenTree> = block.stream().into_iter().collect();
285 match tts.pop()? {
286 TokenTree::Literal(l) if l.to_string() == "0" => {}
287 _ => return None,
288 }
289 let item = if tts.is_empty() {
290 None
291 } else {
292 Some(tts.into_iter().collect())
293 };
294 Some((args?, item))
295}
296
297fn read_dummy_with_syn(input: TokenStream) -> syn::Result<Dummy> {
299 let di: syn::DeriveInput = syn::parse2(input)?;
300 let attr = di
301 .attrs
302 .iter()
303 .find(|a| a.path().is_ident("__announce"))
304 .ok_or_else(|| {
305 syn::Error::new_spanned(
306 &di.ident,
307 "announce: missing #[__announce] on capture dummy",
308 )
309 })?;
310 let args: options::CaptureArgs = attr.parse_args()?;
311 let expr = match &di.data {
312 syn::Data::Enum(e) => e
313 .variants
314 .first()
315 .and_then(|v| v.discriminant.as_ref())
316 .map(|d| &d.1),
317 _ => None,
318 };
319 let stmts = match expr {
320 Some(syn::Expr::Block(b)) => &b.block.stmts,
321 _ => {
322 return Err(syn::Error::new_spanned(
323 &di.ident,
324 "announce: malformed capture dummy",
325 ))
326 }
327 };
328 let item = stmts.iter().find_map(|s| match s {
330 syn::Stmt::Item(i) => Some(i.to_token_stream()),
331 _ => None,
332 });
333 Ok(Dummy { args, item })
334}
335
336fn read_dummy(input: TokenStream) -> syn::Result<Dummy> {
337 match scan_dummy(input.clone()) {
338 Some((args, item)) => Ok(Dummy {
339 args: syn::parse2(args)?,
340 item,
341 }),
342 None => read_dummy_with_syn(input),
343 }
344}
345
346#[doc(hidden)]
352pub fn capture(input: TokenStream) -> syn::Result<TokenStream> {
353 let mut t = timing::Timer::new("capture");
354 let dummy = read_dummy(input)?;
355 t.step("read");
356 let tokens = match dummy.item {
357 Some(tokens) => tokens,
358 None => return Ok(TokenStream::new()),
359 };
360 let args = dummy.args;
361 let item: syn::Item = match syn::parse2(tokens.clone()) {
362 Ok(item) => item,
363 Err(e) => match (args.sentinel, scan::head(&tokens)) {
365 (Some(sentinel), Ok(head)) => {
366 let mut out = e.to_compile_error();
367 out.extend(generate::sentinel_stub(
368 sentinel,
369 &head.ident,
370 &args.opts,
371 args.random,
372 ));
373 return Ok(out);
374 }
375 _ => return Err(e),
376 },
377 };
378 let ident = generate::ident_vis(&item).ok().map(|(i, _)| i);
379 t.subject(|| ident.as_ref().map_or_else(String::new, |i| i.to_string()));
380 t.step("parse");
381 let prep = generate::prepare(item, Some(tokens), &args.opts, &args.owned);
382 t.step("strip");
383 Ok(
384 match generate::emit(&prep, &args.opts, args.random, &args.owned, &mut t) {
385 Ok(generated) => generated,
386 Err(e) => {
387 let mut out = e.to_compile_error();
388 out.extend(generate::real_item(&prep, &args.opts, &args.owned));
389 if let (Some(sentinel), Some(ident)) = (args.sentinel, &ident) {
390 out.extend(generate::sentinel_stub(
391 sentinel,
392 ident,
393 &args.opts,
394 args.random,
395 ));
396 }
397 out
398 }
399 },
400 )
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use template_quote::ToTokens;
407
408 fn norm(ts: &TokenStream) -> String {
409 ts.to_string().split_whitespace().collect()
410 }
411
412 const CHK: &str = "#[allow(deprecated)]const_:()={trait__AnnounceSentinel{}\
415 impl__AnnounceSentinelforSwhereS:::l::announce::Announce{}};";
416
417 fn ts(s: &str) -> TokenStream {
418 s.parse().unwrap()
419 }
420
421 #[test]
422 fn options_round_trip() {
423 let src = "crate = ::l::announce, macro_name = N, mock = a::B, repeater = crate::R, \
424 allow_primitive = false, allow_std_prelude = true, emit_item = false, \
425 layout = single, docs = true, meta = m::info(1, 2), meta = m::bare, \
426 c::check(x), c::bare";
427 let opts: Options = syn::parse2(ts(src)).unwrap();
428 let printed = opts.to_token_stream();
429 let again: Options = syn::parse2(printed.clone()).unwrap();
430 assert_eq!(norm(&printed), norm(&again.to_token_stream()));
431 assert_eq!(
432 norm(&printed),
433 "crate=::l::announce,macro_name=N,mock=a::B,repeater=crate::R,\
434 allow_primitive=false,allow_std_prelude=true,emit_item=false,layout=single,docs=true,\
435 meta=m::info(1,2),meta=m::bare,c::check(x),c::bare"
436 );
437 for (src, want) in [("layout = auto", ""), ("layout = direct", "layout=direct")] {
438 let o: Options = syn::parse2(ts(src)).unwrap();
439 assert_eq!(norm(&o.to_token_stream()), want);
440 }
441 let e = syn::parse2::<Options>(ts("layout = fast")).err().unwrap();
442 assert!(
443 e.to_string().contains("`auto`, `single` or `direct`"),
444 "{}",
445 e
446 );
447 }
448
449 #[test]
450 fn define_repeater_is_type_leaks_definition() {
451 for (input, vis) in [
453 ("pub Repeater", "pub"),
454 ("pub(crate) Repeater", "pub(crate)"),
455 ("Repeater", ""),
456 ] {
457 let out = try_define_repeater(ts(input)).unwrap();
458 let def = type_leak::repeater_definition(
459 &syn::parse_str::<syn::Visibility>(vis).unwrap(),
460 &Ident::new("Repeater", Span::call_site()),
461 );
462 let expected = norm(&def.to_token_stream());
463 let got = norm(&out);
464 assert!(got.ends_with(&expected), "{}\n{}", got, expected);
465 assert!(got.starts_with("#[doc="), "{}", got);
466 }
467 }
468
469 #[test]
470 fn define_repeater_reports_a_bad_input() {
471 for bad in ["", "pub", "pub Repeater extra", "pub 1", "Rep, pub"] {
472 let e = try_define_repeater(ts(bad)).err();
473 assert!(e.is_some(), "{:?} was accepted", bad);
474 }
475 let out = norm(&define_repeater(ts("pub")));
476 assert!(
477 out.starts_with("::core::compile_error!")
478 && out.contains("expected`define_repeater!(<visibility><Name>)`"),
479 "{}",
480 out
481 );
482 }
483
484 #[test]
485 fn default_options_print_nothing() {
486 assert!(Options::default().to_token_stream().is_empty());
487 }
488
489 #[test]
490 fn announce_names_the_given_crate() {
491 for item in ["pub struct S { pub a: u8 }", "pub trait T { fn f(&self); }"] {
494 let out = norm(&announce(ts("crate = ::l::announce"), ts(item)));
495 assert!(out.starts_with(&norm(&ts(item))), "{}", out);
496 assert!(
497 out.contains("derive(::l::announce::__private::Capture)"),
498 "{}",
499 out
500 );
501 assert!(
502 out.contains("#[::l::announce::__private::remove]"),
503 "{}",
504 out
505 );
506 assert!(!out.contains("__announce_runner"), "{}", out);
507 }
508 let out = norm(&announce(
511 ts("crate = ::l::announce"),
512 ts("#[derive(Debug)] pub struct S { pub a: u8 }"),
513 ));
514 assert_eq!(
515 out,
516 [
517 "#[derive(Debug)]#[::l::announce::__private::__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"impl\",crate=::l::announce)]pubstructS{puba:u8}",
518 CHK
519 ]
520 .concat()
521 );
522 }
523
524 #[test]
525 fn announce_through_options_equals_the_attribute() {
526 let mut opts: Options = syn::parse2(ts("macro_name = N")).unwrap();
527 opts.krate = Some(syn::parse_quote!(::l::announce));
528 let item = ts("pub struct S;");
529 let a = norm(&announce(opts.to_token_stream(), item.clone()));
530 let b = norm(&announce(ts("crate = ::l::announce, macro_name = N"), item));
531 let args = |s: &str| {
533 s.split("u64;")
534 .nth(1)
535 .unwrap()
536 .split(")]")
537 .next()
538 .unwrap()
539 .to_string()
540 };
541 assert_eq!(args(&a), "crate=::l::announce,macro_name=N");
542 assert_eq!(args(&a), args(&b));
543 }
544
545 #[test]
546 fn errors_are_compile_error_tokens_or_syn_errors() {
547 let bad = ts("fn f() {}");
548 let out = announce(TokenStream::new(), bad.clone());
549 assert!(out.to_string().contains("compile_error"), "{}", out);
550 let e = try_announce(TokenStream::new(), bad).err().unwrap();
551 assert!(e.to_string().contains("supports struct, enum"), "{}", e);
552 let e = try_announce(ts("nope = 1"), ts("struct S;")).err().unwrap();
553 assert!(e.to_string().contains("unknown announce option"), "{}", e);
554 }
555
556 #[test]
557 fn layout_is_an_option_of_the_attribute() {
558 let item = "pub struct S { pub a: u8 }";
559 let captured = |opts: &str| {
561 norm(
562 &capture(derive_input(
563 &format!("7u64; crate = ::l::announce{}", opts),
564 item,
565 ))
566 .unwrap(),
567 )
568 };
569 assert!(!captured("").contains("__announce_emit"));
570 assert!(!captured(", layout = direct").contains("__announce_emit"));
571 assert!(captured(", layout = single").contains("__announce_emit"));
572 let out = norm(&announce(ts("layout = single"), ts(item)));
574 assert!(out.contains("#[__announce("), "{}", out);
575 assert!(out.contains("layout=single"), "{}", out);
576 let out = norm(&announce(
577 ts("layout = single"),
578 ts(&format!("#[x] {}", item)),
579 ));
580 assert!(
581 out.contains(
582 "__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"impl\",layout=single)"
583 ),
584 "{}",
585 out
586 );
587 }
588
589 fn finish_input(attr: &str, item: &str, answers: &str) -> TokenStream {
591 ts(&format!(
592 "@__announce_attrs [ ::l::announce::__private::finish! ] {{ [7u64; attr ({}) {{ {} }}] {} }}",
593 attr, item, answers
594 ))
595 }
596
597 #[test]
598 fn checks_send_the_query_before_anything_is_emitted() {
599 let out = norm(&announce(
600 ts("crate = ::l::announce, a::check, b::check(x), meta = m::info"),
601 ts("pub struct S { pub a: u8 }"),
602 ));
603 assert!(
606 out.starts_with(
607 "a::check!{@announce_format\"1\"@__announce_attrs[a::check!,b::check!,::l::announce::__private::finish!]{[",
608 ),
609 "{}",
610 out
611 );
612 assert!(
614 out.contains(";attr(crate=::l::announce,meta=m::info,a::check,b::check(x)){pubstructS{puba:u8}}]}}"),
615 "{}",
616 out
617 );
618 assert!(
620 !out.contains("Capture")
621 && !out.contains("__announce_runner")
622 && out.matches("pubstructS").count() == 1,
623 "{}",
624 out
625 );
626 let out = norm(&announce(
628 ts("crate = ::l::announce, meta = m::info"),
629 ts("pub struct S;"),
630 ));
631 assert!(
632 !out.contains("__announce_attrs") && out.contains("Capture"),
633 "{}",
634 out
635 );
636 }
637
638 #[test]
639 fn finish_without_owned_attributes_emits_what_announce_emits_without_checks() {
640 let item = "pub struct S { pub a: u8 }";
641 let out = norm(&finish(finish_input(
643 "crate=::l::announce,a::check",
644 item,
645 "",
646 )));
647 assert!(
648 out.starts_with("pubstructS{puba:u8}#[derive(::l::announce::__private::Capture)]"),
649 "{}",
650 out
651 );
652 assert!(
653 out.contains("#[__announce(7u64;crate=::l::announce,a::check)]"),
654 "{}",
655 out
656 );
657 let out = norm(&finish(finish_input(
659 "crate=::l::announce,a::check",
660 &format!("#[x] {}", item),
661 "",
662 )));
663 assert_eq!(
664 out,
665 [
666 "#[x]#[::l::announce::__private::__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"impl\",crate=::l::announce,a::check)]pubstructS{puba:u8}",
667 CHK
668 ]
669 .concat()
670 );
671 }
672
673 #[test]
674 fn finish_with_owned_attributes_leaves_the_item_to_the_capture_derive() {
675 let item = "pub struct S { #[my_lib(skip)] pub a: u8 }";
676 let out = norm(&finish(finish_input(
677 "crate=::l::announce,a::check",
678 item,
679 "attrs(my_lib) attrs(other, my_lib)",
680 )));
681 assert!(
685 out.starts_with("#[derive(::l::announce::__private::Capture)]"),
686 "{}",
687 out
688 );
689 assert!(
690 out.contains("#[__announce(7u64;[my_lib,other];crate=::l::announce,a::check)]"),
691 "{}",
692 out
693 );
694 assert_eq!(out.matches("#[my_lib(skip)]").count(), 1, "{}", out);
695 let out = norm(&finish(finish_input(
698 "crate=::l::announce,a::check",
699 &format!("#[my_lib(on_item)] #[x] {}", item),
700 "attrs(my_lib) attrs(other, my_lib)",
701 )));
702 assert_eq!(
703 out,
704 [
705 "#[x]#[::l::announce::__private::__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"impl\",__announce_owned(my_lib,other),crate=::l::announce,a::check)]\
706 #[my_lib(on_item)]pubstructS{#[my_lib(skip)]puba:u8}",
707 CHK
708 ]
709 .concat()
710 );
711 }
712
713 #[test]
714 fn finish_reports_a_bad_query() {
715 let item = "struct S;";
716 let attr = "a::check";
717 let err = |ts: TokenStream| try_finish(ts).err().unwrap().to_string();
718 let e = err(finish_input(attr, item, "attrs(doc)"));
720 assert!(
721 e.contains("`doc` cannot be an owned attribute name"),
722 "{}",
723 e
724 );
725 let e = err(finish_input(attr, item, "other(x)"));
727 assert!(e.contains("was changed by a check"), "{}", e);
728 let e = err(finish_input(attr, item, "attrs[x]"));
729 assert!(e.contains("was changed by a check"), "{}", e);
730 let e = err(ts(
731 "@__announce_attrs [ f::finish! ] { [7u64; weird (a) { struct S; }] }",
732 ));
733 assert!(e.contains("was changed by a check"), "{}", e);
734 let e = err(ts(
736 "@__announce_attrs [ x!, f::finish! ] { [7u64; attr (a) { struct S; }] }",
737 ));
738 assert!(e.contains("elements left in its list"), "{}", e);
739 let e = err(ts(
741 "@__announce_attrs [] { [7u64; attr (a) { struct S; }] }",
742 ));
743 assert!(e.contains("with an empty list"), "{}", e);
744 let e = err(ts(
745 "@__announce_attrs [ #[derive(A)] ] { [7u64; attr (a) { struct S; }] }",
746 ));
747 assert!(e.contains("elements left in its list"), "{}", e);
748 try_finish(ts(
750 "@__announce_attrs [ x::y! ] { [7u64; attr () { struct S; }] }",
751 ))
752 .unwrap();
753 let e = err(ts("struct S;"));
755 assert!(e.contains("cannot be called by hand"), "{}", e);
756 assert!(try_finish(finish_input(attr, "fn f() {}", "")).is_err());
758 }
759
760 #[test]
761 fn finish_reads_the_query_with_the_prefix_and_without_it() {
762 let state = |prefix: &str| {
763 ts(&format!(
764 "{} @__announce_attrs [ ::l::announce::__private::finish! ] {{ [7u64; attr (crate=::l::announce) {{ pub struct S; }}] attrs(a) }}",
765 prefix
766 ))
767 };
768 let without = norm(&finish(state("")));
769 let with = norm(&finish(state("@announce_format \"1\"")));
770 assert!(without.contains("Capture"), "{}", without);
771 assert_eq!(without, with);
772 let e = try_finish(state("@announce_format \"2\""))
775 .err()
776 .unwrap()
777 .to_string();
778 assert!(
779 e.starts_with("announce: incompatible wire format: found `@announce_format \"2\"`, expected `@announce_format \"1\"`")
780 && !e.contains("cannot be called by hand"),
781 "{}",
782 e
783 );
784 let out = norm(&finish(state("@announce_format \"2\"")));
786 assert!(
787 out.starts_with("::core::compile_error!") && out.contains("incompatiblewireformat"),
788 "{}",
789 out
790 );
791 }
792
793 #[test]
794 fn capture_arguments_read_the_owned_names() {
795 let with: options::CaptureArgs =
796 syn::parse2(ts("5u64; [a, b]; crate = ::l::announce, c::check")).unwrap();
797 assert_eq!(with.random, 5);
798 assert_eq!(
799 with.owned
800 .names()
801 .iter()
802 .map(|n| n.to_string())
803 .collect::<Vec<_>>(),
804 ["a", "b"]
805 );
806 assert_eq!(
807 norm(&with.opts.to_token_stream()),
808 "crate=::l::announce,c::check"
809 );
810 let without: options::CaptureArgs = syn::parse2(ts("5u64; c::check")).unwrap();
811 assert!(without.owned.is_empty());
812 assert_eq!(norm(&without.opts.to_token_stream()), "c::check");
813 }
814
815 fn drive(attr: &str, item: &str, owned: &[(&str, &[&str])]) -> (String, usize) {
823 let (mut attr, mut item) = (ts(attr), ts(item));
824 let mut queries = 0;
825 let mut out = announce(attr.clone(), item.clone());
826 for _ in 0..20 {
827 if norm(&out).contains("@__announce_attrs") {
828 queries += 1;
831 let group = match out.clone().into_iter().last() {
832 Some(proc_macro2::TokenTree::Group(g)) => g,
833 _ => panic!("{}", out),
834 };
835 let q: helper::Query = syn::parse2(group.stream()).unwrap();
836 let mut names = OwnedAttrs::new();
837 for c in &q.list.0 {
838 let text: String = norm(&c.to_token_stream()).replace('!', "");
839 for (check, own) in owned {
840 if *check == text {
841 names.extend(OwnedAttrs::from_strs(own).names().iter().cloned());
842 }
843 }
844 }
845 let (state, last) = (&q.state, q.list.0.last().unwrap());
846 out = finish(quote! { @__announce_attrs [ #last ] { #state #names } });
847 continue;
848 }
849 if norm(&out).contains("Capture") {
850 return (norm(&out), queries);
851 }
852 let o = scan::outer(&out);
855 let at = o
856 .attrs
857 .iter()
858 .position(|a| a.segments.last().map_or(false, |l| l == "announce"))
859 .or_else(|| {
860 o.attrs.iter().position(|a| {
861 a.segments
862 .last()
863 .map_or(false, |l| l == "__announce_runner")
864 })
865 })
866 .unwrap_or_else(|| panic!("nothing left to expand: {}", norm(&out)));
867 attr = o.attrs[at].args.clone().unwrap().stream();
868 item = o
869 .tts
870 .iter()
871 .enumerate()
872 .filter(|(i, _)| *i != o.attrs[at].at && *i != o.attrs[at].at + 1)
873 .map(|(_, t)| t.clone())
874 .collect();
875 let is_runner = o.attrs[at]
876 .segments
877 .last()
878 .map_or(false, |l| l == "__announce_runner");
879 out = if is_runner {
880 run_announce(attr.clone(), item.clone())
881 } else {
882 announce(attr.clone(), item.clone())
883 };
884 }
885 panic!("no end: {}", norm(&out));
886 }
887
888 #[test]
889 fn the_query_is_sent_once_for_an_announcement() {
890 let owned: &[(&str, &[&str])] = &[("a::check", &["my_lib"]), ("b::check", &["other"])];
891 let first = norm(&announce(
895 ts("crate = ::l::announce, a::check"),
896 ts("#[other] #[my_lib(x)] pub struct S;"),
897 ));
898 assert!(
899 first.starts_with("a::check!{@announce_format\"1\"@__announce_attrs"),
900 "{}",
901 first
902 );
903 let runner = norm(&finish(finish_input(
904 "crate=::l::announce,a::check",
905 "#[other] #[my_lib(x)] pub struct S;",
906 "attrs(my_lib)",
907 )));
908 assert_eq!(
909 runner,
910 [
911 "#[other]#[::l::announce::__private::__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"impl\",__announce_owned(my_lib),crate=::l::announce,a::check)]#[my_lib(x)]pubstructS;",
912 CHK
913 ]
914 .concat()
915 );
916 let (end, queries) = drive(
918 "crate = ::l::announce, a::check",
919 "#[other] #[my_lib(x)] pub struct S;",
920 owned,
921 );
922 assert_eq!(queries, 1, "{}", end);
923 assert_eq!(end.matches("Capture").count(), 1, "{}", end);
924 assert!(
926 end.contains("#[__announce(")
927 && end.contains("[my_lib];crate=::l::announce,a::check)]"),
928 "{}",
929 end
930 );
931 let (end, queries) = drive("crate = ::l::announce, b::check", "#[x] pub struct S;", &[]);
933 assert_eq!(queries, 1, "{}", end);
934 let (end, queries) = drive("crate = ::l::announce, a::check", "pub struct S;", owned);
936 assert_eq!(queries, 1);
937 assert!(!end.contains("__announce_runner"), "{}", end);
938 }
939
940 #[test]
941 fn two_announcements_with_checks_ask_once_each() {
942 let owned: &[(&str, &[&str])] = &[("a::check", &["my_lib"]), ("b::check", &["other"])];
946 let item = "#[::l::announce::announce(b::check)] #[my_lib(x)] #[other(y)] pub struct S;";
947 let (end, queries) = drive("crate = ::l::announce, a::check", item, owned);
948 assert_eq!(queries, 2, "{}", end);
949 assert_eq!(end.matches("Capture").count(), 1, "{}", end);
950 assert!(
951 end.contains("[my_lib,other];crate=::l::announce,a::check,b::check)]"),
952 "{}",
953 end
954 );
955 let item = "#[::l::announce::announce(a::check)] #[my_lib(x)] pub struct S;";
957 let (end, queries) = drive("crate = ::l::announce, a::check", item, owned);
958 assert_eq!(queries, 2, "{}", end);
959 assert_eq!(end.matches("a::check").count(), 1, "{}", end);
960 let item = "#[::l::announce::announce(b::check)] #[my_lib(x)] #[other(y)] pub trait T {}";
962 let (end, queries) = drive("crate = ::l::announce, a::check", item, owned);
963 assert_eq!(queries, 2, "{}", end);
964 assert!(end.contains("[my_lib,other];"), "{}", end);
965 }
966
967 #[test]
968 fn an_item_that_is_not_emitted_goes_through_the_runner_as_well() {
969 for opt in ["emit_item = false", "mock = a::B"] {
970 let attr = format!("crate = ::l::announce, {}", opt);
971 let out = norm(&announce(ts(&attr), ts("#[derive(Debug)] pub struct S;")));
974 let want = format!(
976 "#[derive(Debug)]#[::l::announce::__private::__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"macro\",crate=::l::announce,{})]pubstructS;S!{{@announce_format\"1\"@__announce_exists}}",
977 opt.replace(' ', "")
978 );
979 assert_eq!(out, want);
980 let out = norm(&announce(ts(&attr), ts("pub struct S;")));
983 assert!(
984 out.starts_with("#[derive(::l::announce::__private::Capture)]")
985 && !out.contains("__announce_runner"),
986 "{}",
987 out
988 );
989 assert_eq!(out.matches("pubstructS").count(), 1, "{}", out);
990 let args = format!("__announce_format_version = \"1\", {}", attr);
992 let out = norm(&run_announce(ts(&args), ts("#[doc = \"d\"] pub struct S;")));
993 assert!(
994 out.starts_with("#[derive(::l::announce::__private::Capture)]"),
995 "{}",
996 out
997 );
998 assert_eq!(out.matches("pubstructS").count(), 1, "{}", out);
999 let out = norm(&announce(
1001 ts(&format!("{}, a::check", attr)),
1002 ts("#[derive(Debug)] pub struct S;"),
1003 ));
1004 assert!(
1005 out.starts_with("a::check!{@announce_format\"1\"@__announce_attrs"),
1006 "{}",
1007 out
1008 );
1009 let end = norm(&finish(finish_input(
1010 &format!("crate=::l::announce,{},a::check", opt.replace(' ', "")),
1011 "#[derive(Debug)] pub struct S;",
1012 "attrs(my_lib)",
1013 )));
1014 assert!(
1015 end.contains("pubstructS;S!{@announce_format\"1\"@__announce_exists}")
1016 && end.contains("__announce_runner(__announce_format_version=\"1\",__announce_sentinel=\"macro\",__announce_owned(my_lib),"),
1017 "{}",
1018 end
1019 );
1020 }
1021 }
1022
1023 #[test]
1024 fn announcements_merge_with_one_that_is_not_emitted() {
1025 let runner = |opt: &str| {
1028 format!(
1029 "#[::l::announce::__private::__announce_runner(__announce_format_version = \"1\"{})]",
1030 opt
1031 )
1032 };
1033 let out = norm(&announce(
1034 ts("meta = a::x"),
1035 ts(&format!(
1036 "#[x] {} pub struct S;",
1037 runner(", emit_item = false")
1038 )),
1039 ));
1040 assert!(out.contains("emit_item=false,meta=a::x)]"), "{}", out);
1041 let out = norm(&announce(
1042 ts("emit_item = false"),
1043 ts(&format!("#[x] {} pub struct S;", runner(", meta = a::x"))),
1044 ));
1045 assert!(out.contains("emit_item=false"), "{}", out);
1046 assert!(out.contains("meta=a::x"), "{}", out);
1047 let out = norm(&announce(
1048 ts("mock = a::B"),
1049 ts(&format!(
1050 "#[x] {} pub trait S {{}}",
1051 runner(", meta = a::x")
1052 )),
1053 ));
1054 assert!(
1055 out.contains("mock=a::B") && !out.contains("compile_error"),
1056 "{}",
1057 out
1058 );
1059 let out = norm(&announce(
1061 ts("emit_item = false"),
1062 ts(&format!(
1063 "#[x] {} pub struct S;",
1064 runner(", emit_item = true")
1065 )),
1066 ));
1067 assert!(out.starts_with("::core::compile_error!"), "{}", out);
1068 assert!(out.contains("`emit_item=false`"), "{}", out);
1069 assert_eq!(out.matches("compile_error").count(), 1, "{}", out);
1070 let out = norm(&announce(
1071 ts("mock = a::B"),
1072 ts(&format!(
1073 "#[x] {} pub trait S {{}}",
1074 runner(", mock = a::C")
1075 )),
1076 ));
1077 assert!(
1078 out.starts_with("::core::compile_error!") && out.contains("mock="),
1079 "{}",
1080 out
1081 );
1082 }
1083
1084 #[test]
1085 fn announcements_merge_only_under_one_trampoline_name() {
1086 let runner = |opts: &str| {
1087 format!(
1088 "#[::l::announce::__private::__announce_runner(__announce_format_version = \"1\"{})]",
1089 opts
1090 )
1091 };
1092 let item = format!("#[x] {} pub trait Parse {{}}", runner(""));
1094 let out = norm(&announce(ts("macro_name = ParseAlt"), ts(&item)));
1095 assert!(out.starts_with("::core::compile_error!"), "{}", out);
1096 assert_eq!(out.matches("compile_error").count(), 1, "{}", out);
1097 assert!(out.contains("(`ParseAlt`and`Parse`)"), "{}", out);
1098 assert!(out.contains("onetrampoline"), "{}", out);
1099 assert!(out.ends_with(&norm(&ts(&item))), "{}", out);
1100 let out = norm(&run_announce(
1102 ts("__announce_format_version = \"1\", macro_name = ParseAlt"),
1103 ts(&format!("{} pub trait Parse {{}}", runner(""))),
1104 ));
1105 assert!(out.starts_with("::core::compile_error!"), "{}", out);
1106 assert!(out.contains("(`ParseAlt`and`Parse`)"), "{}", out);
1107 for (attr, set) in [
1109 ("macro_name = Parse", ""),
1110 ("", ", macro_name = Parse"),
1111 ("macro_name = ParseAlt", ", macro_name = ParseAlt"),
1112 ] {
1113 let item = format!("#[x] {} pub trait Parse {{}}", runner(set));
1114 let out = norm(&announce(ts(attr), ts(&item)));
1115 assert!(!out.contains("compile_error"), "{}: {}", item, out);
1116 assert_eq!(out.matches("__announce_runner").count(), 1, "{}", out);
1117 }
1118 }
1119
1120 const DUMMY_ARGS: &str = "7u64; crate = ::l::announce";
1123
1124 fn dummy(args: &str, body: &str) -> TokenStream {
1125 ts(&format!(
1126 "#[derive(::l::announce::__private::Capture)] #[__announce({})] \
1127 #[::l::announce::__private::remove] #[allow(dead_code, non_camel_case_types)] \
1128 enum __AnnounceCap_0 {{ V = {{ {} 0 }} }}",
1129 args, body
1130 ))
1131 }
1132
1133 fn derive_input(args: &str, body: &str) -> TokenStream {
1135 let mut it = dummy(args, body).into_iter();
1136 it.next();
1137 it.next();
1138 it.collect()
1139 }
1140
1141 #[test]
1142 fn the_dummy_is_read_from_its_top_level_tokens() {
1143 let (args, item) =
1144 scan_dummy(derive_input(DUMMY_ARGS, "pub struct S { pub a: u8 }")).unwrap();
1145 assert_eq!(norm(&args), "7u64;crate=::l::announce");
1146 assert_eq!(norm(&item.unwrap()), "pubstructS{puba:u8}");
1147 let (_, item) = scan_dummy(derive_input(DUMMY_ARGS, "")).unwrap();
1149 assert!(item.is_none());
1150 for bad in [
1152 "struct S;",
1153 "enum E { V = 1 }",
1154 "enum E { V = { 1 } }",
1155 "enum E { V = { S } }",
1156 "#[__announce] enum E { V = { 0 } }",
1157 "#[__announce(1u64;)] enum E { V = { S 1 } }",
1158 ] {
1159 assert!(scan_dummy(ts(bad)).is_none(), "{}", bad);
1160 }
1161 }
1162
1163 #[test]
1164 fn a_dummy_of_another_shape_is_read_with_syn() {
1165 let input = ts(
1167 "#[__announce(7u64; crate = ::l::announce)] pub enum Cap { V = { pub struct S; 0 } }",
1168 );
1169 assert!(scan_dummy(input.clone()).is_none());
1170 let d = read_dummy(input).unwrap();
1171 assert_eq!(d.args.random, 7);
1172 assert_eq!(norm(&d.item.unwrap()), "pubstructS;");
1173 let e = read_dummy(ts("pub enum Cap { V = { 0 } }")).err().unwrap();
1174 assert!(e.to_string().contains("missing #[__announce]"), "{}", e);
1175 }
1176
1177 #[test]
1178 fn capture_emits_what_the_derive_emits() {
1179 let out = norm(&capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: u8 }")).unwrap());
1180 assert!(out.contains("macro_rules!__announce_S_"), "{}", out);
1181 assert!(out.contains("::l::announce::AnnounceforS{}"), "{}", out);
1182 assert!(!out.starts_with("pubstruct"), "{}", out);
1184 assert!(capture(derive_input(DUMMY_ARGS, "")).unwrap().is_empty());
1186 assert!(capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: }")).is_err());
1188 }
1189
1190 #[test]
1191 fn capture_keeps_the_real_item_next_to_its_error() {
1192 let args = "7u64; [my_lib]; crate = ::l::announce";
1195 let out = norm(
1196 &capture(derive_input(
1197 args,
1198 "pub struct S { #[my_lib] #[cfg(a)] pub a: Local, pub b: u8 }",
1199 ))
1200 .unwrap(),
1201 );
1202 assert!(out.starts_with("::core::compile_error!"), "{}", out);
1203 assert!(out.ends_with("pubstructS{puba:Local,pubb:u8}"), "{}", out);
1204 let out =
1206 norm(&capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: Local }")).unwrap());
1207 assert!(out.starts_with("::core::compile_error!"), "{}", out);
1208 assert!(!out.contains("pubstructS"), "{}", out);
1209 }
1210
1211 #[test]
1212 fn the_capture_arguments_read_the_sentinel() {
1213 use runner::Sentinel;
1214 let with: options::CaptureArgs =
1215 syn::parse2(ts("5u64; sentinel_impl; [a]; crate = ::l::announce")).unwrap();
1216 assert_eq!(with.sentinel, Some(Sentinel::Impl));
1217 assert_eq!(with.owned.names().len(), 1);
1218 let with: options::CaptureArgs = syn::parse2(ts("5u64; sentinel_macro; c::check")).unwrap();
1219 assert_eq!(with.sentinel, Some(Sentinel::Macro));
1220 assert_eq!(norm(&with.opts.to_token_stream()), "c::check");
1221 let without: options::CaptureArgs = syn::parse2(ts("5u64; c::check")).unwrap();
1222 assert_eq!(without.sentinel, None);
1223 assert!(syn::parse2::<options::CaptureArgs>(ts("5u64; sentinel_cake; c::check")).is_err());
1224 let out = norm(&capture_output(
1226 &Options::default(),
1227 &TokenStream::new(),
1228 ts("pub struct S;"),
1229 5,
1230 &OwnedAttrs::new(),
1231 Some(runner::Sentinel::Macro),
1232 ));
1233 assert!(
1234 out.contains("#[__announce(5u64;sentinel_macro;)]"),
1235 "{}",
1236 out
1237 );
1238 }
1239
1240 #[test]
1241 fn capture_leaves_what_the_sentinel_needs_next_to_its_error() {
1242 let args = "7u64; sentinel_impl; crate = ::l::announce";
1245 for body in ["pub struct S { pub a: Local }", "pub struct S { pub a: }"] {
1246 let out = norm(&capture(derive_input(args, body)).unwrap());
1247 assert!(
1248 out.starts_with("::core::compile_error!"),
1249 "{}: {}",
1250 body,
1251 out
1252 );
1253 assert!(
1254 out.ends_with("impl::l::announce::AnnounceforS{}"),
1255 "{}: {}",
1256 body,
1257 out
1258 );
1259 }
1260 let args = "7u64; sentinel_macro; crate = ::l::announce, macro_name = N";
1261 for body in [
1262 "pub trait S { fn f(&self) -> Local; }",
1263 "pub trait S { fn }",
1264 ] {
1265 let out = norm(&capture(derive_input(args, body)).unwrap());
1266 assert!(
1267 out.starts_with("::core::compile_error!"),
1268 "{}: {}",
1269 body,
1270 out
1271 );
1272 assert!(
1273 out.contains(
1274 "macro_rules!__announce_stub_0000000000000007{($(@announce_format\"1\")?@__announce_exists)=>{};}"
1275 ) && out.ends_with("use__announce_stub_0000000000000007asN;"),
1276 "{}: {}",
1277 body,
1278 out
1279 );
1280 }
1281 let out =
1283 norm(&capture(derive_input(DUMMY_ARGS, "pub struct S { pub a: Local }")).unwrap());
1284 assert!(
1285 !out.contains("Announce") && !out.contains("__announce_exists"),
1286 "{}",
1287 out
1288 );
1289 let args = "7u64; sentinel_macro; crate = ::l::announce";
1291 assert!(capture(derive_input(args, "")).unwrap().is_empty());
1292 }
1293
1294 #[test]
1295 fn the_attribute_reads_no_more_than_the_head_of_the_item() {
1296 for item in ["pub struct S { pub a: }", "pub trait T { fn }"] {
1299 let out = norm(&announce(TokenStream::new(), ts(item)));
1300 assert!(
1301 (out.contains("Capture") || out.contains("__announce_runner"))
1302 && !out.contains("compile_error"),
1303 "{}",
1304 out
1305 );
1306 }
1307 for (src, msg) in [
1309 ("pub fn f() {}", "supports struct, enum"),
1310 ("mod m;", "only inline modules"),
1311 ("impl S {}", "supports struct, enum"),
1312 ] {
1313 let e = try_announce(TokenStream::new(), ts(src)).err().unwrap();
1314 assert!(e.to_string().contains(msg), "{}: {}", src, e);
1315 }
1316 for src in [
1318 "struct S;",
1319 "pub(crate) enum E {}",
1320 "union U { a: u8 }",
1321 "pub unsafe trait T {}",
1322 "type A = u8;",
1323 "mod m { }",
1324 ] {
1325 assert!(try_announce(TokenStream::new(), ts(src)).is_ok(), "{}", src);
1326 }
1327 }
1328}