derive_deftly_macros/
meta.rs

1//! `#[deftly(...)]` meta attributes
2//!
3//! # Used meta checking
4//!
5//! Most of this file is concerned with generating
6//! accurate and useful messages
7//! when a driver is decorated with `#[deftly(...)]` attributes
8//! which are not used by any template.
9//!
10//! We distinguish "used" metas from "recognised" ones.
11//!
12//! "Used" ones are those actually tested, and used,
13//! during the dynamic expansion of the template.
14//! They are recorded in the [`PreprocessedMetas`],
15//! which contains a `Cell` for each supplied node.
16//!
17//! "Recognised" ones are those which appear anywhere in the template.
18//! These are represented in a data structure [``Recognised`].
19//! This is calculated by scanning the template,
20//! using the `FindRecogMetas` trait.
21//!
22//! Both of these sets are threaded through
23//! the ACCUM data in successive template expansions;
24//! in the final call (`EngineFinalInput`),
25//! they are combined together,
26//! and the driver's metas are checked against them.
27
28use super::framework::*;
29
30use indexmap::IndexMap;
31
32use Usage as U;
33
34//---------- common definitions ----------
35
36/// Indicates one of `fmeta`, `vmeta` or `tmeta`
37#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
38#[derive(AsRefStr, EnumString, EnumIter)]
39#[rustfmt::skip]
40pub enum Scope {
41    // NB these keywords are duplicated in SubstDetails
42    #[strum(serialize = "tmeta")] T,
43    #[strum(serialize = "vmeta")] V,
44    #[strum(serialize = "fmeta")] F,
45}
46
47/// Scope of a *supplied* meta (`#[deftly(...)]`) attribute
48///
49/// Also encodes, for metas at the toplevel,
50/// whether it's a struct or an enum.
51#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] //
52#[derive(strum::Display, EnumIter)]
53#[strum(serialize_all = "snake_case")]
54pub enum SuppliedScope {
55    Struct,
56    Enum,
57    Variant,
58    Field,
59}
60
61impl SuppliedScope {
62    fn recog_search(self) -> impl Iterator<Item = Scope> {
63        use Scope as S;
64        use SuppliedScope as SS;
65        match self {
66            SS::Struct => &[S::T, S::V] as &[_],
67            SS::Enum => &[S::T],
68            SS::Variant => &[S::V],
69            SS::Field => &[S::F],
70        }
71        .iter()
72        .copied()
73    }
74}
75
76/// `(foo(bar))` in eg `fmeta(foo(bar))`
77///
78/// includes the parens
79#[derive(Debug, Clone, Eq, PartialEq, Hash)]
80pub struct Label {
81    // Nonempty list, each with nonempty segments.
82    // Outermost first.
83    pub lpaths: Vec<syn::Path>,
84}
85
86/// Meta designator eg `fmeta(foo(bar))`
87// Field order must be the same as BorrowedDesig
88#[derive(Debug, Clone, Eq, PartialEq, Hash)]
89pub struct Desig {
90    pub scope: Scope,
91    pub label: Label,
92}
93
94#[derive(Hash)]
95// Field order must be the same as meta::Desig
96struct BorrowedDesig<'p> {
97    pub scope: Scope,
98    pub lpaths: &'p [&'p syn::Path],
99}
100
101//---------- substitutions in a template ----------
102
103#[derive(Debug)]
104pub struct SubstMeta<O: SubstParseContext> {
105    pub desig: Desig,
106    pub as_: Option<SubstAs<O>>,
107    pub default: Option<(Argument<O>, O::NotInBool, beta::Enabled)>,
108}
109
110#[derive(Debug, Clone, AsRefStr, Display)]
111#[allow(non_camel_case_types)] // clearer to use the exact ident
112pub enum SubstAs<O: SubstParseContext> {
113    expr(O::NotInBool, AllowTokens<O>, SubstAsSupported<ValueExpr>),
114    ident(O::NotInBool),
115    items(O::NotInBool, AllowTokens<O>, SubstAsSupported<ValueItems>),
116    path(O::NotInBool),
117    str(O::NotInBool),
118    token_stream(O::NotInBool, AllowTokens<O>),
119    ty(O::NotInBool),
120}
121
122//---------- meta attrs in a driver ----------
123
124/// A part like `(foo,bar(baz),zonk="value")`
125#[derive(Debug)]
126pub struct PreprocessedValueList {
127    pub content: Punctuated<PreprocessedTree, token::Comma>,
128}
129
130/// `#[deftly(...)]` helper attributes
131pub type PreprocessedMetas = Vec<PreprocessedValueList>;
132
133/// An `#[deftly()]` attribute, or a sub-tree within one
134///
135/// Has interior mutability, for tracking whether the value is used.
136/// (So should ideally not be Clone, to help avoid aliasing bugs.)
137#[derive(Debug)]
138pub struct PreprocessedTree {
139    pub path: syn::Path,
140    pub value: PreprocessedValue,
141    pub used: Cell<Usage>,
142}
143
144/// Content of a meta attribute
145///
146/// Examples in doc comments are for
147/// `PreprocessedMeta.path` of `foo`,
148/// ie the examples are for `#[deftly(foo ..)]`.
149#[derive(Debug)]
150pub enum PreprocessedValue {
151    /// `#[deftly(foo)]`
152    Unit,
153    /// `#[deftly(foo = "lit")]`
154    Value { value: syn::Lit },
155    /// `#[deftly(foo(...))]`
156    List(PreprocessedValueList),
157}
158
159//---------- search and match results ----------
160
161/// Node in tree structure found in driver `#[deftly(some(thing))]`
162#[derive(Debug)]
163pub struct FoundNode<'l> {
164    kind: FoundNodeKind<'l>,
165    path_span: Span,
166    ptree: &'l PreprocessedTree,
167}
168
169/// Node in tree structure found in driver `#[deftly(some(thing))]`
170#[derive(Debug)]
171pub enum FoundNodeKind<'l> {
172    Unit,
173    Lit(&'l syn::Lit),
174}
175
176/// Information about a nearby meta node we found
177///
178/// "Nearby" means that the node we found is a prefix (in tree descent)
179/// of the one we were looking for, or vice versa.
180#[derive(Debug)]
181pub struct FoundNearbyNode<'l> {
182    pub kind: FoundNearbyNodeKind,
183    /// Span of the identifier in the actual `#[deftly]` driver attribute
184    pub path_span: Span,
185    pub ptree: &'l PreprocessedTree,
186}
187
188/// How the nearby node relates to the one we were looking for
189#[derive(Debug)]
190pub enum FoundNearbyNodeKind {
191    /// We were looking to go deeper, but found a unit in `#[deftly]`
192    Unit,
193    /// We were looking to go deeper, but found a `name = value` in `#[deftly]`
194    Lit,
195    /// We were looking for a leaf, but we found nested list in `#[deftly]`
196    List,
197}
198
199pub use FoundNearbyNodeKind as FNNK;
200pub use FoundNodeKind as FNK;
201
202//---------- meta attr enumeration and checking ----------
203
204#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
205pub struct UsageInfo<M> {
206    unit: Option<M>,
207    value: Option<M>,
208}
209
210pub type Usage = UsageInfo<IsUsed>;
211
212impl Usage {
213    pub const BOOL_ONLY: Usage = UsageInfo {
214        unit: Some(IsUsed),
215        value: None,
216    };
217    pub const VALUE_ONLY: Usage = UsageInfo {
218        unit: None,
219        value: Some(IsUsed),
220    };
221    pub const VALUE: Usage = UsageInfo {
222        unit: Some(IsUsed),
223        value: Some(IsUsed),
224    };
225    pub const NONE: Usage = UsageInfo {
226        unit: None,
227        value: None,
228    };
229}
230
231#[derive(Debug, Clone, Copy, Eq, PartialEq, strum::EnumIter)]
232pub enum UsageMode {
233    Unit,
234    Value,
235}
236
237impl<M> UsageInfo<M> {
238    pub fn get(&self, mode: UsageMode) -> Option<&M> {
239        match mode {
240            UsageMode::Unit => self.unit.as_ref(),
241            UsageMode::Value => self.value.as_ref(),
242        }
243    }
244    pub fn get_mut(&mut self, mode: UsageMode) -> &mut Option<M> {
245        match mode {
246            UsageMode::Unit => &mut self.unit,
247            UsageMode::Value => &mut self.value,
248        }
249    }
250}
251
252impl std::ops::BitOr for Usage {
253    type Output = Usage;
254    fn bitor(self, other: Usage) -> Usage {
255        let mut out = self;
256        for mode in UsageMode::iter() {
257            let ent = out.get_mut(mode);
258            *ent = cmp::max(*ent, other.get(mode).copied());
259        }
260        out
261    }
262}
263
264#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
265pub struct IsUsed;
266
267/// One lot of used metas in accumulation - argument to a `_meta_used` accum
268#[derive(Debug)]
269pub struct UsedGroup {
270    pub content: TokenStream,
271}
272
273/// Something representing possibly checking that meta attributes are used
274#[derive(Debug, Clone)]
275pub enum CheckUsed<T> {
276    /// Yes, check them, by/with/from/to `T`
277    Check(T),
278    /// No, don't check them.
279    Unchecked,
280}
281
282/// Information for meta checking, found in accumulation
283#[derive(Debug, Default)]
284pub struct Accum {
285    pub recog: Recognised,
286    pub used: Vec<UsedGroup>,
287}
288
289#[derive(Default, Debug, Clone)]
290pub struct Recognised {
291    map: IndexMap<Desig, Usage>,
292}
293
294pub trait FindRecogMetas {
295    /// Search for `fmeta(..)` etc. expansions
296    ///
297    /// Add to `acc` any that are
298    /// (recusively) within `self`, syntactically,
299    fn find_recog_metas(&self, acc: &mut Recognised);
300}
301
302//==================== implementations) ====================
303
304//---------- template parsing ----------
305
306impl<O: SubstParseContext> SubstMeta<O> {
307    fn span_whole(&self, scope_span: Span) -> Span {
308        spans_join(chain!(
309            [scope_span], //
310            self.desig.label.spans(),
311        ))
312        .unwrap()
313    }
314}
315
316impl Label {
317    /// Nonempty
318    pub fn spans(&self) -> impl Iterator<Item = Span> + '_ {
319        self.lpaths.iter().map(|path| path.span())
320    }
321}
322
323impl<O: SubstParseContext> SubstAs<O> {
324    fn parse(input: ParseStream, nb: O::NotInBool) -> syn::Result<Self> {
325        let kw: IdentAny = input.parse()?;
326        let from_sma = |sma: SubstAs<_>| Ok(sma);
327
328        // See keyword_general! in utils.rs
329        macro_rules! keyword { { $($args:tt)* } => {
330            keyword_general! { kw from_sma SubstAs; $($args)* }
331        } }
332
333        let allow_tokens = || O::allow_tokens(&kw);
334        fn supported<P>(kw: &IdentAny) -> syn::Result<SubstAsSupported<P>>
335        where
336            P: SubstAsSupportStatus,
337        {
338            SubstAsSupportStatus::new(&kw)
339        }
340
341        keyword! { expr(nb, allow_tokens()?, supported(&kw)?) }
342        keyword! { ident(nb) }
343        keyword! { items(nb, allow_tokens()?, supported(&kw)?) }
344        keyword! { path(nb) }
345        keyword! { str(nb) }
346        keyword! { token_stream(nb, allow_tokens()?) }
347        keyword! { ty(nb) }
348
349        Err(kw.error("unknown derive-deftly 'as' syntax type keyword"))
350    }
351}
352
353impl<O: SubstParseContext> SubstMeta<O> {
354    pub fn parse(
355        input: ParseStream,
356        kw_span: Span,
357        scope: Scope,
358    ) -> syn::Result<Self> {
359        if input.is_empty() {
360            O::missing_keyword_arguments(kw_span)?;
361        }
362
363        let label: Label = input.parse()?;
364
365        fn store<V>(
366            kw: Span,
367            already: &mut Option<(Span, V)>,
368            call: impl FnOnce() -> syn::Result<V>,
369        ) -> syn::Result<()> {
370            if let Some((already, _)) = already {
371                return Err([(*already, "first"), (kw, "second")]
372                    .error("`${Xmeta ..}` option repeated"));
373            }
374            let v = call()?;
375            *already = Some((kw, v));
376            Ok(())
377        }
378
379        let mut as_ = None::<(Span, SubstAs<O>)>;
380        let mut default = None;
381
382        while !O::IS_BOOL && !input.is_empty() {
383            let keyword = Ident::parse_any(input)?;
384            let kw_span = keyword.span();
385            let nb = O::not_in_bool(&kw_span).expect("checked already");
386            let ue = || beta::Enabled::new_for_syntax(kw_span);
387            if keyword == "as" {
388                store(kw_span, &mut as_, || SubstAs::parse(input, nb))?;
389            } else if keyword == "default" {
390                store(kw_span, &mut default, || {
391                    Ok((input.parse()?, nb, ue()?))
392                })?;
393            } else {
394                return Err(keyword.error("unknown option in `${Xmeta }`"));
395            }
396            if input.is_empty() {
397                break;
398            }
399            let _: Token![,] = input.parse()?;
400        }
401
402        macro_rules! ret { { $( $f:ident )* } => {
403            SubstMeta {
404                desig: Desig { label, scope },
405                $( $f: $f.map(|(_span, v)| v), )*
406            }
407        } }
408
409        Ok(ret! {
410            as_
411            default
412        })
413    }
414}
415
416//---------- driver parsing ----------
417
418impl PreprocessedValueList {
419    fn parse_outer(input: ParseStream) -> syn::Result<Self> {
420        let meta;
421        let _paren = parenthesized!(meta in input);
422        Self::parse_inner(&meta)
423    }
424}
425
426impl PreprocessedValueList {
427    pub fn parse_inner(input: ParseStream) -> syn::Result<Self> {
428        let content = Punctuated::parse_terminated(input)?;
429        Ok(PreprocessedValueList { content })
430    }
431}
432
433impl Parse for PreprocessedTree {
434    fn parse(input: ParseStream) -> syn::Result<Self> {
435        use PreprocessedValue as PV;
436
437        let path = input.call(syn::Path::parse_mod_style)?;
438        let la = input.lookahead1();
439        let value = if la.peek(Token![=]) {
440            let _: Token![=] = input.parse()?;
441            let value = input.parse()?;
442            PV::Value { value }
443        } else if la.peek(token::Paren) {
444            let list = input.call(PreprocessedValueList::parse_outer)?;
445            PV::List(list)
446        } else if la.peek(Token![,]) || input.is_empty() {
447            PV::Unit
448        } else {
449            return Err(la.error());
450        };
451        let used = Usage::default().into(); // will be filled in later
452        Ok(PreprocessedTree { path, value, used })
453    }
454}
455
456impl Parse for Label {
457    fn parse(outer: ParseStream) -> syn::Result<Self> {
458        fn recurse(
459            lpaths: &mut Vec<syn::Path>,
460            outer: ParseStream,
461        ) -> syn::Result<()> {
462            let input;
463            let paren = parenthesized!(input in outer);
464            let path = input.call(syn::Path::parse_mod_style)?;
465            if path.segments.is_empty() {
466                return Err(paren
467                    .span
468                    .error("`deftly` attribute must have nonempty path"));
469            }
470
471            lpaths.push(path);
472            if !input.is_empty() {
473                recurse(lpaths, &input)?;
474            }
475            Ok(())
476        }
477
478        let mut lpaths = vec![];
479        recurse(&mut lpaths, outer)?;
480
481        Ok(Label { lpaths })
482    }
483}
484
485//---------- searching and matching ----------
486
487impl Label {
488    /// Caller must note meta attrs that end up being used!
489    pub fn search<'a, F, G, E>(
490        &self,
491        pmetas: &'a [PreprocessedValueList],
492        f: &mut F,
493        g: &mut G,
494    ) -> Result<(), E>
495    where
496        F: FnMut(FoundNode<'a>) -> Result<(), E>,
497        G: FnMut(FoundNearbyNode<'a>) -> Result<(), E>,
498    {
499        for m in pmetas {
500            for l in &m.content {
501                Self::search_1(&self.lpaths, l, &mut *f, &mut *g)?;
502            }
503        }
504        Ok(())
505    }
506
507    fn search_1<'a, E, F, G>(
508        // Nonempty
509        lpaths: &[syn::Path],
510        ptree: &'a PreprocessedTree,
511        f: &mut F,
512        g: &mut G,
513    ) -> Result<(), E>
514    where
515        F: FnMut(FoundNode<'a>) -> Result<(), E>,
516        G: FnMut(FoundNearbyNode<'a>) -> Result<(), E>,
517    {
518        use PreprocessedValue as PV;
519
520        if ptree.path != lpaths[0] {
521            return Ok(());
522        }
523
524        let path_span = ptree.path.span();
525
526        let mut nearby = |kind| {
527            g(FoundNearbyNode {
528                kind,
529                path_span,
530                ptree,
531            })
532        };
533
534        let deeper = if lpaths.len() <= 1 {
535            None
536        } else {
537            Some(&lpaths[1..])
538        };
539
540        match (deeper, &ptree.value) {
541            (None, PV::Unit) => f(FoundNode {
542                path_span,
543                kind: FNK::Unit,
544                ptree,
545            })?,
546            (None, PV::List(_)) => nearby(FNNK::List)?,
547            (None, PV::Value { value, .. }) => f(FoundNode {
548                path_span,
549                kind: FNK::Lit(value),
550                ptree,
551            })?,
552            (Some(_), PV::Value { .. }) => nearby(FNNK::Lit)?,
553            (Some(_), PV::Unit) => nearby(FNNK::Unit)?,
554            (Some(d), PV::List(l)) => {
555                for m in l.content.iter() {
556                    Self::search_1(d, m, &mut *f, &mut *g)?;
557                }
558            }
559        }
560
561        Ok(())
562    }
563}
564
565impl Label {
566    pub fn search_eval_bool(
567        &self,
568        pmetas: &PreprocessedMetas,
569    ) -> Result<(), Found> {
570        let found = |ptree: &PreprocessedTree| {
571            ptree.update_used(Usage::BOOL_ONLY);
572            Err(Found)
573        };
574        self.search(
575            pmetas,
576            &mut |av| /* got it! */ found(av.ptree),
577            &mut |nearby| match nearby.kind {
578                FNNK::List => found(nearby.ptree),
579                FNNK::Unit => Ok(()),
580                FNNK::Lit => Ok(()),
581            },
582        )
583    }
584}
585
586//---------- scope and designator handling ----------
587
588impl<O> SubstMeta<O>
589where
590    O: SubstParseContext,
591{
592    pub fn repeat_over(&self) -> Option<RepeatOver> {
593        match self.desig.scope {
594            Scope::T => None,
595            Scope::V => Some(RO::Variants),
596            Scope::F => Some(RO::Fields),
597        }
598    }
599}
600
601impl<O> SubstMeta<O>
602where
603    O: SubstParseContext,
604{
605    pub fn pmetas<'c>(
606        &self,
607        ctx: &'c Context<'c>,
608        kw_span: Span,
609    ) -> syn::Result<&'c PreprocessedMetas> {
610        Ok(match self.desig.scope {
611            Scope::T => &ctx.pmetas,
612            Scope::V => &ctx.variant(&kw_span)?.pmetas,
613            Scope::F => &ctx.field(&kw_span)?.pfield.pmetas,
614        })
615    }
616}
617
618impl ToTokens for Label {
619    fn to_tokens(&self, out: &mut TokenStream) {
620        let mut lpaths = self.lpaths.iter().rev();
621        let mut current =
622            lpaths.next().expect("empty path!").to_token_stream();
623        let r = loop {
624            let group = group_new_with_span(
625                Delimiter::Parenthesis,
626                current.span(),
627                current,
628            );
629            let wrap = if let Some(y) = lpaths.next() {
630                y
631            } else {
632                break group;
633            };
634            current = quote! { #wrap #group };
635        };
636        r.to_tokens(out);
637    }
638}
639
640impl Desig {
641    fn to_tokens(&self, scope_span: Span, out: &mut TokenStream) {
642        let scope: &str = self.scope.as_ref();
643        Ident::new(scope, scope_span).to_tokens(out);
644        self.label.to_tokens(out);
645    }
646}
647
648impl Parse for Desig {
649    fn parse(input: ParseStream) -> syn::Result<Self> {
650        let scope: syn::Ident = input.parse()?;
651        let scope = scope
652            .to_string()
653            .parse()
654            .map_err(|_| scope.error("invalid meta keyword/level"))?;
655        let label = input.parse()?;
656        Ok(Self { scope, label })
657    }
658}
659
660impl indexmap::Equivalent<Desig> for BorrowedDesig<'_> {
661    fn equivalent(&self, desig: &Desig) -> bool {
662        let BorrowedDesig { scope, lpaths } = self;
663        *scope == desig.scope
664            && itertools::equal(lpaths.iter().copied(), &desig.label.lpaths)
665    }
666}
667
668/// `Display`s as a `#[deftly(...)]` as the user might write it
669struct DisplayAsIfSpecified<'r> {
670    lpaths: &'r [&'r syn::Path],
671    /// Included after the innermost lpath, inside the parens
672    inside_after: &'r str,
673}
674impl Display for DisplayAsIfSpecified<'_> {
675    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
676        write!(f, "#[deftly")?;
677        for p in self.lpaths {
678            write!(f, "({}", p.to_token_stream())?;
679        }
680        write!(f, "{}", self.inside_after)?;
681        for _ in self.lpaths {
682            write!(f, ")")?;
683        }
684        Ok(())
685    }
686}
687
688// Tests that our `BorrowedDesig` `equivalent` impl is justified.
689#[test]
690fn check_borrowed_desig() {
691    use super::*;
692    use indexmap::Equivalent;
693    use itertools::iproduct;
694    use std::hash::{Hash, Hasher};
695
696    #[derive(PartialEq, Eq, Debug, Default)]
697    struct TrackingHasher(Vec<Vec<u8>>);
698    impl Hasher for TrackingHasher {
699        fn write(&mut self, bytes: &[u8]) {
700            self.0.push(bytes.to_owned());
701        }
702        fn finish(&self) -> u64 {
703            unreachable!()
704        }
705    }
706    impl TrackingHasher {
707        fn hash(t: impl Hash) -> Self {
708            let mut self_ = Self::default();
709            t.hash(&mut self_);
710            self_
711        }
712    }
713
714    type Case = (Scope, &'static [&'static str]);
715    const TEST_CASES: &[Case] = &[
716        (Scope::T, &["path"]),
717        (Scope::T, &["r#path"]),
718        (Scope::V, &["path", "some::path"]),
719        (Scope::V, &["r#struct", "with_generics::<()>"]),
720        (Scope::F, &[]), // illegal Desig, but test anyway
721    ];
722
723    struct Desigs<'b> {
724        owned: Desig,
725        borrowed: BorrowedDesig<'b>,
726    }
727    impl Desigs<'_> {
728        fn with((scope, lpaths): &Case, f: impl FnOnce(Desigs<'_>)) {
729            let scope = *scope;
730            let lpaths = lpaths
731                .iter()
732                .map(|l| syn::parse_str(l).expect(l))
733                .collect_vec();
734            let owned = {
735                let label = Label {
736                    lpaths: lpaths.clone(),
737                };
738                Desig { scope, label }
739            };
740            let lpaths_borrowed;
741            let borrowed = {
742                lpaths_borrowed = lpaths.iter().collect_vec();
743                BorrowedDesig {
744                    scope,
745                    lpaths: &*lpaths_borrowed,
746                }
747            };
748            f(Desigs { owned, borrowed })
749        }
750    }
751
752    // Test that for each entry in TEST_CASES, when parsed into Paths, etc.,
753    // BorrowedDesig is `equivalent` to, and hashes the same as, Desig.
754
755    for case in TEST_CASES {
756        Desigs::with(case, |d| {
757            assert!(d.borrowed.equivalent(&d.owned));
758            assert_eq!(
759                TrackingHasher::hash(&d.owned),
760                TrackingHasher::hash(&d.borrowed),
761            );
762        });
763    }
764
765    // Compare every TEST_CASES entry with every other entry.
766    // See if the owned forms are equal (according to `PartialEq`).
767    // Insist that the Borrowed vs owned `equivalent` relation agrees,
768    // in both directions.
769    // And, if they are equal, insist that the hashes all agree.
770
771    for (case0, case1) in iproduct!(TEST_CASES, TEST_CASES) {
772        Desigs::with(case0, |d0| {
773            Desigs::with(case1, |d1| {
774                let equal = d0.owned == d1.owned;
775                assert_eq!(equal, d0.borrowed.equivalent(&d1.owned));
776                assert_eq!(equal, d1.borrowed.equivalent(&d0.owned));
777                if equal {
778                    let hash = TrackingHasher::hash(&d0.owned);
779                    assert_eq!(hash, TrackingHasher::hash(&d1.owned));
780                    assert_eq!(hash, TrackingHasher::hash(&d0.borrowed));
781                    assert_eq!(hash, TrackingHasher::hash(&d1.borrowed));
782                }
783            });
784        });
785    }
786}
787
788//---------- conditional support for `Xmeta as items` ----------
789
790#[cfg(feature = "meta-as-expr")]
791pub type ValueExpr = syn::Expr;
792#[cfg(not(feature = "meta-as-expr"))]
793pub type ValueExpr = MetaUnsupported;
794
795#[cfg(feature = "meta-as-items")]
796pub type ValueItems = Concatenated<syn::Item>;
797#[cfg(not(feature = "meta-as-items"))]
798pub type ValueItems = MetaUnsupported;
799
800/// newtype to avoid coherence - it doesn't impl `Parse + ToTokens`
801#[derive(Debug, Copy, Clone)]
802#[allow(dead_code)] // rust-lang/rust/issues/145936
803pub struct MetaUnsupported(Void);
804
805#[derive(Debug, Copy, Clone)]
806pub struct SubstAsSupported<P: SubstAsSupportStatus>(P::Marker);
807
808/// Implemented for syn types supported in this build, and `MetaUnsupported`
809pub trait SubstAsSupportStatus: Sized {
810    type Marker;
811    type Parsed: Parse + ToTokens;
812    fn new(kw: &IdentAny) -> syn::Result<SubstAsSupported<Self>>;
813}
814
815impl<P: SubstAsSupportStatus> SubstAsSupported<P> {
816    fn infer_type(&self, _parsed: &P::Parsed) {}
817}
818
819impl<T: Parse + ToTokens> SubstAsSupportStatus for T {
820    type Marker = ();
821    type Parsed = T;
822    fn new(_kw: &IdentAny) -> syn::Result<SubstAsSupported<Self>> {
823        Ok(SubstAsSupported(()))
824    }
825}
826
827impl SubstAsSupportStatus for MetaUnsupported {
828    type Marker = MetaUnsupported;
829    type Parsed = TokenStream;
830    fn new(kw: &IdentAny) -> syn::Result<SubstAsSupported<Self>> {
831        Err(kw.error(format_args!(
832            // We're a bit fast and loose here: if kw contained `_`,
833            // or there were aliases, this message would be a bit wrong.
834            "${{Xmeta as {}}} used but cargo feature meta-as-{} disabled",
835            **kw, **kw,
836        )))
837    }
838}
839
840impl ToTokens for MetaUnsupported {
841    fn to_tokens(&self, _out: &mut TokenStream) {
842        void::unreachable(self.0)
843    }
844}
845
846//---------- template expansion ----------
847
848impl<O> SubstMeta<O>
849where
850    O: ExpansionOutput,
851    TemplateElement<O>: Expand<O>,
852{
853    pub fn expand(
854        &self,
855        ctx: &Context,
856        kw_span: Span,
857        out: &mut O,
858        pmetas: &PreprocessedMetas,
859    ) -> syn::Result<()> {
860        let SubstMeta {
861            desig,
862            as_,
863            default,
864        } = self;
865        let mut found = None::<FoundNode>;
866        let mut hint = None::<FoundNearbyNode>;
867        let span_whole = self.span_whole(kw_span);
868        let self_loc = || (span_whole, "expansion");
869        let error_loc = || [ctx.error_loc(), self_loc()];
870
871        desig.label.search(
872            pmetas,
873            &mut |av: FoundNode| {
874                if let Some(first) = &found {
875                    return Err([(first.path_span, "first occurrence"),
876                            (av.path_span, "second occurrence"),
877                            self_loc()].error(
878 "tried to expand just attribute value, but it was specified multiple times"
879                    ));
880                }
881                found = Some(av);
882                Ok(())
883            },
884            &mut |nearby| {
885                hint.get_or_insert(nearby);
886                Ok(())
887            },
888        )?;
889
890        if let Some((def, ..)) = default {
891            if match found {
892                None => true,
893                Some(FoundNode {
894                    kind: FNK::Unit,
895                    ptree,
896                    ..
897                }) => {
898                    // Specified as unit, but we have a default.
899                    // So ignore the unit for these purposes.
900                    ptree.update_used(Usage::VALUE_ONLY);
901                    true
902                }
903                _ => false,
904            } {
905                return Ok(def.expand(ctx.as_general(), out));
906            }
907        }
908
909        let found = found.ok_or_else(|| {
910            if let Some(hint) = hint {
911                let hint_msg = match hint.kind {
912                    FNNK::Unit =>
913 "expected a list with sub-attributes, found a unit",
914                    FNNK::Lit =>
915 "expected a list with sub-attributes, found a simple value",
916                    FNNK::List =>
917 "expected a leaf node, found a list with sub-attributes",
918                };
919                let mut err = hint.path_span.error(hint_msg);
920                err.combine(error_loc().error(
921 "attribute value expanded, but no suitable value in data structure definition"
922                ));
923                err
924            } else {
925                error_loc().error(
926 "attribute value expanded, but no value in data structure definition"
927                )
928            }
929        })?;
930
931        found.ptree.update_used(Usage::VALUE);
932        found.expand(span_whole, as_, out)?;
933
934        Ok(())
935    }
936}
937
938fn metavalue_spans(tspan: Span, vspan: Span) -> [ErrorLoc<'static>; 2] {
939    [(vspan, "attribute value"), (tspan, "template")]
940}
941
942/// Obtain the `LiStr` from a meta node value (ie, a `Lit`)
943///
944/// This is the thing we actually use.
945/// Non-string-literal values are not allowed.
946fn metavalue_litstr<'l>(
947    lit: &'l syn::Lit,
948    tspan: Span,
949    msg: fmt::Arguments<'_>,
950) -> syn::Result<&'l syn::LitStr> {
951    match lit {
952        syn::Lit::Str(s) => Ok(s),
953        // having checked derive_builder, it doesn't handle
954        // Lit::Verbatim so I guess we don't need to either.
955        _ => Err(metavalue_spans(tspan, lit.span()).error(msg)),
956    }
957}
958
959/// Convert a literal found in a meta item into `T`
960///
961/// `into_what` is used only for error reporting
962pub fn metavalue_lit_as<T>(
963    lit: &syn::Lit,
964    tspan: Span,
965    into_what: &dyn Display,
966) -> syn::Result<T>
967where
968    T: Parse + ToTokens,
969{
970    let s = metavalue_litstr(
971        lit,
972        tspan,
973        format_args!(
974            "expected string literal, for conversion to {}",
975            into_what,
976        ),
977    )?;
978
979    let t: TokenStream = s.parse().map_err(|e| {
980        // Empirically, parsing a LitStr in actual proc macro context, with
981        // proc_macro2, into tokens, can generate a lexical error with a
982        // "fallback" Span.  Then, attempting to render the results,
983        // including the eventual compiler_error! invocation, back to
984        // a compiler proc_ma cor::TokenStream can panic with
985        // "compiler/fallback mismatch".
986        //
987        // https://github.com/dtolnay/syn/issues/1504
988        //
989        // Attempt to detect this situation.
990        match (|| {
991            let _: String = (&e).into_iter().next()?.span().source_text()?;
992            Some(())
993        })() {
994            Some(()) => e,
995            None => lit.span().error(e.to_string()),
996        }
997    })?;
998
999    let thing: T = syn::parse2(t)?;
1000    Ok(thing)
1001}
1002
1003impl<'l> FoundNode<'l> {
1004    fn expand<O>(
1005        &self,
1006        tspan: Span,
1007        as_: &Option<SubstAs<O>>,
1008        out: &mut O,
1009    ) -> syn::Result<()>
1010    where
1011        O: ExpansionOutput,
1012    {
1013        let spans = |vspan| metavalue_spans(tspan, vspan);
1014
1015        let lit = match self.kind {
1016            FNK::Unit => return Err(spans(self.path_span).error(
1017 "tried to expand attribute which is just a unit, not a literal"
1018            )),
1019            FNK::Lit(lit) => lit,
1020        };
1021
1022        use SubstAs as SA;
1023
1024        let default_buf;
1025        let as_ = match as_ {
1026            Some(as_) => as_,
1027            None => {
1028                default_buf = O::default_subst_meta_as(tspan)?;
1029                &default_buf
1030            }
1031        };
1032
1033        match as_ {
1034            as_ @ SA::expr(.., at, supported) => {
1035                let expr = metavalue_lit_as(lit, tspan, as_)?;
1036                supported.infer_type(&expr);
1037                let span = expr.span();
1038                out.append_tokens(at, Grouping::Parens.surround(span, expr))?;
1039            }
1040            as_ @ SA::ident(..) => {
1041                let ident: IdentAny = metavalue_lit_as(lit, tspan, as_)?;
1042                out.append_identfrag_toks(&*ident)?;
1043            }
1044            SA::items(_, np, supported) => {
1045                let items = metavalue_lit_as(lit, tspan, &"items")?;
1046                supported.infer_type(&items);
1047                out.append_tokens(np, items)?;
1048            }
1049            as_ @ SA::path(..) => out.append_syn_type(
1050                tspan,
1051                syn::Type::Path(metavalue_lit_as(lit, tspan, as_)?),
1052                Grouping::Invisible,
1053            ),
1054            SA::str(..) => {
1055                let s = metavalue_litstr(
1056                    lit,
1057                    tspan,
1058                    format_args!("expected string literal, for meta value",),
1059                )?;
1060                out.append_syn_litstr(s);
1061            }
1062            as_ @ SA::ty(..) => out.append_syn_type(
1063                tspan,
1064                metavalue_lit_as(lit, tspan, as_)?,
1065                Grouping::Invisible,
1066            ),
1067            SA::token_stream(_, np) => {
1068                let tokens: TokenStream =
1069                    metavalue_lit_as(lit, tspan, &"tokens")?;
1070                out.append_tokens(np, tokens)?;
1071            }
1072        }
1073        Ok(())
1074    }
1075}
1076
1077//==================== implementations - usage checking ====================
1078
1079impl Parse for CheckUsed<UsedGroup> {
1080    fn parse(input: ParseStream) -> syn::Result<Self> {
1081        let la = input.lookahead1();
1082        Ok(if la.peek(Token![*]) {
1083            let _star: Token![*] = input.parse()?;
1084            mCU::Unchecked
1085        } else if la.peek(token::Bracket) {
1086            let group: proc_macro2::Group = input.parse()?;
1087            let content = group.stream();
1088            mCU::Check(UsedGroup { content })
1089        } else {
1090            return Err(la.error());
1091        })
1092    }
1093}
1094
1095impl Recognised {
1096    /// Ensures that `self[k] >= v`
1097    pub fn update(&mut self, k: Desig, v: Usage) {
1098        let ent = self.map.entry(k).or_insert(v);
1099        *ent = *ent | v
1100    }
1101}
1102
1103impl ToTokens for Recognised {
1104    fn to_tokens(&self, out: &mut TokenStream) {
1105        for (desig, allow) in &self.map {
1106            match *allow {
1107                Usage::BOOL_ONLY => out.extend(quote! { ? }),
1108                Usage::VALUE_ONLY => out.extend(quote! { + }),
1109                Usage::VALUE => {}
1110                Usage::NONE => panic!("should be impossible!"),
1111            }
1112            desig.to_tokens(Span::call_site(), out);
1113        }
1114    }
1115}
1116
1117impl PreprocessedTree {
1118    pub fn update_used(&self, ra: Usage) {
1119        self.used.set(self.used.get() | ra);
1120    }
1121}
1122
1123//---------- decoding used metas ----------
1124
1125impl PreprocessedValueList {
1126    fn decode_update_used(&self, input: ParseStream) -> syn::Result<()> {
1127        use PreprocessedValue as PV;
1128
1129        for ptree in &self.content {
1130            if input.is_empty() {
1131                return Ok(());
1132            }
1133            if !input.peek(Token![,]) {
1134                let path = input.call(syn::Path::parse_mod_style)?;
1135                if path != ptree.path {
1136                    return Err([
1137                        (path.span(), "found"),
1138                        (ptree.path.span(), "expected"),
1139                    ].error(
1140 "mismatch (desynchronised) incorporating previous expansions' used metas"
1141                    ));
1142                }
1143                let used = if input.peek(Token![=]) {
1144                    let _: Token![=] = input.parse()?;
1145                    Some(Usage::VALUE)
1146                } else if input.peek(Token![?]) {
1147                    let _: Token![?] = input.parse()?;
1148                    Some(Usage::BOOL_ONLY)
1149                } else if input.peek(Token![+]) {
1150                    let _: Token![+] = input.parse()?;
1151                    Some(Usage::VALUE_ONLY)
1152                } else {
1153                    None
1154                };
1155                if let Some(used) = used {
1156                    ptree.update_used(used);
1157                }
1158                if input.peek(token::Paren) {
1159                    let inner;
1160                    let paren = parenthesized!(inner in input);
1161                    let sub_list = match &ptree.value {
1162                        PV::Unit | PV::Value { .. } => return Err([
1163                            (paren.span.open(), "found"),
1164                            (ptree.path.span(), "defined"),
1165                        ].error(
1166 "mismatch (tree vs terminal) incorporating previous expansions' used metas"
1167                        )),
1168                        PV::List(l) => l,
1169                    };
1170                    sub_list.decode_update_used(&inner)?;
1171                }
1172            }
1173            if input.is_empty() {
1174                return Ok(());
1175            }
1176            let _: Token![,] = input.parse()?;
1177        }
1178        Ok(())
1179    }
1180}
1181
1182impl<'c> Context<'c> {
1183    pub fn decode_update_metas_used(
1184        &self,
1185        input: /* group content */ ParseStream,
1186    ) -> syn::Result<()> {
1187        #[derive(Default)]
1188        struct Intended {
1189            variant: Option<syn::Ident>,
1190            field: Option<Either<syn::Ident, u32>>,
1191            attr_i: usize,
1192        }
1193        let mut intended = Intended::default();
1194
1195        let mut visit =
1196            |pmetas: &PreprocessedMetas,
1197             current_variant: Option<&syn::Ident>,
1198             current_field: Option<Either<&syn::Ident, &u32>>| {
1199                loop {
1200                    let la = input.lookahead1();
1201                    if input.is_empty() {
1202                        // keep visiting until we exit all the loops
1203                        return Ok(());
1204                    } else if la.peek(Token![::]) {
1205                        let _: Token![::] = input.parse()?;
1206                        intended = Intended {
1207                            variant: Some(input.parse()?),
1208                            field: None,
1209                            attr_i: 0,
1210                        };
1211                    } else if la.peek(Token![.]) {
1212                        let _: Token![.] = input.parse()?;
1213                        intended.field = Some(match input.parse()? {
1214                            syn::Member::Named(n) => Either::Left(n),
1215                            syn::Member::Unnamed(i) => Either::Right(i.index),
1216                        });
1217                        intended.attr_i = 0;
1218                    } else if {
1219                        let intended_field_refish = intended
1220                            .field
1221                            .as_ref()
1222                            .map(|some: &Either<_, _>| some.as_ref());
1223
1224                        !(current_variant == intended.variant.as_ref()
1225                            && current_field == intended_field_refish)
1226                    } {
1227                        // visit subsequent things, hopefully one will match
1228                        return Ok(());
1229                    } else if la.peek(token::Paren) {
1230                        // we're in the right place and have found a #[deftly()]
1231                        let i = intended.attr_i;
1232                        intended.attr_i += 1;
1233                        let m = pmetas.get(i).ok_or_else(|| {
1234                            input.error("more used metas, out of range!")
1235                        })?;
1236                        let r;
1237                        let _ = parenthesized!(r in input);
1238                        m.decode_update_used(&r)?;
1239                    } else {
1240                        return Err(la.error());
1241                    }
1242                }
1243            };
1244
1245        visit(&self.pmetas, None, None)?;
1246
1247        WithinVariant::for_each(self, |ctx, wv| {
1248            let current_variant = wv.variant.map(|wv| &wv.ident);
1249
1250            if !wv.is_struct_toplevel_as_variant() {
1251                visit(&wv.pmetas, current_variant, None)?;
1252            }
1253
1254            WithinField::for_each(ctx, |_ctx, wf| {
1255                let current_field = if let Some(ref ident) = wf.field.ident {
1256                    Either::Left(ident)
1257                } else {
1258                    Either::Right(&wf.index)
1259                };
1260
1261                visit(&wf.pfield.pmetas, current_variant, Some(current_field))
1262            })
1263        })
1264
1265        // if we didn't consume all of the input, due to mismatches/
1266        // misordering, then syn will give an error for us
1267    }
1268}
1269
1270//---------- encoding used metas ---------
1271
1272impl PreprocessedTree {
1273    /// Returns `(....)`
1274    fn encode_useds(
1275        list: &PreprocessedValueList,
1276    ) -> Option<proc_macro2::Group> {
1277        let preamble = syn::parse::Nothing;
1278        let sep = Token![,](Span::call_site());
1279        let mut ts = TokenStream::new();
1280        let mut ot = TokenOutputTrimmer::new(&mut ts, &preamble, &sep);
1281        for t in &list.content {
1282            t.encode_used(&mut ot);
1283            ot.push_sep();
1284        }
1285        if ts.is_empty() {
1286            None
1287        } else {
1288            Some(proc_macro2::Group::new(Delimiter::Parenthesis, ts))
1289        }
1290    }
1291
1292    /// Writes `path?=(...)` (or, rather, the parts of it that are needed)
1293    fn encode_used(&self, out: &mut TokenOutputTrimmer) {
1294        use PreprocessedValue as PV;
1295
1296        struct OutputTrimmerWrapper<'or, 'o, 't, 'p> {
1297            // None if we have written the path already
1298            path: Option<&'p syn::Path>,
1299            out: &'or mut TokenOutputTrimmer<'t, 'o>,
1300        }
1301
1302        let mut out = OutputTrimmerWrapper {
1303            path: Some(&self.path),
1304            out,
1305        };
1306
1307        impl OutputTrimmerWrapper<'_, '_, '_, '_> {
1308            fn push_reified(&mut self, t: &dyn ToTokens) {
1309                if let Some(path) = self.path.take() {
1310                    self.out.push_reified(path);
1311                }
1312                self.out.push_reified(t);
1313            }
1314        }
1315
1316        let tspan = Span::call_site();
1317
1318        match self.used.get() {
1319            Usage::BOOL_ONLY => out.push_reified(&Token![?](tspan)),
1320            Usage::VALUE => out.push_reified(&Token![=](tspan)),
1321            Usage::VALUE_ONLY => out.push_reified(&Token![+](tspan)),
1322            Usage::NONE => {}
1323        }
1324
1325        match &self.value {
1326            PV::Unit | PV::Value { .. } => {}
1327            PV::List(l) => {
1328                if let Some(group) = PreprocessedTree::encode_useds(l) {
1329                    out.push_reified(&group);
1330                }
1331            }
1332        }
1333    }
1334}
1335
1336impl<'c> Context<'c> {
1337    /// Returns `[::Variant .field () ...]`
1338    pub fn encode_metas_used(&self) -> proc_macro2::Group {
1339        let parenthesize =
1340            |ts| proc_macro2::Group::new(Delimiter::Parenthesis, ts);
1341        let an_empty = parenthesize(TokenStream::new());
1342
1343        let mut ts = TokenStream::new();
1344
1345        struct Preamble<'p> {
1346            variant: Option<&'p syn::Variant>,
1347            field: Option<&'p WithinField<'p>>,
1348        }
1349        impl ToTokens for Preamble<'_> {
1350            fn to_tokens(&self, out: &mut TokenStream) {
1351                let span = Span::call_site();
1352                if let Some(v) = self.variant {
1353                    Token![::](span).to_tokens(out);
1354                    v.ident.to_tokens(out);
1355                }
1356                if let Some(f) = self.field {
1357                    Token![.](span).to_tokens(out);
1358                    f.fname(span).to_tokens(out);
1359                }
1360            }
1361        }
1362
1363        let mut last_variant: *const syn::Variant = ptr::null();
1364        let mut last_field: *const syn::Field = ptr::null();
1365
1366        fn ptr_of_ref<'i, InDi>(r: Option<&'i InDi>) -> *const InDi {
1367            r.map(|r| r as _).unwrap_or_else(ptr::null)
1368        }
1369
1370        let mut encode = |pmetas: &PreprocessedMetas,
1371                          wv: Option<&WithinVariant>,
1372                          wf: Option<&WithinField>| {
1373            let now_variant: *const syn::Variant =
1374                ptr_of_ref(wv.map(|wv| wv.variant).flatten());
1375            let now_field: *const syn::Field =
1376                ptr_of_ref(wf.map(|wf| wf.field));
1377
1378            let preamble = Preamble {
1379                variant: (!ptr::eq(last_variant, now_variant)).then(|| {
1380                    last_field = ptr::null();
1381                    let v = wv.expect("had WithinVariant, now not");
1382                    v.variant.expect("variant was syn::Variant, now not")
1383                }),
1384                field: (!ptr::eq(last_field, now_field)).then(|| {
1385                    wf.expect("had WithinField (Field), now not") //
1386                }),
1387            };
1388            let mut ot =
1389                TokenOutputTrimmer::new(&mut ts, &preamble, &an_empty);
1390            for m in pmetas {
1391                if let Some(group) = PreprocessedTree::encode_useds(m) {
1392                    ot.push_reified(group);
1393                } else {
1394                    ot.push_sep();
1395                }
1396            }
1397            if ot.did_preamble().is_some() {
1398                last_variant = now_variant;
1399                last_field = now_field;
1400            }
1401
1402            Ok::<_, Void>(())
1403        };
1404
1405        encode(&self.pmetas, None, None).void_unwrap();
1406        WithinVariant::for_each(self, |ctx, wv| {
1407            if !wv.is_struct_toplevel_as_variant() {
1408                encode(&wv.pmetas, Some(wv), None)?;
1409            }
1410            WithinField::for_each(ctx, |_ctx, wf| {
1411                encode(&wf.pfield.pmetas, Some(wv), Some(wf))
1412            })
1413        })
1414        .void_unwrap();
1415
1416        proc_macro2::Group::new(Delimiter::Bracket, ts)
1417    }
1418}
1419
1420//---------- checking used metas ----------
1421
1422struct UsedChecker<'c, 'e> {
1423    current: Vec<&'c syn::Path>,
1424    reported: &'e mut HashSet<Label>,
1425    recog: &'c Recognised,
1426    supplied_scope: SuppliedScope,
1427    errors: &'e mut ErrorAccumulator,
1428}
1429
1430impl PreprocessedTree {
1431    fn check_used<'c>(&'c self, checker: &mut UsedChecker<'c, '_>) {
1432        checker.current.push(&self.path);
1433
1434        let mut err = |err| {
1435            let lpaths = checker.current.iter().copied().cloned().collect();
1436            if checker.reported.insert(Label { lpaths }) {
1437                checker.errors.push(err);
1438            }
1439        };
1440
1441        let mut need = |supplied_mode| {
1442            let used = self.used.get();
1443            match used.get(supplied_mode) {
1444                Some(IsUsed) => {}
1445                None => err(unrecognised_error(
1446                    checker.recog,
1447                    checker.supplied_scope,
1448                    supplied_mode,
1449                    self.path.span(),
1450                    used,
1451                    &checker.current,
1452                )
1453                .void_unwrap_err()),
1454            }
1455        };
1456
1457        match &self.value {
1458            PreprocessedValue::Unit => need(UsageMode::Unit),
1459            PreprocessedValue::Value { .. } => need(UsageMode::Value),
1460            PreprocessedValue::List(l) => {
1461                if l.content.is_empty() {
1462                    err(self.path.error(
1463 "empty nested list in #[deftly], is not useable by any template"
1464                    ));
1465                }
1466                for subtree in &l.content {
1467                    subtree.check_used(checker);
1468                }
1469            }
1470        }
1471
1472        checker
1473            .current
1474            .pop()
1475            .expect("pushed earlier, but can't pop?");
1476    }
1477}
1478
1479impl<'c> Context<'c> {
1480    pub(crate) fn check_metas_used(
1481        &self,
1482        errors: &mut ErrorAccumulator,
1483        recog: &Recognised,
1484    ) {
1485        use SuppliedScope as SS;
1486
1487        let mut reported = HashSet::new();
1488
1489        let mut chk_pmetas = |supplied_scope, pmetas: &PreprocessedMetas| {
1490            let mut checker = UsedChecker {
1491                reported: &mut reported,
1492                current: vec![],
1493                recog,
1494                errors,
1495                supplied_scope,
1496            };
1497
1498            for a in pmetas {
1499                for l in &a.content {
1500                    l.check_used(&mut checker);
1501                }
1502            }
1503
1504            Ok::<_, Void>(())
1505        };
1506
1507        chk_pmetas(
1508            match &self.top.data {
1509                syn::Data::Struct(_) | syn::Data::Union(_) => SS::Struct,
1510                syn::Data::Enum(_) => SS::Enum,
1511            },
1512            &self.pmetas,
1513        )
1514        .void_unwrap();
1515
1516        WithinVariant::for_each(self, |ctx, wv| {
1517            // If variant is None, this is the imaginary variant for
1518            // the toplevel, and it has a copy of the ref to the metas,
1519            // in which case we don't want to process it again.
1520            if !wv.is_struct_toplevel_as_variant() {
1521                chk_pmetas(SS::Variant, &wv.pmetas)?;
1522            }
1523            WithinField::for_each(ctx, |_ctx, wf| {
1524                chk_pmetas(SS::Field, &wf.pfield.pmetas)
1525            })
1526        })
1527        .void_unwrap();
1528    }
1529}
1530
1531fn unrecognised_error(
1532    recog: &Recognised,
1533    supplied_scope: SuppliedScope,
1534    supplied_mode: UsageMode,
1535    supplied_span: Span,
1536    used: Usage,
1537    lpaths: &[&syn::Path],
1538) -> Result<Void, syn::Error> /* return type allows (ab)use of ? */ {
1539    // This could have been a method on UsedChecker, but that runs into
1540    // borrowck trouble.
1541
1542    let try_case = |e: Option<_>| e.map(Err).unwrap_or(Ok(()));
1543    let some_err = |m: &dyn Display| Some(supplied_span.error(m));
1544
1545    // Maybe this would have been recognised in other circumstances.
1546    // If so, report that.
1547    try_case((|| {
1548        let recog_allow = supplied_scope
1549            .recog_search()
1550            .map(|scope| {
1551                recog
1552                    .map
1553                    .get(&BorrowedDesig { scope, lpaths })
1554                    .copied()
1555                    .unwrap_or_default()
1556            })
1557            .reduce(std::ops::BitOr::bitor)
1558            .unwrap_or_default();
1559
1560        // Have we supplied more than would ever be recognised?
1561        let _: &IsUsed = recog_allow.get(supplied_mode)?;
1562
1563        match used {
1564            U::NONE => some_err(
1565 &"meta attribute provided, and (conditionally) recognised; but not used in these particular circumstances"
1566            ),
1567            U::BOOL_ONLY => some_err(
1568 &"meta attribute provided with value, and (conditionally) recognised with value; but in these particular circumstances only used as a boolean"
1569            ),
1570            U::VALUE_ONLY => some_err(
1571 &"meta attribute provided as a unit, and (conditionally) recognised as such; but in these particular circumstances only used in contexts with a default value, so the unit is ignored"
1572            ),
1573            U::VALUE => unreachable!(),
1574        }
1575    })())?;
1576
1577    // Now we know that even the static scan doesn't
1578    // recognise this item.
1579
1580    // Maybe it's just that a value was supplied by mistake
1581    try_case((|| {
1582        (supplied_mode == UsageMode::Value).then(|| ())?;
1583        let _: IsUsed = used.unit?;
1584        some_err(
1585            &"meta attribute value provided, but is used only as a boolean",
1586        )
1587    })())?;
1588
1589    // Maybe it's just that a *unit* was supplied by mistake
1590    try_case((|| {
1591        (supplied_mode == UsageMode::Unit).then(|| ())?;
1592        let _: IsUsed = used.value?;
1593        some_err(
1594 &"meta attribute provided as unit (flag), but is used only with default values, so the unit is ignored"
1595        )
1596    })())?;
1597
1598    // Look to see if it would have been recognised in
1599    // another scope.  That would mean the attr is
1600    // merely misplaced, rather than totally wrong.
1601    try_case((|| {
1602        let y_scopes = Scope::iter()
1603            .filter(|&scope| {
1604                recog.map.contains_key(&BorrowedDesig { scope, lpaths })
1605            })
1606            .collect_vec();
1607
1608        if y_scopes.is_empty() {
1609            return None;
1610        }
1611        let y_ss = SuppliedScope::iter()
1612            .filter(|ss| ss.recog_search().any(|s| y_scopes.contains(&s)))
1613            .map(|ss| ss.to_string())
1614            .join("/");
1615        let y_scopes = y_scopes.iter().map(|s| s.as_ref()).join("/");
1616        some_err(&format_args!(
1617 "meta attribute provided for {}, but recognised by template only for {} ({})",
1618            supplied_scope,
1619            y_ss,
1620            y_scopes,
1621        ))
1622    })())?;
1623
1624    // Look to see if user supplied `#[deftly(foo(bar))]`
1625    // when the template wanted `#[deftly(foo = "bar")]`.
1626    try_case((|| {
1627        let (upper, recog) =
1628            supplied_scope.recog_search().find_map(|scope| {
1629                let upper = BorrowedDesig {
1630                    scope,
1631                    lpaths: lpaths.split_last()?.1,
1632                };
1633                let recog = recog.map.get(&upper)?;
1634                Some((upper, recog))
1635            })?;
1636        let _: IsUsed = recog.value?;
1637        some_err(&format_args!(
1638 "nested meta provided and not recognised; but, template would recognise {}",
1639            DisplayAsIfSpecified {
1640                lpaths: upper.lpaths,
1641                inside_after: " = ..",
1642            },
1643        ))
1644    })())?;
1645
1646    // Look to see if the specified attribute is a prefix of
1647    // a recognised one
1648    try_case((|| {
1649        recog.map.keys().find_map(|desig| {
1650            // deliberately ignore scope
1651            if !itertools::equal(
1652                lpaths.iter().copied(),
1653                desig.label.lpaths.iter().take(lpaths.len()),
1654            ) {
1655                return None;
1656            }
1657            Some(())
1658        })?;
1659        some_err(&format_args!(
1660 "meta attribute provided, but not recognised; template only uses it as a container"
1661        ))
1662    })())?;
1663
1664    Err(supplied_span
1665        .error("meta attribute provided, but not recognised by template"))
1666}
1667
1668//---------- impls of FindRecogMetas ----------
1669
1670impl<O: SubstParseContext> FindRecogMetas for Template<O> {
1671    fn find_recog_metas(&self, acc: &mut Recognised) {
1672        for e in &self.elements {
1673            e.find_recog_metas(acc)
1674        }
1675    }
1676}
1677
1678impl<O: SubstParseContext> FindRecogMetas for TemplateElement<O> {
1679    fn find_recog_metas(&self, acc: &mut Recognised) {
1680        match self {
1681            TE::Ident(..)
1682            | TE::LitStr(_)
1683            | TE::Literal(..)
1684            | TE::Punct(..) => {}
1685            TE::Group { template, .. } => template.find_recog_metas(acc),
1686            TE::Subst(n) => n.find_recog_metas(acc),
1687            TE::Repeat(n) => n.find_recog_metas(acc),
1688        }
1689    }
1690}
1691
1692impl<O: SubstParseContext> FindRecogMetas for RepeatedTemplate<O> {
1693    fn find_recog_metas(&self, acc: &mut Recognised) {
1694        self.template.find_recog_metas(acc);
1695        self.whens.find_recog_metas(acc);
1696    }
1697}
1698
1699impl<O: SubstParseContext> FindRecogMetas for Subst<O> {
1700    fn find_recog_metas(&self, acc: &mut Recognised) {
1701        self.sd.find_recog_metas(acc);
1702    }
1703}
1704
1705macro_rules! impL_find_recog_metas_via_iter { {
1706    ( $($gens:tt)* ), $i:ty
1707} => {
1708    impl<T: FindRecogMetas, $($gens)*> FindRecogMetas for $i {
1709        fn find_recog_metas(&self, acc: &mut Recognised) {
1710            #[allow(for_loops_over_fallibles)]
1711            for item in self {
1712                item.find_recog_metas(acc)
1713            }
1714        }
1715    }
1716} }
1717impL_find_recog_metas_via_iter!((), [T]);
1718impL_find_recog_metas_via_iter!((), Option<T>);
1719impL_find_recog_metas_via_iter!((U), Punctuated<T, U>);
1720
1721macro_rules! impL_find_recog_metas_via_deref { {
1722    ( $($gens:tt)* ), $i:ty
1723} => {
1724    impl<$($gens)*> FindRecogMetas for $i {
1725        fn find_recog_metas(&self, acc: &mut Recognised) {
1726            (**self).find_recog_metas(acc)
1727        }
1728    }
1729} }
1730impL_find_recog_metas_via_deref!((O: SubstParseContext), Argument<O>);
1731impL_find_recog_metas_via_deref!((T: FindRecogMetas), Box<T>);
1732
1733impl<O: SubstParseContext> FindRecogMetas for SubstDetails<O> {
1734    fn find_recog_metas(&self, acc: &mut Recognised) {
1735        use SD::*;
1736
1737        match self {
1738            Xmeta(v) => v.find_recog_metas(acc),
1739            vpat(v, _, _) => v.find_recog_metas(acc),
1740            vtype(v, _, _) => v.find_recog_metas(acc),
1741            tdefvariants(v, _, _) => v.find_recog_metas(acc),
1742            fdefine(v, _, _) => v.find_recog_metas(acc),
1743            vdefbody(a, b, _, _) => {
1744                a.find_recog_metas(acc);
1745                b.find_recog_metas(acc);
1746            }
1747            paste(v, _) => v.find_recog_metas(acc),
1748            paste_spanned(_span, content, ..) => {
1749                content.find_recog_metas(acc);
1750            }
1751            ChangeCase(v, _, _) => v.find_recog_metas(acc),
1752            concat(v, ..) => v.find_recog_metas(acc),
1753
1754            when(v, _) => v.find_recog_metas(acc),
1755            define(v, _) => v.find_recog_metas(acc),
1756            defcond(v, _) => v.find_recog_metas(acc),
1757
1758            not(v, _) => v.find_recog_metas(acc),
1759            any(v, _) | all(v, _) => v.find_recog_metas(acc),
1760            is_empty(_, v) => v.find_recog_metas(acc),
1761            approx_equal(_, v) => v.find_recog_metas(acc),
1762
1763            For(v, _) => v.find_recog_metas(acc),
1764            If(v, _) | select1(v, _) => v.find_recog_metas(acc),
1765            ignore(v, _) => v.find_recog_metas(acc),
1766            dbg(v) => v.content_parsed.find_recog_metas(acc),
1767
1768            tname(_)
1769            | ttype(_)
1770            | tdeftype(_)
1771            | vname(_)
1772            | fname(_)
1773            | ftype(_)
1774            | fpatname(_)
1775            | vindex(..)
1776            | findex(..)
1777            | tdefkwd(_)
1778            | Vis(_, _)
1779            | tattrs(_, _, _)
1780            | vattrs(_, _, _)
1781            | fattrs(_, _, _)
1782            | tgens(_)
1783            | tdefgens(_, _)
1784            | tgnames(_, _)
1785            | twheres(_, _)
1786            | UserDefined(_)
1787            | False(_)
1788            | True(_)
1789            | is_struct(_)
1790            | is_enum(_)
1791            | is_union(_)
1792            | v_is_unit(_)
1793            | v_is_tuple(_)
1794            | v_is_named(_)
1795            | error(..)
1796            | require_beta(..)
1797            | dbg_all_keywords(_)
1798            | Crate(_, _) => {}
1799        }
1800    }
1801}
1802
1803impl<O: SubstParseContext> FindRecogMetas for SubstMeta<O> {
1804    fn find_recog_metas(&self, acc: &mut Recognised) {
1805        let recog = O::meta_recog_usage(self);
1806        acc.update(self.desig.clone(), recog);
1807    }
1808}
1809
1810impl FindRecogMetas for SubstVType {
1811    fn find_recog_metas(&self, acc: &mut Recognised) {
1812        let Self { self_, vname } = self;
1813        self_.find_recog_metas(acc);
1814        vname.find_recog_metas(acc);
1815    }
1816}
1817
1818impl FindRecogMetas for SubstVPat {
1819    fn find_recog_metas(&self, acc: &mut Recognised) {
1820        let Self { vtype, fprefix } = self;
1821        vtype.find_recog_metas(acc);
1822        fprefix.find_recog_metas(acc);
1823    }
1824}
1825
1826impl<B: FindRecogMetas> FindRecogMetas for Definition<B> {
1827    fn find_recog_metas(&self, acc: &mut Recognised) {
1828        let Self {
1829            name: _,
1830            body_span: _,
1831            body,
1832        } = self;
1833        body.find_recog_metas(acc);
1834    }
1835}
1836
1837impl FindRecogMetas for DefinitionBody {
1838    fn find_recog_metas(&self, acc: &mut Recognised) {
1839        match self {
1840            DefinitionBody::Normal(v) => v.find_recog_metas(acc),
1841            DefinitionBody::Paste(v) => v.find_recog_metas(acc),
1842            DefinitionBody::Concat(v) => v.find_recog_metas(acc),
1843        }
1844    }
1845}
1846
1847impl<O: SubstParseContext> FindRecogMetas for SubstIf<O> {
1848    fn find_recog_metas(&self, acc: &mut Recognised) {
1849        let Self {
1850            tests,
1851            otherwise,
1852            kw_span: _,
1853        } = self;
1854        for (if_, then) in tests {
1855            if_.find_recog_metas(acc);
1856            then.find_recog_metas(acc);
1857        }
1858        otherwise.find_recog_metas(acc);
1859    }
1860}