Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-driver/src/walk/gates.rs

//! Features and gates: feature nodes, ENABLES, GATED_BY, and the stubs of
//! items the build compiled out.
//!
//! Each fact from the source that is the truth of it:
//!
//! | fact | source |
//! |---|---|
//! | which features a package DECLARES, and what each switches on | the cargo-metadata map (`[features]`, resolved to package@version) |
//! | which features this crate was COMPILED with | rustc's own `--cfg feature="…"` set |
//! | which kept item is gated by what (its OWN `#[cfg]`) | rustc's `CfgTrace` (it evaluated the `cfg` and kept the item) |
//! | which item was compiled OUT, and by what | a token scan of the source (`cfg_scan`) — rustc has nothing |
//! | which item holds gated code INSIDE it (`partial`) | the same token scan: a `cfg` on a statement / expression / `use` / macro call, a `cfg!` |
//!
//! The fourth row is the honest part: a compiled-out item has no DefId, so
//! its row is a `cfg_gated_out` STUB — name, file, line, predicate — never a
//! resolved item. Active or not is decided by rustc, not by evaluating the
//! predicate here: an item the scan finds is active exactly when rustc holds
//! a `CfgTrace` at its attribute's position.
//!
//! The fifth is a fact about the SOURCE, the same in every configuration: a
//! part of the item is conditional on the feature. Whether that part is in
//! this build is not recorded (and for `cfg!` there is nothing to record: the
//! code is always compiled). The item it belongs to is the innermost real
//! node around the site's line — or the stub around it, inside a
//! compiled-out item.

use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::time::Instant;

use mimir_driver::cfg_scan::{self, CfgAttr, Seg};
use mimir_driver::{EnableForm, PackageVersion, VersionMap, ENV_CFG_SCAN, ENV_VERSIONS};
use mimir_schema::moniker::{self, Suffix};
use mimir_schema::{kind, EdgeKind, Level, NodeRow, Origin};
use rustc_attr_ir::{Attribute, AttributeKind};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_span::{FileName, SourceFile};

use super::{Gate, Walker};

/// A cfg attribute's place: (source file, 1-based line, 0-based char column of `#`).
type AttrPos = (usize, u32, u32);

/// Where the contents of a compiled-out module hang: the moniker prefix its
/// items are named in, and the node (real or stub) that contains them.
#[derive(Clone)]
struct Base {
    scope: String,
    parent: String,
}

/// One file to scan.
struct ScanFile<'a> {
    text: &'a str,
    /// the `file` column value
    file: String,
    /// where the file is on disk (for `mod x;` below it)
    path: PathBuf,
    /// rustc loaded it (key into the active set); `None` = it never did, so
    /// nothing in it is active
    key: Option<usize>,
    /// set when the whole file is compiled out
    base: Option<Base>,
    /// its child modules live next to it (`mod.rs`, `lib.rs`, `main.rs`, a
    /// crate root, a `#[path]` file), not in a directory named after it
    mod_rs: bool,
    depth: usize,
}

/// The state of one `walk_gated_out` pass.
struct ScanState {
    /// every cfg attribute rustc evaluated true and kept
    active: HashSet<AttrPos>,
    /// `file` → real container nodes `(start, end, id)`
    containers: HashMap<String, Vec<(u32, u32, String)>>,
    /// `file` → every real item-ish node `(start, end, id)`: what a cfg site
    /// inside a body belongs to
    owners: HashMap<String, Vec<(u32, u32, String)>>,
    /// id → `cfg` of the kept nodes that carry one
    real_cfg: HashMap<String, String>,
    /// loaded module files → where their `mod x;` hangs (for a file that
    /// gates itself with `#![cfg(…)]`)
    declared_at: HashMap<PathBuf, (Base, String)>,
    /// cfg attributes already counted as attributed
    counted: HashSet<(String, u32, u32)>,
    visited: HashSet<PathBuf>,
    root_id: String,
}

fn file_key(f: &SourceFile) -> usize {
    f as *const SourceFile as usize
}

fn canon(p: &Path) -> PathBuf {
    std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf())
}

impl<'tcx> Walker<'tcx> {
    /// This crate's package with its declared features (their own file next
    /// to the version map).
    fn package(&self) -> Option<PackageVersion> {
        let m = self.versions.as_ref()?;
        // by its manifest first: a build can hold a path checkout AND the
        // published copy of one name and version, each with its own manifest
        let dir = self.local.manifest_dir.clone();
        let same = |p: &&PackageVersion| p.name == self.local.package && p.version == self.local.version;
        let mut p = m
            .packages
            .iter()
            .filter(same)
            .find(|p| dir.as_ref().is_some_and(|d| Path::new(&p.manifest_path).parent().is_some_and(|pd| canon(pd) == canon(d))))
            .or_else(|| m.packages.iter().find(same))
            .cloned()?;
        let map = std::env::var_os(ENV_VERSIONS)?;
        p.features = match &dir {
            Some(d) => VersionMap::load_features_at(Path::new(&map), &p.name, &p.version, d),
            None => VersionMap::load_features(Path::new(&map), &p.name, &p.version),
        };
        Some(p)
    }

    fn package_origin(&self, package: &str, version: &str) -> Origin {
        if package == self.local.package && version == self.local.version {
            return self.local.origin;
        }
        let local = self.versions.as_ref().is_some_and(|m| m.packages.iter().any(|p| p.local && p.name == package && p.version == version));
        if local { Origin::Local } else { Origin::Library }
    }

    /// The feature node of `package@version` (written once): `declared` = its
    /// manifest declares it, else a reference.
    fn feature_node(&mut self, package: &str, version: &str, name: &str, declared: bool, line: Option<u32>, manifest: Option<&str>) -> String {
        let id = moniker::feature(package, version, name);
        if self.node_ids.contains(&id) {
            return id;
        }
        let origin = self.package_origin(package, version);
        let own = package == self.local.package && version == self.local.version;
        // a feature hangs off its package's LIB root — the one crate node all
        // units of the package agree on; a package without a lib has none
        let has_lib = self.versions.as_ref().is_none_or(|m| {
            m.packages.iter().find(|p| p.name == package && p.version == version).is_none_or(|p| !p.crate_names.is_empty())
        });
        let mut row = NodeRow::new(id.clone(), kind::FEATURE, Level::Item, name, package, version, origin);
        row.parent = has_lib.then(|| moniker::crate_moniker(package, version));
        row.repo = if own && origin == Origin::Local { self.local.repo.clone() } else { None };
        row.valid_from_sha = self.local.sha.clone();
        row.defined_here = declared;
        if let (Some(m), Some(l)) = (manifest, line) {
            row.file = Some(self.rel_path(m));
            row.start_line = Some(l);
            row.end_line = Some(l);
        }
        if declared {
            self.x.feature_nodes += 1;
        } else {
            self.x.feature_refs += 1;
        }
        self.push_node(row);
        id
    }

    /// A feature of the LOCAL package a cfg or the compile mentions: the
    /// declared node when the manifest has it, else a reference.
    fn local_feature(&mut self, name: &str) -> String {
        let (p, v) = (self.local.package.clone(), self.local.version.clone());
        self.feature_node(&p, &v, name, false, None, None)
    }

    /// The crate node of a dependency an ENABLES edge points at — a reference
    /// stub (an optional dependency that is off is compiled by nobody).
    fn crate_ref(&mut self, package: &str, version: &str) -> String {
        let id = moniker::crate_moniker(package, version);
        if self.node_ids.contains(&id) {
            return id;
        }
        let name = self
            .versions
            .as_ref()
            .and_then(|m| m.packages.iter().find(|p| p.name == package && p.version == version))
            .and_then(|p| p.crate_names.first().cloned())
            .unwrap_or_else(|| package.replace('-', "_"));
        let mut row = NodeRow::new(id.clone(), kind::CRATE, Level::Crate, name, package, version, self.package_origin(package, version));
        row.valid_from_sha = self.local.sha.clone();
        row.defined_here = false;
        self.push_node(row);
        id
    }

    /// Feature nodes of this package, what each switches on (ENABLES /
    /// ENABLES_WEAK), and crate → the features this compile was built with.
    pub(super) fn walk_features(&mut self) {
        let tcx = self.tcx;
        for (name, value) in tcx.sess.config.iter() {
            if let (true, Some(v)) = (*name == rustc_span::sym::feature, value) {
                self.active_features.insert(v.to_string());
            }
        }
        let (pkg_name, pkg_version) = (self.local.package.clone(), self.local.version.clone());
        let root = self.moniker(LOCAL_CRATE.as_def_id());
        let lib_root = moniker::crate_moniker(&pkg_name, &pkg_version);
        if let Some(pkg) = self.package() {
            for f in &pkg.features {
                let id = self.feature_node(&pkg_name, &pkg_version, &f.name, true, f.line, Some(&pkg.manifest_path));
                // CONTAINS is written by the unit that owns the lib root's id
                if root == lib_root {
                    self.push_edge(lib_root.clone(), id, EdgeKind::Contains, None, None);
                }
            }
            for f in &pkg.features {
                let from = moniker::feature(&pkg_name, &pkg_version, &f.name);
                for e in &f.enables {
                    let dep = e.dep_package.as_deref().zip(e.dep_version.as_deref());
                    match (e.form, dep, e.feature.as_deref()) {
                        (EnableForm::Feature, _, Some(other)) => {
                            let to = self.local_feature(other);
                            self.push_edge(from.clone(), to, EdgeKind::Enables, None, None);
                        }
                        (EnableForm::Dep, Some((dp, dv)), _) => {
                            let to = self.crate_ref(dp, dv);
                            self.push_edge(from.clone(), to, EdgeKind::Enables, None, None);
                        }
                        (EnableForm::DepFeature, Some((dp, dv)), Some(df)) => {
                            // `x/f` switches on the dependency AND its feature
                            let krate = self.crate_ref(dp, dv);
                            self.push_edge(from.clone(), krate, EdgeKind::Enables, None, None);
                            let to = self.feature_node(dp, dv, df, false, None, None);
                            self.push_edge(from.clone(), to, EdgeKind::Enables, None, None);
                        }
                        (EnableForm::WeakDepFeature, Some((dp, dv)), Some(df)) => {
                            // `x?/f`: only if `x` is on by another route
                            let to = self.feature_node(dp, dv, df, false, None, None);
                            self.push_edge(from.clone(), to, EdgeKind::EnablesWeak, None, None);
                        }
                        // the dependency is not in the lockfile under ONE
                        // version: no edge is better than a guessed one
                        _ => self.x.feature_enables_unresolved.push(format!("{} = {}", f.name, e.raw)),
                    }
                }
            }
        }
        // the build's own selection
        for f in self.active_features.clone() {
            let to = self.local_feature(&f);
            self.push_edge(root.clone(), to, EdgeKind::Enables, None, None);
        }
    }

    /// GATED_BY from one KEPT item to every feature its own cfgs mention.
    pub(super) fn gate_edges(&mut self, id: &str, gate: &Gate) {
        for (sp, features) in &gate.sites {
            let (file, line, _) = self.file_lines(*sp, true);
            for (feature, negated) in features {
                self.gate_site(id, feature, *negated, true, file.clone(), line);
            }
        }
    }

    /// One site of a GATED_BY edge `owner` → `feature`: the edge (its count
    /// and span follow the sites), `negated` = every site so far is negated,
    /// `partial` = no site so far is the owner's own `#[cfg]`.
    fn gate_site(&mut self, owner: &str, feature: &str, negated: bool, own_cfg: bool, file: Option<String>, line: Option<u32>) {
        let to = self.local_feature(feature);
        self.push_edge(owner.to_string(), to.clone(), EdgeKind::GatedBy, file, line);
        let Some(&i) = self.edge_index.get(&(owner.to_string(), to, EdgeKind::GatedBy)) else { return };
        let st = self.gate_state.entry(i).or_insert((true, false));
        st.0 &= negated;
        st.1 |= own_cfg;
        self.edges[i].negated = Some(st.0);
        self.edges[i].partial = Some(!st.1);
    }

    /// The `file` column value of a source file rustc loaded.
    fn file_column(&self, f: &SourceFile) -> Option<(String, PathBuf)> {
        match &f.name {
            FileName::Real(_) => {
                let local = f.name.prefer_local_unconditionally().to_string();
                Some((self.rel_path(&local), PathBuf::from(local)))
            }
            _ => None,
        }
    }

    /// Stubs for the items the build compiled OUT, with their GATED_BY edges.
    pub(super) fn walk_gated_out(&mut self) {
        // the workspace's own crates by default; `all` = dependencies too
        let scan = match std::env::var(ENV_CFG_SCAN).as_deref() {
            Ok("all") => true,
            Ok("off") => false,
            _ => self.local.origin == Origin::Local,
        };
        if !scan {
            return;
        }
        let t = Instant::now();
        let tcx = self.tcx;
        let sm = tcx.sess.source_map();

        // ---- rustc's half: every cfg attribute it evaluated TRUE and kept,
        // on any HIR node (items, fields, statements …)
        let mut active: HashSet<AttrPos> = HashSet::new();
        for owner in tcx.hir_crate_items(()).owners() {
            for attrs in tcx.hir_attr_map(owner).map.values() {
                for a in attrs.iter() {
                    if let Attribute::Parsed(AttributeKind::CfgTrace(v)) = a {
                        for (_, sp) in v.iter() {
                            let lo = sm.lookup_char_pos(sp.lo());
                            active.insert((file_key(&lo.file), lo.line as u32, lo.col.0 as u32));
                        }
                    }
                }
            }
        }
        let mut containers: HashMap<String, Vec<(u32, u32, String)>> = HashMap::new();
        let mut owners: HashMap<String, Vec<(u32, u32, String)>> = HashMap::new();
        let mut real_cfg = HashMap::new();
        for n in self.nodes.iter().filter(|n| n.defined_here) {
            if let Some(c) = &n.cfg {
                real_cfg.insert(n.id.clone(), c.clone());
            }
            let (Some(f), Some(s), Some(e)) = (&n.file, n.start_line, n.end_line) else { continue };
            if matches!(n.kind.as_str(), kind::CRATE | kind::MOD | kind::IMPL | kind::TRAIT | kind::STRUCT | kind::ENUM | kind::UNION) {
                containers.entry(f.clone()).or_default().push((s, e, n.id.clone()));
            }
            if !matches!(n.kind.as_str(), kind::PARAM | kind::GENERIC | kind::FIELD | kind::FEATURE) {
                owners.entry(f.clone()).or_default().push((s, e, n.id.clone()));
            }
        }
        let root_id = self.moniker(LOCAL_CRATE.as_def_id());
        let mut st = ScanState {
            active,
            containers,
            owners,
            real_cfg,
            declared_at: HashMap::new(),
            counted: HashSet::new(),
            visited: HashSet::new(),
            root_id,
        };

        // ---- the scan's half: every `.rs` file of this crate rustc loaded,
        // in load order (a parent module before the files it declares)
        let root_file = sm.lookup_char_pos(tcx.hir_get_module(rustc_hir::def_id::LocalModId::new_unchecked(rustc_hir::def_id::CRATE_DEF_ID)).0.spans.inner_span.lo()).file;
        let files: Vec<_> = sm.files().iter().filter(|f| f.cnum == LOCAL_CRATE).cloned().collect();
        for f in files {
            let Some((file, path)) = self.file_column(&f) else { continue };
            let Some(src) = f.src.as_ref() else { continue };
            if path.extension().is_none_or(|e| e != "rs") {
                continue;
            }
            let is_root = file_key(&f) == file_key(&root_file);
            let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or_default().to_string();
            let mod_rs = is_root || matches!(stem.as_str(), "mod" | "lib" | "main");
            st.visited.insert(canon(&path));
            self.scan_file(&mut st, ScanFile { text: src.as_str(), file, path, key: Some(file_key(&f)), base: None, mod_rs, depth: 0 }, is_root);
        }
        self.x.cfg_scan_ms = t.elapsed().as_millis();
    }

    /// The innermost node of `spans[file]` that holds `line` (the crate root
    /// when none does).
    fn innermost<'a>(spans: &'a HashMap<String, Vec<(u32, u32, String)>>, root: &'a str, file: &str, line: u32) -> &'a str {
        spans
            .get(file)
            .into_iter()
            .flatten()
            .filter(|(s, e, _)| *s <= line && line <= *e)
            .min_by_key(|(s, e, _)| (e - s, u32::MAX - *s))
            .map(|(_, _, id)| id.as_str())
            .unwrap_or(root)
    }

    /// The innermost real container node of `file` that holds `line`.
    fn real_parent(st: &ScanState, file: &str, line: u32) -> Base {
        let id = Self::innermost(&st.containers, &st.root_id, file, line);
        Base { scope: moniker::child_scope(id).to_string(), parent: id.to_string() }
    }

    fn scan_file(&mut self, st: &mut ScanState, f: ScanFile<'_>, is_root: bool) {
        let scan = match cfg_scan::scan(f.text) {
            Ok(s) => s,
            Err(e) => {
                self.x.cfg_scan_failed.push(format!("{}: {e}", f.file));
                return;
            }
        };
        self.x.cfg_scan_files += 1;
        self.x.feature_cfg_mentions += scan.mentions;
        let is_active = |st: &ScanState, c: &CfgAttr| f.key.is_some_and(|k| st.active.contains(&(k, c.line, c.col)));

        // ---- the file as a whole: compiled out from outside (`base`), or by
        // its own `#![cfg(…)]`
        let mut base = f.base.clone();
        if base.is_none() && !scan.file_cfgs.is_empty() && !scan.file_cfgs.iter().all(|c| is_active(st, c)) && !is_root {
            let (at, name) = match st.declared_at.get(&canon(&f.path)) {
                Some((b, n)) => (b.clone(), n.clone()),
                None => {
                    self.x.cfg_scan_unresolved_mods.push(format!("{} (gated by its own #![cfg], declaring `mod` not found)", f.file));
                    let stem = f.path.file_stem().and_then(|s| s.to_str()).unwrap_or_default().to_string();
                    (Base { scope: moniker::child_scope(&st.root_id).to_string(), parent: st.root_id.clone() }, stem)
                }
            };
            let own = moniker::descriptor(Suffix::Namespace, &name);
            let predicate = scan.file_cfgs.iter().map(|c| c.text.as_str()).collect::<Vec<_>>().join(" && ");
            let features = cfg_scan::merge_features(scan.file_cfgs.iter().flat_map(|c| c.features.iter().cloned()));
            base = Some(if features.is_empty() {
                Base { scope: format!("{}{own}", at.scope), parent: at.parent }
            } else {
                let id = self.gated_stub(st, &f.file, &at, "mod", &name, &own, 1, 1, &scan.file_cfgs, &predicate, "private", None);
                Base { scope: id.clone(), parent: id }
            });
        } else if scan.file_cfgs.iter().all(|c| is_active(st, c)) {
            self.count_attributed(st, &f.file, &scan.file_cfgs);
        } else if is_root {
            // the crate gates ITSELF out (`#![cfg(…)]` in its root file): rustc
            // kept the root node and nothing else, and no `CfgTrace`
            self.count_attributed(st, &f.file, &scan.file_cfgs);
            for c in &scan.file_cfgs {
                for (feature, negated) in cfg_scan::merge_features(c.features.iter().cloned()) {
                    let root = st.root_id.clone();
                    self.gate_site(&root, &feature, negated, true, Some(f.file.clone()), Some(c.line));
                }
            }
        }

        // ---- items
        let n = scan.items.len();
        let mut gated = vec![false; n];
        let mut stub_id: Vec<Option<String>> = vec![None; n];
        for (idx, it) in scan.items.iter().enumerate() {
            let outer = base.is_some() || it.scope.iter().any(|s| s.item.is_some_and(|k| gated[k]));
            gated[idx] = outer || !it.cfgs.iter().all(|c| is_active(st, c));
            if !gated[idx] {
                // rustc kept it: its GATED_BY came from `CfgTrace`
                self.count_attributed(st, &f.file, &it.cfgs);
                continue;
            }
            let features = it.features();
            if features.is_empty() {
                continue;
            }
            let at = Self::resolve(st, &f.file, base.as_ref(), &it.scope, it.line, &gated, &stub_id);
            let predicate = it.predicate();
            // the same item, kept: a proc macro that rewrote its tokens moved
            // the attribute, so the position did not match — not compiled out
            let resolved = format!("{}{}", at.scope, it.descriptor);
            if st.real_cfg.get(&resolved).is_some_and(|c| *c == predicate) {
                gated[idx] = false;
                self.count_attributed(st, &f.file, &it.cfgs);
                continue;
            }
            let id = self.gated_stub(st, &f.file, &at, it.kind, &it.name, &it.descriptor, it.line, it.end_line, &it.cfgs, &predicate, &it.vis, it.doc.clone());
            stub_id[idx] = Some(id);
        }

        // ---- `mod x;`: a compiled-out one is a file rustc never loaded
        for m in &scan.mods {
            let dir = if f.mod_rs {
                f.path.parent().map(Path::to_path_buf).unwrap_or_default()
            } else {
                f.path.with_extension("")
            };
            let mut inline = dir.clone();
            for s in m.scope.iter().filter_map(|s| s.module.as_deref()) {
                inline.push(s);
            }
            let (candidates, by_path) = match &m.path {
                // `#[path]`: relative to this file's directory (to the inline
                // module's, inside one)
                Some(p) if m.scope.is_empty() => (vec![f.path.parent().map(Path::to_path_buf).unwrap_or_default().join(p)], true),
                Some(p) => (vec![inline.join(p)], true),
                None => (vec![inline.join(format!("{}.rs", m.name)), inline.join(&m.name).join("mod.rs")], false),
            };
            let found = candidates.iter().find(|p| p.is_file()).cloned();
            let mut at = Self::resolve(st, &f.file, base.as_ref(), &m.scope, m.line, &gated, &stub_id);
            let out = base.is_some() || m.item.is_some_and(|i| gated[i]) || m.scope.iter().any(|s| s.item.is_some_and(|k| gated[k]));
            if !out {
                // rustc loaded it; remember where it hangs in case it gates itself
                if let Some(p) = found {
                    st.declared_at.insert(canon(&p), (at, m.name.clone()));
                }
                continue;
            }
            match m.item.and_then(|i| stub_id[i].clone()) {
                Some(id) => at = Base { scope: id.clone(), parent: id },
                None => at.scope.push_str(&moniker::descriptor(Suffix::Namespace, &m.name)),
            }
            let Some(p) = found else {
                self.x.cfg_scan_unresolved_mods.push(format!("{}:{} mod {}", f.file, m.line, m.name));
                continue;
            };
            if f.depth >= 16 || !st.visited.insert(canon(&p)) {
                continue;
            }
            let Ok(text) = std::fs::read_to_string(&p) else {
                self.x.cfg_scan_unresolved_mods.push(format!("{}:{} mod {} (unreadable {})", f.file, m.line, m.name, p.display()));
                continue;
            };
            let file = self.rel_path(&p.to_string_lossy());
            let mod_rs = by_path || p.file_stem().is_some_and(|s| s == "mod");
            self.scan_file(st, ScanFile { text: &text, file, path: p, key: None, base: Some(at), mod_rs, depth: f.depth + 1 }, false);
        }

        // ---- every other cfg that mentions a feature gates a PART of the
        // item it stands in: a statement, an expression, a match arm, a
        // `use`, a macro call (`cfg_if!`), a `cfg!(…)`. Kept or compiled out
        // makes no difference to that fact, so it is not looked up.
        for site in &scan.sites {
            if st.counted.contains(&(f.file.clone(), site.line, site.col)) {
                // an item's own gate, written above (or by `gate_edges`)
                continue;
            }
            let owner = Self::site_owner(st, &f.file, base.as_ref(), &scan, &stub_id, site.line);
            for (feature, negated) in cfg_scan::merge_features(site.features.iter().cloned()) {
                self.gate_site(&owner, &feature, negated, false, Some(f.file.clone()), Some(site.line));
            }
            self.x.feature_cfg_partial += site.features.len();
        }
    }

    /// What a cfg site at `line` gates a PART of: the innermost compiled-out
    /// stub around it, else (in a file that is compiled out as a whole) what
    /// the file hangs on, else the innermost real item around it.
    fn site_owner(st: &ScanState, file: &str, base: Option<&Base>, scan: &cfg_scan::Scan, stub_id: &[Option<String>], line: u32) -> String {
        let stub = scan
            .items
            .iter()
            .zip(stub_id)
            .filter_map(|(it, id)| Some((it, id.as_ref()?)))
            .filter(|(it, _)| it.line <= line && line <= it.end_line)
            .min_by_key(|(it, _)| it.end_line - it.line);
        match (stub, base) {
            (Some((_, id)), _) => id.clone(),
            (None, Some(b)) => b.parent.clone(),
            (None, None) => Self::innermost(&st.owners, &st.root_id, file, line).to_string(),
        }
    }

    /// Where a compiled-out item hangs: the innermost REAL container (or the
    /// file's `base`), then every compiled-out container between it and the
    /// item — a stub's id when it has one, its descriptor when it does not.
    fn resolve(st: &ScanState, file: &str, base: Option<&Base>, scope: &[Seg], line: u32, gated: &[bool], stub_id: &[Option<String>]) -> Base {
        let (mut at, first_out) = match base {
            Some(b) => (b.clone(), 0),
            None => (Self::real_parent(st, file, line), scope.iter().position(|s| s.item.is_some_and(|k| gated[k])).unwrap_or(scope.len())),
        };
        for seg in &scope[first_out..] {
            match seg.item.and_then(|k| stub_id[k].clone()) {
                Some(id) => at = Base { scope: id.clone(), parent: id },
                None => at.scope.push_str(&seg.descriptor),
            }
        }
        at
    }

    /// Cfg attributes that became GATED_BY sites: count their feature atoms once.
    fn count_attributed(&mut self, st: &mut ScanState, file: &str, cfgs: &[CfgAttr]) {
        for c in cfgs {
            if st.counted.insert((file.to_string(), c.line, c.col)) {
                self.x.feature_cfg_attributed += c.features.len();
            }
        }
    }

    /// One `cfg_gated_out` stub: the node, CONTAINS from where it hangs, and
    /// GATED_BY (its own gate) to every feature its predicate mentions.
    #[allow(clippy::too_many_arguments)]
    fn gated_stub(
        &mut self,
        st: &mut ScanState,
        file: &str,
        at: &Base,
        item_kind: &str,
        name: &str,
        descriptor: &str,
        line: u32,
        end_line: u32,
        cfgs: &[CfgAttr],
        predicate: &str,
        vis: &str,
        doc: Option<String>,
    ) -> String {
        let id = moniker::cfg_gated_out(&at.scope, descriptor, predicate);
        self.count_attributed(st, file, cfgs);
        if self.node_ids.contains(&id) {
            // the same item twice (two identical gated blocks): one node
            return id;
        }
        let level = match item_kind {
            "mod" => Level::Module,
            "field" | "variant" | "method" | "assoc_const" | "assoc_type" => Level::Part,
            _ => Level::Item,
        };
        let mut row = NodeRow::new(id.clone(), kind::CFG_GATED_OUT, level, name, self.local.package.clone(), self.local.version.clone(), self.local.origin);
        row.parent = Some(at.parent.clone());
        row.repo = self.local.repo.clone();
        row.file = Some(file.to_string());
        row.start_line = Some(line);
        row.end_line = Some(end_line.max(line));
        row.cfg = Some(predicate.to_string());
        row.valid_from_sha = self.local.sha.clone();
        // what the source says it is — NOT a resolved kind
        row.ty = Some(item_kind.to_string());
        row.visibility = Some(vis.to_string());
        row.has_doc = Some(doc.is_some());
        row.doc = doc;
        self.push_node(row);
        self.x.gated_out_stubs += 1;
        self.push_edge(at.parent.clone(), id.clone(), EdgeKind::Contains, None, None);
        for c in cfgs {
            for (feature, negated) in cfg_scan::merge_features(c.features.iter().cloned()) {
                self.gate_site(&id, &feature, negated, true, Some(file.to_string()), Some(c.line));
            }
        }
        id
    }
}