1pub mod attrs;
26mod kv;
27pub mod leak;
28mod transformer;
29mod typeleak;
30
31pub use attrs::{remove_attributes, CheckCall, OwnedAttrs, Query};
32pub use kv::KvArgs;
33pub use transformer::signature_only;
34pub use type_leak;
37pub use typeleak::TypeLeak;
38
39pub use crate::{callback, transformer};
43
44use proc_macro2::{Ident, Span, TokenStream};
45use std::sync::atomic::{AtomicU64, Ordering};
46use syn::ext::IdentExt;
47use syn::parse::{Parse, ParseStream};
48use syn::punctuated::Punctuated;
49use syn::{Attribute, Path, Result, Token};
50use template_quote::{quote, ToTokens};
51
52pub fn gensym(prefix: &str) -> Ident {
54 use std::hash::{BuildHasher, Hasher};
55 static COUNTER: AtomicU64 = AtomicU64::new(0);
56 let mut h = std::collections::hash_map::RandomState::new().build_hasher();
57 h.write_u64(COUNTER.fetch_add(1, Ordering::Relaxed));
58 Ident::new(&format!("{}{:016x}", prefix, h.finish()), Span::call_site())
59}
60
61#[derive(Clone, Debug)]
63#[allow(clippy::large_enum_variant)]
64pub enum Cont {
65 Bang(Path),
67 Attr {
70 attr: Attribute,
71 name: Option<Ident>,
72 },
73}
74
75impl Cont {
76 pub fn bang(mut path: Path) -> Self {
78 for seg in &mut path.segments {
79 seg.arguments = syn::PathArguments::None;
80 }
81 Cont::Bang(path)
82 }
83
84 pub fn derive(path: Path) -> Self {
86 Cont::Attr {
87 attr: syn::parse_quote!(#[derive(#path)]),
88 name: None,
89 }
90 }
91
92 pub fn path(&self) -> Path {
95 match self {
96 Cont::Bang(p) => p.clone(),
97 Cont::Attr { attr, .. } => {
98 if attr.path().is_ident("derive") {
99 let paths =
100 attr.parse_args_with(Punctuated::<Path, Token![,]>::parse_terminated);
101 if let Some(p) = paths.ok().and_then(|p| p.into_iter().next()) {
102 return p;
103 }
104 }
105 attr.path().clone()
106 }
107 }
108 }
109}
110
111fn parse_macro_path(input: ParseStream) -> Result<Path> {
112 let leading_colon: Option<Token![::]> = input.parse()?;
113 let mut segments = Punctuated::new();
114 loop {
115 let ident = Ident::parse_any(input)?;
116 segments.push_value(syn::PathSegment::from(ident));
117 if input.peek(Token![::]) {
118 segments.push_punct(input.parse()?);
119 } else {
120 break;
121 }
122 }
123 Ok(Path {
124 leading_colon,
125 segments,
126 })
127}
128
129impl Parse for Cont {
130 fn parse(input: ParseStream) -> Result<Self> {
131 if input.peek(Token![#]) {
132 let pound_token = input.parse()?;
133 let content;
134 let bracket_token = syn::bracketed!(content in input);
135 let attr = Attribute {
136 pound_token,
137 style: syn::AttrStyle::Outer,
138 bracket_token,
139 meta: content.parse()?,
140 };
141 let name = if input.peek(syn::Ident) {
142 Some(input.parse()?)
143 } else {
144 None
145 };
146 Ok(Cont::Attr { attr, name })
147 } else {
148 let path = parse_macro_path(input)?;
149 input.parse::<Token![!]>()?;
150 Ok(Cont::Bang(path))
151 }
152 }
153}
154
155impl Cont {
156 fn eq_key(&self) -> String {
159 match self {
160 Cont::Bang(_) => self.to_token_stream().to_string(),
161 Cont::Attr { attr, .. } => attr.to_token_stream().to_string(),
162 }
163 }
164}
165
166impl PartialEq for Cont {
168 fn eq(&self, other: &Self) -> bool {
169 self.eq_key() == other.eq_key()
170 }
171}
172impl Eq for Cont {}
173impl std::hash::Hash for Cont {
174 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
175 self.eq_key().hash(state)
176 }
177}
178
179impl ToTokens for Cont {
180 fn to_tokens(&self, tokens: &mut TokenStream) {
181 match self {
182 Cont::Bang(p) => {
183 let mut p = p.clone();
185 for seg in &mut p.segments {
186 seg.arguments = syn::PathArguments::None;
187 }
188 tokens.extend(quote! { #p ! })
189 }
190 Cont::Attr { attr, name } => tokens.extend(quote! { #attr #name }),
191 }
192 }
193}
194
195#[derive(Clone, Debug, Default)]
197pub struct ContList(pub Vec<Cont>);
198
199impl Parse for ContList {
200 fn parse(input: ParseStream) -> Result<Self> {
202 let p: Punctuated<Cont, Token![,]> = Punctuated::parse_terminated(input)?;
203 Ok(ContList(p.into_iter().collect()))
204 }
205}
206
207impl ToTokens for ContList {
208 fn to_tokens(&self, tokens: &mut TokenStream) {
210 let items = &self.0;
211 tokens.extend(quote! { [ #(#items),* ] });
212 }
213}
214
215impl FromIterator<Cont> for ContList {
216 fn from_iter<I: IntoIterator<Item = Cont>>(iter: I) -> Self {
217 ContList(iter.into_iter().collect())
218 }
219}
220
221pub fn is_expression_position(options: &TokenStream) -> bool {
228 matches!(
229 options.clone().into_iter().next(),
230 Some(proc_macro2::TokenTree::Ident(i)) if i == "expr"
231 )
232}
233
234impl ContList {
235 pub fn name_dummies(&mut self) {
238 for c in &mut self.0 {
239 if let Cont::Attr { name, .. } = c {
240 if name.is_none() {
241 *name = Some(gensym("__AnnounceT_"));
242 }
243 }
244 }
245 }
246
247 pub fn invoke(&self, body: TokenStream) -> TokenStream {
249 self.invoke_with_options(None, body)
250 }
251
252 pub fn invoke_with_options(
256 &self,
257 options: Option<&TokenStream>,
258 body: TokenStream,
259 ) -> TokenStream {
260 let opts = options.map(|o| quote! { ( #o ) });
261 let in_expr = options.map_or(false, is_expression_position);
262 let mut list = self.clone();
263 list.name_dummies();
264 match list.0.first() {
265 None => quote! { compile_error! { "announce: empty continuation list" } },
266 Some(Cont::Attr { .. }) if in_expr => quote! {
267 compile_error! { "announce: a derive/attribute continuation emits items, so it cannot be used with `(expr)`" }
268 },
269 Some(Cont::Bang(p)) => quote! { #p ! { #opts #list #body } },
270 Some(Cont::Attr { attr, name }) => quote! {
271 #attr
272 #[allow(dead_code, non_camel_case_types)]
273 enum #name { __AnnouncePayload = (stringify! { #opts #list #body }, 0).1 }
274 },
275 }
276 }
277}
278
279#[derive(Clone, Debug)]
281pub struct Meta {
282 pub path: Path,
284 pub args: Option<TokenStream>,
286}
287
288impl Meta {
289 pub fn parse_args_with<P: syn::parse::Parser>(&self, parser: P) -> Result<P::Output> {
291 parser.parse2(self.args.clone().unwrap_or_default())
292 }
293
294 pub fn kv(&self) -> Result<KvArgs> {
296 self.parse_args_with(KvArgs::parse)
297 }
298
299 pub fn name_values(&self) -> Result<Vec<syn::MetaNameValue>> {
302 let p: Punctuated<syn::MetaNameValue, Token![,]> =
303 self.parse_args_with(Punctuated::parse_terminated)?;
304 Ok(p.into_iter().collect())
305 }
306}
307
308impl Parse for Meta {
309 fn parse(input: ParseStream) -> Result<Self> {
310 let path = Path::parse_mod_style(input)?;
311 let args = if input.peek(syn::token::Paren) {
312 let content;
313 syn::parenthesized!(content in input);
314 Some(content.parse()?)
315 } else {
316 None
317 };
318 Ok(Meta { path, args })
319 }
320}
321
322impl ToTokens for Meta {
323 fn to_tokens(&self, tokens: &mut TokenStream) {
324 let p = &self.path;
325 match &self.args {
326 Some(a) => tokens.extend(quote! { #p(#a) }),
327 None => tokens.extend(quote! { #p }),
328 }
329 }
330}
331
332#[derive(Clone, Debug)]
334pub struct Entry {
335 pub key: Cont,
337 pub meta: Vec<Meta>,
339 pub item: TokenStream,
341}
342
343impl Entry {
344 pub fn item(&self) -> Result<syn::Item> {
346 syn::parse2(self.item.clone())
347 }
348
349 pub fn mock(&self) -> Option<Path> {
352 self.meta
353 .iter()
354 .find(|m| m.path.is_ident("mock"))
355 .and_then(|m| syn::parse2(m.args.clone()?).ok())
356 }
357
358 pub fn find_meta(&self, name: &str) -> Option<&Meta> {
361 self.meta
362 .iter()
363 .find(|m| m.path.segments.last().map_or(false, |s| s.ident == name))
364 }
365
366 pub fn type_leak(&self) -> Result<Option<TypeLeak>> {
369 let m = match self.find_meta("type_leak") {
370 Some(m) => m,
371 None => return Ok(None),
372 };
373 TypeLeak::from_kv(&mut m.kv()?).map(Some)
374 }
375
376 pub fn has_check(&self, name: &str) -> bool {
379 self.meta
380 .iter()
381 .any(|m| m.path.segments.last().map_or(false, |s| s.ident == name))
382 }
383
384 pub fn owned_attrs(&self) -> OwnedAttrs {
393 attrs::owned_from_metas(&self.meta)
394 }
395
396 pub fn item_without_owned_attrs(&self) -> Result<syn::Item> {
400 let mut item = self.item()?;
401 self.owned_attrs().strip(&mut item)?;
402 Ok(item)
403 }
404}
405
406impl Parse for Entry {
407 fn parse(input: ParseStream) -> Result<Self> {
408 let key = input.parse()?;
409 input.parse::<Token![=>]>()?;
410 let meta_content;
411 syn::bracketed!(meta_content in input);
412 let meta: Punctuated<Meta, Token![,]> = Punctuated::parse_terminated(&meta_content)?;
413 let item_content;
414 syn::braced!(item_content in input);
415 Ok(Entry {
416 key,
417 meta: meta.into_iter().collect(),
418 item: item_content.parse()?,
419 })
420 }
421}
422
423impl ToTokens for Entry {
424 fn to_tokens(&self, tokens: &mut TokenStream) {
425 let (key, meta, item) = (&self.key, &self.meta, &self.item);
426 tokens.extend(quote! { #key => [ #(#meta),* ] { #item } });
427 }
428}
429
430#[derive(Clone, Debug)]
578pub struct Input {
579 pub options: Option<TokenStream>,
584 pub list: ContList,
586 pub entries: Vec<Entry>,
588 pub rest: TokenStream,
590}
591
592impl Parse for Input {
593 fn parse(input: ParseStream) -> Result<Self> {
594 while input.peek(Token![@]) {
598 let fork = input.fork();
599 let _: Token![@] = fork.parse()?;
600 match fork.parse::<Ident>() {
601 Ok(i) if i == "__announce_dollar" || i == "__announce_self" => {
602 let _g: proc_macro2::Group = fork.parse()?;
603 syn::parse::discouraged::Speculative::advance_to(input, &fork);
604 }
605 _ => break,
606 }
607 }
608 let options = if input.peek(syn::token::Paren) {
609 let o;
610 syn::parenthesized!(o in input);
611 Some(o.parse()?)
612 } else {
613 None
614 };
615 let content;
616 syn::bracketed!(content in input);
617 let list = content.parse()?;
618 let mut entries = Vec::new();
619 loop {
620 let fork = input.fork();
621 match fork.parse::<Entry>() {
622 Ok(e) => {
623 entries.push(e);
624 syn::parse::discouraged::Speculative::advance_to(input, &fork);
625 }
626 Err(e) => {
627 let probe = input.fork();
632 if probe.parse::<Cont>().is_ok()
633 && probe.parse::<Token![=>]>().is_ok()
634 && (probe.peek(syn::token::Bracket) || probe.peek(syn::token::Brace))
635 {
636 return Err(syn::Error::new(
637 e.span(),
638 format!("announce: malformed entry: {}", e),
639 ));
640 }
641 break;
642 }
643 }
644 }
645 Ok(Input {
646 options,
647 list,
648 entries,
649 rest: input.parse()?,
650 })
651 }
652}
653
654impl Input {
655 pub fn entries_in_order(&self) -> impl Iterator<Item = &Entry> {
657 self.entries.iter().rev()
658 }
659
660 pub fn fetch(&self, new: impl IntoIterator<Item = Cont>) -> TokenStream {
671 self.fetch_conts(new.into_iter().collect())
672 }
673
674 fn fetch_conts(&self, new: Vec<Cont>) -> TokenStream {
678 let fetched = self.fetched_paths();
681 if let Some(c) = new
682 .iter()
683 .find(|c| fetched.contains(&path_key(&c.path())) && self.entry_for(c).is_none())
684 {
685 let msg = format!(
686 "announce: `{}` was fetched but did not provide a definition; is it an `#[announce]` trampoline?",
687 c.to_token_stream()
688 );
689 return quote! { compile_error! { #msg } };
690 }
691 let mut options: Vec<TokenStream> = self.option_items();
692 if !new.is_empty() {
693 let paths = new.iter().map(|c| c.path());
694 options.push(quote! { __announce_fetched(#(#paths),*) });
695 }
696 let opts = if options.is_empty() {
697 None
698 } else {
699 Some(quote! { #(#options),* })
700 };
701 let list: ContList = new.into_iter().chain(self.list.0.iter().cloned()).collect();
702 let (entries, rest) = (&self.entries, &self.rest);
703 list.invoke_with_options(opts.as_ref(), quote! { #(#entries)* #rest })
704 }
705
706 pub fn has_option(&self, name: &str) -> bool {
708 self.options.as_ref().map_or(false, |o| {
709 o.clone()
710 .into_iter()
711 .any(|tt| matches!(tt, proc_macro2::TokenTree::Ident(i) if i == name))
712 })
713 }
714
715 pub fn option_items(&self) -> Vec<TokenStream> {
717 let mut items = Vec::new();
718 let mut cur = TokenStream::new();
719 for tt in self.options.clone().unwrap_or_default() {
720 if matches!(&tt, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') {
721 if !cur.is_empty() {
722 items.push(std::mem::take(&mut cur));
723 }
724 } else {
725 cur.extend(Some(tt));
726 }
727 }
728 if !cur.is_empty() {
729 items.push(cur);
730 }
731 items
732 }
733
734 fn fetched_paths(&self) -> Vec<String> {
735 let mut out = Vec::new();
736 for item in self.option_items() {
737 let parsed: Result<Meta> = syn::parse2(item);
738 if let Ok(m) = parsed {
739 if m.path.is_ident("__announce_fetched") {
740 let paths: Result<Punctuated<Path, Token![,]>> =
741 m.parse_args_with(Punctuated::parse_terminated);
742 if let Ok(paths) = paths {
743 out.extend(paths.iter().map(path_key));
744 }
745 }
746 }
747 }
748 out
749 }
750
751 pub fn rest_as<T: Parse>(&self) -> Result<T> {
753 syn::parse2(self.rest.clone())
754 }
755
756 pub fn entry_for(&self, cont: &Cont) -> Option<&Entry> {
759 let want = path_key(&cont.path());
760 self.entries
761 .iter()
762 .find(|e| path_key(&e.key.path()) == want)
763 }
764
765 pub fn gather(&self, needed: impl IntoIterator<Item = Cont>) -> Gather<'_> {
778 let needed: Vec<Cont> = needed.into_iter().collect();
779 if let Some(head) = self.list.0.first() {
782 if let Some(c) = needed
783 .iter()
784 .find(|c| path_key(&c.path()) == path_key(&head.path()))
785 {
786 let msg = format!(
787 "announce: `gather` asked for `{}`, which is the callback itself; \
788 request the definitions' trampolines instead",
789 c.to_token_stream()
790 );
791 return Gather::Fetch(quote! { compile_error! { #msg } });
792 }
793 }
794 let mut missing: Vec<Cont> = Vec::new();
795 for c in &needed {
796 if self.entry_for(c).is_none()
797 && !missing
798 .iter()
799 .any(|m| path_key(&m.path()) == path_key(&c.path()))
800 {
801 missing.push(c.clone());
802 }
803 }
804 if missing.is_empty() {
805 return Gather::Ready(
806 needed
807 .iter()
808 .map(|c| self.entry_for(c).expect("checked above"))
809 .collect(),
810 );
811 }
812 Gather::Fetch(self.fetch_conts(missing))
813 }
814
815 pub fn forward(&self) -> Option<TokenStream> {
818 if self.list.0.len() < 2 {
819 return None;
820 }
821 let tail: ContList = self.list.0[1..].iter().cloned().collect();
822 let (entries, rest) = (&self.entries, &self.rest);
823 Some(tail.invoke_with_options(self.options.as_ref(), quote! { #(#entries)* #rest }))
824 }
825}
826
827pub enum Gather<'a> {
829 Ready(Vec<&'a Entry>),
831 Fetch(TokenStream),
833}
834
835fn path_key(p: &Path) -> String {
836 let mut p = p.clone();
837 for seg in &mut p.segments {
838 seg.arguments = syn::PathArguments::None;
839 }
840 p.to_token_stream().to_string()
841}
842
843#[doc(hidden)]
844pub mod __private {
845 pub use proc_macro2;
846 pub use syn;
847}
848
849#[macro_export]
869macro_rules! callback {
870 ($(#[$m:meta])* $vis:vis fn $name:ident ($input:ident) $body:block) => {
871 $(#[$m])*
872 #[proc_macro]
873 $vis fn $name(__announce_ts: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
874 let __announce_run = |__announce_ts: ::proc_macro::TokenStream|
875 -> $crate::helper::__private::syn::Result<$crate::helper::__private::proc_macro2::TokenStream> {
876 let $input: $crate::helper::Input = $crate::helper::__private::syn::parse(__announce_ts)?;
877 $body
878 };
879 match __announce_run(__announce_ts) {
880 ::core::result::Result::Ok(ts) => ts.into(),
881 ::core::result::Result::Err(e) => e.to_compile_error().into(),
882 }
883 }
884 };
885}
886
887pub fn check(
952 input: TokenStream,
953 attributes: &[&str],
954 f: impl FnOnce(&mut CheckInput) -> Result<TokenStream>,
955) -> TokenStream {
956 let run = || -> Result<TokenStream> {
957 for name in attributes {
958 if !is_attribute_name(name) {
959 return Err(syn::Error::new(
960 Span::call_site(),
961 format!(
962 "announce: `{}` is not an identifier: it cannot be an owned attribute name",
963 name
964 ),
965 ));
966 }
967 }
968 let owned = OwnedAttrs::from_strs(attributes);
969 match syn::parse2::<CheckCall>(input)? {
970 CheckCall::Query(q) => Ok(q.answer(&owned)),
972 CheckCall::Run(c) => {
973 owned.check_names()?;
974 f(&mut c.with_owned(owned))
975 }
976 }
977 };
978 run().unwrap_or_else(|e| e.to_compile_error())
979}
980
981fn is_attribute_name(name: &str) -> bool {
983 let name = name.strip_prefix("r#").unwrap_or(name);
984 let mut chars = name.chars();
985 matches!(chars.next(), Some(c) if c == '_' || c.is_alphabetic())
986 && chars.all(|c| c == '_' || c.is_alphanumeric())
987 && name != "_"
988}
989
990pub fn payload(item: TokenStream) -> Option<TokenStream> {
993 let di: syn::DeriveInput = syn::parse2(item).ok()?;
994 let e = match di.data {
995 syn::Data::Enum(e) => e,
996 _ => return None,
997 };
998 let v = e.variants.first()?;
999 if v.ident != "__AnnouncePayload" || e.variants.len() != 1 {
1000 return None;
1001 }
1002 let f = match &v.discriminant.as_ref()?.1 {
1003 syn::Expr::Field(f) => f,
1004 _ => return None,
1005 };
1006 let t = match &*f.base {
1007 syn::Expr::Tuple(t) => t,
1008 _ => return None,
1009 };
1010 let m = match t.elems.first()? {
1011 syn::Expr::Macro(m) => m,
1012 _ => return None,
1013 };
1014 if !m.mac.path.is_ident("stringify") {
1015 return None;
1016 }
1017 Some(m.mac.tokens.clone())
1018}
1019
1020pub fn redirect(item: TokenStream, target: &Path) -> Option<TokenStream> {
1040 let body = payload(item)?;
1041 let dollar = proc_macro2::Punct::new('$', proc_macro2::Spacing::Alone);
1047 Some(quote! { #target ! { @__announce_self { #target } @__announce_dollar { #dollar } #body } })
1048}
1049
1050pub struct CheckInput {
1057 pub args: TokenStream,
1059 pub item: syn::Item,
1061 pub owned: OwnedAttrs,
1064}
1065
1066impl CheckInput {
1067 pub fn with_owned(mut self, owned: OwnedAttrs) -> Self {
1069 self.owned = owned;
1070 self
1071 }
1072
1073 pub fn owned_attributes(&self) -> Vec<Attribute> {
1076 self.owned.find(&self.item)
1077 }
1078}
1079
1080impl Parse for CheckInput {
1081 fn parse(input: ParseStream) -> Result<Self> {
1082 if input.peek(Token![@]) {
1083 return Err(input.error(
1084 "announce: this check received the attribute query `@__announce_attrs` that \
1085 `#[announce]` sends first, and does not answer it. Run the check with \
1086 `announce::helper::check`, or parse `announce::helper::CheckCall` and answer \
1087 `Query` (a `macro_rules!` check needs one more arm); every check must \
1088 answer. See the `announce::helper::attrs` docs",
1089 ));
1090 }
1091 let content;
1092 syn::parenthesized!(content in input);
1093 let args = content.parse()?;
1094 input.parse::<Token![,]>()?;
1095 Ok(CheckInput {
1096 args,
1097 item: input.parse()?,
1098 owned: OwnedAttrs::new(),
1099 })
1100 }
1101}
1102
1103pub fn check_type(krate: &Path, ty: &syn::Type) -> TokenStream {
1106 quote! {
1107 const _: () = {
1108 fn __announce_chk<__AnnounceT: ?::core::marker::Sized + #krate::Announce>() {}
1109 let _ = __announce_chk::<#ty>;
1110 };
1111 }
1112}
1113
1114pub fn check_trait(krate: &Path, path: &Path) -> TokenStream {
1117 check_type(krate, &syn::parse_quote!(dyn #path))
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122 use super::*;
1123
1124 fn s(ts: impl ToTokens) -> String {
1125 ts.to_token_stream()
1126 .to_string()
1127 .split_whitespace()
1128 .collect()
1129 }
1130
1131 #[test]
1132 fn bang_drops_generic_arguments() {
1133 assert_eq!(s(Cont::bang(syn::parse_quote!(a::Enum<A, B>))), "a::Enum!");
1134 }
1135
1136 #[test]
1137 fn input_roundtrip_and_meta() {
1138 let input: Input = syn::parse_quote! {
1139 [cb!, #[derive(p::D)] N]
1140 a::F! => [x::check(1), mock(::core::iter::Iterator)] { pub trait F {} }
1141 #[derive(p::D)] M => [] { pub struct S; }
1142 (rest)
1143 };
1144 assert_eq!(input.list.0.len(), 2);
1145 assert_eq!(input.entries.len(), 2);
1146 assert_eq!(s(&input.entries[0].meta[0]), "x::check(1)");
1147 assert_eq!(
1148 s(input.entries[0].find_meta("check").unwrap().args.clone()),
1149 "1"
1150 );
1151 assert!(input.entries[0].has_check("check"));
1152 assert_eq!(
1153 s(input.entries[0].mock().unwrap()),
1154 "::core::iter::Iterator"
1155 );
1156 assert_eq!(s(input.entries[1].key.path()), "p::D");
1157 assert_eq!(s(&input.rest), "(rest)");
1158 let order: Vec<String> = input.entries_in_order().map(|e| s(e.key.path())).collect();
1159 assert_eq!(order, ["p::D", "a::F"]);
1160 let fwd = input.forward().unwrap().to_string();
1161 assert!(fwd.contains("__AnnouncePayload"), "{}", fwd);
1162 }
1163
1164 #[test]
1165 fn entries_know_the_attributes_their_checks_own() {
1166 let input: Input = syn::parse_quote! {
1167 [cb!]
1168 a::F! => [x::check, attrs(my_lib, other), m::info(1), attrs(other, third)] {
1169 #[my_lib(on_item)]
1170 pub struct F { #[other] pub a: u8, #[third(x)] #[doc = "d"] pub b: u8 }
1171 }
1172 a::G! => [] { pub struct G; }
1173 };
1174 let owned = input.entries[0].owned_attrs();
1175 let names: Vec<String> = owned.names().iter().map(|n| n.to_string()).collect();
1176 assert_eq!(names, ["my_lib", "other", "third"]);
1177 assert_eq!(owned.find(&input.entries[0].item().unwrap()).len(), 3);
1178 let stripped = input.entries[0].item_without_owned_attrs().unwrap();
1179 assert_eq!(s(&stripped), "pubstructF{puba:u8,#[doc=\"d\"]pubb:u8}");
1180 assert!(input.entries[1].owned_attrs().is_empty());
1182 assert_eq!(
1183 s(input.entries[1].item_without_owned_attrs().unwrap()),
1184 "pubstructG;"
1185 );
1186 let input: Input = syn::parse_quote! { [cb!] a::F! => [lib::attrs(x)] { struct F; } };
1188 assert!(input.entries[0].owned_attrs().is_empty());
1189 }
1190
1191 #[test]
1192 fn check_input_reports_the_query_it_does_not_answer() {
1193 let e = syn::parse2::<CheckInput>(template_quote::quote! {
1194 @__announce_attrs [ c!, f! ] { st }
1195 })
1196 .err()
1197 .unwrap()
1198 .to_string();
1199 assert!(
1200 e.contains("does not answer it") && e.contains("announce::helper::check`"),
1201 "{}",
1202 e
1203 );
1204 let ok: CheckInput = syn::parse_quote! { (x, y), struct S; };
1205 assert!(ok.owned.is_empty() && ok.owned_attributes().is_empty());
1206 let ok = ok.with_owned(OwnedAttrs::from_strs(&["a"]));
1207 assert!(ok.owned.contains("a"));
1208 }
1209
1210 #[test]
1211 fn check_answers_the_query_without_running_the_closure() {
1212 let q = || -> TokenStream {
1213 syn::parse_quote! { @__announce_attrs [ a::check!, b::check!, f::finish! ] { st } }
1214 };
1215 let mut ran = false;
1216 let out = check(q(), &["my_lib", "other"], |_| {
1217 ran = true;
1218 Ok(TokenStream::new())
1219 });
1220 assert!(!ran);
1221 assert_eq!(
1222 s(out),
1223 "b::check!{@__announce_attrs[b::check!,f::finish!]{stattrs(my_lib,other)}}"
1224 );
1225 let out = check(q(), &[], |_| panic!("the query is not a call"));
1227 assert_eq!(
1228 s(out),
1229 "b::check!{@__announce_attrs[b::check!,f::finish!]{st}}"
1230 );
1231 let out = check(q(), &["r#type", "type"], |_| unreachable!());
1233 assert_eq!(
1234 s(out),
1235 "b::check!{@__announce_attrs[b::check!,f::finish!]{stattrs(type)}}"
1236 );
1237 let out = s(check(
1239 syn::parse_quote! { @__announce_attrs [ a::check! ] { st } },
1240 &[],
1241 |_| unreachable!(),
1242 ));
1243 assert!(out.contains("compile_error") && out.contains("nomacrotocallafterthischeck"));
1244 }
1245
1246 #[test]
1247 fn check_runs_the_closure_with_the_owned_names() {
1248 let mut seen = None;
1249 let out = check(
1250 syn::parse_quote! { (x, y), #[my_lib(a)] struct S { #[other] a: u8, #[kept] b: u8 } },
1251 &["my_lib", "other"],
1252 |c| {
1253 seen = Some((
1254 s(&c.args),
1255 s(&c.item),
1256 c.owned
1257 .names()
1258 .iter()
1259 .map(|n| n.to_string())
1260 .collect::<Vec<_>>(),
1261 c.owned_attributes().len(),
1262 ));
1263 Ok(quote! { pub const SEEN: u8 = 1; })
1264 },
1265 );
1266 assert_eq!(s(out), "pubconstSEEN:u8=1;");
1268 let (args, item, owned, found) = seen.unwrap();
1269 assert_eq!(args, "x,y");
1270 assert!(
1271 item.starts_with("#[my_lib(a)]structS{#[other]a:u8"),
1272 "{}",
1273 item
1274 );
1275 assert_eq!(owned, ["my_lib", "other"]);
1276 assert_eq!(found, 2);
1277 let out = check(syn::parse_quote! { (), struct S; }, &[], |c| {
1279 assert!(c.owned.is_empty());
1280 c.args = quote! { changed };
1281 Ok(c.args.clone())
1282 });
1283 assert_eq!(s(out), "changed");
1284 }
1285
1286 #[test]
1287 fn check_turns_errors_into_compile_errors() {
1288 let out = s(check(
1290 syn::parse_quote! { (), struct S(u8); },
1291 &[],
1292 |c| match &c.item {
1293 syn::Item::Struct(it) => Err(syn::Error::new_spanned(&it.fields, "no tuples")),
1294 _ => Ok(TokenStream::new()),
1295 },
1296 ));
1297 assert_eq!(out, "::core::compile_error!{\"notuples\"}");
1298 let mut ran = false;
1300 let out = s(check(syn::parse_quote! { not a check call }, &[], |_| {
1301 ran = true;
1302 Ok(TokenStream::new())
1303 }));
1304 assert!(!ran && out.contains("compile_error"), "{}", out);
1305 for input in [
1307 syn::parse_quote! { @__announce_attrs [ a!, f::finish! ] { st } },
1308 syn::parse_quote! { (), struct S; },
1309 ] {
1310 let out = s(check(input, &["my_lib", "doc"], |_| unreachable!()));
1311 assert!(
1312 out.contains("compile_error") && out.contains("`doc`cannotbeanownedattributename"),
1313 "{}",
1314 out
1315 );
1316 }
1317 for bad in ["", "my-lib", "1st", "_", "a b", "r#"] {
1319 let out = s(check(
1320 syn::parse_quote! { (), struct S; },
1321 &[bad],
1322 |_| unreachable!(),
1323 ));
1324 assert!(
1325 out.contains("compile_error") && out.contains("isnotanidentifier"),
1326 "{:?}: {}",
1327 bad,
1328 out
1329 );
1330 }
1331 }
1332
1333 #[test]
1334 fn fetch_prepends_and_returns() {
1335 let input: Input = syn::parse_quote! { [me!, next!] a::F! => [] { struct S; } (rest) };
1336 let out = s(input.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1337 assert_eq!(
1339 out,
1340 "g::G!{(__announce_fetched(g::G))[g::G!,me!,next!]a::F!=>[]{structS;}(rest)}"
1341 );
1342 assert_eq!(Cont::bang(syn::parse_quote!(a::F)), input.entries[0].key);
1343 let out = s(input.fetch([]));
1345 assert_eq!(out, "me!{[me!,next!]a::F!=>[]{structS;}(rest)}");
1346 }
1347
1348 #[test]
1349 fn fetch_of_a_path_that_left_no_entry_is_an_error() {
1350 let again: Input = syn::parse_quote! {
1354 (__announce_fetched(g::G)) [me!] a::F! => [] { struct S; }
1355 };
1356 let out = s(again.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1357 assert_eq!(
1358 out,
1359 "compile_error!{\"announce:`g::G!`wasfetchedbutdidnotprovideadefinition;isitan`#[announce]`trampoline?\"}"
1360 );
1361 let from_gather = match again.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1363 Gather::Fetch(ts) => s(ts),
1364 Gather::Ready(_) => panic!(),
1365 };
1366 assert_eq!(out, from_gather);
1367 let out = s(again.fetch([Cont::bang(syn::parse_quote!(g::G<u8>))]));
1369 assert!(out.contains("wasfetchedbutdidnotprovide"), "{}", out);
1370 let answered: Input = syn::parse_quote! {
1373 (mine, __announce_fetched(g::G)) [me!] g::G! => [] { struct G; }
1374 };
1375 let out = s(answered.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1376 assert!(
1377 out.starts_with("g::G!{(mine,__announce_fetched(g::G),__announce_fetched(g::G))"),
1378 "{}",
1379 out
1380 );
1381 let out = s(answered.fetch([Cont::bang(syn::parse_quote!(h::H))]));
1382 assert!(
1383 out.starts_with("h::H!{(mine,__announce_fetched(g::G),__announce_fetched(h::H))"),
1384 "{}",
1385 out
1386 );
1387 let out = s(again.fetch([
1389 Cont::bang(syn::parse_quote!(h::H)),
1390 Cont::bang(syn::parse_quote!(g::G)),
1391 ]));
1392 assert!(out.starts_with("compile_error!"), "{}", out);
1393 }
1394
1395 #[test]
1396 fn fetch_and_gather_share_the_record() {
1397 let input: Input = syn::parse_quote! { [me!] };
1398 let first = match input.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1400 Gather::Fetch(ts) => ts,
1401 Gather::Ready(_) => panic!(),
1402 };
1403 assert_eq!(s(&first), "g::G!{(__announce_fetched(g::G))[g::G!,me!]}");
1404 let body: Input =
1407 syn::parse2(template_quote::quote! { (__announce_fetched(g::G)) [me!] }).unwrap();
1408 assert!(s(body.fetch([Cont::bang(syn::parse_quote!(g::G))]))
1409 .contains("wasfetchedbutdidnotprovide"));
1410 let raw = input.fetch([Cont::bang(syn::parse_quote!(g::G))]);
1412 assert_eq!(s(&raw), "g::G!{(__announce_fetched(g::G))[g::G!,me!]}");
1413 match body.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1414 Gather::Fetch(ts) => assert!(s(ts).contains("wasfetchedbutdidnotprovide")),
1415 Gather::Ready(_) => panic!(),
1416 }
1417 }
1418
1419 #[test]
1420 fn malformed_entry_is_an_error() {
1421 let r = syn::parse2::<Input>(template_quote::quote! { [cb!] a::F! => { struct S; } });
1422 assert!(r.err().unwrap().to_string().contains("malformed entry"));
1423 }
1424
1425 #[test]
1426 fn invoke_names_attribute_dummies() {
1427 let list: ContList = [
1428 Cont::derive(syn::parse_quote!(p::D)),
1429 Cont::bang(syn::parse_quote!(cb)),
1430 ]
1431 .into_iter()
1432 .collect();
1433 let out = s(list.invoke(TokenStream::new()));
1434 assert!(
1435 out.starts_with(
1436 "#[derive(p::D)]#[allow(dead_code,non_camel_case_types)]enum__AnnounceT_"
1437 ),
1438 "{}",
1439 out
1440 );
1441 let body = payload(list.invoke(TokenStream::new())).map(s).unwrap();
1442 assert!(body.starts_with("[#[derive(p::D)]__AnnounceT_"), "{}", body);
1443 assert!(redirect(list.invoke(TokenStream::new()), &syn::parse_quote!(::x::Y)).is_some());
1444 }
1445}
1446
1447#[cfg(test)]
1448mod kv_tests {
1449 use super::*;
1450
1451 #[test]
1452 fn kv_types_lists_flags_and_errors() {
1453 let m: Meta = syn::parse_quote!(lib::info(ty = ::std::collections::HashMap<u8, Vec<u8>>, out = fn() -> u8, xs = [A, b::C], on));
1454 let mut kv = m.kv().unwrap();
1455 let ty: syn::Type = kv.require("ty").unwrap();
1456 assert_eq!(
1457 ty.to_token_stream().to_string().replace(' ', ""),
1458 "::std::collections::HashMap<u8,Vec<u8>>"
1459 );
1460 let out: syn::Type = kv.require("out").unwrap();
1461 assert_eq!(
1462 out.to_token_stream().to_string().replace(' ', ""),
1463 "fn()->u8"
1464 );
1465 let xs: Vec<Path> = kv.list("xs").unwrap().unwrap();
1466 assert_eq!(xs.len(), 2);
1467 assert!(kv.flag("on").unwrap());
1468 assert!(kv.get::<syn::Type>("absent").unwrap().is_none());
1469 kv.finish().unwrap();
1470
1471 let mut kv: KvArgs = syn::parse_quote!(a = 1, b = 2);
1472 let _: syn::LitInt = kv.require("a").unwrap();
1473 assert!(kv
1474 .finish()
1475 .unwrap_err()
1476 .to_string()
1477 .contains("unknown key `b`"));
1478 assert!(syn::parse2::<KvArgs>(template_quote::quote!(a = 1, a = 2)).is_err());
1479 let mut kv: KvArgs = syn::parse_quote!();
1480 assert!(kv
1481 .require::<syn::Type>("x")
1482 .unwrap_err()
1483 .to_string()
1484 .contains("missing `x`"));
1485 }
1486
1487 #[test]
1488 fn stable_referrer_is_deterministic() {
1489 let item: syn::ItemStruct = syn::parse_quote! {
1490 pub struct S { a: Foo, b: Bar, c: Baz, d: Qux, e: Quux, f: Corge }
1491 };
1492 let analyse = || {
1493 let mut l = type_leak::Leaker::new();
1494 let context = l.intern_struct(&item, true).unwrap();
1495 (
1496 crate::helper::leak::StableReferrer::new(l.finish()),
1497 context,
1498 )
1499 };
1500 let order = || {
1501 let (r, _) = analyse();
1502 r.types()
1503 .iter()
1504 .map(|t| t.to_token_stream().to_string())
1505 .collect::<Vec<_>>()
1506 };
1507 let first = order();
1508 assert_eq!(first.len(), 6);
1509 let mut sorted = first.clone();
1510 sorted.sort();
1511 assert_eq!(first, sorted);
1512 for _ in 0..20 {
1513 assert_eq!(order(), first);
1514 }
1515 let (r, context) = analyse();
1516 let mut it: syn::Item = syn::Item::Struct(item.clone());
1517 r.visit_item_mut(context, &mut it, |_, i| {
1518 syn::parse_str(&format!("T{}", i)).unwrap()
1519 });
1520 let s = it.to_token_stream().to_string();
1521 assert!(
1522 s.contains("a : T3") && s.contains("b : T0") && s.contains("f : T2"),
1523 "{}",
1524 s
1525 );
1526 let ty: syn::Type = syn::parse_quote!(Qux);
1527 assert_eq!(r.index_of(&ty), Some(5));
1528 let ty = r.expand(context, ty, |_, i| {
1529 syn::parse_str(&format!("T{}", i)).unwrap()
1530 });
1531 assert_eq!(ty.to_token_stream().to_string(), "T5");
1532 }
1533
1534 #[test]
1535 fn stable_referrer_numbers_required_entries_only() {
1536 let item: syn::ItemStruct = syn::parse_quote! {
1538 pub struct S { a: Foo, b: ::other::X, c: Bar, d: ::std::string::String }
1539 };
1540 let mut l = type_leak::Leaker::new();
1541 let context = l.intern_struct(&item, true).unwrap();
1542 let r = crate::helper::leak::StableReferrer::new(l.finish());
1543 let types: Vec<String> = r
1544 .types()
1545 .iter()
1546 .map(|t| t.to_token_stream().to_string())
1547 .collect();
1548 assert_eq!(types, ["Bar", "Foo"]);
1549 let mut it = syn::Item::Struct(item);
1550 r.visit_item_mut(context, &mut it, |_, i| {
1551 syn::parse_str(&format!("T{}", i)).unwrap()
1552 });
1553 let s = it.to_token_stream().to_string();
1554 assert!(
1555 s.contains("a : T1") && s.contains("b : :: other :: X") && s.contains("c : T0"),
1556 "{}",
1557 s
1558 );
1559 }
1560}
1561
1562#[cfg(test)]
1563mod options_tests {
1564 use super::*;
1565
1566 #[test]
1567 fn options_are_parsed_and_forwarded() {
1568 let mut leaker = type_leak::Leaker::new();
1569 let context = leaker
1570 .intern_struct(
1571 &syn::parse_quote!(
1572 struct F<T> {
1573 a: Local,
1574 b: Vec<T>,
1575 }
1576 ),
1577 false,
1578 )
1579 .unwrap();
1580 let tl = TypeLeak::new(
1581 syn::parse_quote!(::my::A),
1582 syn::parse_quote!(7u64),
1583 leaker.finish(),
1584 context,
1585 );
1586 let meta = tl.meta();
1587 let input: Input = syn::parse_quote! {
1588 (type_leak) [me!, next!]
1589 a::F! => [#meta] { struct F<T>(T); }
1590 (rest)
1591 };
1592 assert!(input.has_option("type_leak"));
1593 let fwd = input.forward().unwrap().to_string().replace(' ', "");
1594 assert!(
1595 fwd.starts_with("next!{(type_leak)[next!]a::F!=>"),
1596 "{}",
1597 fwd
1598 );
1599 let fetch = input
1600 .fetch([Cont::bang(syn::parse_quote!(g::G))])
1601 .to_string()
1602 .replace(' ', "");
1603 assert!(
1604 fetch.starts_with("g::G!{(type_leak,__announce_fetched(g::G))[g::G!,me!,next!]"),
1605 "{}",
1606 fetch
1607 );
1608 let read = input.entries[0].type_leak().unwrap().unwrap();
1609 assert_eq!(read.types().count(), 2);
1610 assert_eq!(read.id.base10_digits(), "7");
1611 assert_eq!(read.referrer, tl.referrer);
1612 let proj = read.projection(1, &syn::parse_quote!((u8,)));
1613 assert_eq!(
1614 proj.to_string().replace(' ', ""),
1615 "<()as::my::A<(u8,),7u64,1usize>>::Type"
1616 );
1617 let plain: Input = syn::parse_quote! { [me!] a::F! => [] { struct F; } };
1618 assert!(!plain.has_option("type_leak"));
1619 assert!(plain.entries[0].type_leak().unwrap().is_none());
1620 }
1621}
1622
1623#[cfg(test)]
1624mod kv_balance_tests {
1625 use super::*;
1626
1627 #[test]
1628 fn unbalanced_angle_is_an_error() {
1629 let e = syn::parse2::<KvArgs>(template_quote::quote!(a = x < y, b = 1)).unwrap_err();
1630 assert!(
1631 e.to_string().contains("unbalanced `<` in the value of `a`"),
1632 "{}",
1633 e
1634 );
1635 assert!(syn::parse2::<KvArgs>(template_quote::quote!(a = 1 << 2, b = 3)).is_err());
1636 let mut kv: KvArgs = syn::parse_quote!(a = (x < y), b = 1, f = fn() -> u8);
1637 assert!(kv.tokens("a").is_some() && kv.tokens("b").is_some() && kv.tokens("f").is_some());
1638 kv.finish().unwrap();
1639 }
1640}
1641
1642#[cfg(test)]
1643mod low_audit_tests {
1644 use super::*;
1645
1646 #[test]
1647 fn cont_equality_ignores_dummy_name_and_generics() {
1648 let a: Cont = syn::parse_quote!(
1649 #[derive(p::D)]
1650 N1
1651 );
1652 let b: Cont = syn::parse_quote!(
1653 #[derive(p::D)]
1654 N2
1655 );
1656 assert_eq!(a, b);
1657 let c = Cont::Bang(syn::parse_quote!(a::E<T>));
1658 assert_eq!(c.to_token_stream().to_string().replace(' ', ""), "a::E!");
1659 let d: Cont = syn::parse_quote!(#[derive(x::A, y::B)]);
1660 assert_eq!(
1661 d.path().to_token_stream().to_string().replace(' ', ""),
1662 "x::A"
1663 );
1664 }
1665
1666 #[test]
1667 fn rest_like_an_entry_without_meta_is_rest() {
1668 let i: Input = syn::parse_quote! { [cb!] foo! => bar };
1669 assert!(i.entries.is_empty());
1670 assert_eq!(i.rest.to_string().replace(' ', ""), "foo!=>bar");
1671 }
1672
1673 #[test]
1674 fn gather_refuses_self_request() {
1675 let i: Input = syn::parse_quote! { [me!] };
1676 match i.gather([Cont::bang(syn::parse_quote!(me))]) {
1677 Gather::Fetch(ts) => assert!(ts.to_string().contains("which is the callback itself")),
1678 Gather::Ready(_) => panic!(),
1679 }
1680 }
1681}
1682
1683#[cfg(test)]
1684mod kv_error_tests {
1685 use super::*;
1686
1687 fn parse(ts: TokenStream) -> Result<KvArgs> {
1688 syn::parse2(ts)
1689 }
1690
1691 #[test]
1692 fn missing_value_after_equals_is_an_error() {
1693 let e = parse(template_quote::quote!(a =)).unwrap_err().to_string();
1694 assert_eq!(e, "missing value for `a`");
1695 let e = parse(template_quote::quote!(a = , b = 1))
1696 .unwrap_err()
1697 .to_string();
1698 assert_eq!(e, "missing value for `a`");
1699 let e = parse(template_quote::quote!(ok = 1, b =))
1700 .unwrap_err()
1701 .to_string();
1702 assert_eq!(e, "missing value for `b`");
1703 }
1704
1705 #[test]
1706 fn get_on_a_flag_key_needs_a_value() {
1707 let mut kv: KvArgs = syn::parse_quote!(f, a = 1);
1708 let e = kv.get::<syn::Type>("f").unwrap_err().to_string();
1709 assert_eq!(e, "`f` needs a value");
1710 let mut kv: KvArgs = syn::parse_quote!(f);
1712 assert_eq!(
1713 kv.require::<syn::Type>("f").unwrap_err().to_string(),
1714 "`f` needs a value"
1715 );
1716 }
1717
1718 #[test]
1719 fn list_on_a_flag_key_needs_a_bracketed_value() {
1720 let mut kv: KvArgs = syn::parse_quote!(f, a = 1);
1721 let e = kv.list::<syn::Path>("f").unwrap_err().to_string();
1722 assert_eq!(e, "`f` needs a `[..]` value");
1723 }
1724
1725 #[test]
1726 fn flag_on_a_key_with_a_value_takes_no_value() {
1727 let mut kv: KvArgs = syn::parse_quote!(a = 1);
1728 let e = kv.flag("a").unwrap_err().to_string();
1729 assert_eq!(e, "`a` takes no value");
1730 let mut kv: KvArgs = syn::parse_quote!(b = 1);
1732 assert!(!kv.flag("a").unwrap());
1733 assert!(kv.tokens("b").is_some());
1734 }
1735
1736 #[test]
1737 fn list_on_a_non_bracketed_value_is_an_error() {
1738 let mut kv: KvArgs = syn::parse_quote!(a = 1);
1739 let e = kv.list::<syn::LitInt>("a").unwrap_err().to_string();
1740 assert!(e.contains("expected square brackets"), "{}", e);
1741 let mut kv: KvArgs = syn::parse_quote!(a = (x, y));
1742 let e = kv.list::<syn::Path>("a").unwrap_err().to_string();
1743 assert!(e.contains("expected square brackets"), "{}", e);
1744 let mut kv: KvArgs = syn::parse_quote!(a = [1, 2] extra);
1745 assert!(kv.list::<syn::LitInt>("a").is_err());
1746 }
1747
1748 #[test]
1749 fn list_element_and_value_parse_errors_surface() {
1750 let mut kv: KvArgs = syn::parse_quote!(a = [x, 1]);
1751 assert!(kv.list::<syn::Path>("a").is_err());
1752 let mut kv: KvArgs = syn::parse_quote!(a = 1);
1753 assert!(kv.get::<syn::Path>("a").is_err());
1754 }
1755
1756 #[test]
1757 fn list_accepts_empty_and_trailing_comma() {
1758 let mut kv: KvArgs = syn::parse_quote!(a = [], b = [x::Y, z,]);
1759 assert!(kv.list::<Path>("a").unwrap().unwrap().is_empty());
1760 assert_eq!(kv.list::<Path>("b").unwrap().unwrap().len(), 2);
1761 kv.finish().unwrap();
1762 }
1763
1764 #[test]
1765 fn taken_keys_are_gone_and_finish_names_the_first_leftover() {
1766 let mut kv: KvArgs = syn::parse_quote!(a = 1, b, c = 2);
1767 assert!(kv.tokens("a").is_some());
1768 assert!(kv.tokens("a").is_none());
1769 assert_eq!(kv.finish().unwrap_err().to_string(), "unknown key `b`");
1770 }
1771
1772 #[test]
1773 fn duplicate_keys_and_keywords() {
1774 let e = parse(template_quote::quote!(a, a)).unwrap_err().to_string();
1775 assert_eq!(e, "duplicate key `a`");
1776 let mut kv: KvArgs = syn::parse_quote!(type = u8, crate = a::b, fn);
1778 assert!(kv.get::<syn::Type>("type").unwrap().is_some());
1779 assert!(kv.get::<Path>("crate").unwrap().is_some());
1780 assert!(kv.flag("fn").unwrap());
1781 kv.finish().unwrap();
1782 }
1783
1784 #[test]
1785 fn values_run_to_the_next_top_level_comma() {
1786 let mut kv = parse(template_quote::quote!(a = 1 b = 2)).unwrap();
1788 assert_eq!(kv.tokens("a").unwrap().to_string().replace(' ', ""), "1b=2");
1789 assert!(parse(template_quote::quote!(a b)).is_err());
1790 }
1791
1792 #[test]
1793 fn equality_comparison_is_a_flag_followed_by_junk() {
1794 assert!(parse(template_quote::quote!(a == b)).is_err());
1796 }
1797}
1798
1799#[cfg(test)]
1800mod gap_tests {
1801 use super::*;
1802 use proc_macro2::{Punct, Spacing};
1803
1804 fn s(ts: impl ToTokens) -> String {
1805 ts.to_token_stream()
1806 .to_string()
1807 .split_whitespace()
1808 .collect()
1809 }
1810
1811 fn mk_dollar() -> Punct {
1812 Punct::new('$', Spacing::Alone)
1813 }
1814
1815 #[test]
1816 fn check_trait_asserts_the_dyn_trait_is_announced() {
1817 let out = s(check_trait(
1818 &syn::parse_quote!(::announce),
1819 &syn::parse_quote!(my::Tr),
1820 ));
1821 assert!(out.contains("dynmy::Tr"), "{}", out);
1822 assert!(out.contains("::announce::Announce"), "{}", out);
1823 assert!(out.contains("?::core::marker::Sized"), "{}", out);
1824 assert!(out.starts_with("const_:()="), "{}", out);
1825 let direct = s(check_type(
1827 &syn::parse_quote!(::announce),
1828 &syn::parse_quote!(dyn my::Tr),
1829 ));
1830 assert_eq!(out, direct);
1831 }
1832
1833 #[test]
1834 fn check_type_uses_the_given_krate_path() {
1835 let out = s(check_type(
1836 &syn::parse_quote!(::renamed),
1837 &syn::parse_quote!(Vec<u8>),
1838 ));
1839 assert!(out.contains("::renamed::Announce"), "{}", out);
1840 assert!(out.contains("__announce_chk::<Vec<u8>>"), "{}", out);
1841 }
1842
1843 #[test]
1844 fn meta_name_values_parse_expression_values() {
1845 let m: Meta = syn::parse_quote!(lib::info(a = 1, b = "x", c = 1 + 2));
1846 let nv = m.name_values().unwrap();
1847 assert_eq!(nv.len(), 3);
1848 assert_eq!(s(&nv[0].path), "a");
1849 assert_eq!(s(&nv[1].value), "\"x\"");
1850 assert_eq!(s(&nv[2].value), "1+2");
1851 let m: Meta = syn::parse_quote!(lib::info);
1853 assert!(m.name_values().unwrap().is_empty());
1854 let m: Meta = syn::parse_quote!(lib::info(a = 1, on));
1856 assert!(m.name_values().is_err());
1857 let m: Meta = syn::parse_quote!(lib::info(a = 1, on));
1858 assert!(m.kv().is_ok());
1859 let m: Meta = syn::parse_quote!(lib::info(xs = [A, B]));
1861 assert_eq!(m.name_values().unwrap().len(), 1);
1862 let m: Meta = syn::parse_quote!(lib::info(ty = Vec<u8, A>));
1863 assert!(m.name_values().is_err());
1864 assert!(m.kv().is_ok());
1865 }
1866
1867 #[test]
1868 fn meta_args_default_to_empty() {
1869 let m: Meta = syn::parse_quote!(lib::info);
1870 assert!(m.args.is_none());
1871 assert!(m.kv().unwrap().finish().is_ok());
1872 assert_eq!(s(&m), "lib::info");
1873 let m: Meta = syn::parse_quote!(lib::info(1, 2));
1874 assert_eq!(s(&m), "lib::info(1,2)");
1875 }
1876
1877 fn hash_of<T: std::hash::Hash>(t: &T) -> u64 {
1878 use std::hash::Hasher;
1879 let mut h = std::collections::hash_map::DefaultHasher::new();
1880 t.hash(&mut h);
1881 h.finish()
1882 }
1883
1884 #[test]
1885 fn cont_hash_agrees_with_eq() {
1886 let a: Cont = syn::parse_quote!(
1887 #[derive(p::D)]
1888 N1
1889 );
1890 let b: Cont = syn::parse_quote!(
1891 #[derive(p::D)]
1892 N2
1893 );
1894 let c: Cont = syn::parse_quote!(#[derive(p::D)]);
1895 assert_eq!(a, b);
1896 assert_eq!(hash_of(&a), hash_of(&b));
1897 assert_eq!(hash_of(&a), hash_of(&c));
1898 let bang = Cont::bang(syn::parse_quote!(p::D));
1899 let bang_generic = Cont::Bang(syn::parse_quote!(p::D<T>));
1900 assert_eq!(bang, bang_generic);
1901 assert_eq!(hash_of(&bang), hash_of(&bang_generic));
1902 let other: Cont = syn::parse_quote!(
1904 #[derive(p::E)]
1905 N1
1906 );
1907 assert_ne!(a, other);
1908 assert_ne!(a, bang);
1909 let set: std::collections::HashSet<Cont> = [a.clone(), b, c, other].into_iter().collect();
1910 assert_eq!(set.len(), 2);
1911 assert!(set.contains(&a));
1912 }
1913
1914 #[test]
1915 fn cont_path_of_attribute_and_derive() {
1916 let c: Cont = syn::parse_quote!(
1917 #[my::attr(x, y)]
1918 N
1919 );
1920 assert_eq!(s(c.path()), "my::attr");
1921 let d = Cont::derive(syn::parse_quote!(::p::D));
1922 assert_eq!(s(d.path()), "::p::D");
1923 assert_eq!(s(&d), "#[derive(::p::D)]");
1924 let e: Cont = syn::parse_quote!(
1926 #[derive()]
1927 N
1928 );
1929 assert_eq!(s(e.path()), "derive");
1930 }
1931
1932 #[test]
1933 fn input_skips_a_leading_dollar_prefix() {
1934 let dollar = mk_dollar();
1935 let ts = template_quote::quote! {
1936 @ __announce_dollar { #dollar }
1937 (opt) [cb!, n!]
1938 a::F! => [x::check(1)] { struct S; }
1939 (rest)
1940 };
1941 let input: Input = syn::parse2(ts).unwrap();
1942 assert_eq!(s(input.options.as_ref().unwrap()), "opt");
1943 assert!(input.has_option("opt"));
1944 assert_eq!(input.list.0.len(), 2);
1945 assert_eq!(s(&input.list), "[cb!,n!]");
1946 assert_eq!(input.entries.len(), 1);
1947 assert_eq!(s(input.entries[0].key.path()), "a::F");
1948 assert_eq!(s(&input.entries[0].meta[0]), "x::check(1)");
1949 assert_eq!(s(&input.rest), "(rest)");
1950 let out = s(input.forward().unwrap());
1952 assert!(!out.contains("__announce_dollar"), "{}", out);
1953 }
1954
1955 #[test]
1956 fn input_dollar_prefix_without_options() {
1957 let dollar = mk_dollar();
1958 let ts = template_quote::quote! { @ __announce_dollar { #dollar } [cb!] };
1959 let input: Input = syn::parse2(ts).unwrap();
1960 assert!(input.options.is_none());
1961 assert_eq!(input.list.0.len(), 1);
1962 assert!(input.entries.is_empty());
1963 assert!(input.rest.is_empty());
1964 }
1965
1966 #[test]
1967 fn input_skips_the_self_and_dollar_prefixes() {
1968 let dollar = mk_dollar();
1969 let ts = template_quote::quote! {
1970 @ __announce_self { ::a::F }
1971 @ __announce_dollar { #dollar }
1972 [cb!, n!] REST
1973 };
1974 let input: Input = syn::parse2(ts).unwrap();
1975 assert!(input.options.is_none());
1976 assert_eq!(s(&input.list), "[cb!,n!]");
1977 assert_eq!(s(&input.rest), "REST");
1978 let ts = template_quote::quote! { @ __announce_self { F } (o) [cb!] };
1980 let input: Input = syn::parse2(ts).unwrap();
1981 assert!(input.has_option("o"));
1982 assert_eq!(input.list.0.len(), 1);
1983 let ts = template_quote::quote! { @ __announce_self { F } [cb!, n!] };
1985 let out = s(syn::parse2::<Input>(ts).unwrap().forward().unwrap());
1986 assert!(!out.contains("__announce_self"), "{}", out);
1987 }
1988
1989 #[test]
1990 fn input_rejects_other_prefixes() {
1991 assert!(syn::parse2::<Input>(template_quote::quote! { @ other { x } [cb!] }).is_err());
1992 assert!(syn::parse2::<Input>(template_quote::quote! { @ [cb!] }).is_err());
1993 let dollar = mk_dollar();
1995 let ts = template_quote::quote! { @ __announce_dollar { #dollar } };
1996 assert!(syn::parse2::<Input>(ts).is_err());
1997 }
1998
1999 #[test]
2000 fn invoke_with_an_empty_list_is_a_compile_error() {
2001 let out = s(ContList::default().invoke(template_quote::quote!(x y)));
2002 assert_eq!(out, "compile_error!{\"announce:emptycontinuationlist\"}");
2003 let opts = template_quote::quote!(expr);
2004 let out = s(ContList::default().invoke_with_options(Some(&opts), TokenStream::new()));
2005 assert!(out.contains("emptycontinuationlist"), "{}", out);
2006 }
2007
2008 #[test]
2009 fn invoke_in_expression_position_refuses_the_derive_route() {
2010 let list: ContList = [
2011 Cont::derive(syn::parse_quote!(p::D)),
2012 Cont::bang(syn::parse_quote!(cb)),
2013 ]
2014 .into_iter()
2015 .collect();
2016 let opts = template_quote::quote!(expr);
2017 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2018 assert!(out.starts_with("compile_error!{"), "{}", out);
2019 assert!(out.contains("cannotbeusedwith`(expr)`"), "{}", out);
2020 assert!(!out.contains("__AnnouncePayload"), "{}", out);
2021 let opts = template_quote::quote!(expr, type_leak);
2023 let out = s(list.invoke_with_options(Some(&opts), TokenStream::new()));
2024 assert!(out.contains("cannotbeusedwith`(expr)`"), "{}", out);
2025 }
2026
2027 #[test]
2028 fn only_a_leading_expr_selects_expression_position() {
2029 let yes = |ts: TokenStream| is_expression_position(&ts);
2031 assert!(yes(template_quote::quote!(expr)));
2032 assert!(yes(template_quote::quote!(expr, type_leak)));
2033 assert!(yes(template_quote::quote!(expr __announce_fetched(a))));
2034 assert!(!yes(template_quote::quote!(type_leak, expr)));
2035 assert!(!yes(template_quote::quote!(__announce_fetched(expr))));
2036 assert!(!yes(template_quote::quote!((expr))));
2037 assert!(!yes(template_quote::quote!(expression)));
2038 assert!(!yes(TokenStream::new()));
2039 let list: ContList = [
2042 Cont::derive(syn::parse_quote!(p::D)),
2043 Cont::bang(syn::parse_quote!(cb)),
2044 ]
2045 .into_iter()
2046 .collect();
2047 let opts = template_quote::quote!(type_leak, expr);
2048 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2049 assert!(out.contains("__AnnouncePayload"), "{}", out);
2050 assert!(!out.contains("cannotbeusedwith"), "{}", out);
2051 let opts = template_quote::quote!(__announce_fetched(expr));
2052 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2053 assert!(out.contains("__AnnouncePayload"), "{}", out);
2054 let bang: ContList = [Cont::bang(syn::parse_quote!(p::cb))].into_iter().collect();
2056 let opts = template_quote::quote!(type_leak, expr);
2057 let out = s(bang.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2058 assert_eq!(out, "p::cb!{(type_leak,expr)[p::cb!]body}");
2059 }
2060
2061 #[test]
2062 fn expression_option_is_fine_for_a_bang_head_and_other_options_for_derives() {
2063 let bang: ContList = [Cont::bang(syn::parse_quote!(p::cb))].into_iter().collect();
2064 let opts = template_quote::quote!(expr);
2065 let out = s(bang.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2066 assert_eq!(out, "p::cb!{(expr)[p::cb!]body}");
2067 let derive: ContList = [Cont::derive(syn::parse_quote!(p::D))]
2068 .into_iter()
2069 .collect();
2070 let opts = template_quote::quote!(type_leak);
2071 let out = s(derive.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2072 assert!(out.contains("__AnnouncePayload"), "{}", out);
2073 assert!(
2074 out.contains("stringify!{(type_leak)[#[derive(p::D)]__AnnounceT_"),
2075 "{}",
2076 out
2077 );
2078 }
2079
2080 #[test]
2081 fn name_dummies_keeps_existing_names_and_gives_distinct_new_ones() {
2082 let mut list: ContList = [
2083 Cont::derive(syn::parse_quote!(p::A)),
2084 Cont::derive(syn::parse_quote!(p::B)),
2085 syn::parse_quote!(
2086 #[derive(p::C)]
2087 Fixed
2088 ),
2089 Cont::bang(syn::parse_quote!(cb)),
2090 ]
2091 .into_iter()
2092 .collect();
2093 list.name_dummies();
2094 let names: Vec<String> = list
2095 .0
2096 .iter()
2097 .filter_map(|c| match c {
2098 Cont::Attr { name: Some(n), .. } => Some(n.to_string()),
2099 _ => None,
2100 })
2101 .collect();
2102 assert_eq!(names.len(), 3);
2103 assert_eq!(names[2], "Fixed");
2104 assert!(names[0].starts_with("__AnnounceT_") && names[1].starts_with("__AnnounceT_"));
2105 assert_ne!(names[0], names[1]);
2106 let before = s(&list);
2108 list.name_dummies();
2109 assert_eq!(s(&list), before);
2110 }
2111
2112 #[test]
2113 fn redirect_ignores_ordinary_items() {
2114 let target: Path = syn::parse_quote!(::x::Y);
2115 for item in [
2116 template_quote::quote! { struct S; },
2117 template_quote::quote! { enum E { A, B } },
2118 template_quote::quote! { enum E { __AnnouncePayload } },
2119 template_quote::quote! { enum E { __AnnouncePayload = 1 } },
2120 template_quote::quote! { enum E { __AnnouncePayload = (1, 0).1 } },
2121 template_quote::quote! { enum E { __AnnouncePayload = (other!{ x }, 0).1 } },
2122 template_quote::quote! { enum E { __AnnouncePayload = (stringify!{ x }, 0).1, B } },
2123 template_quote::quote! { enum E { Other = (stringify!{ x }, 0).1 } },
2124 template_quote::quote! { fn f() {} },
2125 ] {
2126 assert!(payload(item.clone()).is_none(), "{}", item);
2127 assert!(redirect(item.clone(), &target).is_none(), "{}", item);
2128 }
2129 }
2130
2131 #[test]
2132 fn redirect_wraps_the_payload_with_the_dollar_prefix() {
2133 let list: ContList = [
2134 Cont::derive(syn::parse_quote!(p::D)),
2135 Cont::bang(syn::parse_quote!(cb)),
2136 ]
2137 .into_iter()
2138 .collect();
2139 let dummy = list.invoke(template_quote::quote!(body));
2140 let out = redirect(dummy.clone(), &syn::parse_quote!(::x::Y)).unwrap();
2141 let out_s = s(&out);
2142 assert!(
2143 out_s.starts_with(
2144 "::x::Y!{@__announce_self{::x::Y}@__announce_dollar{$}[#[derive(p::D)]__AnnounceT_"
2145 ),
2146 "{}",
2147 out_s
2148 );
2149 assert!(out_s.ends_with("cb!]body}"), "{}", out_s);
2150 let has_dollar = out.into_iter().any(|tt| match tt {
2152 proc_macro2::TokenTree::Group(g) => g
2153 .stream()
2154 .into_iter()
2155 .any(|t| matches!(t, proc_macro2::TokenTree::Group(g2) if g2.stream().into_iter().any(|x| matches!(x, proc_macro2::TokenTree::Punct(p) if p.as_char() == '$')))),
2156 _ => false,
2157 });
2158 assert!(has_dollar);
2159 let input: Input = syn::parse2(
2161 match redirect(dummy, &syn::parse_quote!(::x::Y))
2162 .unwrap()
2163 .into_iter()
2164 .last()
2165 {
2166 Some(proc_macro2::TokenTree::Group(g)) => g.stream(),
2167 other => panic!("{:?}", other),
2168 },
2169 )
2170 .unwrap();
2171 assert_eq!(input.list.0.len(), 2);
2172 assert_eq!(s(input.rest), "body");
2173 }
2174}