Skip to main content

announce_core/helper/
attrs.rs

1//! Attributes that a check owns, and the query that collects them.
2//!
3//! A check can own attribute names. An announced item may then carry attributes with
4//! these names (`#[my_lib(skip)]`). `#[announce]` removes them from the item it
5//! emits, so rustc never sees them, and keeps them in the payload that callbacks
6//! receive. What the definer writes and gets is described in
7//! [`#[announce]`][announce-checks]. This page has the protocol between `#[announce]`
8//! and the checks, called the *query*, and the types that find and remove owned
9//! attributes.
10//!
11//! # The query
12//!
13//! `#[announce(a::check, b::check)]` does not emit the item at once. It calls the
14//! first check with a list of the calls to make. The list is headed by the macro
15//! being called, like the continuation lists of trampolines. It holds the checks in
16//! the order written, and ends with a hidden macro of `announce`, `finish!`:
17//!
18//! ```text
19//! a::check! { @announce_format "1" @__announce_attrs [ a::check!, b::check!, ::announce::__private::finish! ] { STATE } }
20//! ```
21//!
22//! Like every call that announce makes, it starts with the format prefix
23//! `@announce_format "1"` ([the wire format](crate::helper::Input#the-wire-format)).
24//! `STATE` is opaque to the checks. A check *answers* by dropping its own element
25//! (the head) and calling the next element with the rest of the list, which is now
26//! headed by the macro that is called. `STATE` is unchanged, followed by one
27//! `attrs(name, ..)` group if the check owns names:
28//!
29//! ```text
30//! b::check! { @announce_format "1" @__announce_attrs [ b::check!, ::announce::__private::finish! ] { STATE attrs(my_lib) } }
31//! ::announce::__private::finish! { @announce_format "1" @__announce_attrs [ ::announce::__private::finish! ] { STATE attrs(my_lib) } }
32//! ```
33//!
34//! A check that forwards the query writes the prefix again, as above
35//! ([`Query::answer`] does). The parsers of announce, and `finish!`, also take a query
36//! without the prefix, as a check written by hand may forward it; another version
37//! (`@announce_format "2"`) is an error that names both versions.
38//!
39//! `finish!` reads the names, rejects a reserved one ([`OwnedAttrs::check_names`]),
40//! and expands to what `#[announce]` expands to without checks, except that the item
41//! it emits has no attribute of the owned names. The `META` of the trampoline lists
42//! the names in one element, `attrs(my_lib, other)`
43//! ([`Entry::owned_attrs`](crate::helper::Entry::owned_attrs)).
44//!
45//! **Every check must answer**, also one that owns no attributes. A check that does
46//! not answer stops the chain: the item is not emitted, and rustc reports the call of
47//! that check. A `macro_rules!` check says "no rules expected the token `@`" (when it
48//! has no arm for the query, or no arm for the prefix `@announce_format "1"`). A
49//! proc-macro check that parses [`CheckInput`](crate::helper::CheckInput) by hand says that
50//! it did not answer.
51//!
52//! A list that does not fit is an error where it is noticed:
53//!
54//! * a check answers a list that is empty, or has nothing after its head (an earlier
55//!   check forwarded too little; `finish!` must end the list);
56//! * `finish!` receives anything but `[ finish! ]`: an empty list, or "elements left
57//!   in its list" (a check forwarded its head or more);
58//! * a check forwards its list without dropping its head, so it calls itself again
59//!   without end: rustc stops it at the recursion limit.
60//!
61//! Listing a check twice, `#[announce(a::check, a::check)]`, is not an error: the
62//! list is `[a!, a!, finish!]`.
63//!
64//! # Writing a check
65//!
66//! A proc-macro check calls [`check`](crate::helper::check) with its input. The function
67//! answers the query with the names the check owns. It parses the check's own input,
68//! `(ARGS), ITEM` ([`CheckInput`](crate::helper::CheckInput)), and runs a closure on it.
69//! (By hand, parse a [`CheckCall`] and answer its [`Query`] with [`Query::answer`].)
70//!
71//! ```ignore
72//! #[proc_macro]
73//! pub fn my_check(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
74//!     announce::helper::check(input.into(), &["my_lib", "other"], |c| {
75//!         for attr in c.owned_attributes() { /* validate */ }
76//!         Ok(proc_macro2::TokenStream::new())
77//!     })
78//!     .into()
79//! }
80//! ```
81//!
82//! A `macro_rules!` check has one more arm. It skips its own head, forwards `STATE`
83//! and adds the names (leave out `attrs(..)` when the check owns none). The paths of
84//! the head and of the next element are matched as in the arms of a trampoline, so
85//! `crate::`, `$crate::` and `::a::b` work in both:
86//!
87//! ```text
88//! macro_rules! my_check {
89//!     (@announce_format "1" @__announce_attrs [ $($hp:ident)? $(:: $hs:ident)* ! , $($np:ident)? $(:: $ns:ident)* ! $(, $($rest:tt)*)? ] { $($state:tt)* }) => {
90//!         $($np)? $(:: $ns)* ! { @announce_format "1" @__announce_attrs [ $($np)? $(:: $ns)* ! $(, $($rest)*)? ] { $($state)* attrs(my_lib, other) } }
91//!     };
92//!     (@announce_format "1" ($($args:tt)*), $($item:tt)*) => { /* the check itself */ };
93//! }
94//! ```
95//!
96//! Both arms start with the prefix, because announce writes it in every call to a
97//! check. (A check that should also be callable by hand writes `$(@announce_format "1")?`
98//! instead.)
99//!
100//! The arm matches the head only to skip it (`$hp` and `$hs` are not used). A check
101//! that wants to see its own path in the head can write that path in the pattern
102//! instead (`[ crate::my_check ! , ..`).
103//!
104//! The check itself runs as `my_check! { @announce_format "1" (ARGS), ITEM }`. `ITEM`
105//! has the owned attributes, with `#[cfg]` and `#[cfg_attr]` evaluated. The check
106//! validates them and returns a `compile_error!` at the offending attribute.
107//!
108//! # What is matched
109//!
110//! An owned name `my_lib` matches an attribute `#[my_lib]`, `#[my_lib(..)]`,
111//! `#[my_lib = ..]` and `#[my_lib::..]` (the first segment of its path, with no
112//! leading `::`: `#[::my_lib::x]` names a crate and is rustc's business). Both outer
113//! (`#[..]`) and inner (`#![..]`) attributes match, wherever syn parses attributes in
114//! the item: on the item, its fields, variants, generic parameters, trait and impl
115//! items, the function parameters and statements of the bodies, and on everything
116//! inside an inline `mod`. Attributes inside macro invocations and `macro_rules!`
117//! bodies are token trees, which are not looked at.
118//!
119//! [announce-checks]: https://docs.rs/announce/latest/announce/attr.announce.html#checks-and-attributes-of-your-own
120
121use 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
132/// The word after `@` in the query.
133pub(crate) const QUERY_TAG: &str = "__announce_attrs";
134
135/// The name of the META element and of the query answer: `attrs(a, b)`.
136pub(crate) const META_NAME: &str = "attrs";
137
138/// Attribute names that rustc, a standard tool or announce itself uses. An owned
139/// name must not be one of them, because the item would lose an attribute that
140/// rustc needs. Names that start with `rustc_` or `__announce` are reserved too.
141const RESERVED: &[&str] = &[
142    // Conditional compilation, macros and tests.
143    "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    // Lints, documentation and diagnostics.
161    "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    // Code generation and layout.
179    "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 level.
219    "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    // Tools.
242    "rustfmt",
243    "clippy",
244    "rustc",
245    "rustdoc",
246    "miri",
247    "rust_analyzer",
248    // announce itself.
249    "announce",
250];
251
252/// Why `name` cannot be an owned attribute name, if it cannot.
253fn 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/// A set of attribute names that a check owns: `my_lib`, `other`.
266///
267/// A check answers the query with it ([`Query::answer`]), META lists it as
268/// `attrs(..)` ([`Entry::owned_attrs`](crate::helper::Entry::owned_attrs)), and it finds
269/// ([`find`](Self::find)) and removes ([`strip`](Self::strip)) the attributes in
270/// an item. The [module docs](self#what-is-matched) say what a name matches.
271///
272/// ```
273/// # use announce_core as announce;
274/// use announce::helper::OwnedAttrs;
275/// let owned = OwnedAttrs::from_strs(&["my_lib"]);
276/// let mut item: syn::Item = syn::parse_quote! {
277///     #[derive(Debug)]
278///     struct S { #[my_lib(skip)] a: u8, #[my_lib::tag = "x"] #[doc = "kept"] b: u8 }
279/// };
280/// assert_eq!(owned.find(&item).len(), 2);
281/// assert!(owned.strip(&mut item).unwrap());
282/// let out = template_quote::ToTokens::to_token_stream(&item).to_string();
283/// assert!(out.contains("derive (Debug)") && out.contains("doc") && !out.contains("my_lib"));
284/// ```
285#[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    /// No names.
299    pub fn new() -> Self {
300        Self::default()
301    }
302
303    /// From identifiers; a repeated name is kept once, in the order of first use.
304    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    /// From names written as text (`"my_lib"`; a raw identifier `"r#type"` is its
311    /// plain name).
312    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    /// Adds names; a name that is already present is skipped.
321    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    /// The names, in the order they were added.
330    pub fn names(&self) -> &[Ident] {
331        &self.names
332    }
333
334    /// Whether there are no names.
335    pub fn is_empty(&self) -> bool {
336        self.names.is_empty()
337    }
338
339    /// Whether `name` is one of the names.
340    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    /// Whether `attr` is owned: the first segment of its path (without a leading
349    /// `::`) is one of the names.
350    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    /// The owned attributes of `item`, in source order, wherever syn parses them
360    /// (see [what is matched](self#what-is-matched)).
361    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    /// Removes the owned attributes from `item`. Returns whether there were any.
376    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    /// An error at the first name that rustc, a standard tool or announce uses.
384    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    /// Reads `attrs(a, b)` (the answer of a check and the META element): the
400    /// tokens inside the parentheses are the names.
401    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    /// `attrs(a, b)`, or nothing when there are no names.
410    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
420/// The path that stands in for an attribute about to be removed.
421const MARK: &str = "__announce_removed_attribute";
422
423/// Removes every attribute of `item` for which `remove` returns true, at any depth
424/// that syn parses (not inside macro invocations). Returns whether any was removed.
425///
426/// `syn`'s `VisitMut` cannot take an attribute out of its list. So this renames
427/// each removed attribute to a mark, drops the marks from the printed tokens, and
428/// parses the item again.
429///
430/// ```
431/// # use announce_core as announce;
432/// let mut item: syn::Item = syn::parse_quote! { #[a] struct S { #[b] x: u8 } };
433/// let removed = announce::helper::remove_attributes(&mut item, |a| a.path().is_ident("b")).unwrap();
434/// assert!(removed);
435/// let out = template_quote::ToTokens::to_token_stream(&item).to_string();
436/// assert!(out.contains("# [a]") && !out.contains("# [b]"));
437/// ```
438pub 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
463/// Removes the `#[MARK ..]` and `#![MARK ..]` attributes, at any depth.
464fn 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/// The query a check receives:
502/// `@announce_format "1" @__announce_attrs [ THIS!, NEXT!, .. ] { STATE }`. The prefix is
503/// optional when parsing (a query written by hand), and always written by
504/// [`answer`](Self::answer).
505///
506/// Answer it with [`answer`](Self::answer). The [module docs](self#the-query) have
507/// the protocol. [`check`](crate::helper::check) does this for a proc-macro check.
508#[derive(Clone, Debug)]
509pub struct Query {
510    /// The macro being called (the head, the check itself), then the elements still
511    /// to be called, in order. The last one is `finish!`.
512    pub list: ContList,
513    /// The state that travels along. Opaque: pass it on unchanged.
514    pub state: TokenStream,
515}
516
517impl Parse for Query {
518    fn parse(input: ParseStream) -> Result<Self> {
519        // `@announce_format "1"` comes first, unless the query was written by hand.
520        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    /// The call that answers the query for a check that owns `owned`. It calls the
546    /// element after the head, with the prefix `@announce_format "1"`, the list from
547    /// there on and the state plus `attrs(..)` (the state unchanged if `owned` is
548    /// empty). A reserved name gives
549    /// a `compile_error!`, and so does a list that is empty or has no element after
550    /// its head.
551    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/// What a proc-macro check receives: the attribute [`Query`], or its own input
579/// (`(ARGS), ITEM`). Both may start with the prefix `@announce_format "1"`.
580///
581/// [`check`](crate::helper::check) parses this and answers the query for you.
582#[allow(clippy::large_enum_variant)]
583pub enum CheckCall {
584    /// The query of `#[announce]`: answer it with [`Query::answer`].
585    Query(Query),
586    /// The check itself.
587    Run(crate::helper::CheckInput),
588}
589
590impl Parse for CheckCall {
591    fn parse(input: ParseStream) -> Result<Self> {
592        // Both forms may start with `@announce_format "1"`: look behind it.
593        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
603/// The attribute names of a META `attrs(..)` element, if it is one.
604pub(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
611/// All the owned names that the META of an entry lists, merged.
612pub(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            /// docs
733            #[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        // Nothing to strip: the item is not touched.
746        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        // The head is the check that is called; the answer drops it and calls the
785        // next element with the list from there.
786        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        // Owning nothing: the state is passed on as it is.
796        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        // The answer is a query again, headed by the macro it calls: the next check
801        // answers it in the same way, and the last call is `finish!` with `[finish!]`.
802        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        // Every path form of the head and of the next element is kept as written.
809        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        // The same check twice is a list like any other.
817        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        // The check before `finish!`.
823        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        // Nothing at all: a check before this one dropped the whole list.
833        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        // Only the head: nothing to call after the check.
841        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        // An attribute continuation is not a macro that can be called.
849        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        // A reserved name is reported where the check answers.
854        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}