1pub mod attrs;
33mod kv;
34pub mod leak;
35mod transformer;
36mod typeleak;
37
38pub use attrs::{remove_attributes, CheckCall, OwnedAttrs, Query};
39pub use kv::KvArgs;
40pub use transformer::{signature_only, transformer};
41pub use type_leak;
44pub use typeleak::TypeLeak;
45
46#[doc(hidden)]
53pub use crate::{callback, transformer};
54
55use proc_macro2::{Ident, Span, TokenStream};
56use std::sync::atomic::{AtomicU64, Ordering};
57use syn::ext::IdentExt;
58use syn::parse::{Parse, ParseStream};
59use syn::punctuated::Punctuated;
60use syn::{Attribute, Path, Result, Token};
61use template_quote::{quote, ToTokens};
62
63pub const ANNOUNCE_FORMAT: &str = "1";
71
72const FORMAT_TAG: &str = "announce_format";
74
75const FORMAT_ERROR: &str = "announce: incompatible wire format";
77
78pub fn format_prefix() -> TokenStream {
86 quote! { @announce_format #ANNOUNCE_FORMAT }
87}
88
89fn format_error(span: Span, found: &str) -> syn::Error {
91 syn::Error::new(
92 span,
93 format!(
94 "{}: found `@announce_format {:?}`, expected `@announce_format {:?}`. The item was \
95 announced, or the macro that reads it was built, with another version of \
96 `announce`, whose wire format this version cannot read: use one version of \
97 `announce` in the whole chain",
98 FORMAT_ERROR, found, ANNOUNCE_FORMAT
99 ),
100 )
101}
102
103pub(crate) fn is_format_error(e: &syn::Error) -> bool {
105 e.to_string().starts_with(FORMAT_ERROR)
106}
107
108pub(crate) fn parse_format(input: ParseStream) -> Result<Option<String>> {
112 if !input.peek(Token![@]) {
113 return Ok(None);
114 }
115 let fork = input.fork();
116 let _: Token![@] = fork.parse()?;
117 match fork.parse::<Ident>() {
118 Ok(i) if i == FORMAT_TAG => {}
119 _ => return Ok(None),
120 }
121 let lit: syn::LitStr = fork.parse().map_err(|e| {
122 syn::Error::new(
123 e.span(),
124 "announce: `@announce_format` must be followed by a string literal, like \
125 `@announce_format \"1\"`",
126 )
127 })?;
128 syn::parse::discouraged::Speculative::advance_to(input, &fork);
129 let found = lit.value();
130 if found != ANNOUNCE_FORMAT {
131 return Err(format_error(lit.span(), &found));
132 }
133 Ok(Some(found))
134}
135
136fn split_format(ts: TokenStream) -> (Option<TokenStream>, TokenStream) {
139 use proc_macro2::TokenTree;
140 let mut it = ts.into_iter();
141 let first: Vec<TokenTree> = it.by_ref().take(3).collect();
142 let is_prefix = matches!(
143 first.as_slice(),
144 [TokenTree::Punct(p), TokenTree::Ident(i), TokenTree::Literal(_)]
145 if p.as_char() == '@' && i == FORMAT_TAG
146 );
147 if is_prefix {
148 (Some(first.into_iter().collect()), it.collect())
149 } else {
150 (None, first.into_iter().chain(it).collect())
151 }
152}
153
154pub fn gensym(prefix: &str) -> Ident {
156 use std::hash::{BuildHasher, Hasher};
157 static COUNTER: AtomicU64 = AtomicU64::new(0);
158 let mut h = std::collections::hash_map::RandomState::new().build_hasher();
159 h.write_u64(COUNTER.fetch_add(1, Ordering::Relaxed));
160 Ident::new(&format!("{}{:016x}", prefix, h.finish()), Span::call_site())
161}
162
163#[derive(Clone, Debug)]
165#[allow(clippy::large_enum_variant)]
166pub enum Cont {
167 Bang(Path),
169 Attr {
172 attr: Attribute,
173 name: Option<Ident>,
174 },
175}
176
177impl Cont {
178 pub fn bang(mut path: Path) -> Self {
180 for seg in &mut path.segments {
181 seg.arguments = syn::PathArguments::None;
182 }
183 Cont::Bang(path)
184 }
185
186 pub fn derive(path: Path) -> Self {
188 Cont::Attr {
189 attr: syn::parse_quote!(#[derive(#path)]),
190 name: None,
191 }
192 }
193
194 pub fn path(&self) -> Path {
197 match self {
198 Cont::Bang(p) => p.clone(),
199 Cont::Attr { attr, .. } => {
200 if attr.path().is_ident("derive") {
201 let paths =
202 attr.parse_args_with(Punctuated::<Path, Token![,]>::parse_terminated);
203 if let Some(p) = paths.ok().and_then(|p| p.into_iter().next()) {
204 return p;
205 }
206 }
207 attr.path().clone()
208 }
209 }
210 }
211}
212
213fn parse_macro_path(input: ParseStream) -> Result<Path> {
214 let leading_colon: Option<Token![::]> = input.parse()?;
215 let mut segments = Punctuated::new();
216 loop {
217 let ident = Ident::parse_any(input)?;
218 segments.push_value(syn::PathSegment::from(ident));
219 if input.peek(Token![::]) {
220 segments.push_punct(input.parse()?);
221 } else {
222 break;
223 }
224 }
225 Ok(Path {
226 leading_colon,
227 segments,
228 })
229}
230
231impl Parse for Cont {
232 fn parse(input: ParseStream) -> Result<Self> {
233 if input.peek(Token![#]) {
234 let pound_token = input.parse()?;
235 let content;
236 let bracket_token = syn::bracketed!(content in input);
237 let attr = Attribute {
238 pound_token,
239 style: syn::AttrStyle::Outer,
240 bracket_token,
241 meta: content.parse()?,
242 };
243 let name = if input.peek(syn::Ident) {
244 Some(input.parse()?)
245 } else {
246 None
247 };
248 Ok(Cont::Attr { attr, name })
249 } else {
250 let path = parse_macro_path(input)?;
251 input.parse::<Token![!]>()?;
252 Ok(Cont::Bang(path))
253 }
254 }
255}
256
257impl Cont {
258 fn eq_key(&self) -> String {
261 match self {
262 Cont::Bang(_) => self.to_token_stream().to_string(),
263 Cont::Attr { attr, .. } => attr.to_token_stream().to_string(),
264 }
265 }
266}
267
268impl PartialEq for Cont {
270 fn eq(&self, other: &Self) -> bool {
271 self.eq_key() == other.eq_key()
272 }
273}
274impl Eq for Cont {}
275impl std::hash::Hash for Cont {
276 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
277 self.eq_key().hash(state)
278 }
279}
280
281impl ToTokens for Cont {
282 fn to_tokens(&self, tokens: &mut TokenStream) {
283 match self {
284 Cont::Bang(p) => {
285 let mut p = p.clone();
287 for seg in &mut p.segments {
288 seg.arguments = syn::PathArguments::None;
289 }
290 tokens.extend(quote! { #p ! })
291 }
292 Cont::Attr { attr, name } => tokens.extend(quote! { #attr #name }),
293 }
294 }
295}
296
297#[derive(Clone, Debug, Default)]
299pub struct ContList(pub Vec<Cont>);
300
301impl Parse for ContList {
302 fn parse(input: ParseStream) -> Result<Self> {
304 let p: Punctuated<Cont, Token![,]> = Punctuated::parse_terminated(input)?;
305 Ok(ContList(p.into_iter().collect()))
306 }
307}
308
309impl ToTokens for ContList {
310 fn to_tokens(&self, tokens: &mut TokenStream) {
312 let items = &self.0;
313 tokens.extend(quote! { [ #(#items),* ] });
314 }
315}
316
317impl FromIterator<Cont> for ContList {
318 fn from_iter<I: IntoIterator<Item = Cont>>(iter: I) -> Self {
319 ContList(iter.into_iter().collect())
320 }
321}
322
323pub fn is_expression_position(options: &TokenStream) -> bool {
330 matches!(
331 options.clone().into_iter().next(),
332 Some(proc_macro2::TokenTree::Ident(i)) if i == "expr"
333 )
334}
335
336impl ContList {
337 pub fn name_dummies(&mut self) {
340 for c in &mut self.0 {
341 if let Cont::Attr { name, .. } = c {
342 if name.is_none() {
343 *name = Some(gensym("__AnnounceT_"));
344 }
345 }
346 }
347 }
348
349 pub fn invoke(&self, body: TokenStream) -> TokenStream {
353 self.invoke_with_options(None, body)
354 }
355
356 pub fn invoke_with_options(
361 &self,
362 options: Option<&TokenStream>,
363 body: TokenStream,
364 ) -> TokenStream {
365 let opts = options.map(|o| quote! { ( #o ) });
366 let in_expr = options.map_or(false, is_expression_position);
367 let mut list = self.clone();
368 list.name_dummies();
369 let format = format_prefix();
370 match list.0.first() {
371 None => quote! { compile_error! { "announce: empty continuation list" } },
372 Some(Cont::Attr { .. }) if in_expr => quote! {
373 compile_error! { "announce: a derive/attribute continuation emits items, so it cannot be used with `(expr)`" }
374 },
375 Some(Cont::Bang(p)) => quote! { #p ! { #format #opts #list #body } },
376 Some(Cont::Attr { attr, name }) => quote! {
377 #attr
378 #[allow(dead_code, non_camel_case_types)]
379 enum #name { __AnnouncePayload = (stringify! { #format #opts #list #body }, 0).1 }
380 },
381 }
382 }
383}
384
385#[derive(Clone, Debug)]
387pub struct Meta {
388 pub path: Path,
390 pub args: Option<TokenStream>,
392}
393
394impl Meta {
395 pub fn parse_args_with<P: syn::parse::Parser>(&self, parser: P) -> Result<P::Output> {
397 parser.parse2(self.args.clone().unwrap_or_default())
398 }
399
400 pub fn kv(&self) -> Result<KvArgs> {
402 self.parse_args_with(KvArgs::parse)
403 }
404
405 pub fn name_values(&self) -> Result<Vec<syn::MetaNameValue>> {
408 let p: Punctuated<syn::MetaNameValue, Token![,]> =
409 self.parse_args_with(Punctuated::parse_terminated)?;
410 Ok(p.into_iter().collect())
411 }
412}
413
414impl Parse for Meta {
415 fn parse(input: ParseStream) -> Result<Self> {
416 let path = Path::parse_mod_style(input)?;
417 let args = if input.peek(syn::token::Paren) {
418 let content;
419 syn::parenthesized!(content in input);
420 Some(content.parse()?)
421 } else {
422 None
423 };
424 Ok(Meta { path, args })
425 }
426}
427
428impl ToTokens for Meta {
429 fn to_tokens(&self, tokens: &mut TokenStream) {
430 let p = &self.path;
431 match &self.args {
432 Some(a) => tokens.extend(quote! { #p(#a) }),
433 None => tokens.extend(quote! { #p }),
434 }
435 }
436}
437
438#[derive(Clone, Debug)]
440pub struct Entry {
441 pub key: Cont,
443 pub meta: Vec<Meta>,
445 pub item: TokenStream,
447}
448
449impl Entry {
450 pub fn item(&self) -> Result<syn::Item> {
452 syn::parse2(self.item.clone())
453 }
454
455 pub fn mock(&self) -> Option<Path> {
458 self.meta
459 .iter()
460 .find(|m| m.path.is_ident("mock"))
461 .and_then(|m| syn::parse2(m.args.clone()?).ok())
462 }
463
464 pub fn find_meta(&self, name: &str) -> Option<&Meta> {
467 self.meta
468 .iter()
469 .find(|m| m.path.segments.last().map_or(false, |s| s.ident == name))
470 }
471
472 pub fn type_leak(&self) -> Result<Option<TypeLeak>> {
475 let m = match self.find_meta("type_leak") {
476 Some(m) => m,
477 None => return Ok(None),
478 };
479 TypeLeak::from_kv(&mut m.kv()?).map(Some)
480 }
481
482 pub fn has_check(&self, name: &str) -> bool {
485 self.meta
486 .iter()
487 .any(|m| m.path.segments.last().map_or(false, |s| s.ident == name))
488 }
489
490 pub fn owned_attrs(&self) -> OwnedAttrs {
499 attrs::owned_from_metas(&self.meta)
500 }
501
502 pub fn item_without_owned_attrs(&self) -> Result<syn::Item> {
506 let mut item = self.item()?;
507 self.owned_attrs().strip(&mut item)?;
508 Ok(item)
509 }
510}
511
512impl Parse for Entry {
513 fn parse(input: ParseStream) -> Result<Self> {
514 let key = input.parse()?;
515 input.parse::<Token![=>]>()?;
516 let meta_content;
517 syn::bracketed!(meta_content in input);
518 let meta: Punctuated<Meta, Token![,]> = Punctuated::parse_terminated(&meta_content)?;
519 let item_content;
520 syn::braced!(item_content in input);
521 Ok(Entry {
522 key,
523 meta: meta.into_iter().collect(),
524 item: item_content.parse()?,
525 })
526 }
527}
528
529impl ToTokens for Entry {
530 fn to_tokens(&self, tokens: &mut TokenStream) {
531 let (key, meta, item) = (&self.key, &self.meta, &self.item);
532 tokens.extend(quote! { #key => [ #(#meta),* ] { #item } });
533 }
534}
535
536#[derive(Clone, Debug)]
786pub struct Input {
787 pub format: Option<String>,
794 pub options: Option<TokenStream>,
799 pub list: ContList,
801 pub entries: Vec<Entry>,
803 pub rest: TokenStream,
805}
806
807impl Parse for Input {
808 fn parse(input: ParseStream) -> Result<Self> {
809 let format = parse_format(input)?;
811 while input.peek(Token![@]) {
815 let fork = input.fork();
816 let _: Token![@] = fork.parse()?;
817 match fork.parse::<Ident>() {
818 Ok(i) if i == "__announce_dollar" || i == "__announce_self" => {
819 let _g: proc_macro2::Group = fork.parse()?;
820 syn::parse::discouraged::Speculative::advance_to(input, &fork);
821 }
822 _ => break,
823 }
824 }
825 let options = if input.peek(syn::token::Paren) {
826 let o;
827 syn::parenthesized!(o in input);
828 Some(o.parse()?)
829 } else {
830 None
831 };
832 let content;
833 syn::bracketed!(content in input);
834 let list = content.parse()?;
835 let mut entries = Vec::new();
836 loop {
837 let fork = input.fork();
838 match fork.parse::<Entry>() {
839 Ok(e) => {
840 entries.push(e);
841 syn::parse::discouraged::Speculative::advance_to(input, &fork);
842 }
843 Err(e) => {
844 let probe = input.fork();
849 if probe.parse::<Cont>().is_ok()
850 && probe.parse::<Token![=>]>().is_ok()
851 && (probe.peek(syn::token::Bracket) || probe.peek(syn::token::Brace))
852 {
853 return Err(syn::Error::new(
854 e.span(),
855 format!("announce: malformed entry: {}", e),
856 ));
857 }
858 break;
859 }
860 }
861 }
862 Ok(Input {
863 format,
864 options,
865 list,
866 entries,
867 rest: input.parse()?,
868 })
869 }
870}
871
872impl Input {
873 pub fn entries_in_order(&self) -> impl Iterator<Item = &Entry> {
875 self.entries.iter().rev()
876 }
877
878 pub fn fetch(&self, new: impl IntoIterator<Item = Cont>) -> TokenStream {
889 self.fetch_conts(new.into_iter().collect())
890 }
891
892 fn fetch_conts(&self, new: Vec<Cont>) -> TokenStream {
896 let fetched = self.fetched_paths();
899 if let Some(c) = new
900 .iter()
901 .find(|c| fetched.contains(&path_key(&c.path())) && self.entry_for(c).is_none())
902 {
903 let msg = format!(
904 "announce: `{}` was fetched but did not provide a definition; is it an `#[announce]` trampoline?",
905 c.to_token_stream()
906 );
907 return quote! { compile_error! { #msg } };
908 }
909 let mut options: Vec<TokenStream> = self.option_items();
910 if !new.is_empty() {
911 let paths = new.iter().map(|c| c.path());
912 options.push(quote! { __announce_fetched(#(#paths),*) });
913 }
914 let opts = if options.is_empty() {
915 None
916 } else {
917 Some(quote! { #(#options),* })
918 };
919 let list: ContList = new.into_iter().chain(self.list.0.iter().cloned()).collect();
920 let (entries, rest) = (&self.entries, &self.rest);
921 list.invoke_with_options(opts.as_ref(), quote! { #(#entries)* #rest })
922 }
923
924 pub fn has_option(&self, name: &str) -> bool {
926 self.options.as_ref().map_or(false, |o| {
927 o.clone()
928 .into_iter()
929 .any(|tt| matches!(tt, proc_macro2::TokenTree::Ident(i) if i == name))
930 })
931 }
932
933 pub fn option_items(&self) -> Vec<TokenStream> {
935 let mut items = Vec::new();
936 let mut cur = TokenStream::new();
937 for tt in self.options.clone().unwrap_or_default() {
938 if matches!(&tt, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') {
939 if !cur.is_empty() {
940 items.push(std::mem::take(&mut cur));
941 }
942 } else {
943 cur.extend(Some(tt));
944 }
945 }
946 if !cur.is_empty() {
947 items.push(cur);
948 }
949 items
950 }
951
952 fn fetched_paths(&self) -> Vec<String> {
953 let mut out = Vec::new();
954 for item in self.option_items() {
955 let parsed: Result<Meta> = syn::parse2(item);
956 if let Ok(m) = parsed {
957 if m.path.is_ident("__announce_fetched") {
958 let paths: Result<Punctuated<Path, Token![,]>> =
959 m.parse_args_with(Punctuated::parse_terminated);
960 if let Ok(paths) = paths {
961 out.extend(paths.iter().map(path_key));
962 }
963 }
964 }
965 }
966 out
967 }
968
969 pub fn rest_as<T: Parse>(&self) -> Result<T> {
971 syn::parse2(self.rest.clone())
972 }
973
974 pub fn entry_for(&self, cont: &Cont) -> Option<&Entry> {
977 let want = path_key(&cont.path());
978 self.entries
979 .iter()
980 .find(|e| path_key(&e.key.path()) == want)
981 }
982
983 pub fn gather(&self, needed: impl IntoIterator<Item = Cont>) -> Gather<'_> {
996 let needed: Vec<Cont> = needed.into_iter().collect();
997 if let Some(head) = self.list.0.first() {
1000 if let Some(c) = needed
1001 .iter()
1002 .find(|c| path_key(&c.path()) == path_key(&head.path()))
1003 {
1004 let msg = format!(
1005 "announce: `gather` asked for `{}`, which is the callback itself; \
1006 request the definitions' trampolines instead",
1007 c.to_token_stream()
1008 );
1009 return Gather::Fetch(quote! { compile_error! { #msg } });
1010 }
1011 }
1012 let mut missing: Vec<Cont> = Vec::new();
1013 for c in &needed {
1014 if self.entry_for(c).is_none()
1015 && !missing
1016 .iter()
1017 .any(|m| path_key(&m.path()) == path_key(&c.path()))
1018 {
1019 missing.push(c.clone());
1020 }
1021 }
1022 if missing.is_empty() {
1023 return Gather::Ready(
1024 needed
1025 .iter()
1026 .map(|c| self.entry_for(c).expect("checked above"))
1027 .collect(),
1028 );
1029 }
1030 Gather::Fetch(self.fetch_conts(missing))
1031 }
1032
1033 pub fn forward(&self) -> Option<TokenStream> {
1036 if self.list.0.len() < 2 {
1037 return None;
1038 }
1039 let tail: ContList = self.list.0[1..].iter().cloned().collect();
1040 let (entries, rest) = (&self.entries, &self.rest);
1041 Some(tail.invoke_with_options(self.options.as_ref(), quote! { #(#entries)* #rest }))
1042 }
1043}
1044
1045pub enum Gather<'a> {
1047 Ready(Vec<&'a Entry>),
1049 Fetch(TokenStream),
1051}
1052
1053fn path_key(p: &Path) -> String {
1054 let mut p = p.clone();
1055 for seg in &mut p.segments {
1056 seg.arguments = syn::PathArguments::None;
1057 }
1058 p.to_token_stream().to_string()
1059}
1060
1061#[doc(hidden)]
1062pub mod __private {
1063 pub use proc_macro2;
1064 pub use syn;
1065}
1066
1067pub fn callback(input: TokenStream, f: impl FnOnce(Input) -> Result<TokenStream>) -> TokenStream {
1144 syn::parse2::<Input>(input)
1145 .and_then(f)
1146 .unwrap_or_else(|e| e.to_compile_error())
1147}
1148
1149#[doc(hidden)]
1202#[macro_export]
1203macro_rules! callback {
1204 ($(#[$m:meta])* $vis:vis fn $name:ident ($input:ident) $body:block) => {
1205 $(#[$m])*
1206 #[proc_macro]
1207 $vis fn $name(__announce_ts: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
1208 ::core::convert::Into::into($crate::helper::callback(
1209 ::core::convert::Into::into(__announce_ts),
1210 |$input: $crate::helper::Input|
1211 -> $crate::helper::__private::syn::Result<$crate::helper::__private::proc_macro2::TokenStream> {
1212 $body
1213 },
1214 ))
1215 }
1216 };
1217}
1218
1219pub fn check(
1284 input: TokenStream,
1285 attributes: &[&str],
1286 f: impl FnOnce(&mut CheckInput) -> Result<TokenStream>,
1287) -> TokenStream {
1288 let run = || -> Result<TokenStream> {
1289 for name in attributes {
1290 if !is_attribute_name(name) {
1291 return Err(syn::Error::new(
1292 Span::call_site(),
1293 format!(
1294 "announce: `{}` is not an identifier: it cannot be an owned attribute name",
1295 name
1296 ),
1297 ));
1298 }
1299 }
1300 let owned = OwnedAttrs::from_strs(attributes);
1301 match syn::parse2::<CheckCall>(input)? {
1302 CheckCall::Query(q) => Ok(q.answer(&owned)),
1304 CheckCall::Run(c) => {
1305 owned.check_names()?;
1306 f(&mut c.with_owned(owned))
1307 }
1308 }
1309 };
1310 run().unwrap_or_else(|e| e.to_compile_error())
1311}
1312
1313fn is_attribute_name(name: &str) -> bool {
1315 let name = name.strip_prefix("r#").unwrap_or(name);
1316 let mut chars = name.chars();
1317 matches!(chars.next(), Some(c) if c == '_' || c.is_alphabetic())
1318 && chars.all(|c| c == '_' || c.is_alphanumeric())
1319 && name != "_"
1320}
1321
1322pub fn payload(item: TokenStream) -> Option<TokenStream> {
1325 let di: syn::DeriveInput = syn::parse2(item).ok()?;
1326 let e = match di.data {
1327 syn::Data::Enum(e) => e,
1328 _ => return None,
1329 };
1330 let v = e.variants.first()?;
1331 if v.ident != "__AnnouncePayload" || e.variants.len() != 1 {
1332 return None;
1333 }
1334 let f = match &v.discriminant.as_ref()?.1 {
1335 syn::Expr::Field(f) => f,
1336 _ => return None,
1337 };
1338 let t = match &*f.base {
1339 syn::Expr::Tuple(t) => t,
1340 _ => return None,
1341 };
1342 let m = match t.elems.first()? {
1343 syn::Expr::Macro(m) => m,
1344 _ => return None,
1345 };
1346 if !m.mac.path.is_ident("stringify") {
1347 return None;
1348 }
1349 Some(m.mac.tokens.clone())
1350}
1351
1352pub fn redirect(item: TokenStream, target: &Path) -> Option<TokenStream> {
1373 let (format, body) = split_format(payload(item)?);
1379 let format = format.unwrap_or_else(format_prefix);
1380 let dollar = proc_macro2::Punct::new('$', proc_macro2::Spacing::Alone);
1386 Some(quote! {
1387 #target ! { #format @__announce_self { #target } @__announce_dollar { #dollar } #body }
1388 })
1389}
1390
1391pub struct CheckInput {
1399 pub args: TokenStream,
1401 pub item: syn::Item,
1403 pub owned: OwnedAttrs,
1406}
1407
1408impl CheckInput {
1409 pub fn with_owned(mut self, owned: OwnedAttrs) -> Self {
1411 self.owned = owned;
1412 self
1413 }
1414
1415 pub fn owned_attributes(&self) -> Vec<Attribute> {
1418 self.owned.find(&self.item)
1419 }
1420}
1421
1422impl Parse for CheckInput {
1423 fn parse(input: ParseStream) -> Result<Self> {
1424 parse_format(input)?;
1426 if input.peek(Token![@]) {
1427 return Err(input.error(
1428 "announce: this check received the attribute query `@__announce_attrs` that \
1429 `#[announce]` sends first, and does not answer it. Run the check with \
1430 `announce::helper::check`, or parse `announce::helper::CheckCall` and answer \
1431 `Query` (a `macro_rules!` check needs one more arm); every check must \
1432 answer. See the `announce::helper::attrs` docs",
1433 ));
1434 }
1435 let content;
1436 syn::parenthesized!(content in input);
1437 let args = content.parse()?;
1438 input.parse::<Token![,]>()?;
1439 Ok(CheckInput {
1440 args,
1441 item: input.parse()?,
1442 owned: OwnedAttrs::new(),
1443 })
1444 }
1445}
1446
1447pub fn check_type(krate: &Path, ty: &syn::Type) -> TokenStream {
1450 quote! {
1451 const _: () = {
1452 fn __announce_chk<__AnnounceT: ?::core::marker::Sized + #krate::Announce>() {}
1453 let _ = __announce_chk::<#ty>;
1454 };
1455 }
1456}
1457
1458pub fn check_trait(krate: &Path, path: &Path) -> TokenStream {
1461 check_type(krate, &syn::parse_quote!(dyn #path))
1462}
1463
1464#[cfg(test)]
1465mod tests {
1466 use super::*;
1467
1468 fn s(ts: impl ToTokens) -> String {
1469 ts.to_token_stream()
1470 .to_string()
1471 .split_whitespace()
1472 .collect()
1473 }
1474
1475 #[test]
1476 fn bang_drops_generic_arguments() {
1477 assert_eq!(s(Cont::bang(syn::parse_quote!(a::Enum<A, B>))), "a::Enum!");
1478 }
1479
1480 #[test]
1481 fn input_roundtrip_and_meta() {
1482 let input: Input = syn::parse_quote! {
1483 [cb!, #[derive(p::D)] N]
1484 a::F! => [x::check(1), mock(::core::iter::Iterator)] { pub trait F {} }
1485 #[derive(p::D)] M => [] { pub struct S; }
1486 (rest)
1487 };
1488 assert_eq!(input.list.0.len(), 2);
1489 assert_eq!(input.entries.len(), 2);
1490 assert_eq!(s(&input.entries[0].meta[0]), "x::check(1)");
1491 assert_eq!(
1492 s(input.entries[0].find_meta("check").unwrap().args.clone()),
1493 "1"
1494 );
1495 assert!(input.entries[0].has_check("check"));
1496 assert_eq!(
1497 s(input.entries[0].mock().unwrap()),
1498 "::core::iter::Iterator"
1499 );
1500 assert_eq!(s(input.entries[1].key.path()), "p::D");
1501 assert_eq!(s(&input.rest), "(rest)");
1502 let order: Vec<String> = input.entries_in_order().map(|e| s(e.key.path())).collect();
1503 assert_eq!(order, ["p::D", "a::F"]);
1504 let fwd = input.forward().unwrap().to_string();
1505 assert!(fwd.contains("__AnnouncePayload"), "{}", fwd);
1506 }
1507
1508 #[test]
1509 fn entries_know_the_attributes_their_checks_own() {
1510 let input: Input = syn::parse_quote! {
1511 [cb!]
1512 a::F! => [x::check, attrs(my_lib, other), m::info(1), attrs(other, third)] {
1513 #[my_lib(on_item)]
1514 pub struct F { #[other] pub a: u8, #[third(x)] #[doc = "d"] pub b: u8 }
1515 }
1516 a::G! => [] { pub struct G; }
1517 };
1518 let owned = input.entries[0].owned_attrs();
1519 let names: Vec<String> = owned.names().iter().map(|n| n.to_string()).collect();
1520 assert_eq!(names, ["my_lib", "other", "third"]);
1521 assert_eq!(owned.find(&input.entries[0].item().unwrap()).len(), 3);
1522 let stripped = input.entries[0].item_without_owned_attrs().unwrap();
1523 assert_eq!(s(&stripped), "pubstructF{puba:u8,#[doc=\"d\"]pubb:u8}");
1524 assert!(input.entries[1].owned_attrs().is_empty());
1526 assert_eq!(
1527 s(input.entries[1].item_without_owned_attrs().unwrap()),
1528 "pubstructG;"
1529 );
1530 let input: Input = syn::parse_quote! { [cb!] a::F! => [lib::attrs(x)] { struct F; } };
1532 assert!(input.entries[0].owned_attrs().is_empty());
1533 }
1534
1535 #[test]
1536 fn check_input_reports_the_query_it_does_not_answer() {
1537 let e = syn::parse2::<CheckInput>(template_quote::quote! {
1538 @__announce_attrs [ c!, f! ] { st }
1539 })
1540 .err()
1541 .unwrap()
1542 .to_string();
1543 assert!(
1544 e.contains("does not answer it") && e.contains("announce::helper::check`"),
1545 "{}",
1546 e
1547 );
1548 let ok: CheckInput = syn::parse_quote! { (x, y), struct S; };
1549 assert!(ok.owned.is_empty() && ok.owned_attributes().is_empty());
1550 let ok = ok.with_owned(OwnedAttrs::from_strs(&["a"]));
1551 assert!(ok.owned.contains("a"));
1552 }
1553
1554 #[test]
1555 fn check_answers_the_query_without_running_the_closure() {
1556 let q = || -> TokenStream {
1557 syn::parse_quote! { @__announce_attrs [ a::check!, b::check!, f::finish! ] { st } }
1558 };
1559 let mut ran = false;
1560 let out = check(q(), &["my_lib", "other"], |_| {
1561 ran = true;
1562 Ok(TokenStream::new())
1563 });
1564 assert!(!ran);
1565 assert_eq!(
1566 s(out),
1567 "b::check!{@announce_format\"1\"@__announce_attrs[b::check!,f::finish!]{stattrs(my_lib,other)}}"
1568 );
1569 let out = check(q(), &[], |_| panic!("the query is not a call"));
1571 assert_eq!(
1572 s(out),
1573 "b::check!{@announce_format\"1\"@__announce_attrs[b::check!,f::finish!]{st}}"
1574 );
1575 let out = check(q(), &["r#type", "type"], |_| unreachable!());
1577 assert_eq!(
1578 s(out),
1579 "b::check!{@announce_format\"1\"@__announce_attrs[b::check!,f::finish!]{stattrs(type)}}"
1580 );
1581 let out = s(check(
1583 syn::parse_quote! { @__announce_attrs [ a::check! ] { st } },
1584 &[],
1585 |_| unreachable!(),
1586 ));
1587 assert!(out.contains("compile_error") && out.contains("nomacrotocallafterthischeck"));
1588 }
1589
1590 #[test]
1591 fn check_runs_the_closure_with_the_owned_names() {
1592 let mut seen = None;
1593 let out = check(
1594 syn::parse_quote! { (x, y), #[my_lib(a)] struct S { #[other] a: u8, #[kept] b: u8 } },
1595 &["my_lib", "other"],
1596 |c| {
1597 seen = Some((
1598 s(&c.args),
1599 s(&c.item),
1600 c.owned
1601 .names()
1602 .iter()
1603 .map(|n| n.to_string())
1604 .collect::<Vec<_>>(),
1605 c.owned_attributes().len(),
1606 ));
1607 Ok(quote! { pub const SEEN: u8 = 1; })
1608 },
1609 );
1610 assert_eq!(s(out), "pubconstSEEN:u8=1;");
1612 let (args, item, owned, found) = seen.unwrap();
1613 assert_eq!(args, "x,y");
1614 assert!(
1615 item.starts_with("#[my_lib(a)]structS{#[other]a:u8"),
1616 "{}",
1617 item
1618 );
1619 assert_eq!(owned, ["my_lib", "other"]);
1620 assert_eq!(found, 2);
1621 let out = check(syn::parse_quote! { (), struct S; }, &[], |c| {
1623 assert!(c.owned.is_empty());
1624 c.args = quote! { changed };
1625 Ok(c.args.clone())
1626 });
1627 assert_eq!(s(out), "changed");
1628 }
1629
1630 #[test]
1631 fn check_turns_errors_into_compile_errors() {
1632 let out = s(check(
1634 syn::parse_quote! { (), struct S(u8); },
1635 &[],
1636 |c| match &c.item {
1637 syn::Item::Struct(it) => Err(syn::Error::new_spanned(&it.fields, "no tuples")),
1638 _ => Ok(TokenStream::new()),
1639 },
1640 ));
1641 assert_eq!(out, "::core::compile_error!{\"notuples\"}");
1642 let mut ran = false;
1644 let out = s(check(syn::parse_quote! { not a check call }, &[], |_| {
1645 ran = true;
1646 Ok(TokenStream::new())
1647 }));
1648 assert!(!ran && out.contains("compile_error"), "{}", out);
1649 for input in [
1651 syn::parse_quote! { @__announce_attrs [ a!, f::finish! ] { st } },
1652 syn::parse_quote! { (), struct S; },
1653 ] {
1654 let out = s(check(input, &["my_lib", "doc"], |_| unreachable!()));
1655 assert!(
1656 out.contains("compile_error") && out.contains("`doc`cannotbeanownedattributename"),
1657 "{}",
1658 out
1659 );
1660 }
1661 for bad in ["", "my-lib", "1st", "_", "a b", "r#"] {
1663 let out = s(check(
1664 syn::parse_quote! { (), struct S; },
1665 &[bad],
1666 |_| unreachable!(),
1667 ));
1668 assert!(
1669 out.contains("compile_error") && out.contains("isnotanidentifier"),
1670 "{:?}: {}",
1671 bad,
1672 out
1673 );
1674 }
1675 }
1676
1677 #[test]
1678 fn fetch_prepends_and_returns() {
1679 let input: Input = syn::parse_quote! { [me!, next!] a::F! => [] { struct S; } (rest) };
1680 let out = s(input.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1681 assert_eq!(
1683 out,
1684 "g::G!{@announce_format\"1\"(__announce_fetched(g::G))[g::G!,me!,next!]a::F!=>[]{structS;}(rest)}"
1685 );
1686 assert_eq!(Cont::bang(syn::parse_quote!(a::F)), input.entries[0].key);
1687 let out = s(input.fetch([]));
1689 assert_eq!(
1690 out,
1691 "me!{@announce_format\"1\"[me!,next!]a::F!=>[]{structS;}(rest)}"
1692 );
1693 }
1694
1695 #[test]
1696 fn fetch_of_a_path_that_left_no_entry_is_an_error() {
1697 let again: Input = syn::parse_quote! {
1701 (__announce_fetched(g::G)) [me!] a::F! => [] { struct S; }
1702 };
1703 let out = s(again.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1704 assert_eq!(
1705 out,
1706 "compile_error!{\"announce:`g::G!`wasfetchedbutdidnotprovideadefinition;isitan`#[announce]`trampoline?\"}"
1707 );
1708 let from_gather = match again.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1710 Gather::Fetch(ts) => s(ts),
1711 Gather::Ready(_) => panic!(),
1712 };
1713 assert_eq!(out, from_gather);
1714 let out = s(again.fetch([Cont::bang(syn::parse_quote!(g::G<u8>))]));
1716 assert!(out.contains("wasfetchedbutdidnotprovide"), "{}", out);
1717 let answered: Input = syn::parse_quote! {
1720 (mine, __announce_fetched(g::G)) [me!] g::G! => [] { struct G; }
1721 };
1722 let out = s(answered.fetch([Cont::bang(syn::parse_quote!(g::G))]));
1723 assert!(
1724 out.starts_with("g::G!{@announce_format\"1\"(mine,__announce_fetched(g::G),__announce_fetched(g::G))"),
1725 "{}",
1726 out
1727 );
1728 let out = s(answered.fetch([Cont::bang(syn::parse_quote!(h::H))]));
1729 assert!(
1730 out.starts_with("h::H!{@announce_format\"1\"(mine,__announce_fetched(g::G),__announce_fetched(h::H))"),
1731 "{}",
1732 out
1733 );
1734 let out = s(again.fetch([
1736 Cont::bang(syn::parse_quote!(h::H)),
1737 Cont::bang(syn::parse_quote!(g::G)),
1738 ]));
1739 assert!(out.starts_with("compile_error!"), "{}", out);
1740 }
1741
1742 #[test]
1743 fn fetch_and_gather_share_the_record() {
1744 let input: Input = syn::parse_quote! { [me!] };
1745 let first = match input.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1747 Gather::Fetch(ts) => ts,
1748 Gather::Ready(_) => panic!(),
1749 };
1750 assert_eq!(
1751 s(&first),
1752 "g::G!{@announce_format\"1\"(__announce_fetched(g::G))[g::G!,me!]}"
1753 );
1754 let body: Input =
1757 syn::parse2(template_quote::quote! { (__announce_fetched(g::G)) [me!] }).unwrap();
1758 assert!(s(body.fetch([Cont::bang(syn::parse_quote!(g::G))]))
1759 .contains("wasfetchedbutdidnotprovide"));
1760 let raw = input.fetch([Cont::bang(syn::parse_quote!(g::G))]);
1762 assert_eq!(
1763 s(&raw),
1764 "g::G!{@announce_format\"1\"(__announce_fetched(g::G))[g::G!,me!]}"
1765 );
1766 match body.gather([Cont::bang(syn::parse_quote!(g::G))]) {
1767 Gather::Fetch(ts) => assert!(s(ts).contains("wasfetchedbutdidnotprovide")),
1768 Gather::Ready(_) => panic!(),
1769 }
1770 }
1771
1772 #[test]
1773 fn malformed_entry_is_an_error() {
1774 let r = syn::parse2::<Input>(template_quote::quote! { [cb!] a::F! => { struct S; } });
1775 assert!(r.err().unwrap().to_string().contains("malformed entry"));
1776 }
1777
1778 #[test]
1779 fn invoke_names_attribute_dummies() {
1780 let list: ContList = [
1781 Cont::derive(syn::parse_quote!(p::D)),
1782 Cont::bang(syn::parse_quote!(cb)),
1783 ]
1784 .into_iter()
1785 .collect();
1786 let out = s(list.invoke(TokenStream::new()));
1787 assert!(
1788 out.starts_with(
1789 "#[derive(p::D)]#[allow(dead_code,non_camel_case_types)]enum__AnnounceT_"
1790 ),
1791 "{}",
1792 out
1793 );
1794 let body = payload(list.invoke(TokenStream::new())).map(s).unwrap();
1795 assert!(
1796 body.starts_with("@announce_format\"1\"[#[derive(p::D)]__AnnounceT_"),
1797 "{}",
1798 body
1799 );
1800 assert!(redirect(list.invoke(TokenStream::new()), &syn::parse_quote!(::x::Y)).is_some());
1801 }
1802}
1803
1804#[cfg(test)]
1805mod kv_tests {
1806 use super::*;
1807
1808 #[test]
1809 fn kv_types_lists_flags_and_errors() {
1810 let m: Meta = syn::parse_quote!(lib::info(ty = ::std::collections::HashMap<u8, Vec<u8>>, out = fn() -> u8, xs = [A, b::C], on));
1811 let mut kv = m.kv().unwrap();
1812 let ty: syn::Type = kv.require("ty").unwrap();
1813 assert_eq!(
1814 ty.to_token_stream().to_string().replace(' ', ""),
1815 "::std::collections::HashMap<u8,Vec<u8>>"
1816 );
1817 let out: syn::Type = kv.require("out").unwrap();
1818 assert_eq!(
1819 out.to_token_stream().to_string().replace(' ', ""),
1820 "fn()->u8"
1821 );
1822 let xs: Vec<Path> = kv.list("xs").unwrap().unwrap();
1823 assert_eq!(xs.len(), 2);
1824 assert!(kv.flag("on").unwrap());
1825 assert!(kv.get::<syn::Type>("absent").unwrap().is_none());
1826 kv.finish().unwrap();
1827
1828 let mut kv: KvArgs = syn::parse_quote!(a = 1, b = 2);
1829 let _: syn::LitInt = kv.require("a").unwrap();
1830 assert!(kv
1831 .finish()
1832 .unwrap_err()
1833 .to_string()
1834 .contains("unknown key `b`"));
1835 assert!(syn::parse2::<KvArgs>(template_quote::quote!(a = 1, a = 2)).is_err());
1836 let mut kv: KvArgs = syn::parse_quote!();
1837 assert!(kv
1838 .require::<syn::Type>("x")
1839 .unwrap_err()
1840 .to_string()
1841 .contains("missing `x`"));
1842 }
1843
1844 #[test]
1845 fn stable_referrer_is_deterministic() {
1846 let item: syn::ItemStruct = syn::parse_quote! {
1847 pub struct S { a: Foo, b: Bar, c: Baz, d: Qux, e: Quux, f: Corge }
1848 };
1849 let analyse = || {
1850 let mut l = type_leak::Leaker::new();
1851 let context = l.intern_struct(&item, true).unwrap();
1852 (
1853 crate::helper::leak::StableReferrer::new(l.finish()),
1854 context,
1855 )
1856 };
1857 let order = || {
1858 let (r, _) = analyse();
1859 r.types()
1860 .iter()
1861 .map(|t| t.to_token_stream().to_string())
1862 .collect::<Vec<_>>()
1863 };
1864 let first = order();
1865 assert_eq!(first.len(), 6);
1866 let mut sorted = first.clone();
1867 sorted.sort();
1868 assert_eq!(first, sorted);
1869 for _ in 0..20 {
1870 assert_eq!(order(), first);
1871 }
1872 let (r, context) = analyse();
1873 let mut it: syn::Item = syn::Item::Struct(item.clone());
1874 r.visit_item_mut(context, &mut it, |_, i| {
1875 syn::parse_str(&format!("T{}", i)).unwrap()
1876 });
1877 let s = it.to_token_stream().to_string();
1878 assert!(
1879 s.contains("a : T3") && s.contains("b : T0") && s.contains("f : T2"),
1880 "{}",
1881 s
1882 );
1883 let ty: syn::Type = syn::parse_quote!(Qux);
1884 assert_eq!(r.index_of(&ty), Some(5));
1885 let ty = r.expand(context, ty, |_, i| {
1886 syn::parse_str(&format!("T{}", i)).unwrap()
1887 });
1888 assert_eq!(ty.to_token_stream().to_string(), "T5");
1889 }
1890
1891 #[test]
1892 fn stable_referrer_numbers_required_entries_only() {
1893 let item: syn::ItemStruct = syn::parse_quote! {
1895 pub struct S { a: Foo, b: ::other::X, c: Bar, d: ::std::string::String }
1896 };
1897 let mut l = type_leak::Leaker::new();
1898 let context = l.intern_struct(&item, true).unwrap();
1899 let r = crate::helper::leak::StableReferrer::new(l.finish());
1900 let types: Vec<String> = r
1901 .types()
1902 .iter()
1903 .map(|t| t.to_token_stream().to_string())
1904 .collect();
1905 assert_eq!(types, ["Bar", "Foo"]);
1906 let mut it = syn::Item::Struct(item);
1907 r.visit_item_mut(context, &mut it, |_, i| {
1908 syn::parse_str(&format!("T{}", i)).unwrap()
1909 });
1910 let s = it.to_token_stream().to_string();
1911 assert!(
1912 s.contains("a : T1") && s.contains("b : :: other :: X") && s.contains("c : T0"),
1913 "{}",
1914 s
1915 );
1916 }
1917}
1918
1919#[cfg(test)]
1920mod options_tests {
1921 use super::*;
1922
1923 #[test]
1924 fn options_are_parsed_and_forwarded() {
1925 let mut leaker = type_leak::Leaker::new();
1926 let context = leaker
1927 .intern_struct(
1928 &syn::parse_quote!(
1929 struct F<T> {
1930 a: Local,
1931 b: Vec<T>,
1932 }
1933 ),
1934 false,
1935 )
1936 .unwrap();
1937 let tl = TypeLeak::new(
1938 syn::parse_quote!(::my::A),
1939 syn::parse_quote!(7u64),
1940 leaker.finish(),
1941 context,
1942 );
1943 let meta = tl.meta();
1944 let input: Input = syn::parse_quote! {
1945 (type_leak) [me!, next!]
1946 a::F! => [#meta] { struct F<T>(T); }
1947 (rest)
1948 };
1949 assert!(input.has_option("type_leak"));
1950 let fwd = input.forward().unwrap().to_string().replace(' ', "");
1951 assert!(
1952 fwd.starts_with("next!{@announce_format\"1\"(type_leak)[next!]a::F!=>"),
1953 "{}",
1954 fwd
1955 );
1956 let fetch = input
1957 .fetch([Cont::bang(syn::parse_quote!(g::G))])
1958 .to_string()
1959 .replace(' ', "");
1960 assert!(
1961 fetch.starts_with(
1962 "g::G!{@announce_format\"1\"(type_leak,__announce_fetched(g::G))[g::G!,me!,next!]"
1963 ),
1964 "{}",
1965 fetch
1966 );
1967 let read = input.entries[0].type_leak().unwrap().unwrap();
1968 assert_eq!(read.types().count(), 2);
1969 assert_eq!(read.id.base10_digits(), "7");
1970 assert_eq!(read.referrer, tl.referrer);
1971 let proj = read.projection(1, &syn::parse_quote!((u8,)));
1972 assert_eq!(
1973 proj.to_string().replace(' ', ""),
1974 "<()as::my::A<(u8,),7u64,1usize>>::Type"
1975 );
1976 let plain: Input = syn::parse_quote! { [me!] a::F! => [] { struct F; } };
1977 assert!(!plain.has_option("type_leak"));
1978 assert!(plain.entries[0].type_leak().unwrap().is_none());
1979 }
1980}
1981
1982#[cfg(test)]
1983mod kv_balance_tests {
1984 use super::*;
1985
1986 #[test]
1987 fn unbalanced_angle_is_an_error() {
1988 let e = syn::parse2::<KvArgs>(template_quote::quote!(a = x < y, b = 1)).unwrap_err();
1989 assert!(
1990 e.to_string().contains("unbalanced `<` in the value of `a`"),
1991 "{}",
1992 e
1993 );
1994 assert!(syn::parse2::<KvArgs>(template_quote::quote!(a = 1 << 2, b = 3)).is_err());
1995 let mut kv: KvArgs = syn::parse_quote!(a = (x < y), b = 1, f = fn() -> u8);
1996 assert!(kv.tokens("a").is_some() && kv.tokens("b").is_some() && kv.tokens("f").is_some());
1997 kv.finish().unwrap();
1998 }
1999}
2000
2001#[cfg(test)]
2002mod low_audit_tests {
2003 use super::*;
2004
2005 #[test]
2006 fn cont_equality_ignores_dummy_name_and_generics() {
2007 let a: Cont = syn::parse_quote!(
2008 #[derive(p::D)]
2009 N1
2010 );
2011 let b: Cont = syn::parse_quote!(
2012 #[derive(p::D)]
2013 N2
2014 );
2015 assert_eq!(a, b);
2016 let c = Cont::Bang(syn::parse_quote!(a::E<T>));
2017 assert_eq!(c.to_token_stream().to_string().replace(' ', ""), "a::E!");
2018 let d: Cont = syn::parse_quote!(#[derive(x::A, y::B)]);
2019 assert_eq!(
2020 d.path().to_token_stream().to_string().replace(' ', ""),
2021 "x::A"
2022 );
2023 }
2024
2025 #[test]
2026 fn rest_like_an_entry_without_meta_is_rest() {
2027 let i: Input = syn::parse_quote! { [cb!] foo! => bar };
2028 assert!(i.entries.is_empty());
2029 assert_eq!(i.rest.to_string().replace(' ', ""), "foo!=>bar");
2030 }
2031
2032 #[test]
2033 fn gather_refuses_self_request() {
2034 let i: Input = syn::parse_quote! { [me!] };
2035 match i.gather([Cont::bang(syn::parse_quote!(me))]) {
2036 Gather::Fetch(ts) => assert!(ts.to_string().contains("which is the callback itself")),
2037 Gather::Ready(_) => panic!(),
2038 }
2039 }
2040}
2041
2042#[cfg(test)]
2043mod kv_error_tests {
2044 use super::*;
2045
2046 fn parse(ts: TokenStream) -> Result<KvArgs> {
2047 syn::parse2(ts)
2048 }
2049
2050 #[test]
2051 fn missing_value_after_equals_is_an_error() {
2052 let e = parse(template_quote::quote!(a =)).unwrap_err().to_string();
2053 assert_eq!(e, "missing value for `a`");
2054 let e = parse(template_quote::quote!(a = , b = 1))
2055 .unwrap_err()
2056 .to_string();
2057 assert_eq!(e, "missing value for `a`");
2058 let e = parse(template_quote::quote!(ok = 1, b =))
2059 .unwrap_err()
2060 .to_string();
2061 assert_eq!(e, "missing value for `b`");
2062 }
2063
2064 #[test]
2065 fn get_on_a_flag_key_needs_a_value() {
2066 let mut kv: KvArgs = syn::parse_quote!(f, a = 1);
2067 let e = kv.get::<syn::Type>("f").unwrap_err().to_string();
2068 assert_eq!(e, "`f` needs a value");
2069 let mut kv: KvArgs = syn::parse_quote!(f);
2071 assert_eq!(
2072 kv.require::<syn::Type>("f").unwrap_err().to_string(),
2073 "`f` needs a value"
2074 );
2075 }
2076
2077 #[test]
2078 fn list_on_a_flag_key_needs_a_bracketed_value() {
2079 let mut kv: KvArgs = syn::parse_quote!(f, a = 1);
2080 let e = kv.list::<syn::Path>("f").unwrap_err().to_string();
2081 assert_eq!(e, "`f` needs a `[..]` value");
2082 }
2083
2084 #[test]
2085 fn flag_on_a_key_with_a_value_takes_no_value() {
2086 let mut kv: KvArgs = syn::parse_quote!(a = 1);
2087 let e = kv.flag("a").unwrap_err().to_string();
2088 assert_eq!(e, "`a` takes no value");
2089 let mut kv: KvArgs = syn::parse_quote!(b = 1);
2091 assert!(!kv.flag("a").unwrap());
2092 assert!(kv.tokens("b").is_some());
2093 }
2094
2095 #[test]
2096 fn list_on_a_non_bracketed_value_is_an_error() {
2097 let mut kv: KvArgs = syn::parse_quote!(a = 1);
2098 let e = kv.list::<syn::LitInt>("a").unwrap_err().to_string();
2099 assert!(e.contains("expected square brackets"), "{}", e);
2100 let mut kv: KvArgs = syn::parse_quote!(a = (x, y));
2101 let e = kv.list::<syn::Path>("a").unwrap_err().to_string();
2102 assert!(e.contains("expected square brackets"), "{}", e);
2103 let mut kv: KvArgs = syn::parse_quote!(a = [1, 2] extra);
2104 assert!(kv.list::<syn::LitInt>("a").is_err());
2105 }
2106
2107 #[test]
2108 fn list_element_and_value_parse_errors_surface() {
2109 let mut kv: KvArgs = syn::parse_quote!(a = [x, 1]);
2110 assert!(kv.list::<syn::Path>("a").is_err());
2111 let mut kv: KvArgs = syn::parse_quote!(a = 1);
2112 assert!(kv.get::<syn::Path>("a").is_err());
2113 }
2114
2115 #[test]
2116 fn list_accepts_empty_and_trailing_comma() {
2117 let mut kv: KvArgs = syn::parse_quote!(a = [], b = [x::Y, z,]);
2118 assert!(kv.list::<Path>("a").unwrap().unwrap().is_empty());
2119 assert_eq!(kv.list::<Path>("b").unwrap().unwrap().len(), 2);
2120 kv.finish().unwrap();
2121 }
2122
2123 #[test]
2124 fn taken_keys_are_gone_and_finish_names_the_first_leftover() {
2125 let mut kv: KvArgs = syn::parse_quote!(a = 1, b, c = 2);
2126 assert!(kv.tokens("a").is_some());
2127 assert!(kv.tokens("a").is_none());
2128 assert_eq!(kv.finish().unwrap_err().to_string(), "unknown key `b`");
2129 }
2130
2131 #[test]
2132 fn duplicate_keys_and_keywords() {
2133 let e = parse(template_quote::quote!(a, a)).unwrap_err().to_string();
2134 assert_eq!(e, "duplicate key `a`");
2135 let mut kv: KvArgs = syn::parse_quote!(type = u8, crate = a::b, fn);
2137 assert!(kv.get::<syn::Type>("type").unwrap().is_some());
2138 assert!(kv.get::<Path>("crate").unwrap().is_some());
2139 assert!(kv.flag("fn").unwrap());
2140 kv.finish().unwrap();
2141 }
2142
2143 #[test]
2144 fn values_run_to_the_next_top_level_comma() {
2145 let mut kv = parse(template_quote::quote!(a = 1 b = 2)).unwrap();
2147 assert_eq!(kv.tokens("a").unwrap().to_string().replace(' ', ""), "1b=2");
2148 assert!(parse(template_quote::quote!(a b)).is_err());
2149 }
2150
2151 #[test]
2152 fn equality_comparison_is_a_flag_followed_by_junk() {
2153 assert!(parse(template_quote::quote!(a == b)).is_err());
2155 }
2156}
2157
2158#[cfg(test)]
2159mod gap_tests {
2160 use super::*;
2161 use proc_macro2::{Punct, Spacing};
2162
2163 fn s(ts: impl ToTokens) -> String {
2164 ts.to_token_stream()
2165 .to_string()
2166 .split_whitespace()
2167 .collect()
2168 }
2169
2170 fn mk_dollar() -> Punct {
2171 Punct::new('$', Spacing::Alone)
2172 }
2173
2174 #[test]
2175 fn check_trait_asserts_the_dyn_trait_is_announced() {
2176 let out = s(check_trait(
2177 &syn::parse_quote!(::announce),
2178 &syn::parse_quote!(my::Tr),
2179 ));
2180 assert!(out.contains("dynmy::Tr"), "{}", out);
2181 assert!(out.contains("::announce::Announce"), "{}", out);
2182 assert!(out.contains("?::core::marker::Sized"), "{}", out);
2183 assert!(out.starts_with("const_:()="), "{}", out);
2184 let direct = s(check_type(
2186 &syn::parse_quote!(::announce),
2187 &syn::parse_quote!(dyn my::Tr),
2188 ));
2189 assert_eq!(out, direct);
2190 }
2191
2192 #[test]
2193 fn check_type_uses_the_given_krate_path() {
2194 let out = s(check_type(
2195 &syn::parse_quote!(::renamed),
2196 &syn::parse_quote!(Vec<u8>),
2197 ));
2198 assert!(out.contains("::renamed::Announce"), "{}", out);
2199 assert!(out.contains("__announce_chk::<Vec<u8>>"), "{}", out);
2200 }
2201
2202 #[test]
2203 fn meta_name_values_parse_expression_values() {
2204 let m: Meta = syn::parse_quote!(lib::info(a = 1, b = "x", c = 1 + 2));
2205 let nv = m.name_values().unwrap();
2206 assert_eq!(nv.len(), 3);
2207 assert_eq!(s(&nv[0].path), "a");
2208 assert_eq!(s(&nv[1].value), "\"x\"");
2209 assert_eq!(s(&nv[2].value), "1+2");
2210 let m: Meta = syn::parse_quote!(lib::info);
2212 assert!(m.name_values().unwrap().is_empty());
2213 let m: Meta = syn::parse_quote!(lib::info(a = 1, on));
2215 assert!(m.name_values().is_err());
2216 let m: Meta = syn::parse_quote!(lib::info(a = 1, on));
2217 assert!(m.kv().is_ok());
2218 let m: Meta = syn::parse_quote!(lib::info(xs = [A, B]));
2220 assert_eq!(m.name_values().unwrap().len(), 1);
2221 let m: Meta = syn::parse_quote!(lib::info(ty = Vec<u8, A>));
2222 assert!(m.name_values().is_err());
2223 assert!(m.kv().is_ok());
2224 }
2225
2226 #[test]
2227 fn meta_args_default_to_empty() {
2228 let m: Meta = syn::parse_quote!(lib::info);
2229 assert!(m.args.is_none());
2230 assert!(m.kv().unwrap().finish().is_ok());
2231 assert_eq!(s(&m), "lib::info");
2232 let m: Meta = syn::parse_quote!(lib::info(1, 2));
2233 assert_eq!(s(&m), "lib::info(1,2)");
2234 }
2235
2236 fn hash_of<T: std::hash::Hash>(t: &T) -> u64 {
2237 use std::hash::Hasher;
2238 let mut h = std::collections::hash_map::DefaultHasher::new();
2239 t.hash(&mut h);
2240 h.finish()
2241 }
2242
2243 #[test]
2244 fn cont_hash_agrees_with_eq() {
2245 let a: Cont = syn::parse_quote!(
2246 #[derive(p::D)]
2247 N1
2248 );
2249 let b: Cont = syn::parse_quote!(
2250 #[derive(p::D)]
2251 N2
2252 );
2253 let c: Cont = syn::parse_quote!(#[derive(p::D)]);
2254 assert_eq!(a, b);
2255 assert_eq!(hash_of(&a), hash_of(&b));
2256 assert_eq!(hash_of(&a), hash_of(&c));
2257 let bang = Cont::bang(syn::parse_quote!(p::D));
2258 let bang_generic = Cont::Bang(syn::parse_quote!(p::D<T>));
2259 assert_eq!(bang, bang_generic);
2260 assert_eq!(hash_of(&bang), hash_of(&bang_generic));
2261 let other: Cont = syn::parse_quote!(
2263 #[derive(p::E)]
2264 N1
2265 );
2266 assert_ne!(a, other);
2267 assert_ne!(a, bang);
2268 let set: std::collections::HashSet<Cont> = [a.clone(), b, c, other].into_iter().collect();
2269 assert_eq!(set.len(), 2);
2270 assert!(set.contains(&a));
2271 }
2272
2273 #[test]
2274 fn cont_path_of_attribute_and_derive() {
2275 let c: Cont = syn::parse_quote!(
2276 #[my::attr(x, y)]
2277 N
2278 );
2279 assert_eq!(s(c.path()), "my::attr");
2280 let d = Cont::derive(syn::parse_quote!(::p::D));
2281 assert_eq!(s(d.path()), "::p::D");
2282 assert_eq!(s(&d), "#[derive(::p::D)]");
2283 let e: Cont = syn::parse_quote!(
2285 #[derive()]
2286 N
2287 );
2288 assert_eq!(s(e.path()), "derive");
2289 }
2290
2291 #[test]
2292 fn input_skips_a_leading_dollar_prefix() {
2293 let dollar = mk_dollar();
2294 let ts = template_quote::quote! {
2295 @ __announce_dollar { #dollar }
2296 (opt) [cb!, n!]
2297 a::F! => [x::check(1)] { struct S; }
2298 (rest)
2299 };
2300 let input: Input = syn::parse2(ts).unwrap();
2301 assert_eq!(s(input.options.as_ref().unwrap()), "opt");
2302 assert!(input.has_option("opt"));
2303 assert_eq!(input.list.0.len(), 2);
2304 assert_eq!(s(&input.list), "[cb!,n!]");
2305 assert_eq!(input.entries.len(), 1);
2306 assert_eq!(s(input.entries[0].key.path()), "a::F");
2307 assert_eq!(s(&input.entries[0].meta[0]), "x::check(1)");
2308 assert_eq!(s(&input.rest), "(rest)");
2309 let out = s(input.forward().unwrap());
2311 assert!(!out.contains("__announce_dollar"), "{}", out);
2312 }
2313
2314 #[test]
2315 fn input_dollar_prefix_without_options() {
2316 let dollar = mk_dollar();
2317 let ts = template_quote::quote! { @ __announce_dollar { #dollar } [cb!] };
2318 let input: Input = syn::parse2(ts).unwrap();
2319 assert!(input.options.is_none());
2320 assert_eq!(input.list.0.len(), 1);
2321 assert!(input.entries.is_empty());
2322 assert!(input.rest.is_empty());
2323 }
2324
2325 #[test]
2326 fn input_skips_the_self_and_dollar_prefixes() {
2327 let dollar = mk_dollar();
2328 let ts = template_quote::quote! {
2329 @ __announce_self { ::a::F }
2330 @ __announce_dollar { #dollar }
2331 [cb!, n!] REST
2332 };
2333 let input: Input = syn::parse2(ts).unwrap();
2334 assert!(input.options.is_none());
2335 assert_eq!(s(&input.list), "[cb!,n!]");
2336 assert_eq!(s(&input.rest), "REST");
2337 let ts = template_quote::quote! { @ __announce_self { F } (o) [cb!] };
2339 let input: Input = syn::parse2(ts).unwrap();
2340 assert!(input.has_option("o"));
2341 assert_eq!(input.list.0.len(), 1);
2342 let ts = template_quote::quote! { @ __announce_self { F } [cb!, n!] };
2344 let out = s(syn::parse2::<Input>(ts).unwrap().forward().unwrap());
2345 assert!(!out.contains("__announce_self"), "{}", out);
2346 }
2347
2348 #[test]
2349 fn input_rejects_other_prefixes() {
2350 assert!(syn::parse2::<Input>(template_quote::quote! { @ other { x } [cb!] }).is_err());
2351 assert!(syn::parse2::<Input>(template_quote::quote! { @ [cb!] }).is_err());
2352 let dollar = mk_dollar();
2354 let ts = template_quote::quote! { @ __announce_dollar { #dollar } };
2355 assert!(syn::parse2::<Input>(ts).is_err());
2356 }
2357
2358 #[test]
2359 fn invoke_with_an_empty_list_is_a_compile_error() {
2360 let out = s(ContList::default().invoke(template_quote::quote!(x y)));
2361 assert_eq!(out, "compile_error!{\"announce:emptycontinuationlist\"}");
2362 let opts = template_quote::quote!(expr);
2363 let out = s(ContList::default().invoke_with_options(Some(&opts), TokenStream::new()));
2364 assert!(out.contains("emptycontinuationlist"), "{}", out);
2365 }
2366
2367 #[test]
2368 fn invoke_in_expression_position_refuses_the_derive_route() {
2369 let list: ContList = [
2370 Cont::derive(syn::parse_quote!(p::D)),
2371 Cont::bang(syn::parse_quote!(cb)),
2372 ]
2373 .into_iter()
2374 .collect();
2375 let opts = template_quote::quote!(expr);
2376 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2377 assert!(out.starts_with("compile_error!{"), "{}", out);
2378 assert!(out.contains("cannotbeusedwith`(expr)`"), "{}", out);
2379 assert!(!out.contains("__AnnouncePayload"), "{}", out);
2380 let opts = template_quote::quote!(expr, type_leak);
2382 let out = s(list.invoke_with_options(Some(&opts), TokenStream::new()));
2383 assert!(out.contains("cannotbeusedwith`(expr)`"), "{}", out);
2384 }
2385
2386 #[test]
2387 fn only_a_leading_expr_selects_expression_position() {
2388 let yes = |ts: TokenStream| is_expression_position(&ts);
2390 assert!(yes(template_quote::quote!(expr)));
2391 assert!(yes(template_quote::quote!(expr, type_leak)));
2392 assert!(yes(template_quote::quote!(expr __announce_fetched(a))));
2393 assert!(!yes(template_quote::quote!(type_leak, expr)));
2394 assert!(!yes(template_quote::quote!(__announce_fetched(expr))));
2395 assert!(!yes(template_quote::quote!((expr))));
2396 assert!(!yes(template_quote::quote!(expression)));
2397 assert!(!yes(TokenStream::new()));
2398 let list: ContList = [
2401 Cont::derive(syn::parse_quote!(p::D)),
2402 Cont::bang(syn::parse_quote!(cb)),
2403 ]
2404 .into_iter()
2405 .collect();
2406 let opts = template_quote::quote!(type_leak, expr);
2407 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2408 assert!(out.contains("__AnnouncePayload"), "{}", out);
2409 assert!(!out.contains("cannotbeusedwith"), "{}", out);
2410 let opts = template_quote::quote!(__announce_fetched(expr));
2411 let out = s(list.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2412 assert!(out.contains("__AnnouncePayload"), "{}", out);
2413 let bang: ContList = [Cont::bang(syn::parse_quote!(p::cb))].into_iter().collect();
2415 let opts = template_quote::quote!(type_leak, expr);
2416 let out = s(bang.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2417 assert_eq!(
2418 out,
2419 "p::cb!{@announce_format\"1\"(type_leak,expr)[p::cb!]body}"
2420 );
2421 }
2422
2423 #[test]
2424 fn expression_option_is_fine_for_a_bang_head_and_other_options_for_derives() {
2425 let bang: ContList = [Cont::bang(syn::parse_quote!(p::cb))].into_iter().collect();
2426 let opts = template_quote::quote!(expr);
2427 let out = s(bang.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2428 assert_eq!(out, "p::cb!{@announce_format\"1\"(expr)[p::cb!]body}");
2429 let derive: ContList = [Cont::derive(syn::parse_quote!(p::D))]
2430 .into_iter()
2431 .collect();
2432 let opts = template_quote::quote!(type_leak);
2433 let out = s(derive.invoke_with_options(Some(&opts), template_quote::quote!(body)));
2434 assert!(out.contains("__AnnouncePayload"), "{}", out);
2435 assert!(
2436 out.contains("stringify!{@announce_format\"1\"(type_leak)[#[derive(p::D)]__AnnounceT_"),
2437 "{}",
2438 out
2439 );
2440 }
2441
2442 #[test]
2443 fn name_dummies_keeps_existing_names_and_gives_distinct_new_ones() {
2444 let mut list: ContList = [
2445 Cont::derive(syn::parse_quote!(p::A)),
2446 Cont::derive(syn::parse_quote!(p::B)),
2447 syn::parse_quote!(
2448 #[derive(p::C)]
2449 Fixed
2450 ),
2451 Cont::bang(syn::parse_quote!(cb)),
2452 ]
2453 .into_iter()
2454 .collect();
2455 list.name_dummies();
2456 let names: Vec<String> = list
2457 .0
2458 .iter()
2459 .filter_map(|c| match c {
2460 Cont::Attr { name: Some(n), .. } => Some(n.to_string()),
2461 _ => None,
2462 })
2463 .collect();
2464 assert_eq!(names.len(), 3);
2465 assert_eq!(names[2], "Fixed");
2466 assert!(names[0].starts_with("__AnnounceT_") && names[1].starts_with("__AnnounceT_"));
2467 assert_ne!(names[0], names[1]);
2468 let before = s(&list);
2470 list.name_dummies();
2471 assert_eq!(s(&list), before);
2472 }
2473
2474 #[test]
2475 fn redirect_ignores_ordinary_items() {
2476 let target: Path = syn::parse_quote!(::x::Y);
2477 for item in [
2478 template_quote::quote! { struct S; },
2479 template_quote::quote! { enum E { A, B } },
2480 template_quote::quote! { enum E { __AnnouncePayload } },
2481 template_quote::quote! { enum E { __AnnouncePayload = 1 } },
2482 template_quote::quote! { enum E { __AnnouncePayload = (1, 0).1 } },
2483 template_quote::quote! { enum E { __AnnouncePayload = (other!{ x }, 0).1 } },
2484 template_quote::quote! { enum E { __AnnouncePayload = (stringify!{ x }, 0).1, B } },
2485 template_quote::quote! { enum E { Other = (stringify!{ x }, 0).1 } },
2486 template_quote::quote! { fn f() {} },
2487 ] {
2488 assert!(payload(item.clone()).is_none(), "{}", item);
2489 assert!(redirect(item.clone(), &target).is_none(), "{}", item);
2490 }
2491 }
2492
2493 #[test]
2494 fn redirect_wraps_the_payload_with_the_dollar_prefix() {
2495 let list: ContList = [
2496 Cont::derive(syn::parse_quote!(p::D)),
2497 Cont::bang(syn::parse_quote!(cb)),
2498 ]
2499 .into_iter()
2500 .collect();
2501 let dummy = list.invoke(template_quote::quote!(body));
2502 let out = redirect(dummy.clone(), &syn::parse_quote!(::x::Y)).unwrap();
2503 let out_s = s(&out);
2504 assert!(
2505 out_s.starts_with(
2506 "::x::Y!{@announce_format\"1\"@__announce_self{::x::Y}@__announce_dollar{$}[#[derive(p::D)]__AnnounceT_"
2507 ),
2508 "{}",
2509 out_s
2510 );
2511 assert!(out_s.ends_with("cb!]body}"), "{}", out_s);
2512 assert_eq!(out_s.matches("@announce_format").count(), 1, "{}", out_s);
2514 let has_dollar = out.into_iter().any(|tt| match tt {
2516 proc_macro2::TokenTree::Group(g) => g
2517 .stream()
2518 .into_iter()
2519 .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() == '$')))),
2520 _ => false,
2521 });
2522 assert!(has_dollar);
2523 let input: Input = syn::parse2(
2525 match redirect(dummy, &syn::parse_quote!(::x::Y))
2526 .unwrap()
2527 .into_iter()
2528 .last()
2529 {
2530 Some(proc_macro2::TokenTree::Group(g)) => g.stream(),
2531 other => panic!("{:?}", other),
2532 },
2533 )
2534 .unwrap();
2535 assert_eq!(input.list.0.len(), 2);
2536 assert_eq!(s(input.rest), "body");
2537 }
2538}
2539
2540#[cfg(test)]
2542mod format_tests {
2543 use super::*;
2544 use proc_macro2::{Punct, Spacing};
2545
2546 fn s(ts: impl ToTokens) -> String {
2547 ts.to_token_stream()
2548 .to_string()
2549 .split_whitespace()
2550 .collect()
2551 }
2552
2553 const PREFIX: &str = "@announce_format\"1\"";
2555
2556 #[test]
2557 fn the_version_is_written_once_and_the_prefix_is_made_of_it() {
2558 assert_eq!(ANNOUNCE_FORMAT, "1");
2559 assert_eq!(s(format_prefix()), PREFIX);
2560 assert_eq!(
2561 s(format_prefix()),
2562 format!("@announce_format{:?}", ANNOUNCE_FORMAT)
2563 );
2564 }
2565
2566 #[test]
2567 fn input_reads_a_call_with_the_prefix_and_one_without() {
2568 let with: Input = syn::parse_quote! { @announce_format "1" (expr) [cb!] REST };
2569 assert_eq!(with.format.as_deref(), Some(ANNOUNCE_FORMAT));
2570 assert!(with.has_option("expr"));
2571 assert_eq!(s(&with.rest), "REST");
2572 let without: Input = syn::parse_quote! { (expr) [cb!] REST };
2573 assert_eq!(without.format, None);
2574 assert_eq!(s(&without.rest), "REST");
2575 assert_eq!(s(&with.list), s(&without.list));
2577 let all: Input = syn::parse_quote! {
2579 @announce_format "1" @__announce_self { a::b } @__announce_dollar { $ } (x) [cb!] REST
2580 };
2581 assert_eq!(all.format.as_deref(), Some("1"));
2582 assert!(all.has_option("x"));
2583 assert_eq!(s(&all.rest), "REST");
2584 let e: Input = syn::parse_quote! {
2586 @announce_format "1" [cb!] a::F! => [] { struct F; } b::G! => [] { struct G; }
2587 };
2588 assert_eq!(e.entries.len(), 2);
2589 }
2590
2591 #[test]
2592 fn the_prefix_after_the_other_prefixes_is_not_a_prefix() {
2593 let late = syn::parse2::<Input>(template_quote::quote! {
2594 @__announce_self { a::b } @announce_format "1" [cb!]
2595 });
2596 assert!(late.is_err());
2597 let twice = syn::parse2::<Input>(template_quote::quote! {
2599 @announce_format "1" @announce_format "1" [cb!]
2600 });
2601 assert!(twice.is_err());
2602 }
2603
2604 #[test]
2605 fn another_version_is_one_error_that_names_both() {
2606 let e = syn::parse2::<Input>(template_quote::quote! { @announce_format "2" [cb!] })
2607 .unwrap_err()
2608 .to_string();
2609 assert_eq!(
2610 e.matches("announce: incompatible wire format").count(),
2611 1,
2612 "{}",
2613 e
2614 );
2615 assert!(
2616 e.contains("found `@announce_format \"2\"`")
2617 && e.contains("expected `@announce_format \"1\"`"),
2618 "{}",
2619 e
2620 );
2621 let query = template_quote::quote! { @announce_format "7" @__announce_attrs [ f! ] { } };
2623 let call = template_quote::quote! { @announce_format "7" (), struct S; };
2624 let errors = vec![
2625 syn::parse2::<CheckCall>(query.clone()).err().unwrap(),
2626 syn::parse2::<CheckCall>(call.clone()).err().unwrap(),
2627 syn::parse2::<Query>(query).err().unwrap(),
2628 syn::parse2::<CheckInput>(call).err().unwrap(),
2629 ];
2630 for e in errors {
2631 let e = e.to_string();
2632 assert!(
2633 e.starts_with("announce: incompatible wire format")
2634 && e.contains("found `@announce_format \"7\"`")
2635 && e.contains("expected `@announce_format \"1\"`"),
2636 "{}",
2637 e
2638 );
2639 }
2640 }
2641
2642 #[test]
2643 fn a_prefix_without_a_string_is_an_error() {
2644 for bad in [
2645 template_quote::quote! { @announce_format [cb!] },
2646 template_quote::quote! { @announce_format 1 [cb!] },
2647 template_quote::quote! { @announce_format },
2648 ] {
2649 let e = syn::parse2::<Input>(bad).unwrap_err().to_string();
2650 assert!(e.contains("followed by a string literal"), "{}", e);
2651 }
2652 let e = syn::parse2::<Input>(template_quote::quote! { @other [cb!] })
2654 .unwrap_err()
2655 .to_string();
2656 assert!(!e.contains("wire format"), "{}", e);
2657 }
2658
2659 #[test]
2660 fn checks_take_a_call_with_the_prefix_and_one_without() {
2661 for tokens in [
2662 template_quote::quote! { @announce_format "1" (a, b), struct S; },
2663 template_quote::quote! { (a, b), struct S; },
2664 ] {
2665 let i: CheckInput = syn::parse2(tokens.clone()).unwrap();
2666 assert_eq!(s(&i.args), "a,b");
2667 match syn::parse2::<CheckCall>(tokens).unwrap() {
2668 CheckCall::Run(i) => assert_eq!(s(&i.args), "a,b"),
2669 CheckCall::Query(_) => panic!("the check itself"),
2670 }
2671 }
2672 for tokens in [
2673 template_quote::quote! { @announce_format "1" @__announce_attrs [ me!, f! ] { st } },
2674 template_quote::quote! { @__announce_attrs [ me!, f! ] { st } },
2675 ] {
2676 let q: Query = syn::parse2(tokens.clone()).unwrap();
2677 assert_eq!(q.list.0.len(), 2);
2678 assert!(matches!(
2679 syn::parse2::<CheckCall>(tokens).unwrap(),
2680 CheckCall::Query(_)
2681 ));
2682 assert!(
2684 s(q.answer(&OwnedAttrs::new())).starts_with(&format!("f!{{{}", PREFIX)),
2685 "{}",
2686 s(q.answer(&OwnedAttrs::new()))
2687 );
2688 }
2689 for tokens in [
2691 template_quote::quote! { @announce_format "1" (), struct S; },
2692 template_quote::quote! { (), struct S; },
2693 ] {
2694 let mut ran = false;
2695 let out = check(tokens, &[], |_| {
2696 ran = true;
2697 Ok(TokenStream::new())
2698 });
2699 assert!(ran && out.is_empty());
2700 }
2701 let out = s(check(
2703 template_quote::quote! { @announce_format "3" @__announce_attrs [ me!, f! ] { st } },
2704 &[],
2705 |_| unreachable!(),
2706 ));
2707 assert!(
2708 out.starts_with("::core::compile_error!") && out.contains("incompatiblewireformat"),
2709 "{}",
2710 out
2711 );
2712 }
2713
2714 #[test]
2715 fn every_call_that_a_helper_makes_starts_with_the_prefix() {
2716 let starts = |ts: TokenStream| {
2717 let out = s(&ts);
2718 assert!(
2720 out.contains(&format!("!{{{}", PREFIX))
2721 || out.contains(&format!("stringify!{{{}", PREFIX)),
2722 "{}",
2723 out
2724 );
2725 let at = out.find(PREFIX).unwrap();
2727 assert!(
2728 out[..at].ends_with("!{") || out[..at].ends_with("stringify!{"),
2729 "{}",
2730 out
2731 );
2732 assert_eq!(out.matches(PREFIX).count(), 1, "{}", out);
2733 };
2734 let input: Input = syn::parse_quote! { [me!, next!] a::F! => [] { struct F; } (rest) };
2735 starts(input.forward().unwrap());
2737 starts(input.fetch([Cont::bang(syn::parse_quote!(g::G))]));
2738 match input.gather([Cont::bang(syn::parse_quote!(g::G))]) {
2739 Gather::Fetch(ts) => starts(ts),
2740 Gather::Ready(_) => panic!("g::G is not there"),
2741 }
2742 starts(input.forward_transformed(input.entries[0].clone()).unwrap());
2744 let bang: ContList = [Cont::bang(syn::parse_quote!(p::cb))].into_iter().collect();
2747 starts(bang.invoke(TokenStream::new()));
2748 starts(bang.invoke_with_options(Some(&template_quote::quote!(expr)), TokenStream::new()));
2749 let derive: ContList = [
2750 Cont::derive(syn::parse_quote!(p::D)),
2751 Cont::bang(syn::parse_quote!(cb)),
2752 ]
2753 .into_iter()
2754 .collect();
2755 let dummy = derive.invoke(template_quote::quote!(body));
2756 starts(dummy.clone());
2757 assert!(s(payload(dummy.clone()).unwrap()).starts_with(&format!("{}[", PREFIX)));
2758 let out = redirect(dummy, &syn::parse_quote!(::x::Y)).unwrap();
2760 starts(out.clone());
2761 assert!(s(out).starts_with(&format!(
2762 "::x::Y!{{{}@__announce_self{{::x::Y}}@__announce_dollar{{$}}",
2763 PREFIX
2764 )));
2765 let q: Query = syn::parse_quote! { @__announce_attrs [ me!, f! ] { st } };
2767 starts(q.answer(&OwnedAttrs::new()));
2768 starts(check(
2769 template_quote::quote! { @__announce_attrs [ me!, f! ] { st } },
2770 &["a"],
2771 |_| unreachable!(),
2772 ));
2773 }
2774
2775 #[test]
2776 fn the_callback_and_transformer_functions_take_and_write_the_prefix() {
2777 let call = |prefix: TokenStream| {
2778 template_quote::quote! { #prefix [me!, next!] a::S! => [] { struct S; } NAME }
2779 };
2780 let with = call(format_prefix());
2781 let without = call(TokenStream::new());
2782 for input in [with.clone(), without.clone()] {
2784 let out = callback(input, |i| {
2785 let name: syn::Ident = i.rest_as()?;
2786 Ok(template_quote::quote! { const #name: usize = 1; })
2787 });
2788 assert_eq!(s(out), "constNAME:usize=1;");
2789 }
2790 for input in [with.clone(), without.clone()] {
2792 let out = s(callback(input, |i| Ok(i.forward().unwrap())));
2793 assert!(out.starts_with(&format!("next!{{{}", PREFIX)), "{}", out);
2794 }
2795 for input in [with.clone(), without] {
2797 let out = s(transformer(input, |mut entry| {
2798 entry.meta.push(syn::parse_quote!(seen));
2799 Ok(entry)
2800 }));
2801 assert!(
2802 out.starts_with(&format!("next!{{{}[next!]a::S!=>[seen]", PREFIX)),
2803 "{}",
2804 out
2805 );
2806 }
2807 let other = template_quote::quote! { @announce_format "5" [me!, next!] a::S! => [] { struct S; } NAME };
2809 for out in [
2810 s(callback(other.clone(), |_| unreachable!())),
2811 s(transformer(other, |_| unreachable!())),
2812 ] {
2813 assert!(
2814 out.starts_with("::core::compile_error!")
2815 && out.contains(
2816 "found`@announce_format\\\"5\\\"`,expected`@announce_format\\\"1\\\"`"
2817 ),
2818 "{}",
2819 out
2820 );
2821 }
2822 }
2823
2824 #[test]
2825 fn a_call_without_the_prefix_is_never_checked_for_a_version() {
2826 let no_error = |what: &str, out: &str| {
2830 assert!(
2831 !out.contains("wire format") && !out.contains("compile_error"),
2832 "{}: {}",
2833 what,
2834 out
2835 );
2836 };
2837 let dollar = Punct::new('$', Spacing::Alone);
2838 let calls = [
2840 template_quote::quote! { [cb!] },
2841 template_quote::quote! { (expr) [cb!] a::F! => [] { struct F; } REST },
2842 template_quote::quote! { @__announce_dollar { #dollar } [cb!] REST },
2843 template_quote::quote! {
2844 @__announce_self { ::a::F } @__announce_dollar { #dollar } (o) [cb!] REST
2845 },
2846 ];
2847 for call in calls {
2848 let input: Input = syn::parse2(call.clone()).unwrap();
2849 assert_eq!(input.format, None, "{}", s(&call));
2850 let fetched = s(input.fetch([Cont::bang(syn::parse_quote!(g::G))]));
2852 assert!(fetched.contains(&format!("!{{{}", PREFIX)), "{}", fetched);
2853 let out = s(callback(call.clone(), |_| Ok(TokenStream::new())));
2855 assert_eq!(out, "", "{}", s(&call));
2856 }
2857 let query = template_quote::quote! { @__announce_attrs [ me!, f! ] { st } };
2859 let own = template_quote::quote! { (a), struct S; };
2860 let q: Query = syn::parse2(query.clone()).unwrap();
2861 no_error(
2862 "Query::answer",
2863 &s(q.answer(&OwnedAttrs::from_strs(&["x"]))),
2864 );
2865 assert!(matches!(
2866 syn::parse2::<CheckCall>(query.clone()).unwrap(),
2867 CheckCall::Query(_)
2868 ));
2869 assert!(matches!(
2870 syn::parse2::<CheckCall>(own.clone()).unwrap(),
2871 CheckCall::Run(_)
2872 ));
2873 syn::parse2::<CheckInput>(own.clone()).unwrap();
2874 let answered = s(check(query, &["x"], |_| unreachable!()));
2875 no_error("check (query)", &answered);
2876 assert!(
2877 answered.starts_with(&format!("f!{{{}", PREFIX)),
2878 "{}",
2879 answered
2880 );
2881 let mut ran = false;
2882 let out = check(own, &["x"], |_| {
2883 ran = true;
2884 Ok(TokenStream::new())
2885 });
2886 assert!(ran && out.is_empty());
2887 let call = template_quote::quote! { [me!, next!] a::S! => [] { struct S; } NAME };
2889 no_error(
2890 "callback",
2891 &s(callback(call.clone(), |i| Ok(i.forward().unwrap()))),
2892 );
2893 no_error("transformer", &s(transformer(call, Ok)));
2894 let dummy = template_quote::quote! {
2896 #[derive(p::D)]
2897 enum N { __AnnouncePayload = (stringify!{ [cb!] rest }, 0).1 }
2898 };
2899 let out = redirect(dummy, &syn::parse_quote!(::x::Y)).unwrap();
2900 no_error("redirect", &s(&out));
2901 assert!(s(out).starts_with(&format!("::x::Y!{{{}", PREFIX)));
2902 }
2903
2904 #[test]
2905 fn check_answers_a_query_with_the_prefix_and_without_it() {
2906 let answer = |tokens: TokenStream| s(check(tokens, &["my_lib"], |_| unreachable!()));
2907 let with = answer(template_quote::quote! {
2908 @announce_format "1" @__announce_attrs [ me!, f! ] { st }
2909 });
2910 let without = answer(template_quote::quote! { @__announce_attrs [ me!, f! ] { st } });
2911 assert_eq!(with, without);
2912 assert_eq!(
2913 with,
2914 format!("f!{{{}@__announce_attrs[f!]{{stattrs(my_lib)}}}}", PREFIX)
2915 );
2916 }
2917
2918 #[test]
2919 fn redirect_moves_the_prefix_of_the_payload_to_the_front_and_keeps_its_version() {
2920 let dollar = Punct::new('$', Spacing::Alone);
2921 let dummy = |payload: TokenStream| {
2922 template_quote::quote! {
2923 #[derive(p::D)]
2924 enum N { __AnnouncePayload = (stringify!{ #payload }, 0).1 }
2925 }
2926 };
2927 let target: Path = syn::parse_quote!(::x::Y);
2928 let out = redirect(
2930 dummy(template_quote::quote!(@announce_format "1" [cb!] rest)),
2931 &target,
2932 )
2933 .unwrap();
2934 assert_eq!(
2935 s(out),
2936 format!(
2937 "::x::Y!{{{}@__announce_self{{::x::Y}}@__announce_dollar{{{}}}[cb!]rest}}",
2938 PREFIX,
2939 dollar.as_char()
2940 )
2941 );
2942 let out = redirect(
2944 dummy(template_quote::quote!(@announce_format "9" [cb!] rest)),
2945 &target,
2946 )
2947 .unwrap();
2948 assert!(s(out).starts_with("::x::Y!{@announce_format\"9\"@__announce_self"));
2949 let out = redirect(dummy(template_quote::quote!([cb!] rest)), &target).unwrap();
2951 assert!(s(out).starts_with(&format!("::x::Y!{{{}@__announce_self", PREFIX)));
2952 let out = redirect(dummy(template_quote::quote!(@announce_format)), &target).unwrap();
2954 assert!(s(out).ends_with("@__announce_dollar{$}@announce_format}"));
2955 }
2956}