Skip to main content

syn_canon/
context.rs

1//! Original module environments and borrowed function declarations.
2
3use alloc::boxed::Box;
4use alloc::collections::btree_map::Entry;
5use alloc::collections::{BTreeMap, BTreeSet};
6use alloc::string::String;
7use alloc::string::ToString;
8use alloc::vec;
9use alloc::vec::Vec;
10
11use core::cell::Cell;
12
13use quote::ToTokens;
14use syn::visit::Visit;
15
16use crate::alpha;
17use crate::form::{self, OWNER_PARAMETER};
18use crate::scope::{Binding, BindingId, Frame, Ns, Origin, PrimTy, ident_name, unraw};
19
20/// The original owner of a function.
21#[derive(Clone, PartialEq, Eq, Debug)]
22pub enum Owner {
23    /// A free function.
24    Free,
25    /// A method of an inherent impl.
26    Inherent(String),
27    /// A default body of a trait.
28    Trait(String),
29}
30
31impl Owner {
32    /// The resolved owner name or an injective encoding of its type.
33    fn self_name(&self, seed: &Frame) -> Option<String> {
34        let text = match self {
35            Self::Free => return None,
36            Self::Inherent(text) | Self::Trait(text) => text,
37        };
38        if let Some(binding) = seed.lookup(Ns::Type, unraw(text)) {
39            return Some(binding.canon.clone());
40        }
41        Some(mangle_owner(text))
42    }
43}
44
45/// A valid-identifier spelling of `text`, injective over its bytes.
46fn mangle_owner(text: &str) -> String {
47    const HEX: [char; 16] = [
48        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
49    ];
50    let mut out = String::from("__dejadoc_self");
51    for byte in text.bytes() {
52        out.push(HEX[usize::from(byte >> 4)]);
53        out.push(HEX[usize::from(byte & 0x0f)]);
54    }
55    out
56}
57
58/// The private id of an indexed module, file-based or inline.
59#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
60struct ModuleId(usize);
61
62/// One indexed module: its seed frame and its own glob uncertainty.
63#[expect(
64    clippy::struct_excessive_bools,
65    reason = "primitive, derive, observer, helper and prelude facts of a module are independent"
66)]
67struct Module {
68    seed: Frame,
69    prim_fallback: bool,
70    /// Every std derive, formatting macro and crate root the module sees names the std item.
71    std_names: bool,
72    observed: bool,
73    helper_blocked: bool,
74    /// No attribute on the module, an ancestor or the crate root removes the standard prelude.
75    prelude: bool,
76    /// Every `use` of the module imports from `std`, `core` or `alloc`.
77    standard_imports: bool,
78}
79
80/// One original declaration and its module environment.
81#[expect(
82    clippy::struct_excessive_bools,
83    reason = "primitive, observer and helper facts of a declaration are independent"
84)]
85struct Decl<'a> {
86    module: ModuleId,
87    /// The original declaration attributes.
88    attrs: &'a [syn::Attribute],
89    owner: Owner,
90    prim_fallback: bool,
91    observed: bool,
92    /// An enclosing inline module, impl or trait carries an attribute macro reading the function.
93    enclosing_observed: bool,
94    helper_module: Vec<String>,
95    helper_blocked: bool,
96    /// The original enclosing impl or trait generics.
97    generics: Option<*const syn::Generics>,
98}
99
100/// The source context, one entry per module file and inline module.
101pub struct SourceContext<'a> {
102    modules: Vec<Module>,
103    decls: BTreeMap<(*const syn::Signature, *const syn::Block), Decl<'a>>,
104    /// Generic frames indexed by their original declarations.
105    gen_frames: BTreeMap<*const syn::Generics, Frame>,
106    /// Complete helper summaries shared by every borrowed declaration.
107    helpers: crate::helpers::HelperTable,
108    /// No trait of the crate declares a method named `to_string` or `to_owned`.
109    owned_methods: bool,
110    /// No trait of the crate declares a method named `clone`.
111    clone_method: bool,
112    /// No trait of the crate declares a collection method the collection readings rely on.
113    collection_method: bool,
114}
115
116/// A function view selected from a source context.
117pub struct FunctionView<'a> {
118    ctx: &'a SourceContext<'a>,
119    entry: &'a Decl<'a>,
120    sig: &'a syn::Signature,
121    block: &'a syn::Block,
122}
123
124impl FunctionView<'_> {
125    /// The original declaration's canonical form in its module environment.
126    #[must_use]
127    pub fn canonicalize(&self) -> crate::CanonicalForm {
128        let seed = self.ctx.seed(self.entry.module);
129        let generics = self.entry.generics.map(|key| self.ctx.gen_frame(key));
130        let self_owner = self.entry.owner.self_name(seed);
131        let mut sig = self.sig.clone();
132        if !matches!(self.entry.owner, Owner::Free) {
133            sig.ident = syn::Ident::new("__dejadoc_method", sig.ident.span());
134        }
135        let mut item = syn::ItemFn {
136            attrs: self
137                .entry
138                .attrs
139                .iter()
140                .filter(|attribute| live(&attribute.meta))
141                .cloned()
142                .collect(),
143            vis: syn::Visibility::Inherited,
144            modifiers: syn::FnModifiers::default(),
145            sig,
146            block: Box::new(self.block.clone()),
147        };
148        let helpers = transfer_helpers(&self.ctx.helpers, self.sig, self.block, &mut item);
149        let file = syn::File {
150            shebang: None,
151            frontmatter: None,
152            attrs: Vec::new(),
153            items: vec![syn::Item::Fn(item)],
154        };
155        form::canonical_file_with_options(
156            file,
157            true,
158            alpha::Options {
159                seed: Some(seed),
160                generics,
161                prim_fallback: self.entry.prim_fallback,
162                observed: self.entry.observed,
163                enclosing_observed: self.entry.enclosing_observed,
164                std_names: self.ctx.modules[self.entry.module.0].std_names,
165                prelude: self.ctx.modules[self.entry.module.0].prelude,
166                owned_methods: self.ctx.owned_methods,
167                clone_free: self.ctx.modules[self.entry.module.0].prelude
168                    && self.ctx.modules[self.entry.module.0].standard_imports
169                    && self.ctx.clone_method,
170                collection_free: self.ctx.modules[self.entry.module.0].prelude
171                    && self.ctx.modules[self.entry.module.0].standard_imports
172                    && self.ctx.collection_method,
173                self_owner,
174                helper_table: Some(&self.ctx.helpers),
175                helper_module: &self.entry.helper_module,
176                helper_blocked: self.entry.helper_blocked,
177                helper_syntax: Some(helpers),
178            },
179        )
180    }
181}
182
183fn transfer_helpers(
184    helpers: &crate::helpers::HelperTable,
185    signature: &syn::Signature,
186    body: &syn::Block,
187    cloned: &mut syn::ItemFn,
188) -> crate::helpers::HelperSyntax {
189    crate::helpers::HelperSyntax::cloned_function(helpers, signature, body, cloned)
190}
191
192impl<'a> SourceContext<'a> {
193    /// The shared seed of the module a declaration was indexed under.
194    fn seed(&self, module: ModuleId) -> &Frame {
195        &self.modules[module.0].seed
196    }
197
198    /// The shared frame of an impl or trait's generics.
199    fn gen_frame(&self, key: *const syn::Generics) -> &Frame {
200        &self.gen_frames[&key]
201    }
202
203    /// Index every module's top-level seed and every function it declares,
204    /// inline modules under their own path, seed, and glob uncertainty.
205    pub fn new<I>(files: I) -> Self
206    where
207        I: IntoIterator<Item = (&'a [String], &'a syn::File)>,
208    {
209        let files: Vec<(&'a [String], &'a syn::File)> = files.into_iter().collect();
210        let mut scopes: ModuleMap<'a> = BTreeMap::new();
211        for (path, file) in &files {
212            declare_module(&mut scopes, (*path).to_vec(), ModuleScope::File(file));
213        }
214        for (path, file) in &files {
215            register_inline(&mut scopes, path, &file.items);
216        }
217        let ancestry = module_ancestry(&scopes);
218        // A `macro_rules!` definition reaches past its module, and a `#[macro_use] extern crate`
219        // reaches every module.
220        let crate_rebound = files
221            .iter()
222            .any(|(_, file)| crate::drift::file_rebound(file) != 0);
223        let blocked: BTreeSet<Vec<String>> = ancestry
224            .iter()
225            .filter(|(_, flags)| flags.blocked)
226            .map(|(path, _)| path.clone())
227            .collect();
228        let mut module_ids: ModuleIds = BTreeMap::new();
229        let mut modules: Vec<Module> = Vec::new();
230        for ((path, declarations), ancestry) in scopes.iter().zip(ancestry.values()) {
231            let ids = declarations
232                .iter()
233                .map(|scope| {
234                    let prim_fallback = scope_primitives_known(scope.items());
235                    let id = ModuleId(modules.len());
236                    modules.push(Module {
237                        seed: seed_frame(&scopes, path, &scope, prim_fallback),
238                        prim_fallback,
239                        std_names: !crate_rebound && std_names_kept(&scopes, path, &scope),
240                        observed: ancestry.observed,
241                        helper_blocked: ancestry.blocked,
242                        prelude: !ancestry.bare,
243                        standard_imports: standard_imports(scope.items()),
244                    });
245                    (scope.key(), id)
246                })
247                .collect();
248            module_ids.insert(path.clone(), ids);
249        }
250        let helpers = crate::helpers::module_table(&crate::helpers::Scopes::new(scopes, blocked));
251        for module in &mut modules {
252            for binding in module.seed.bindings_mut(Ns::Value).values_mut() {
253                binding.helper = helpers.by_canon(&binding.canon);
254            }
255        }
256        let mut decls: BTreeMap<(*const syn::Signature, *const syn::Block), Decl<'a>> =
257            BTreeMap::new();
258        let mut gen_frames: BTreeMap<*const syn::Generics, Frame> = BTreeMap::new();
259        for (path, file) in &files {
260            index_scope(
261                &mut decls,
262                &module_ids,
263                &modules,
264                path,
265                ModuleScope::File(file),
266                false,
267                false,
268                false,
269                &mut gen_frames,
270            );
271        }
272        let owned_methods = !files
273            .iter()
274            .any(|(_, file)| declares_trait_method(&file.items, &["to_string", "to_owned"]));
275        let clone_method = !files
276            .iter()
277            .any(|(_, file)| declares_trait_method(&file.items, &["clone"]));
278        let collection_method = !files
279            .iter()
280            .any(|(_, file)| declares_trait_method(&file.items, &alpha::COLLECTION_METHODS));
281        Self {
282            modules,
283            decls,
284            gen_frames,
285            helpers,
286            owned_methods,
287            clone_method,
288            collection_method,
289        }
290    }
291
292    /// The view of the exact declaration its signature and block pointers
293    /// index, `None` when the pair does not select one.
294    #[must_use]
295    pub fn function<'s>(
296        &'s self,
297        sig: &'s syn::Signature,
298        block: &'s syn::Block,
299    ) -> Option<FunctionView<'s>> {
300        let key = (
301            core::ptr::from_ref::<syn::Signature>(sig),
302            core::ptr::from_ref::<syn::Block>(block),
303        );
304        let entry = self.decls.get(&key)?;
305        Some(FunctionView {
306            ctx: self,
307            entry,
308            sig,
309            block,
310        })
311    }
312}
313
314/// Whether a module imports no wildcard names.
315fn glob_free(items: &[syn::Item]) -> bool {
316    !items
317        .iter()
318        .any(|item| matches!(item, syn::Item::Use(use_item) if has_glob(&use_item.tree)))
319}
320
321pub(crate) fn scope_primitives_known(items: &[syn::Item]) -> bool {
322    let mut uncertainty = ScopeUncertainty(false);
323    for item in items {
324        uncertainty.visit_item(item);
325        if uncertainty.0 {
326            return false;
327        }
328    }
329    glob_free(items)
330}
331
332struct ScopeUncertainty(bool);
333
334impl<'ast> Visit<'ast> for ScopeUncertainty {
335    fn visit_attribute(&mut self, attr: &'ast syn::Attribute) {
336        self.0 |=
337            live(&attr.meta) || attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr");
338    }
339
340    fn visit_item_mod(&mut self, item: &'ast syn::ItemMod) {
341        for attr in &item.attrs {
342            self.visit_attribute(attr);
343        }
344    }
345
346    fn visit_item_macro(&mut self, item: &'ast syn::ItemMacro) {
347        self.0 |= item.ident.is_none();
348        syn::visit::visit_item_macro(self, item);
349    }
350
351    fn visit_item_extern_crate(&mut self, _: &'ast syn::ItemExternCrate) {
352        self.0 = true;
353    }
354
355    fn visit_block(&mut self, _: &'ast syn::Block) {}
356}
357
358pub(crate) fn has_glob(tree: &syn::UseTree) -> bool {
359    match tree {
360        syn::UseTree::Glob(_) => true,
361        syn::UseTree::Path(path) => has_glob(&path.tree),
362        syn::UseTree::Group(group) => group.items.iter().any(has_glob),
363        _ => false,
364    }
365}
366
367/// A module's items, inline content included.
368#[derive(Clone, Copy)]
369pub(crate) enum ModuleScope<'a> {
370    File(&'a syn::File),
371    Inline(&'a syn::ItemMod),
372}
373
374impl<'a> ModuleScope<'a> {
375    pub(crate) fn items(&self) -> &'a [syn::Item] {
376        match self {
377            Self::File(file) => &file.items,
378            Self::Inline(module) => module.content.as_ref().map_or(&[], |(_, items)| items),
379        }
380    }
381
382    /// The original declaration's address.
383    fn key(&self) -> *const () {
384        match self {
385            Self::File(file) => core::ptr::from_ref(*file).cast(),
386            Self::Inline(module) => core::ptr::from_ref(*module).cast(),
387        }
388    }
389
390    fn shadows_primitive_crate(&self, name: &str) -> bool {
391        let raw = if name == "core" { "r#core" } else { "r#std" };
392        let matches = |ident: &syn::Ident| ident == name || ident == raw;
393        self.items().iter().any(|item| {
394            alpha::type_item_ident(item).is_some_and(matches)
395                || matches!(item, syn::Item::Use(import)
396                    if alpha::imported_name_matches(&import.tree, matches))
397        })
398    }
399}
400
401/// Every declaration of one module path, in registration order.
402#[derive(Clone)]
403pub(crate) struct Declarations<'a> {
404    first: ModuleScope<'a>,
405    rest: Vec<ModuleScope<'a>>,
406}
407
408impl<'a> Declarations<'a> {
409    pub(crate) fn iter(&self) -> impl Iterator<Item = ModuleScope<'a>> + '_ {
410        core::iter::once(self.first).chain(self.rest.iter().copied())
411    }
412
413    pub(crate) fn repeated(&self) -> bool {
414        !self.rest.is_empty()
415    }
416
417    fn primitives_known(&self) -> bool {
418        self.iter()
419            .all(|scope| scope_primitives_known(scope.items()))
420    }
421
422    fn shadows_primitive_crate(&self, name: &str) -> bool {
423        self.iter().any(|scope| scope.shadows_primitive_crate(name))
424    }
425}
426
427/// Module declarations by module path.
428pub(crate) type ModuleMap<'a> = BTreeMap<Vec<String>, Declarations<'a>>;
429
430/// Whether the module declaration `scope` at `path` leaves every std name bound to the std
431/// item, following each `use super::*` to every declaration of the module it reads.
432fn std_names_kept(scopes: &ModuleMap<'_>, path: &[String], scope: &ModuleScope<'_>) -> bool {
433    let (kept, supers) = crate::drift::module_std_names(scope.items());
434    kept && (1..=supers).all(|levels| {
435        let Some(end) = path.len().checked_sub(levels) else {
436            return false;
437        };
438        scopes.get(&path[..end]).is_some_and(|declarations| {
439            declarations
440                .iter()
441                .all(|ancestor| std_names_kept(scopes, &path[..end], &ancestor))
442        })
443    })
444}
445
446/// Module ids by module path and original declaration address.
447type ModuleIds = BTreeMap<Vec<String>, Vec<(*const (), ModuleId)>>;
448
449/// Record `scope` as a declaration of `path`, once per original declaration.
450pub(crate) fn declare_module<'a>(
451    scopes: &mut ModuleMap<'a>,
452    path: Vec<String>,
453    scope: ModuleScope<'a>,
454) {
455    match scopes.entry(path) {
456        Entry::Vacant(entry) => {
457            entry.insert(Declarations {
458                first: scope,
459                rest: Vec::new(),
460            });
461        }
462        Entry::Occupied(mut entry) => {
463            let declarations = entry.get_mut();
464            if declarations.iter().all(|known| known.key() != scope.key()) {
465                declarations.rest.push(scope);
466            }
467        }
468    }
469}
470
471/// Register every inline module body under its module path.
472pub(crate) fn register_inline<'a>(
473    scopes: &mut ModuleMap<'a>,
474    path: &[String],
475    items: &'a [syn::Item],
476) {
477    for item in items {
478        if let syn::Item::Mod(mod_item) = item
479            && let Some((_, inner)) = &mod_item.content
480        {
481            let mut child = path.to_vec();
482            child.push(unraw(&mod_item.ident.to_string()).to_string());
483            declare_module(scopes, child.clone(), ModuleScope::Inline(mod_item));
484            register_inline(scopes, &child, inner);
485        }
486    }
487}
488
489/// The attribute facts a module inherits from itself and every ancestor.
490#[derive(Clone, Copy, Default)]
491struct Ancestry {
492    observed: bool,
493    blocked: bool,
494    /// An attribute on the module or an ancestor may remove the standard prelude.
495    bare: bool,
496}
497
498impl Ancestry {
499    fn absorb(&mut self, attrs: &[syn::Attribute]) {
500        self.observed |= live_attr(attrs);
501        self.blocked |= blocking_attr(attrs);
502        self.bare |= removes_prelude(attrs);
503    }
504}
505
506fn module_ancestry(scopes: &ModuleMap<'_>) -> BTreeMap<Vec<String>, Ancestry> {
507    let mut direct: BTreeMap<Vec<String>, Ancestry> = BTreeMap::new();
508    for (path, declarations) in scopes {
509        if declarations.repeated() {
510            direct.entry(path.clone()).or_default().blocked = true;
511        }
512        for scope in declarations.iter() {
513            if let ModuleScope::File(file) = scope {
514                direct.entry(path.clone()).or_default().absorb(&file.attrs);
515            }
516            for item in scope.items() {
517                if let syn::Item::Mod(module) = item {
518                    let mut child = path.clone();
519                    child.push(ident_name(&module.ident));
520                    direct.entry(child).or_default().absorb(&module.attrs);
521                }
522            }
523        }
524    }
525    scopes
526        .keys()
527        .map(|path| {
528            let mut flags = Ancestry::default();
529            for end in 0..=path.len() {
530                if let Some(direct) = direct.get(&path[..end]) {
531                    flags.observed |= direct.observed;
532                    flags.blocked |= direct.blocked;
533                    flags.bare |= direct.bare;
534                }
535            }
536            (path.clone(), flags)
537        })
538        .collect()
539}
540
541pub(crate) fn blocked_modules(scopes: &ModuleMap<'_>) -> BTreeSet<Vec<String>> {
542    module_ancestry(scopes)
543        .into_iter()
544        .filter_map(|(path, flags)| flags.blocked.then_some(path))
545        .collect()
546}
547
548/// Whether attributes may remove the standard prelude: `no_std`, `no_implicit_prelude`, or a
549/// `cfg_attr` that may apply either.
550pub(crate) fn removes_prelude(attrs: &[syn::Attribute]) -> bool {
551    attrs.iter().any(|attr| {
552        let path = attr.path();
553        path.is_ident("no_std")
554            || path.is_ident("no_implicit_prelude")
555            || (path.is_ident("cfg_attr")
556                && attr
557                    .meta
558                    .to_token_stream()
559                    .into_iter()
560                    .any(|token| prelude_attribute_token(&token)))
561    })
562}
563
564fn prelude_attribute_token(token: &proc_macro2::TokenTree) -> bool {
565    match token {
566        proc_macro2::TokenTree::Ident(ident) => ident == "no_std" || ident == "no_implicit_prelude",
567        proc_macro2::TokenTree::Group(group) => group
568            .stream()
569            .into_iter()
570            .any(|token| prelude_attribute_token(&token)),
571        _ => false,
572    }
573}
574
575/// Whether a trait among `items`, at any depth, declares a method with one of `names`, which
576/// the readings of standard method calls leave to the compiler.
577pub(crate) fn declares_trait_method(items: &[syn::Item], names: &[&str]) -> bool {
578    struct Search<'a>(&'a [&'a str], bool);
579    impl syn::visit::Visit<'_> for Search<'_> {
580        fn visit_trait_item_fn(&mut self, method: &syn::TraitItemFn) {
581            let name = ident_name(&method.sig.ident);
582            self.1 |= self.0.contains(&name.as_str());
583        }
584    }
585    let mut search = Search(names, false);
586    for item in items {
587        syn::visit::Visit::visit_item(&mut search, item);
588    }
589    search.1
590}
591
592/// Whether every `use` among `items`, at any depth, imports from `std`, `core` or `alloc`, so
593/// it brings no trait method beyond the standard library's into view.
594pub(crate) fn standard_imports(items: &[syn::Item]) -> bool {
595    struct Search(bool);
596    impl syn::visit::Visit<'_> for Search {
597        fn visit_item_use(&mut self, item: &syn::ItemUse) {
598            self.0 &= standard_tree(&item.tree);
599        }
600    }
601    fn standard_tree(tree: &syn::UseTree) -> bool {
602        match tree {
603            syn::UseTree::Path(path) => {
604                matches!(ident_name(&path.ident).as_str(), "std" | "core" | "alloc")
605            }
606            syn::UseTree::Group(group) => group.items.iter().all(standard_tree),
607            _ => false,
608        }
609    }
610    let mut search = Search(true);
611    for item in items {
612        syn::visit::Visit::visit_item(&mut search, item);
613    }
614    search.0
615}
616
617pub(crate) fn qualified_name(module: &[String], name: &str) -> String {
618    let mut out = String::from("__dejadoc_inherited");
619    for part in module
620        .iter()
621        .map(|part| unraw(part))
622        .chain(core::iter::once(unraw(name)))
623    {
624        qualified_part(&mut out, part);
625    }
626    out
627}
628
629pub(crate) fn qualified_part(out: &mut String, part: &str) {
630    const HEX: &[u8; 16] = b"0123456789abcdef";
631    out.push('_');
632    for byte in part.bytes() {
633        out.push(char::from(HEX[usize::from(byte >> 4)]));
634        out.push(char::from(HEX[usize::from(byte & 15)]));
635    }
636}
637
638#[derive(Clone)]
639pub(crate) struct ImportedTarget {
640    pub(crate) canon: String,
641    pub(crate) prim: Option<PrimTy>,
642    module: Option<Vec<String>>,
643    macro_resolution: Option<crate::macros::MacroResolution>,
644}
645
646impl ImportedTarget {
647    /// The facts `self` shares with `other`, `None` when they name different items.
648    fn agree(self, other: &Self) -> Option<Self> {
649        (self.canon == other.canon).then(|| Self {
650            prim: self.prim.filter(|prim| other.prim == Some(*prim)),
651            macro_resolution: self
652                .macro_resolution
653                .filter(|standard| other.macro_resolution == Some(*standard)),
654            module: self
655                .module
656                .filter(|module| other.module.as_ref() == Some(module)),
657            canon: self.canon,
658        })
659    }
660}
661
662/// One resolved `use` target.
663pub(crate) enum UseTarget {
664    /// An external path.
665    External,
666    /// An indexed item's name and occupied namespaces.
667    Item(ImportedTarget, [bool; 5]),
668    /// A name the context could not resolve.
669    Unresolved,
670}
671
672const IMPORT_NAMESPACES: [Ns; 3] = [Ns::Value, Ns::Type, Ns::Macro];
673
674#[derive(Default)]
675enum UnknownImport {
676    #[default]
677    None,
678    Unique(ImportedTarget),
679    Ambiguous,
680}
681
682#[derive(Default)]
683struct ImportedName {
684    resolved: [Option<ImportedTarget>; 5],
685    unknown: UnknownImport,
686    /// Whether a `cfg` or `cfg_attr` gates one of the `use` items binding the alias.
687    gated: bool,
688}
689
690impl ImportedName {
691    /// Make every namespace the opaque module-scoped alias when gated imports
692    /// bind the alias to both a resolved item and an unknown target.
693    fn settle(&mut self, from: &[String], alias: &str) {
694        if self.gated {
695            for target in self.resolved.iter_mut().flatten() {
696                target.macro_resolution = Some(crate::macros::MacroResolution::Opaque);
697            }
698            if let UnknownImport::Unique(target) = &mut self.unknown {
699                target.macro_resolution = Some(crate::macros::MacroResolution::Opaque);
700            }
701        }
702        if !self.gated
703            || matches!(self.unknown, UnknownImport::None)
704            || self.resolved.iter().all(Option::is_none)
705        {
706            return;
707        }
708        for slot in self.resolved.iter_mut().filter(|slot| slot.is_some()) {
709            *slot = Some(ImportedTarget {
710                canon: qualified_name(from, alias),
711                prim: None,
712                module: None,
713                macro_resolution: None,
714            });
715        }
716        self.unknown = UnknownImport::Ambiguous;
717    }
718}
719
720/// Whether a `cfg` or `cfg_attr` attribute gates the item carrying `attrs`.
721fn cfg_gated(attrs: &[syn::Attribute]) -> bool {
722    attrs
723        .iter()
724        .any(|attr| attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr"))
725}
726
727struct ImportPath<'a> {
728    module: &'a [String],
729    name: &'a str,
730    parent: Option<&'a Self>,
731}
732
733impl ImportPath<'_> {
734    fn contains(&self, module: &[String], name: &str) -> bool {
735        let mut active = Some(self);
736        while let Some(query) = active {
737            if query.module == module && query.name == name {
738                return true;
739            }
740            active = query.parent;
741        }
742        false
743    }
744}
745
746struct UseBinder<'a> {
747    scopes: &'a ModuleMap<'a>,
748    from: &'a [String],
749    import: &'a syn::ItemUse,
750    imports: &'a mut BTreeMap<String, ImportedName>,
751    only_name: Option<&'a str>,
752    depth: usize,
753    active: Option<&'a ImportPath<'a>>,
754    steps: &'a Cell<usize>,
755}
756
757fn import_slots(
758    slots: &mut [Option<ImportedTarget>; 5],
759    namespaces: [bool; 5],
760    target: ImportedTarget,
761) {
762    let mut active = namespaces
763        .into_iter()
764        .enumerate()
765        .filter_map(|(index, present)| present.then_some(index))
766        .peekable();
767    while let Some(index) = active.next() {
768        if active.peek().is_some() {
769            slots[index] = Some(target.clone());
770        } else {
771            slots[index] = Some(target);
772            break;
773        }
774    }
775}
776
777/// Merge one more `use` of `alias` into its slots, a namespace bound to two
778/// different items becoming the opaque module-scoped alias.
779fn merge_import_slots(
780    slots: &mut [Option<ImportedTarget>; 5],
781    namespaces: [bool; 5],
782    target: &ImportedTarget,
783    from: &[String],
784    alias: &str,
785) {
786    for (slot, present) in slots.iter_mut().zip(namespaces) {
787        if !present {
788            continue;
789        }
790        *slot = Some(match slot.take() {
791            None => target.clone(),
792            Some(current) => current.agree(target).unwrap_or_else(|| ImportedTarget {
793                canon: qualified_name(from, alias),
794                prim: None,
795                module: None,
796                macro_resolution: None,
797            }),
798        });
799    }
800}
801
802fn bind_imports(
803    frame: &mut Frame,
804    from: &[String],
805    imports: BTreeMap<String, ImportedName>,
806    next_id: &mut usize,
807) {
808    for (alias, mut imported) in imports {
809        imported.settle(from, &alias);
810        let unknown = match imported.unknown {
811            UnknownImport::None => None,
812            UnknownImport::Unique(target) => Some(target),
813            UnknownImport::Ambiguous => Some(ImportedTarget {
814                canon: qualified_name(from, &alias),
815                prim: None,
816                module: None,
817                macro_resolution: None,
818            }),
819        };
820        if let Some(target) = unknown {
821            let mut unclaimed = [false; 5];
822            for ns in IMPORT_NAMESPACES {
823                unclaimed[ns as usize] =
824                    imported.resolved[ns as usize].is_none() && frame.lookup(ns, &alias).is_none();
825            }
826            import_slots(&mut imported.resolved, unclaimed, target);
827        }
828        for ns in IMPORT_NAMESPACES {
829            if let Some(target) = imported.resolved[ns as usize].take() {
830                frame.bind(
831                    ns,
832                    &alias,
833                    Binding {
834                        id: BindingId(*next_id),
835                        canon: target.canon,
836                        origin: Origin::Use,
837                        prim: if ns == Ns::Type { target.prim } else { None },
838                        target: None,
839                        raw: false,
840                        port: 0,
841                        value_port: None,
842                        helper: None,
843                        macro_resolution: target.macro_resolution,
844                    },
845                );
846                *next_id += 1;
847            }
848        }
849    }
850}
851
852fn use_tree_bindings(binder: &mut UseBinder<'_>, prefix: &[String], tree: &syn::UseTree) {
853    if binder.depth >= 64 {
854        return;
855    }
856    binder.depth += 1;
857    match tree {
858        syn::UseTree::Path(path) => {
859            let mut next = prefix.to_vec();
860            next.push(ident_name(&path.ident));
861            use_tree_bindings(binder, &next, &path.tree);
862        }
863        syn::UseTree::Name(name) if name.ident == "self" && !prefix.is_empty() => {
864            bind_use_name(
865                binder,
866                prefix,
867                prefix.last().expect("nonempty import prefix"),
868            );
869        }
870        syn::UseTree::Name(name) => {
871            let mut path = prefix.to_vec();
872            path.push(ident_name(&name.ident));
873            bind_use_name(binder, &path, &name.ident.to_string());
874        }
875        syn::UseTree::Rename(rename) => {
876            let mut path = prefix.to_vec();
877            if rename.ident != "self" || path.is_empty() {
878                path.push(ident_name(&rename.ident));
879            }
880            bind_use_name(binder, &path, &rename.rename.to_string());
881        }
882        syn::UseTree::Group(group) => {
883            for inner in &group.items {
884                use_tree_bindings(binder, prefix, inner);
885            }
886        }
887        syn::UseTree::Glob(_) => {}
888    }
889    binder.depth -= 1;
890}
891
892fn bind_use_name(binder: &mut UseBinder<'_>, path: &[String], alias: &str) {
893    if binder.only_name.is_some_and(|name| unraw(alias) != name) || path.is_empty() {
894        return;
895    }
896    let target = resolve_use_at(
897        binder.scopes,
898        binder.from,
899        path,
900        binder.import.leading_colon.is_some(),
901        binder.depth,
902        binder.active,
903        binder.steps,
904    );
905    let imported = if let Some(imported) = binder.imports.get_mut(alias) {
906        imported
907    } else {
908        binder.imports.entry(alias.to_string()).or_default()
909    };
910    imported.gated |= cfg_gated(&binder.import.attrs);
911    let target = match target {
912        UseTarget::Item(target, namespaces) => {
913            if imported.resolved.iter().all(Option::is_none) {
914                import_slots(&mut imported.resolved, namespaces, target);
915            } else {
916                merge_import_slots(
917                    &mut imported.resolved,
918                    namespaces,
919                    &target,
920                    binder.from,
921                    alias,
922                );
923            }
924            return;
925        }
926        UseTarget::External => ImportedTarget {
927            canon: qualified_name(&path[..path.len() - 1], &path[path.len() - 1]),
928            prim: external_prim(path).filter(|_| {
929                binder
930                    .scopes
931                    .get(binder.from)
932                    .expect("indexed importing module")
933                    .primitives_known()
934            }),
935            module: None,
936            macro_resolution: crate::macros::MacroResolution::external_path(path),
937        },
938        UseTarget::Unresolved => ImportedTarget {
939            canon: qualified_name(binder.from, alias),
940            prim: None,
941            module: None,
942            macro_resolution: None,
943        },
944    };
945    imported.unknown = match imported.unknown {
946        UnknownImport::None => UnknownImport::Unique(target),
947        UnknownImport::Unique(_) | UnknownImport::Ambiguous => UnknownImport::Ambiguous,
948    };
949}
950
951/// Resolve a use path against the module map, `from` the importing module,
952/// spending `steps` of the repeated-module budget.
953pub(crate) fn resolve_use(
954    scopes: &ModuleMap<'_>,
955    from: &[String],
956    path: &[String],
957    leading_colon: bool,
958    steps: &Cell<usize>,
959) -> UseTarget {
960    resolve_use_at(scopes, from, path, leading_colon, 0, None, steps)
961}
962
963/// The repeated-module declarations one budget may visit.
964const UNION_STEPS: usize = 1024;
965
966fn resolve_use_at(
967    scopes: &ModuleMap<'_>,
968    from: &[String],
969    path: &[String],
970    leading_colon: bool,
971    depth: usize,
972    active: Option<&ImportPath<'_>>,
973    steps: &Cell<usize>,
974) -> UseTarget {
975    if depth >= 64 || path.len() > 64 {
976        return UseTarget::Unresolved;
977    }
978    let Some(first) = path.first().map(|name| unraw(name)) else {
979        return UseTarget::Unresolved;
980    };
981    if leading_colon {
982        return UseTarget::External;
983    }
984    let local = scopes.get(from).expect("indexed importing module");
985    if matches!(first, "std" | "core") && !local.shadows_primitive_crate(first) {
986        return UseTarget::External;
987    }
988    let (mut base, mut rest) = match first {
989        "crate" => (Vec::new(), &path[1..]),
990        "self" => (from.to_vec(), &path[1..]),
991        _ => (from.to_vec(), path),
992    };
993    while rest.first().is_some_and(|name| unraw(name) == "super") {
994        if base.pop().is_none() {
995            return UseTarget::Unresolved;
996        }
997        rest = &rest[1..];
998    }
999    let Some(mut scope) = scopes.get(&base) else {
1000        return UseTarget::Unresolved;
1001    };
1002    if rest.is_empty() {
1003        let Some(name) = base.last() else {
1004            return UseTarget::Unresolved;
1005        };
1006        return UseTarget::Item(
1007            ImportedTarget {
1008                canon: qualified_name(&base[..base.len() - 1], name),
1009                prim: None,
1010                macro_resolution: None,
1011                module: Some(base.clone()),
1012            },
1013            [false, true, false, false, false],
1014        );
1015    }
1016    for (index, name) in rest.iter().enumerate() {
1017        let name = unraw(name);
1018        if index + 1 == rest.len() {
1019            return resolve_item(scopes, scope, &base, name, depth + 1, active, steps);
1020        }
1021        let mut next_path = base.clone();
1022        next_path.push(name.to_string());
1023        if !scopes.contains_key(&next_path) {
1024            let UseTarget::Item(target, namespaces) =
1025                resolve_item(scopes, scope, &base, name, depth + 1, active, steps)
1026            else {
1027                return UseTarget::Unresolved;
1028            };
1029            if !namespaces[1] {
1030                return UseTarget::Unresolved;
1031            }
1032            let Some(module) = target.module else {
1033                return UseTarget::Unresolved;
1034            };
1035            next_path = module;
1036        }
1037        scope = scopes
1038            .get(&next_path)
1039            .expect("indexed import target module");
1040        base = next_path;
1041    }
1042    UseTarget::Unresolved
1043}
1044
1045/// `name` in the module at `module`, every declaration of a repeated module
1046/// naming one item and only the facts they all prove kept.
1047fn resolve_item(
1048    scopes: &ModuleMap<'_>,
1049    declarations: &Declarations<'_>,
1050    module: &[String],
1051    name: &str,
1052    depth: usize,
1053    active: Option<&ImportPath<'_>>,
1054    steps: &Cell<usize>,
1055) -> UseTarget {
1056    if !declarations.repeated() {
1057        let items = declarations.first.items();
1058        return resolve_declared(scopes, items, module, name, depth, active, steps);
1059    }
1060    let mut merged: Option<(ImportedTarget, [bool; 5])> = None;
1061    for scope in declarations.iter() {
1062        steps.set(steps.get() + 1);
1063        if steps.get() > UNION_STEPS {
1064            return UseTarget::Unresolved;
1065        }
1066        let items = scope.items();
1067        match resolve_declared(scopes, items, module, name, depth, active, steps) {
1068            UseTarget::Item(target, namespaces) => {
1069                merged = Some(match merged {
1070                    None => (target, namespaces),
1071                    Some((current, seen)) => {
1072                        let Some(target) = current.agree(&target) else {
1073                            return UseTarget::Unresolved;
1074                        };
1075                        (
1076                            target,
1077                            core::array::from_fn(|ns| seen[ns] || namespaces[ns]),
1078                        )
1079                    }
1080                });
1081            }
1082            UseTarget::Unresolved if !may_name(items, name) => {}
1083            UseTarget::External | UseTarget::Unresolved => return UseTarget::Unresolved,
1084        }
1085    }
1086    merged.map_or(UseTarget::Unresolved, |(target, namespaces)| {
1087        UseTarget::Item(target, namespaces)
1088    })
1089}
1090
1091/// Whether `items` may bind `name` beyond the items they declare.
1092fn may_name(items: &[syn::Item], name: &str) -> bool {
1093    items.iter().any(|item| match item {
1094        syn::Item::Use(import) => {
1095            has_glob(&import.tree)
1096                || alpha::imported_name_matches(&import.tree, |ident| ident_name(ident) == name)
1097        }
1098        syn::Item::ExternCrate(krate) => {
1099            let bound = krate
1100                .rename
1101                .as_ref()
1102                .map_or(&krate.ident, |(_, rename)| rename);
1103            ident_name(bound) == name
1104        }
1105        syn::Item::Macro(item) => item.ident.is_none(),
1106        _ => false,
1107    })
1108}
1109
1110fn resolve_declared(
1111    scopes: &ModuleMap<'_>,
1112    items: &[syn::Item],
1113    module: &[String],
1114    name: &str,
1115    depth: usize,
1116    active: Option<&ImportPath<'_>>,
1117    steps: &Cell<usize>,
1118) -> UseTarget {
1119    if depth >= 64 {
1120        return UseTarget::Unresolved;
1121    }
1122    let mut namespaces = [false; 5];
1123    for item in items {
1124        let value = match item {
1125            syn::Item::Fn(item) => Some(&item.sig.ident),
1126            syn::Item::Const(item) => Some(&item.ident),
1127            syn::Item::Static(item) => Some(&item.ident),
1128            _ => None,
1129        };
1130        if value.is_some_and(|ident| ident_name(ident) == name) {
1131            namespaces[Ns::Value as usize] = true;
1132        }
1133        if alpha::type_item_ident(item).is_some_and(|ident| ident_name(ident) == name) {
1134            namespaces[Ns::Type as usize] = true;
1135            if let syn::Item::Struct(item) = item
1136                && !matches!(item.fields, syn::Fields::Named(_))
1137            {
1138                namespaces[Ns::Value as usize] = true;
1139            }
1140        }
1141        if let syn::Item::Macro(item) = item
1142            && item
1143                .ident
1144                .as_ref()
1145                .is_some_and(|ident| ident_name(ident) == name)
1146        {
1147            namespaces[Ns::Macro as usize] = true;
1148        }
1149    }
1150    if namespaces.iter().any(|present| *present) {
1151        let prim = items.iter().find_map(|item| match item {
1152            syn::Item::Type(alias) if ident_name(&alias.ident) == name => {
1153                alias_chain_prim(alias, &items.iter(), scope_primitives_known(items), 0).or_else(
1154                    || resolved_alias_prim(scopes, module, alias, depth + 1, active, steps),
1155                )
1156            }
1157            _ => None,
1158        });
1159        UseTarget::Item(
1160            ImportedTarget {
1161                canon: qualified_name(module, name),
1162                prim,
1163                macro_resolution: None,
1164                module: items.iter().find_map(|item| {
1165                    if let syn::Item::Mod(item) = item
1166                        && ident_name(&item.ident) == name
1167                    {
1168                        let mut path = module.to_vec();
1169                        path.push(name.to_string());
1170                        scopes.contains_key(&path).then_some(path)
1171                    } else {
1172                        None
1173                    }
1174                }),
1175            },
1176            namespaces,
1177        )
1178    } else {
1179        resolve_imported_item(scopes, items, module, name, depth, active, steps)
1180    }
1181}
1182
1183fn resolve_imported_item(
1184    scopes: &ModuleMap<'_>,
1185    items: &[syn::Item],
1186    module: &[String],
1187    name: &str,
1188    depth: usize,
1189    active: Option<&ImportPath<'_>>,
1190    steps: &Cell<usize>,
1191) -> UseTarget {
1192    if active.is_some_and(|query| query.contains(module, name)) {
1193        return UseTarget::Unresolved;
1194    }
1195    let query = ImportPath {
1196        module,
1197        name,
1198        parent: active,
1199    };
1200    let mut namespaces = [false; 5];
1201    let mut imports = BTreeMap::new();
1202    for item in items {
1203        if let syn::Item::Use(import) = item {
1204            use_tree_bindings(
1205                &mut UseBinder {
1206                    scopes,
1207                    from: module,
1208                    import,
1209                    imports: &mut imports,
1210                    only_name: Some(name),
1211                    depth: depth + 1,
1212                    active: Some(&query),
1213                    steps,
1214                },
1215                &[],
1216                &import.tree,
1217            );
1218        }
1219    }
1220    let Some((alias, mut imported)) = imports.into_iter().find(|(alias, _)| unraw(alias) == name)
1221    else {
1222        return UseTarget::Unresolved;
1223    };
1224    imported.settle(module, &alias);
1225    let mut target: Option<ImportedTarget> = None;
1226    for (index, slot) in imported.resolved.into_iter().enumerate() {
1227        if let Some(resolved) = slot {
1228            if target
1229                .as_ref()
1230                .is_some_and(|current| current.canon != resolved.canon)
1231            {
1232                return UseTarget::Unresolved;
1233            }
1234            namespaces[index] = true;
1235            target = Some(resolved);
1236        }
1237    }
1238    if target.is_none()
1239        && let UnknownImport::Unique(external) = imported.unknown
1240        && external.prim.is_some()
1241    {
1242        namespaces[1] = true;
1243        target = Some(external);
1244    }
1245    target.map_or(UseTarget::Unresolved, |target| {
1246        UseTarget::Item(target, namespaces)
1247    })
1248}
1249
1250/// The primitive an external `std::primitive` or `core::primitive` path
1251/// names, when it names one.
1252pub(crate) fn external_prim(path: &[String]) -> Option<PrimTy> {
1253    if let [first, middle, third] = path
1254        && middle == "primitive"
1255        && (first == "std" || first == "core")
1256    {
1257        return crate::scope::primitive_suffix(third);
1258    }
1259    None
1260}
1261
1262/// The proven primitive a type alias names, its target resolved through
1263/// the sibling items, the chain bounded to sixteen hops.
1264fn alias_chain_prim<'a>(
1265    alias: &syn::ItemType,
1266    siblings: &(impl Iterator<Item = &'a syn::Item> + Clone),
1267    prim_fallback: bool,
1268    hops: usize,
1269) -> Option<PrimTy> {
1270    if hops > 16 || !prim_fallback || !alias.generics.params.is_empty() {
1271        return None;
1272    }
1273    let target = alpha::single_segment_type_name(&alias.ty)?;
1274    let target = unraw(&target);
1275    for item in siblings.clone() {
1276        match item {
1277            syn::Item::Type(other) if ident_name(&other.ident) == target => {
1278                return alias_chain_prim(other, siblings, prim_fallback, hops + 1);
1279            }
1280            syn::Item::Use(use_item) => {
1281                if alpha::imported_name_matches(&use_item.tree, |ident| ident_name(ident) == target)
1282                {
1283                    return None;
1284                }
1285            }
1286            other => {
1287                if let Some(ident) = alpha::type_item_ident(other)
1288                    && ident_name(ident) == target
1289                {
1290                    return None;
1291                }
1292            }
1293        }
1294    }
1295    crate::scope::primitive_suffix(target)
1296}
1297
1298fn alias_type_path(mut ty: &syn::Type) -> Option<&syn::Path> {
1299    for _ in 0..64 {
1300        match ty {
1301            syn::Type::Paren(paren) => ty = &paren.elem,
1302            syn::Type::Group(group) => ty = &group.elem,
1303            syn::Type::Path(path)
1304                if path.qself.is_none()
1305                    && path
1306                        .path
1307                        .segments
1308                        .iter()
1309                        .all(|segment| segment.arguments.is_empty()) =>
1310            {
1311                return Some(&path.path);
1312            }
1313            _ => return None,
1314        }
1315    }
1316    None
1317}
1318
1319fn resolved_alias_prim(
1320    scopes: &ModuleMap<'_>,
1321    module: &[String],
1322    alias: &syn::ItemType,
1323    depth: usize,
1324    active: Option<&ImportPath<'_>>,
1325    steps: &Cell<usize>,
1326) -> Option<PrimTy> {
1327    if depth >= 64 || !alias.generics.params.is_empty() || blocking_attr(&alias.attrs) {
1328        return None;
1329    }
1330    let scope = scopes.get(module)?;
1331    if !scope.primitives_known() {
1332        return None;
1333    }
1334    let path = alias_type_path(&alias.ty)?;
1335    let names: Vec<String> = path
1336        .segments
1337        .iter()
1338        .map(|segment| ident_name(&segment.ident))
1339        .collect();
1340    match resolve_use_at(
1341        scopes,
1342        module,
1343        &names,
1344        path.leading_colon.is_some(),
1345        depth + 1,
1346        active,
1347        steps,
1348    ) {
1349        UseTarget::Item(target, namespaces) if namespaces[1] => target.prim,
1350        UseTarget::External => external_prim(&names),
1351        UseTarget::Unresolved if names.len() == 1 && !scope.shadows_primitive_crate(&names[0]) => {
1352            crate::scope::primitive_suffix(&names[0])
1353        }
1354        _ => None,
1355    }
1356}
1357
1358fn seed_alias_prim(
1359    scopes: &ModuleMap<'_>,
1360    module: &[String],
1361    alias: &syn::ItemType,
1362    frame: &Frame,
1363    prim_fallback: bool,
1364    steps: &Cell<usize>,
1365) -> Option<PrimTy> {
1366    if !alias.generics.params.is_empty() || blocking_attr(&alias.attrs) {
1367        return None;
1368    }
1369    let path = alias_type_path(&alias.ty)?;
1370    if path.leading_colon.is_none() && path.segments.len() == 1 {
1371        let name = ident_name(&path.segments[0].ident);
1372        return match frame.lookup(Ns::Type, &name) {
1373            Some(binding) => binding.prim,
1374            None if prim_fallback => crate::scope::primitive_suffix(&name),
1375            None => None,
1376        };
1377    }
1378    resolved_alias_prim(scopes, module, alias, 0, None, steps)
1379}
1380
1381fn bind_item_ident(
1382    frame: &mut Frame,
1383    module: &[String],
1384    ns: Ns,
1385    ident: &syn::Ident,
1386    origin: Origin,
1387    prim: Option<PrimTy>,
1388    next_id: &mut usize,
1389) {
1390    let name = ident.to_string();
1391    let binding = Binding {
1392        id: BindingId(*next_id),
1393        canon: qualified_name(module, &name),
1394        origin,
1395        target: None,
1396        raw: false,
1397        prim,
1398        port: 0,
1399        value_port: None,
1400        helper: None,
1401        macro_resolution: None,
1402    };
1403    frame.bind(ns, &name, binding);
1404    *next_id += 1;
1405}
1406
1407fn bind_extern_crate(
1408    frame: &mut Frame,
1409    module: &[String],
1410    item: &syn::ItemExternCrate,
1411    next_id: &mut usize,
1412) {
1413    let ident = item.rename.as_ref().map_or(&item.ident, |(_, alias)| alias);
1414    let name = ident.to_string();
1415    frame.bind(
1416        Ns::Type,
1417        &name,
1418        Binding {
1419            id: BindingId(*next_id),
1420            canon: qualified_name(module, &name),
1421            origin: Origin::Use,
1422            target: None,
1423            raw: false,
1424            prim: None,
1425            port: 0,
1426            value_port: None,
1427            helper: None,
1428            macro_resolution: crate::macros::MacroResolution::external_crate(item),
1429        },
1430    );
1431    *next_id += 1;
1432}
1433
1434fn bind_macro_rule(
1435    frame: &mut Frame,
1436    module: &[String],
1437    item: &syn::ItemMacro,
1438    next_id: &mut usize,
1439) {
1440    if let Some(ident) = &item.ident {
1441        bind_item_ident(
1442            frame,
1443            module,
1444            Ns::Macro,
1445            ident,
1446            Origin::MacroRule,
1447            None,
1448            next_id,
1449        );
1450    }
1451}
1452
1453/// The identity a module item seeds at its own spelling: the namespace it
1454/// occupies, its identifier, and the prim its alias target proves.
1455fn item_seed<'a>(
1456    item: &'a syn::Item,
1457    siblings: &(impl Iterator<Item = &'a syn::Item> + Clone),
1458    prim_fallback: bool,
1459) -> Option<(Ns, &'a syn::Ident, Origin, Option<PrimTy>)> {
1460    match item {
1461        syn::Item::Fn(i) => Some((Ns::Value, &i.sig.ident, Origin::Item, None)),
1462        syn::Item::Const(i) => Some((Ns::Value, &i.ident, Origin::Item, None)),
1463        syn::Item::Static(i) => Some((Ns::Value, &i.ident, Origin::Item, None)),
1464        syn::Item::Struct(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1465        syn::Item::Enum(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1466        syn::Item::Union(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1467        syn::Item::Trait(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1468        syn::Item::TraitAlias(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1469        syn::Item::Mod(i) => Some((Ns::Type, &i.ident, Origin::Item, None)),
1470        syn::Item::Type(i) => Some((
1471            Ns::Type,
1472            &i.ident,
1473            Origin::TypeAlias,
1474            alias_chain_prim(i, siblings, prim_fallback, 0),
1475        )),
1476        _ => None,
1477    }
1478}
1479
1480/// The seed frame of one module scope: its items at their identity
1481/// spelling, the prims its type aliases and use imports prove.
1482pub(crate) fn seed_frame(
1483    scopes: &ModuleMap<'_>,
1484    path: &[String],
1485    scope: &ModuleScope<'_>,
1486    prim_fallback: bool,
1487) -> Frame {
1488    seed_scope(scopes, path, &scope.items().iter(), None, prim_fallback)
1489}
1490
1491pub(crate) fn seed_items(
1492    scopes: &ModuleMap<'_>,
1493    module: &[String],
1494    items: &[&syn::Item],
1495    outer: &Frame,
1496    prim_fallback: bool,
1497) -> Frame {
1498    seed_scope(
1499        scopes,
1500        module,
1501        &items.iter().copied(),
1502        Some(outer),
1503        prim_fallback,
1504    )
1505}
1506
1507fn seed_scope<'a>(
1508    scopes: &ModuleMap<'_>,
1509    path: &[String],
1510    items: &(impl Iterator<Item = &'a syn::Item> + Clone),
1511    outer: Option<&Frame>,
1512    prim_fallback: bool,
1513) -> Frame {
1514    let mut frame = outer.cloned().unwrap_or_default();
1515    let mut next_id = frame.next_id();
1516    let mut imports = BTreeMap::new();
1517    let steps = Cell::new(0);
1518    for item in items.clone() {
1519        if let Some((ns, ident, origin, prim)) =
1520            item_seed(item, items, prim_fallback && outer.is_none())
1521        {
1522            bind_item_ident(&mut frame, path, ns, ident, origin, prim, &mut next_id);
1523            if let syn::Item::Enum(item) = item {
1524                frame.set_variants(&ident_name(&item.ident), item);
1525            }
1526            if let syn::Item::Struct(item) = item
1527                && !matches!(item.fields, syn::Fields::Named(_))
1528            {
1529                bind_item_ident(
1530                    &mut frame,
1531                    path,
1532                    Ns::Value,
1533                    &item.ident,
1534                    Origin::Item,
1535                    None,
1536                    &mut next_id,
1537                );
1538            }
1539            continue;
1540        }
1541        match item {
1542            syn::Item::ExternCrate(item) => {
1543                bind_extern_crate(&mut frame, path, item, &mut next_id);
1544            }
1545            syn::Item::Use(use_item) => {
1546                frame.glob |= has_glob(&use_item.tree);
1547                use_tree_bindings(
1548                    &mut UseBinder {
1549                        scopes,
1550                        from: path,
1551                        import: use_item,
1552                        imports: &mut imports,
1553                        only_name: None,
1554                        depth: 0,
1555                        active: None,
1556                        steps: &steps,
1557                    },
1558                    &[],
1559                    &use_item.tree,
1560                );
1561            }
1562            syn::Item::Macro(item) => bind_macro_rule(&mut frame, path, item, &mut next_id),
1563            _ => {}
1564        }
1565    }
1566    bind_imports(&mut frame, path, imports, &mut next_id);
1567    for _ in 0..16 {
1568        let mut changed = false;
1569        for item in items.clone() {
1570            if let syn::Item::Type(alias) = item {
1571                if outer.is_none()
1572                    && alias_type_path(&alias.ty).is_some_and(|path| path.segments.len() == 1)
1573                {
1574                    continue;
1575                }
1576                let name = ident_name(&alias.ident);
1577                let lexical_shadow = outer.is_some()
1578                    && alias_type_path(&alias.ty).is_some_and(|target| {
1579                        target.leading_colon.is_none()
1580                            && target.segments.len() > 1
1581                            && target.segments.first().is_some_and(|first| {
1582                                let first_name = ident_name(&first.ident);
1583                                !matches!(first_name.as_str(), "crate" | "self" | "super")
1584                                    && items.clone().any(|item| {
1585                                        alpha::type_item_ident(item)
1586                                            .is_some_and(|ident| ident_name(ident) == first_name)
1587                                    })
1588                            })
1589                    });
1590                let prim = if lexical_shadow {
1591                    None
1592                } else {
1593                    seed_alias_prim(scopes, path, alias, &frame, prim_fallback, &steps)
1594                };
1595                if let Some(binding) = frame.bindings_mut(Ns::Type).get_mut(&name)
1596                    && binding.origin == Origin::TypeAlias
1597                    && binding.prim != prim
1598                {
1599                    binding.prim = prim;
1600                    changed = true;
1601                }
1602            }
1603        }
1604        if !changed {
1605            break;
1606        }
1607    }
1608    frame
1609}
1610
1611/// Bind one generic parameter in `namespaces` under one id and its owner role `label`.
1612fn bind_generic_param(
1613    frame: &mut Frame,
1614    next_id: &mut usize,
1615    namespaces: &[Ns],
1616    ident: &syn::Ident,
1617    origin: Origin,
1618    label: String,
1619) {
1620    let name = ident.to_string();
1621    let binding = Binding {
1622        id: BindingId(*next_id),
1623        canon: label,
1624        origin,
1625        target: None,
1626        raw: false,
1627        prim: None,
1628        port: 0,
1629        value_port: None,
1630        helper: None,
1631        macro_resolution: None,
1632    };
1633    for ns in namespaces {
1634        frame.bind(*ns, &name, binding.clone());
1635    }
1636    *next_id += 1;
1637}
1638
1639/// The owner's generic parameters, each bound under its role label, `labels` in parameter order.
1640fn generics_frame(
1641    module: ModuleId,
1642    modules: &[Module],
1643    generics: &syn::Generics,
1644    labels: Vec<String>,
1645) -> Frame {
1646    let mut frame = Frame::default();
1647    let mut next_id = modules[module.0].seed.next_id();
1648    for (param, label) in generics.params.iter().zip(labels) {
1649        let (namespaces, ident, origin): (&[Ns], _, _) = match param {
1650            syn::GenericParam::Type(ty_param) => (&[Ns::Type], &ty_param.ident, Origin::Generic),
1651            syn::GenericParam::Lifetime(lifetime) => {
1652                (&[Ns::Lifetime], &lifetime.lifetime.ident, Origin::Lifetime)
1653            }
1654            syn::GenericParam::Const(r#const) => {
1655                (&[Ns::Value, Ns::Type], &r#const.ident, Origin::Generic)
1656            }
1657        };
1658        bind_generic_param(&mut frame, &mut next_id, namespaces, ident, origin, label);
1659    }
1660    frame
1661}
1662
1663/// The role labels of a trait's own parameters, their argument positions.
1664fn trait_roles(generics: &syn::Generics) -> Vec<String> {
1665    (0..generics.params.len())
1666        .map(|position| alloc::format!("{OWNER_PARAMETER}t_{position}"))
1667        .collect()
1668}
1669
1670/// The role labels of an impl's parameters, in parameter order. A role is the path of argument
1671/// positions to the first appearance in the self type, then in the trait arguments. Parameters
1672/// appearing in neither take their declaration order among those parameters.
1673fn impl_roles(item: &syn::ItemImpl) -> Vec<String> {
1674    let mut roles = OwnerRoles {
1675        params: &item.generics,
1676        labels: BTreeMap::new(),
1677    };
1678    let mut path = String::from("s");
1679    roles.ty(&item.self_ty, &mut path);
1680    if let Some((trait_path, _)) = &item.trait_ {
1681        let mut path = String::from("t");
1682        roles.path_args(trait_path, &mut path, &mut 0);
1683    }
1684    let mut unplaced = 0;
1685    item.generics
1686        .params
1687        .iter()
1688        .map(|param| {
1689            let role = roles
1690                .labels
1691                .get(&param_key(param))
1692                .cloned()
1693                .unwrap_or_else(|| {
1694                    unplaced += 1;
1695                    alloc::format!("b_{}", unplaced - 1)
1696                });
1697            alloc::format!("{OWNER_PARAMETER}{role}")
1698        })
1699        .collect()
1700}
1701
1702/// A parameter's lookup key, lifetimes apart from type and const names.
1703fn param_key(param: &syn::GenericParam) -> (bool, String) {
1704    match param {
1705        syn::GenericParam::Lifetime(lifetime) => (true, lifetime.lifetime.ident.to_string()),
1706        syn::GenericParam::Type(ty_param) => (false, ty_param.ident.to_string()),
1707        syn::GenericParam::Const(r#const) => (false, r#const.ident.to_string()),
1708    }
1709}
1710
1711/// The first appearance of each owner parameter in its owner type, as a path of positions.
1712struct OwnerRoles<'a> {
1713    params: &'a syn::Generics,
1714    labels: BTreeMap<(bool, String), String>,
1715}
1716
1717impl OwnerRoles<'_> {
1718    /// Record `ident` at `path` when it names a parameter not yet placed.
1719    fn place(&mut self, lifetime: bool, ident: &syn::Ident, path: &str) {
1720        let key = (lifetime, ident.to_string());
1721        if !self.labels.contains_key(&key)
1722            && self
1723                .params
1724                .params
1725                .iter()
1726                .any(|param| param_key(param) == key)
1727        {
1728            self.labels.insert(key, path.to_string());
1729        }
1730    }
1731
1732    /// Place the parameters of `ty`, numbering its direct children by syntactic position.
1733    fn ty(&mut self, ty: &syn::Type, path: &mut String) {
1734        match ty {
1735            syn::Type::Path(type_path) if type_path.qself.is_none() => {
1736                if let Some(ident) = type_path.path.get_ident() {
1737                    self.place(false, ident, path);
1738                } else {
1739                    self.path_args(&type_path.path, path, &mut 0);
1740                }
1741            }
1742            syn::Type::Reference(reference) => {
1743                if let Some(lifetime) = &reference.lifetime {
1744                    let len = enter(path, &mut 0);
1745                    self.place(true, &lifetime.ident, path);
1746                    path.truncate(len);
1747                }
1748                let len = enter(path, &mut 1);
1749                self.ty(&reference.elem, path);
1750                path.truncate(len);
1751            }
1752            syn::Type::Array(array) => {
1753                let len = enter(path, &mut 0);
1754                self.ty(&array.elem, path);
1755                path.truncate(len);
1756                let len = enter(path, &mut 1);
1757                self.expr(&array.len, path);
1758                path.truncate(len);
1759            }
1760            syn::Type::Tuple(tuple) => {
1761                let mut index = 0;
1762                for elem in &tuple.elems {
1763                    let len = enter(path, &mut index);
1764                    self.ty(elem, path);
1765                    path.truncate(len);
1766                }
1767            }
1768            syn::Type::Slice(syn::TypeSlice { elem, .. })
1769            | syn::Type::Ptr(syn::TypePtr { elem, .. })
1770            | syn::Type::Paren(syn::TypeParen { elem, .. })
1771            | syn::Type::Group(syn::TypeGroup { elem, .. }) => {
1772                let len = enter(path, &mut 0);
1773                self.ty(elem, path);
1774                path.truncate(len);
1775            }
1776            other => self.occurrences(|visitor| visitor.visit_type(other), path),
1777        }
1778    }
1779
1780    /// Place the parameters of a path's angle-bracketed arguments, numbered across all segments.
1781    /// Other parameters stay unplaced and take their declaration order among the unplaced.
1782    fn path_args(&mut self, path_syntax: &syn::Path, path: &mut String, index: &mut usize) {
1783        for segment in &path_syntax.segments {
1784            if let syn::PathArguments::AngleBracketed(arguments) = &segment.arguments {
1785                for argument in &arguments.args {
1786                    let len = enter(path, index);
1787                    self.argument(argument, path);
1788                    path.truncate(len);
1789                }
1790            }
1791        }
1792    }
1793
1794    fn argument(&mut self, argument: &syn::GenericArgument, path: &mut String) {
1795        match argument {
1796            syn::GenericArgument::Type(ty) => self.ty(ty, path),
1797            syn::GenericArgument::Lifetime(lifetime) => self.place(true, &lifetime.ident, path),
1798            syn::GenericArgument::Const(expr) => self.expr(expr, path),
1799            other => self.occurrences(|visitor| visitor.visit_generic_argument(other), path),
1800        }
1801    }
1802
1803    fn expr(&mut self, expr: &syn::Expr, path: &str) {
1804        if let syn::Expr::Path(expr_path) = expr
1805            && expr_path.qself.is_none()
1806            && let Some(ident) = expr_path.path.get_ident()
1807        {
1808            self.place(false, ident, path);
1809        } else {
1810            self.occurrences(|visitor| visitor.visit_expr(expr), path);
1811        }
1812    }
1813
1814    /// Place the parameters a syntax node `visit` reaches, each occurrence numbered in order.
1815    fn occurrences(&mut self, visit: impl FnOnce(&mut Occurrences<'_, '_>), path: &str) {
1816        let mut visitor = Occurrences {
1817            roles: self,
1818            path,
1819            count: 0,
1820        };
1821        visit(&mut visitor);
1822    }
1823}
1824
1825/// Append child `index` to `path`, advancing it, and return the length to truncate back to.
1826fn enter(path: &mut String, index: &mut usize) -> usize {
1827    let len = path.len();
1828    path.push('_');
1829    path.push_str(&index.to_string());
1830    *index += 1;
1831    len
1832}
1833
1834/// Parameter occurrences inside syntax with no positional structure, numbered in visit order.
1835struct Occurrences<'r, 'g> {
1836    roles: &'r mut OwnerRoles<'g>,
1837    path: &'r str,
1838    count: usize,
1839}
1840
1841impl Occurrences<'_, '_> {
1842    fn place(&mut self, lifetime: bool, ident: &syn::Ident) {
1843        let label = alloc::format!("{}_v{}", self.path, self.count);
1844        self.count += 1;
1845        self.roles.place(lifetime, ident, &label);
1846    }
1847}
1848
1849impl<'ast> Visit<'ast> for Occurrences<'_, '_> {
1850    fn visit_path(&mut self, path: &'ast syn::Path) {
1851        if let Some(ident) = path.get_ident() {
1852            self.place(false, ident);
1853        }
1854        syn::visit::visit_path(self, path);
1855    }
1856
1857    fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
1858        self.place(true, &lifetime.ident);
1859    }
1860}
1861
1862/// Index every function `scope` declares, nested modules included.
1863#[expect(
1864    clippy::too_many_arguments,
1865    reason = "the index carries its original module and observer evidence"
1866)]
1867#[expect(
1868    clippy::too_many_lines,
1869    reason = "each item kind carries its original owner and ancestry"
1870)]
1871fn index_scope<'a>(
1872    decls: &mut BTreeMap<(*const syn::Signature, *const syn::Block), Decl<'a>>,
1873    module_ids: &ModuleIds,
1874    modules: &[Module],
1875    path: &[String],
1876    scope: ModuleScope<'a>,
1877    observed: bool,
1878    macro_input: bool,
1879    helper_blocked: bool,
1880    gen_frames: &mut BTreeMap<*const syn::Generics, Frame>,
1881) {
1882    let module = module_ids
1883        .get(path)
1884        .and_then(|ids| ids.iter().find(|(key, _)| *key == scope.key()))
1885        .expect("a module id")
1886        .1;
1887    let prim_fallback = modules[module.0].prim_fallback;
1888    let observed = observed || modules[module.0].observed;
1889    let helper_blocked = helper_blocked || modules[module.0].helper_blocked;
1890    for item in scope.items() {
1891        match item {
1892            syn::Item::Fn(f) => {
1893                index_fn(
1894                    decls,
1895                    FnDecl {
1896                        module,
1897                        prim_fallback,
1898                        owner: Owner::Free,
1899                        observed,
1900                        macro_input,
1901                        helper_module: path,
1902                        helper_blocked,
1903                        attrs: &f.attrs,
1904                        generics: None,
1905                    },
1906                    &f.sig,
1907                    &f.block,
1908                );
1909            }
1910            syn::Item::Mod(m) => {
1911                if m.content.is_some() {
1912                    let mut child = path.to_vec();
1913                    child.push(unraw(&m.ident.to_string()).to_string());
1914                    index_scope(
1915                        decls,
1916                        module_ids,
1917                        modules,
1918                        &child,
1919                        ModuleScope::Inline(m),
1920                        observed || live_attr(&m.attrs),
1921                        macro_input || crate::drift::has_macro_attribute(&m.attrs),
1922                        helper_blocked || blocking_attr(&m.attrs),
1923                        gen_frames,
1924                    );
1925                }
1926            }
1927            syn::Item::Impl(i) => {
1928                let text = i.self_ty.to_token_stream().to_string();
1929                let owner = if i.trait_.is_some() {
1930                    Owner::Trait(text)
1931                } else {
1932                    Owner::Inherent(text)
1933                };
1934                let observed = observed || live_attr(&i.attrs);
1935                let macro_input = macro_input || crate::drift::has_macro_attribute(&i.attrs);
1936                let key = &raw const i.generics;
1937                gen_frames
1938                    .entry(key)
1939                    .or_insert_with(|| generics_frame(module, modules, &i.generics, impl_roles(i)));
1940                for member in &i.items {
1941                    if let syn::ImplItem::Fn(f) = member {
1942                        index_fn(
1943                            decls,
1944                            FnDecl {
1945                                module,
1946                                prim_fallback,
1947                                owner: owner.clone(),
1948                                observed,
1949                                macro_input,
1950                                helper_module: path,
1951                                helper_blocked: helper_blocked || blocking_attr(&i.attrs),
1952                                attrs: &f.attrs,
1953                                generics: Some(key),
1954                            },
1955                            &f.sig,
1956                            &f.block,
1957                        );
1958                    }
1959                }
1960            }
1961            syn::Item::Trait(t) => {
1962                let text = t.ident.to_string();
1963                let observed = observed || live_attr(&t.attrs);
1964                let macro_input = macro_input || crate::drift::has_macro_attribute(&t.attrs);
1965                let key = &raw const t.generics;
1966                gen_frames.entry(key).or_insert_with(|| {
1967                    generics_frame(module, modules, &t.generics, trait_roles(&t.generics))
1968                });
1969                for member in &t.items {
1970                    if let syn::TraitItem::Fn(f) = member
1971                        && let Some(block) = &f.default
1972                    {
1973                        index_fn(
1974                            decls,
1975                            FnDecl {
1976                                module,
1977                                prim_fallback,
1978                                owner: Owner::Trait(text.clone()),
1979                                observed,
1980                                macro_input,
1981                                attrs: &f.attrs,
1982                                helper_module: path,
1983                                helper_blocked: helper_blocked || blocking_attr(&t.attrs),
1984                                generics: Some(key),
1985                            },
1986                            &f.sig,
1987                            block,
1988                        );
1989                    }
1990                }
1991            }
1992            _ => {}
1993        }
1994    }
1995}
1996
1997/// The module facts one indexed function is recorded with.
1998#[expect(
1999    clippy::struct_excessive_bools,
2000    reason = "primitive, observer, macro input and helper facts of a function are independent"
2001)]
2002struct FnDecl<'a, 'path> {
2003    module: ModuleId,
2004    prim_fallback: bool,
2005    owner: Owner,
2006    observed: bool,
2007    /// An enclosing inline module, impl or trait carries an attribute macro reading the function.
2008    macro_input: bool,
2009    helper_module: &'path [String],
2010    helper_blocked: bool,
2011    attrs: &'a [syn::Attribute],
2012    generics: Option<*const syn::Generics>,
2013}
2014
2015fn index_fn<'a>(
2016    decls: &mut BTreeMap<(*const syn::Signature, *const syn::Block), Decl<'a>>,
2017    facts: FnDecl<'a, '_>,
2018    sig: &syn::Signature,
2019    block: &syn::Block,
2020) {
2021    let key = (
2022        core::ptr::from_ref::<syn::Signature>(sig),
2023        core::ptr::from_ref::<syn::Block>(block),
2024    );
2025    decls.insert(
2026        key,
2027        Decl {
2028            module: facts.module,
2029            attrs: facts.attrs,
2030            owner: facts.owner,
2031            prim_fallback: facts.prim_fallback,
2032            observed: facts.observed || live_attr(facts.attrs),
2033            enclosing_observed: facts.macro_input,
2034            helper_module: facts.helper_module.to_vec(),
2035            helper_blocked: facts.helper_blocked || blocking_attr(facts.attrs),
2036            generics: facts.generics,
2037        },
2038    );
2039}
2040
2041/// Whether `meta` takes part in the comparison. `cfg`, the hints that
2042/// leave the body's meaning alone, the attributes that export a symbol,
2043/// and a `cfg_attr` wrapping only those do not.
2044pub(crate) fn live(meta: &syn::Meta) -> bool {
2045    let path = meta.path();
2046    if path.is_ident("cfg_attr") {
2047        return wrapped(meta, 1).is_none_or(|metas| metas.iter().any(live));
2048    }
2049    if path.is_ident("unsafe") {
2050        return wrapped(meta, 0).is_none_or(|metas| metas.iter().any(live));
2051    }
2052    // A `doc` computed by a macro runs at compile time and can fail.
2053    if path.is_ident("doc")
2054        && matches!(meta, syn::Meta::NameValue(nv) if matches!(nv.value, syn::Expr::Macro(_)))
2055    {
2056        return true;
2057    }
2058    let inert = [
2059        "cfg",
2060        "inline",
2061        "cold",
2062        "must_use",
2063        "track_caller",
2064        "doc",
2065        "allow",
2066        "warn",
2067        "deny",
2068        "forbid",
2069        "expect",
2070        "no_mangle",
2071        "export_name",
2072    ];
2073    !inert.iter().any(|name| path.is_ident(name))
2074        && path.segments.first().is_none_or(|s| s.ident != "rustfmt")
2075}
2076
2077/// Whether any attribute in `attrs` takes part in the comparison.
2078pub(crate) fn live_attr(attrs: &[syn::Attribute]) -> bool {
2079    attrs.iter().any(|attr| live(&attr.meta))
2080}
2081
2082/// Whether `meta` can change a helper's body or observe its caller.
2083pub(crate) fn blocking_meta(meta: &syn::Meta) -> bool {
2084    let path = meta.path();
2085    path.is_ident("cfg")
2086        || path.is_ident("cfg_attr")
2087        || path.is_ident("track_caller")
2088        || (path.is_ident("unsafe")
2089            && wrapped(meta, 0).is_none_or(|metas| metas.iter().any(blocking_meta)))
2090        || live(meta)
2091}
2092
2093/// Whether any attribute in `attrs` can change a helper's denotation.
2094pub(crate) fn blocking_attr(attrs: &[syn::Attribute]) -> bool {
2095    attrs.iter().any(|attr| blocking_meta(&attr.meta))
2096}
2097
2098/// The metas `meta` wraps after its first `skip` arguments, `None` when
2099/// they don't parse.
2100///
2101/// ```
2102/// let attr: syn::Attribute = syn::parse_quote!(#[cfg_attr(unix, inline, cold)]);
2103/// let metas = syn_canon::wrapped(&attr.meta, 1).unwrap();
2104/// assert!(metas[0].path().is_ident("inline") && metas[1].path().is_ident("cold"));
2105/// ```
2106#[must_use]
2107pub fn wrapped(meta: &syn::Meta, skip: usize) -> Option<Vec<syn::Meta>> {
2108    let syn::Meta::List(list) = meta else {
2109        return None;
2110    };
2111    let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
2112    list.parse_args_with(parser)
2113        .ok()
2114        .map(|metas| metas.into_iter().skip(skip).collect())
2115}