1use crate::helper::{format_prefix, parse_format, Cont, ContList};
122use proc_macro2::{Ident, Span, TokenStream};
123use std::collections::HashSet;
124use syn::ext::IdentExt;
125use syn::parse::{Parse, ParseStream};
126use syn::punctuated::Punctuated;
127use syn::visit::Visit;
128use syn::visit_mut::VisitMut;
129use syn::{Attribute, Item, Result, Token};
130use template_quote::{quote, ToTokens};
131
132pub(crate) const QUERY_TAG: &str = "__announce_attrs";
134
135pub(crate) const META_NAME: &str = "attrs";
137
138const RESERVED: &[&str] = &[
142 "cfg",
144 "cfg_attr",
145 "cfg_eval",
146 "cfg_accessible",
147 "derive",
148 "derive_const",
149 "test",
150 "bench",
151 "test_case",
152 "global_allocator",
153 "proc_macro",
154 "proc_macro_derive",
155 "proc_macro_attribute",
156 "macro_use",
157 "macro_export",
158 "macro_reexport",
159 "path",
160 "allow",
162 "warn",
163 "deny",
164 "forbid",
165 "expect",
166 "doc",
167 "deprecated",
168 "must_use",
169 "must_not_suspend",
170 "ignore",
171 "should_panic",
172 "automatically_derived",
173 "diagnostic",
174 "stable",
175 "unstable",
176 "allow_internal_unstable",
177 "allow_internal_unsafe",
178 "inline",
180 "cold",
181 "track_caller",
182 "target_feature",
183 "instruction_set",
184 "optimize",
185 "naked",
186 "no_mangle",
187 "export_name",
188 "link_name",
189 "link_section",
190 "link_ordinal",
191 "link",
192 "linkage",
193 "used",
194 "repr",
195 "non_exhaustive",
196 "thread_local",
197 "coverage",
198 "no_sanitize",
199 "sanitize",
200 "cfi_encoding",
201 "patchable_function_entry",
202 "ffi_const",
203 "ffi_pure",
204 "ffi_returns_twice",
205 "collapse_debuginfo",
206 "debugger_visualizer",
207 "export_stable",
208 "pointee",
209 "const_trait",
210 "marker",
211 "fundamental",
212 "lang",
213 "unsafe",
214 "define_opaque",
215 "custom_mir",
216 "loop_match",
217 "const_continue",
218 "crate_name",
220 "crate_type",
221 "no_std",
222 "no_core",
223 "no_main",
224 "no_implicit_prelude",
225 "no_link",
226 "no_builtins",
227 "recursion_limit",
228 "type_length_limit",
229 "windows_subsystem",
230 "panic_handler",
231 "global_asm",
232 "feature",
233 "register_tool",
234 "register_attr",
235 "plugin",
236 "prelude_import",
237 "start",
238 "default_lib_allocator",
239 "needs_allocator",
240 "panic_runtime",
241 "rustfmt",
243 "clippy",
244 "rustc",
245 "rustdoc",
246 "miri",
247 "rust_analyzer",
248 "announce",
250];
251
252fn reserved(name: &str) -> Option<&'static str> {
254 if RESERVED.contains(&name) {
255 Some("an attribute that rustc or a standard tool uses")
256 } else if name.starts_with("rustc_") {
257 Some("a name reserved for rustc (`rustc_*`)")
258 } else if name.starts_with("__announce") {
259 Some("a name reserved for announce (`__announce*`)")
260 } else {
261 None
262 }
263}
264
265#[derive(Clone, Debug, Default)]
286pub struct OwnedAttrs {
287 names: Vec<Ident>,
288}
289
290impl PartialEq for OwnedAttrs {
291 fn eq(&self, other: &Self) -> bool {
292 self.keys() == other.keys()
293 }
294}
295impl Eq for OwnedAttrs {}
296
297impl OwnedAttrs {
298 pub fn new() -> Self {
300 Self::default()
301 }
302
303 pub fn from_idents(names: impl IntoIterator<Item = Ident>) -> Self {
305 let mut out = OwnedAttrs::new();
306 out.extend(names);
307 out
308 }
309
310 pub fn from_strs(names: &[&str]) -> Self {
313 Self::from_idents(
314 names
315 .iter()
316 .map(|n| Ident::new(n.strip_prefix("r#").unwrap_or(n), Span::call_site())),
317 )
318 }
319
320 pub fn extend(&mut self, names: impl IntoIterator<Item = Ident>) {
322 for n in names {
323 if !self.contains(&n.unraw().to_string()) {
324 self.names.push(n);
325 }
326 }
327 }
328
329 pub fn names(&self) -> &[Ident] {
331 &self.names
332 }
333
334 pub fn is_empty(&self) -> bool {
336 self.names.is_empty()
337 }
338
339 pub fn contains(&self, name: &str) -> bool {
341 self.names.iter().any(|n| n.unraw() == name)
342 }
343
344 fn keys(&self) -> Vec<String> {
345 self.names.iter().map(|n| n.unraw().to_string()).collect()
346 }
347
348 pub fn owns(&self, attr: &Attribute) -> bool {
351 let path = attr.path();
352 path.leading_colon.is_none()
353 && path
354 .segments
355 .first()
356 .map_or(false, |s| self.contains(&s.ident.unraw().to_string()))
357 }
358
359 pub fn find(&self, item: &Item) -> Vec<Attribute> {
362 struct V<'a>(&'a OwnedAttrs, Vec<Attribute>);
363 impl<'a, 'ast> Visit<'ast> for V<'a> {
364 fn visit_attribute(&mut self, a: &'ast Attribute) {
365 if self.0.owns(a) {
366 self.1.push(a.clone());
367 }
368 }
369 }
370 let mut v = V(self, Vec::new());
371 v.visit_item(item);
372 v.1
373 }
374
375 pub fn strip(&self, item: &mut Item) -> Result<bool> {
377 if self.is_empty() {
378 return Ok(false);
379 }
380 remove_attributes(item, |a| self.owns(a))
381 }
382
383 pub fn check_names(&self) -> Result<()> {
385 for n in &self.names {
386 if let Some(why) = reserved(&n.unraw().to_string()) {
387 return Err(syn::Error::new(
388 n.span(),
389 format!(
390 "announce: `{}` cannot be an owned attribute name: it is {}",
391 n, why
392 ),
393 ));
394 }
395 }
396 Ok(())
397 }
398
399 pub fn parse_names(tokens: TokenStream) -> Result<Self> {
402 let p: Punctuated<Ident, Token![,]> =
403 syn::parse::Parser::parse2(Punctuated::parse_terminated, tokens)?;
404 Ok(Self::from_idents(p))
405 }
406}
407
408impl ToTokens for OwnedAttrs {
409 fn to_tokens(&self, tokens: &mut TokenStream) {
411 if self.is_empty() {
412 return;
413 }
414 let name = Ident::new(META_NAME, Span::call_site());
415 let names = &self.names;
416 tokens.extend(quote! { #name(#(#names),*) });
417 }
418}
419
420const MARK: &str = "__announce_removed_attribute";
422
423pub fn remove_attributes(item: &mut Item, remove: impl FnMut(&Attribute) -> bool) -> Result<bool> {
439 struct V<F>(F, bool);
440 impl<F: FnMut(&Attribute) -> bool> VisitMut for V<F> {
441 fn visit_attribute_mut(&mut self, a: &mut Attribute) {
442 if (self.0)(a) {
443 let mark: syn::Path = Ident::new(MARK, Span::call_site()).into();
444 match &mut a.meta {
445 syn::Meta::Path(p) => *p = mark,
446 syn::Meta::List(l) => l.path = mark,
447 syn::Meta::NameValue(nv) => nv.path = mark,
448 }
449 self.1 = true;
450 }
451 syn::visit_mut::visit_attribute_mut(self, a);
452 }
453 }
454 let mut v = V(remove, false);
455 v.visit_item_mut(item);
456 if !v.1 {
457 return Ok(false);
458 }
459 *item = syn::parse2(drop_marked_attributes(item.to_token_stream()))?;
460 Ok(true)
461}
462
463fn drop_marked_attributes(ts: TokenStream) -> TokenStream {
465 use proc_macro2::{Delimiter, Group, TokenTree};
466 let tts: Vec<TokenTree> = ts.into_iter().collect();
467 let mut out = Vec::with_capacity(tts.len());
468 let mut i = 0;
469 while i < tts.len() {
470 match &tts[i] {
471 TokenTree::Punct(p) if p.as_char() == '#' => {
472 let mut j = i + 1;
473 if matches!(tts.get(j), Some(TokenTree::Punct(b)) if b.as_char() == '!') {
474 j += 1;
475 }
476 if let Some(TokenTree::Group(g)) = tts.get(j) {
477 let marked = g.delimiter() == Delimiter::Bracket
478 && matches!(
479 g.stream().into_iter().next(),
480 Some(TokenTree::Ident(id)) if id == MARK
481 );
482 if marked {
483 i = j + 1;
484 continue;
485 }
486 }
487 out.push(tts[i].clone());
488 }
489 TokenTree::Group(g) => {
490 let mut ng = Group::new(g.delimiter(), drop_marked_attributes(g.stream()));
491 ng.set_span(g.span());
492 out.push(TokenTree::Group(ng));
493 }
494 t => out.push(t.clone()),
495 }
496 i += 1;
497 }
498 out.into_iter().collect()
499}
500
501#[derive(Clone, Debug)]
509pub struct Query {
510 pub list: ContList,
513 pub state: TokenStream,
515}
516
517impl Parse for Query {
518 fn parse(input: ParseStream) -> Result<Self> {
519 parse_format(input)?;
521 input.parse::<Token![@]>()?;
522 let tag: Ident = input.parse()?;
523 if tag != QUERY_TAG {
524 return Err(syn::Error::new(
525 tag.span(),
526 format!("announce: expected `@{}`", QUERY_TAG),
527 ));
528 }
529 let list;
530 syn::bracketed!(list in input);
531 let state;
532 syn::braced!(state in input);
533 let q = Query {
534 list: list.parse()?,
535 state: state.parse()?,
536 };
537 if !input.is_empty() {
538 return Err(input.error("announce: unexpected tokens after the attribute query"));
539 }
540 Ok(q)
541 }
542}
543
544impl Query {
545 pub fn answer(&self, owned: &OwnedAttrs) -> TokenStream {
552 if let Err(e) = owned.check_names() {
553 return e.to_compile_error();
554 }
555 let mut it = self.list.0.iter();
556 match it.next() {
557 Some(Cont::Bang(_)) => {}
558 _ => {
559 return quote! {
560 ::core::compile_error! { "announce: the list of the attribute query is empty or does not start with a macro: it must start with the macro that is called (this check) and end with `finish!`. A check before this one did not forward its list (without its own first element)" }
561 }
562 }
563 }
564 let next = match it.clone().next() {
565 Some(c @ Cont::Bang(_)) => c,
566 _ => {
567 return quote! {
568 ::core::compile_error! { "announce: the attribute query has no macro to call after this check: the list ends with this check, and `finish!` must end it. A check before this one forwarded too little of its list" }
569 }
570 }
571 };
572 let rest: ContList = it.cloned().collect();
573 let (format, state) = (format_prefix(), &self.state);
574 quote! { #next { #format @__announce_attrs #rest { #state #owned } } }
575 }
576}
577
578#[allow(clippy::large_enum_variant)]
583pub enum CheckCall {
584 Query(Query),
586 Run(crate::helper::CheckInput),
588}
589
590impl Parse for CheckCall {
591 fn parse(input: ParseStream) -> Result<Self> {
592 let ahead = input.fork();
594 parse_format(&ahead)?;
595 if ahead.peek(Token![@]) {
596 Ok(CheckCall::Query(input.parse()?))
597 } else {
598 Ok(CheckCall::Run(input.parse()?))
599 }
600 }
601}
602
603pub(crate) fn owned_from_meta(meta: &crate::helper::Meta) -> Option<OwnedAttrs> {
605 if !meta.path.is_ident(META_NAME) {
606 return None;
607 }
608 OwnedAttrs::parse_names(meta.args.clone().unwrap_or_default()).ok()
609}
610
611pub(crate) fn owned_from_metas(metas: &[crate::helper::Meta]) -> OwnedAttrs {
613 let mut out = OwnedAttrs::new();
614 let mut seen = HashSet::new();
615 for m in metas {
616 if let Some(o) = owned_from_meta(m) {
617 for n in o.names {
618 if seen.insert(n.unraw().to_string()) {
619 out.names.push(n);
620 }
621 }
622 }
623 }
624 out
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630
631 fn s(ts: impl ToTokens) -> String {
632 ts.to_token_stream()
633 .to_string()
634 .split_whitespace()
635 .collect()
636 }
637
638 fn item(ts: TokenStream) -> Item {
639 syn::parse2(ts).unwrap()
640 }
641
642 #[test]
643 fn matches_the_first_path_segment() {
644 let owned = OwnedAttrs::from_strs(&["my_lib"]);
645 let attrs: Vec<Attribute> = vec![
646 syn::parse_quote!(#[my_lib]),
647 syn::parse_quote!(#[my_lib(skip)]),
648 syn::parse_quote!(#[my_lib = "x"]),
649 syn::parse_quote!(#[my_lib::skip]),
650 syn::parse_quote!(#[my_lib::a::b(1)]),
651 ];
652 assert!(attrs.iter().all(|a| owned.owns(a)));
653 let others: Vec<Attribute> = vec![
654 syn::parse_quote!(#[other]),
655 syn::parse_quote!(#[my_libs]),
656 syn::parse_quote!(#[a::my_lib]),
657 syn::parse_quote!(#[::my_lib::x]),
658 syn::parse_quote!(#[doc = "my_lib"]),
659 syn::parse_quote!(#[cfg(my_lib)]),
660 ];
661 assert!(others.iter().all(|a| !owned.owns(a)));
662 }
663
664 #[test]
665 fn finds_attributes_in_every_position() {
666 let owned = OwnedAttrs::from_strs(&["t"]);
667 let it = item(quote! {
668 #[t(0)]
669 pub struct S<#[t(1)] T, #[t(2)] const N: usize> {
670 #[t(3)] a: T,
671 b: [u8; N],
672 }
673 });
674 assert_eq!(owned.find(&it).len(), 4);
675
676 let it = item(quote! {
677 #[t(0)]
678 pub enum E { #[t(1)] A, B(#[t(2)] u8), #[t(3)] C { #[t(4)] x: u8 } }
679 });
680 assert_eq!(owned.find(&it).len(), 5);
681
682 let it = item(quote! {
683 #[t(0)]
684 pub trait Tr {
685 #[t(1)] type A;
686 #[t(2)] const C: u8;
687 #[t(3)] fn f(&self, #[t(4)] x: u8) -> u8 { #[t(5)] let y = x; y }
688 }
689 });
690 assert_eq!(owned.find(&it).len(), 6);
691
692 let it = item(quote! {
693 #[t(0)]
694 mod m {
695 #![t(1)]
696 #[t(2)] pub struct S { #[t(3)] a: u8 }
697 #[t(4)] fn f() {}
698 }
699 });
700 let found = owned.find(&it);
701 assert_eq!(found.len(), 5);
702 assert!(found
703 .iter()
704 .any(|a| matches!(a.style, syn::AttrStyle::Inner(_))));
705 }
706
707 #[test]
708 fn leaves_macro_bodies_alone() {
709 let owned = OwnedAttrs::from_strs(&["t"]);
710 let mut it = item(quote! {
711 mod m { m!(#[t] x); macro_rules! k { () => { #[t] struct Z; } } #[t] struct Y; }
712 });
713 assert_eq!(owned.find(&it).len(), 1);
714 assert!(owned.strip(&mut it).unwrap());
715 let out = s(&it);
716 assert!(
717 out.contains("m!(#[t]x)") && out.contains("#[t]structZ"),
718 "{}",
719 out
720 );
721 assert!(
722 out.contains("structY") && !out.contains("#[t]structY"),
723 "{}",
724 out
725 );
726 }
727
728 #[test]
729 fn strips_and_keeps_everything_else() {
730 let owned = OwnedAttrs::from_strs(&["my_lib", "other"]);
731 let mut it = item(quote! {
732 #[derive(Clone)]
734 #[my_lib(a)]
735 pub struct S {
736 #[other] #[serde(rename = "x")] pub a: u8,
737 #[my_lib::tag = "y"] pub b: u8,
738 }
739 });
740 assert!(owned.strip(&mut it).unwrap());
741 assert_eq!(
742 s(&it),
743 "#[doc=\"docs\"]#[derive(Clone)]pubstructS{#[serde(rename=\"x\")]puba:u8,pubb:u8,}"
744 );
745 assert!(!owned.strip(&mut it).unwrap());
747 assert!(!OwnedAttrs::new().strip(&mut it).unwrap());
748 }
749
750 #[test]
751 fn names_are_unique_ordered_and_printed() {
752 let o = OwnedAttrs::from_strs(&["b", "a", "b", "r#a"]);
753 assert_eq!(s(&o), "attrs(b,a)");
754 assert_eq!(s(OwnedAttrs::new()), "");
755 let back = OwnedAttrs::parse_names(quote! { b, a }).unwrap();
756 assert_eq!(back, o);
757 assert!(o.contains("a") && !o.contains("c"));
758 }
759
760 #[test]
761 fn reserved_names_are_errors() {
762 for bad in [
763 "cfg",
764 "doc",
765 "derive",
766 "allow",
767 "inline",
768 "repr",
769 "rustc_foo",
770 "__announce_x",
771 "announce",
772 "clippy",
773 ] {
774 let e = OwnedAttrs::from_strs(&[bad]).check_names().unwrap_err();
775 assert!(e.to_string().contains(bad), "{}: {}", bad, e);
776 }
777 for ok in ["my_lib", "skip", "sumtype", "serde", "doc_hidden"] {
778 OwnedAttrs::from_strs(&[ok]).check_names().unwrap();
779 }
780 }
781
782 #[test]
783 fn the_query_is_answered_by_calling_the_next_element() {
784 let q: Query = syn::parse_quote! {
787 @__announce_attrs [ a::check!, b::check!, ::announce::__private::finish! ] { [1u64; attr (x) { struct S; }] }
788 };
789 assert_eq!(q.list.0.len(), 3);
790 let answered = q.answer(&OwnedAttrs::from_strs(&["a", "c"]));
791 assert_eq!(
792 s(&answered),
793 "b::check!{@announce_format\"1\"@__announce_attrs[b::check!,::announce::__private::finish!]{[1u64;attr(x){structS;}]attrs(a,c)}}"
794 );
795 assert_eq!(
797 s(q.answer(&OwnedAttrs::new())),
798 "b::check!{@announce_format\"1\"@__announce_attrs[b::check!,::announce::__private::finish!]{[1u64;attr(x){structS;}]}}"
799 );
800 let q2: Query = syn::parse2(syn::parse2::<syn::Macro>(answered).unwrap().tokens).unwrap();
803 assert_eq!(s(&q2.list), "[b::check!,::announce::__private::finish!]");
804 assert_eq!(
805 s(q2.answer(&OwnedAttrs::from_strs(&["b"]))),
806 "::announce::__private::finish!{@announce_format\"1\"@__announce_attrs[::announce::__private::finish!]{[1u64;attr(x){structS;}]attrs(a,c)attrs(b)}}"
807 );
808 let q: Query = syn::parse_quote! {
810 @__announce_attrs [ crate::a!, ::x::b!, self::d!, e!, f::finish! ] { st }
811 };
812 assert_eq!(
813 s(q.answer(&OwnedAttrs::new())),
814 "::x::b!{@announce_format\"1\"@__announce_attrs[::x::b!,self::d!,e!,f::finish!]{st}}"
815 );
816 let q: Query = syn::parse_quote! { @__announce_attrs [ a!, a!, f::finish! ] { st } };
818 assert_eq!(
819 s(q.answer(&OwnedAttrs::new())),
820 "a!{@announce_format\"1\"@__announce_attrs[a!,f::finish!]{st}}"
821 );
822 let q: Query = syn::parse_quote! { @__announce_attrs [ b!, f::finish! ] { st } };
824 assert_eq!(
825 s(q.answer(&OwnedAttrs::from_strs(&["a"]))),
826 "f::finish!{@announce_format\"1\"@__announce_attrs[f::finish!]{stattrs(a)}}"
827 );
828 }
829
830 #[test]
831 fn a_list_that_does_not_fit_is_an_error_not_a_panic() {
832 let q: Query = syn::parse_quote! { @__announce_attrs [] { st } };
834 let out = s(q.answer(&OwnedAttrs::new()));
835 assert!(
836 out.contains("compile_error") && out.contains("muststartwiththemacrothatiscalled"),
837 "{}",
838 out
839 );
840 let q: Query = syn::parse_quote! { @__announce_attrs [ b::check! ] { st } };
842 let out = s(q.answer(&OwnedAttrs::new()));
843 assert!(
844 out.contains("compile_error") && out.contains("nomacrotocallafterthischeck"),
845 "{}",
846 out
847 );
848 let q: Query = syn::parse_quote! { @__announce_attrs [ #[derive(A)] ] { st } };
850 assert!(s(q.answer(&OwnedAttrs::new())).contains("compile_error"));
851 let q: Query = syn::parse_quote! { @__announce_attrs [ b!, #[derive(A)] ] { st } };
852 assert!(s(q.answer(&OwnedAttrs::new())).contains("compile_error"));
853 let q: Query = syn::parse_quote! { @__announce_attrs [ b!, f::finish! ] { st } };
855 let out = s(q.answer(&OwnedAttrs::from_strs(&["doc"])));
856 assert!(
857 out.contains("compile_error") && out.contains("cannotbeanownedattributename"),
858 "{}",
859 out
860 );
861 }
862
863 #[test]
864 fn malformed_queries_are_errors() {
865 for bad in [
866 quote! { @other [ f! ] { } },
867 quote! { @__announce_attrs { } },
868 quote! { @__announce_attrs [ f! ] { } extra },
869 quote! { (a), struct S; },
870 ] {
871 assert!(syn::parse2::<Query>(bad).is_err());
872 }
873 }
874
875 #[test]
876 fn a_check_call_is_a_query_or_its_input() {
877 let q: CheckCall = syn::parse_quote! { @__announce_attrs [ c!, f! ] { } };
878 assert!(matches!(q, CheckCall::Query(_)));
879 let r: CheckCall = syn::parse_quote! { (a, b), struct S; };
880 match r {
881 CheckCall::Run(i) => assert_eq!(s(i.args), "a,b"),
882 _ => panic!(),
883 }
884 }
885}