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.rs

//! The `after_analysis` walk: nodes + edges of ONE crate, from `tcx`.
//!
//! ## Moniker rules (one formatter: `mimir_schema::moniker`)
//!
//! `rust-analyzer cargo <package> <version> <descriptors>` where descriptors are
//! built from the DefId's parent chain:
//!
//! | DefKind                                   | descriptor                    |
//! |-------------------------------------------|-------------------------------|
//! | crate root                                | `crate/` (node id only; children start empty) |
//! | Mod                                       | `name/`                       |
//! | Struct Enum Union Trait TraitAlias TyAlias ForeignTy Variant | `name#`    |
//! | Fn (free, nested, foreign)                | `name().`                     |
//! | Const Static Field                        | `name.`                       |
//! | Macro (bang) / (derive, attribute)        | `name!` / `Name:`             |
//! | TyParam ConstParam                        | `[name]`                      |
//! | Impl                                      | `impl#[Self]` / `impl#[Self][Trait]` |
//! | AssocFn/Const/Ty in a trait               | `Trait#name().` …             |
//! | AssocFn/Const/Ty in an impl               | `impl#[Self][Trait]name().` (rust-analyzer) |
//! | fn param                                  | `<fn>(name)` (`_<i>` for `_` / unnamed) |
//! | Closure AnonConst                         | `$n:` (n = rustc's DefPath disambiguator) |
//! | Ctor ForeignMod Use OpaqueTy …            | transparent (parent's scope)  |
//!
//! Lang crates (std, core, alloc …) carry rust-analyzer's version field
//! `https://github.com/rust-lang/rust/library/<crate>`; the rustc release is in
//! `.stats.json`.
//!
//! The target is byte-identity with `rust-analyzer scip` (tests/fixture_dump.rs
//! `matches_rust_analyzer_scip` diffs the two on the fixture). Deviations exist
//! ONLY where rust-analyzer itself emits duplicate symbols:
//! * a name whose DefPath disambiguator is non-zero (`const _`, hygienic macro
//!   twins, serde's `__FieldVisitor`s) gets `$n` appended; anonymous scopes
//!   (closures, anon consts) add `$n:`;
//! * an ADT self type with concrete args (`NonZero<u8>`) prints its args;
//! * non-lib targets (bins, integration tests, benches, examples) prefix a
//!   meta descriptor `` `<kind>=<crate_name>`: `` (rust-analyzer gives every
//!   bin's `main().` and `crate/` the lib's id).
//! * Several inherent impl blocks of one type in one module share one impl node
//!   (merged by design, not counted as a collision).
//!
//! Non-lib targets (bins, integration tests, benches, examples) prefix every
//! descriptor with a meta descriptor `` `<kind>=<crate_name>`: ``
//! (`mimir_schema::moniker::target_prefix`), so no two targets of one package
//! share an id — a bin named like the lib, a test and a bench of one name,
//! `main().` of two bins. Which target a unit is comes from cargo metadata
//! (the target whose root file is rustc's input), never from the crate name.
//! A target's `--test` harness build keeps the target's ids (the lib's
//! harness the lib's).
//!
//! ## Visibility and docs (schema 2)
//!
//! Every defined node that has a visibility in rustc carries it twice:
//! `visibility` is the NOMINAL one as rustc resolves it (`tcx.visibility`:
//! `pub` / `pub(crate)` / `pub(in crate::a::b)` / `private`), and
//! `effective_visibility` is how far outside the crate the item really is
//! visible (`tcx.effective_visibilities`: `direct` / `reexported` /
//! `reachable` / `reachable_impl_trait` / `internal`). `doc` is the first line
//! of the doc comment, `has_doc` whether there is one. Reference stubs carry
//! none of it (null): the defining crate's dump has it.
//!
//! ## Attributes (schema 3)
//!
//! Two attributes rustc already has on the item, as nullable booleans:
//! `ignored` — a test fn (`is_test`) carries `#[ignore]`, with or without a
//! reason; null on everything that is not a test. `doc_hidden` — the item
//! ITSELF carries `#[doc(hidden)]` (the items inside a hidden module are not
//! marked, CONTAINS says where they sit); set wherever `has_doc` is, null on
//! reference stubs and on `cfg_gated_out` stubs.
//!
//! ## Tables (schema 4, `tables.rs`)
//!
//! TOUCHES_TABLE: fn → `table` node (id = the table's NAME, one node for the
//! whole log), with `access` (read / write / both) and the sites. Anchored on
//! the storage engine's typed API, derived through every wrapper, written
//! where a table name stops being a parameter; a runtime name is an edge to
//! the one `<unresolved>` table node. What counts, and what does not, is the
//! header of `tables.rs`.
//!
//! ## The FFI border's link name (schema 4)
//!
//! `link_name` — on a foreign fn / static (kind `ffi`), the symbol it is
//! linked as: rustc's symbol name of the item (`#[link_name]` applied), the
//! same name the `.symbols.arrow` row of role `import` carries. Null on every
//! other node and on a reference stub (the declaring crate's dump has it).
//!
//! ## MIR facts (`mir.rs`, `MIMIR_MIR`)
//!
//! Every fn / method body's `mir_promoted` MIR (opt-level independent),
//! closures folded in: a `mir_body` PART per fn (block fingerprints,
//! terminator kinds, successors) for EVERY crate, and for the workspace's own
//! crates a `mir_block` PART per basic block (statements, terminator with the
//! resolved callee, positions relative to the fn, locals with types, local
//! def-use pairs) plus `CFG_SUCC` edges. Payload grammar:
//! `mimir_schema::mir`. No column was added: the lake schema is unchanged.
//!
//! ## Features and gates (`gates.rs`)
//!
//! Feature nodes + ENABLES edges come from the cargo-metadata map; GATED_BY
//! from rustc's own `CfgTrace` on the items it kept; items it compiled OUT are
//! `cfg_gated_out` stubs from the token scan (`mimir_driver::cfg_scan`), and
//! a `cfg` INSIDE an item (a statement, a `use`, `cfg!`) is a `partial`
//! GATED_BY of that item — both for the workspace's own crates
//! (`MIMIR_CFG_SCAN=all` adds the dependencies).

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::panic::AssertUnwindSafe;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;

use mimir_driver::{
    artifact_key_of, read_sidecar, stable_crate_id_hex, sysroot_crates, CrateId, CrateStats,
    Invocation, VersionMap, ENV_INDEX, ENV_REPO, ENV_SHA, ENV_STD, ENV_VERSIONS,
};
use mimir_schema::moniker::{self, Suffix};
use mimir_schema::{
    edge_key, kind, role, visibility, EdgeKind, EdgeRow, EffectiveVisibility, Level, NodeRow, Origin, Provenance,
    SpanRow, SymbolRow, SPAN_EDGE, SPAN_NODE,
};
use rustc_ast::attr::data_structures::CfgEntry;
use rustc_hir::def::{DefKind, MacroKinds, Res};
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir};
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::privacy::Level as EffLevel;
use rustc_middle::ty::print::NoTrimmedGuard;
use rustc_middle::ty::{self, GenericArgKind, Instance, Ty, TyCtxt, TypeVisitableExt, TypingEnv};
use rustc_span::hygiene::{AstPass, ExpnKind};
use rustc_span::{FileName, Span};

mod gates;
pub(crate) mod mir;
mod tables;

/// Entry point: walk + write, never panicking out into rustc.
pub fn dump_guarded(tcx: TyCtxt<'_>, inv: &Invocation, out: &Path) {
    let prev_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {}));
    let res = std::panic::catch_unwind(AssertUnwindSafe(|| dump(tcx, inv, out)));
    std::panic::set_hook(prev_hook);
    let err = match res {
        Ok(Ok(())) => return,
        Ok(Err(e)) => e,
        Err(p) => format!(
            "panic: {}",
            p.downcast_ref::<String>()
                .cloned()
                .or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string()))
                .unwrap_or_default()
        ),
    };
    let dir = out.join(".errors");
    let _ = std::fs::create_dir_all(&dir);
    let _ = std::fs::write(
        dir.join(format!("{}-{}.txt", inv.crate_name.as_deref().unwrap_or("?"), inv.short_hash())),
        err,
    );
}

struct CrateInfo {
    package: String,
    version: String,
    origin: Origin,
}

struct Local {
    package: String,
    version: String,
    origin: Origin,
    repo: Option<String>,
    sha: Option<String>,
    /// descriptor prefix for non-lib targets (`<bin>:`), empty for libs
    root_prefix: String,
    manifest_dir: Option<PathBuf>,
    workspace_root: Option<PathBuf>,
}

struct Walker<'tcx> {
    tcx: TyCtxt<'tcx>,
    local: Local,
    versions: Option<VersionMap>,
    index_dir: Option<PathBuf>,
    externs: Vec<(String, PathBuf)>,
    sysroot: PathBuf,
    /// crates written under the lang identity although not loaded from the
    /// sysroot (non-empty only in the `-Zbuild-std` dump, see [`ENV_STD`])
    lang_crates: BTreeSet<String>,
    rustc_release: String,
    crate_cache: HashMap<CrateNum, Arc<CrateInfo>>,
    desc_cache: HashMap<DefId, Arc<str>>,
    nodes: Vec<NodeRow>,
    node_ids: HashSet<String>,
    stub_done: HashSet<DefId>,
    edges: Vec<EdgeRow>,
    /// Semantic edge `(from, to, kind)` → its row in `edges`.
    edge_index: HashMap<(String, String, EdgeKind), usize>,
    /// Distinct sites per edge row (the multiplicity `count`).
    edge_sites: HashSet<(usize, Option<String>, Option<u32>)>,
    /// Edge sites with a position (`code_spans`, target = edge).
    edge_spans: Vec<SpanRow>,
    test_fns: HashSet<LocalDefId>,
    unresolved: Vec<String>,
    version_sources: BTreeMap<String, usize>,
    collisions: usize,
    collision_samples: Vec<String>,
    cross_crate: usize,
    cross_repo: usize,
    /// features this compile was built with (`--cfg feature="…"`)
    active_features: BTreeSet<String>,
    /// GATED_BY edge row → (every mention negated, some mention is the
    /// item's own `#[cfg]`): what `negated` / `partial` are folded from
    gate_state: HashMap<usize, (bool, bool)>,
    /// schema-2 counters (visibility / docs / features / gates)
    x: Extra,
    /// the table pass (TOUCHES_TABLE): summaries, sites, counters
    tables: tables::Tables,
    /// this unit's sidecar key (`<crate><extra-filename>`)
    artifact_key: Option<String>,
}

/// The schema-2 counters of one crate's walk (→ [`CrateStats`]).
#[derive(Default)]
struct Extra {
    api_items: usize,
    api_documented: usize,
    feature_nodes: usize,
    feature_refs: usize,
    feature_enables_unresolved: Vec<String>,
    gated_out_stubs: usize,
    cfg_scan_files: usize,
    cfg_scan_failed: Vec<String>,
    cfg_scan_unresolved_mods: Vec<String>,
    feature_cfg_mentions: usize,
    feature_cfg_attributed: usize,
    feature_cfg_partial: usize,
    cfg_scan_ms: u128,
}

/// What gates one item: its own `#[cfg(…)]`s as rustc parsed them.
struct Gate {
    /// `cfg(a) && cfg(b)`, canonical spelling.
    text: String,
    /// each attribute's span with the features it mentions (feature →
    /// negated: every mention in that attribute under an odd number of `not`)
    sites: Vec<(Span, Vec<(String, bool)>)>,
}

/// Only the table summaries of this crate (its `<key>.tables.json`): the
/// crate is compiled but not dumped (`MIMIR_DUMP=workspace` and it is a
/// dependency), and a crate that IS dumped later calls into it.
pub fn table_summaries_guarded(tcx: TyCtxt<'_>, inv: &Invocation) {
    if std::env::var_os(ENV_INDEX).is_none() {
        return;
    }
    let prev_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {}));
    let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
        let _g1 = NoTrimmedGuard::new();
        let (mut w, ..) = walker(tcx, inv);
        w.init_tables();
        if w.tables.enabled {
            w.collect_tests();
            w.collect_calls();
            w.walk_tables(false);
        }
    }));
    std::panic::set_hook(prev_hook);
    if let Err(p) = res {
        // not silently "summarises nothing": the dependents must know
        let msg = p.downcast_ref::<String>().cloned().or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default();
        if let (Some(dir), Some(key)) = (std::env::var_os(ENV_INDEX), inv.artifact_key()) {
            let _ = mimir_driver::write_table_sidecar(Path::new(&dir), &key, &mimir_driver::TableSidecar { v: mimir_driver::TABLE_PASS, error: Some(format!("panic: {msg}")), ..Default::default() });
        }
    }
}

/// The walker of this compile, with the crate's name, package and version.
fn walker<'tcx>(tcx: TyCtxt<'tcx>, inv: &Invocation) -> (Walker<'tcx>, String, String, String) {
    let versions = std::env::var_os(ENV_VERSIONS).and_then(|p| VersionMap::load(Path::new(&p)));
    let crate_name = tcx.crate_name(LOCAL_CRATE).to_string();
    let package = std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| crate_name.clone());
    let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "?".into());
    let primary = std::env::var_os("CARGO_PRIMARY_PACKAGE").is_some();
    // a member by its MANIFEST: one build can hold a member and the published
    // copy of that member, under one name and version (`VersionMap::is_member`)
    let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from);
    let member = versions.as_ref().is_some_and(|m| m.is_member(&package, &version, manifest_dir.as_deref()));
    let mut origin = if primary || member { Origin::Local } else { Origin::Library };
    // The toolchain-crate dump (`-Zbuild-std`): a crate the sysroot ships is
    // written under its lang identity — the ids every other dump gives it.
    let sysroot = tcx.sess.opts.sysroot.path().to_path_buf();
    let lang_crates = if std::env::var(ENV_STD).as_deref() == Ok("1") { sysroot_crates(&sysroot) } else { BTreeSet::new() };
    let (package, version) = if lang_crates.contains(&crate_name) {
        origin = Origin::Lang;
        (crate_name.clone(), moniker::lang_version(&crate_name))
    } else {
        (package, version)
    };
    // Which TARGET of its package this unit is decides its ids
    // (`moniker::target_prefix`): the lib — and the lib's `--test` harness —
    // has rust-analyzer's, any other target is prefixed with kind + name. The
    // target is the one whose root file is rustc's input (cargo metadata); a
    // bin, a test and a bench may all carry the lib's crate NAME.
    let target_kind = match (&versions, &inv.input) {
        (Some(m), Some(input)) => m.target_kind(&package, &version, input).map(str::to_string),
        _ => None,
    };
    let root_prefix = match target_kind {
        Some(kind) => moniker::target_prefix(&kind, &crate_name),
        // no declared target (a dependency — only its lib is compiled — or a
        // run without cargo metadata): by what the argv says
        None if inv.is_lib() => String::new(),
        None => moniker::target_prefix(if inv.is_test { "test" } else { "bin" }, &crate_name),
    };
    let repo = if origin == Origin::Local {
        std::env::var(ENV_REPO).ok().or_else(|| versions.as_ref().map(|m| m.repo.clone()))
    } else {
        None
    };
    let workspace_root = versions.as_ref().map(|m| PathBuf::from(&m.workspace_root));
    let rustc_release = rustc_interface::util::rustc_version_str()
        .and_then(|s| s.split_whitespace().next())
        .unwrap_or("?")
        .to_string();

    let w = Walker {
        tcx,
        local: Local {
            package: package.clone(),
            version: version.clone(),
            origin,
            repo,
            sha: std::env::var(ENV_SHA).ok(),
            root_prefix,
            manifest_dir: std::env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from),
            workspace_root,
        },
        versions,
        index_dir: std::env::var_os(ENV_INDEX).map(PathBuf::from),
        externs: inv.externs.clone(),
        sysroot,
        lang_crates,
        rustc_release,
        crate_cache: HashMap::new(),
        desc_cache: HashMap::new(),
        nodes: Vec::new(),
        node_ids: HashSet::new(),
        stub_done: HashSet::new(),
        edges: Vec::new(),
        edge_index: HashMap::new(),
        edge_sites: HashSet::new(),
        edge_spans: Vec::new(),
        test_fns: HashSet::new(),
        unresolved: Vec::new(),
        version_sources: BTreeMap::new(),
        collisions: 0,
        collision_samples: Vec::new(),
        cross_crate: 0,
        cross_repo: 0,
        active_features: BTreeSet::new(),
        gate_state: HashMap::new(),
        x: Extra::default(),
        tables: tables::Tables::default(),
        artifact_key: inv.artifact_key(),
    };
    (w, crate_name, package, version)
}

fn dump(tcx: TyCtxt<'_>, inv: &Invocation, out: &Path) -> Result<(), String> {
    let t0 = Instant::now();
    // NEVER touch the trimmed_def_paths query (it ICEs when no diagnostic was
    // emitted): types in monikers print with full (visible) paths; the human
    // `ty` column prints full paths cut to their last segment (see `plain_ty`).
    let _g1 = NoTrimmedGuard::new();
    let (mut w, crate_name, package, version) = walker(tcx, inv);
    w.init_tables_guarded();
    w.collect_tests();
    w.walk_features();
    w.walk_defs();
    w.walk_crate_deps();
    w.walk_bodies();
    w.walk_tables_guarded();
    w.walk_gated_out();
    // MIR facts last: they only ADD rows (parts under fns the walk wrote)
    let mir_mode = mir::Mode::of_env(w.local.origin == Origin::Local);
    // a panic in it loses this crate's MIR facts only (`mir_error`)
    let (n0, e0) = (w.nodes.len(), w.edges.len());
    let mut mir_error = None;
    let mir_st = match std::panic::catch_unwind(AssertUnwindSafe(|| w.walk_mir(mir_mode))) {
        Ok(s) => s,
        Err(p) => {
            mir::clear_stash();
            for n in w.nodes.drain(n0..) {
                w.node_ids.remove(&n.id);
            }
            for e in w.edges.drain(e0..) {
                w.edge_index.remove(&(e.from, e.to, e.kind));
            }
            w.edge_sites.retain(|(i, _, _)| *i < e0);
            mir_error = Some(format!("panic: {}", p.downcast_ref::<String>().cloned().or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default()));
            mir::MirStats { mode: "off", ..Default::default() }
        }
    };
    let walk_ms = t0.elapsed().as_millis();
    let ts = Instant::now();
    let (symbols, symbol_errors) = w.walk_symbols();
    let crate_ids = w.crate_ids();
    let symbols_ms = ts.elapsed().as_millis();

    // ---- write ----
    let t1 = Instant::now();
    std::fs::create_dir_all(out).map_err(|e| e.to_string())?;
    // a lang crate's version is a URL: its files say `@lang`
    let file_version = if w.local.origin == Origin::Lang { "lang".to_string() } else { version.replace(['/', ':'], "_") };
    let stem = format!("{crate_name}@{file_version}-{}", inv.short_hash());
    let nodes_file = format!("{stem}.nodes.arrow");
    let edges_file = format!("{stem}.edges.arrow");
    let spans_file = format!("{stem}.spans.arrow");
    // Positions live apart from identity: each defined node's span + every
    // positioned edge site.
    let mut spans: Vec<SpanRow> = w
        .nodes
        .iter()
        .filter(|n| n.defined_here)
        .filter_map(|n| {
            let (file, start) = (n.file.clone()?, n.start_line?);
            Some(SpanRow { target: SPAN_NODE, key: n.id.clone(), file, start_line: start, end_line: n.end_line.unwrap_or(start) })
        })
        .collect();
    spans.append(&mut w.edge_spans);
    spans.sort();
    spans.dedup();
    let nb = mimir_schema::nodes_batch(&w.nodes).map_err(|e| e.to_string())?;
    let eb = mimir_schema::edges_batch(&w.edges).map_err(|e| e.to_string())?;
    let sb = mimir_schema::spans_batch(&spans).map_err(|e| e.to_string())?;
    write_ipc(&out.join(&nodes_file), &nb)?;
    write_ipc(&out.join(&edges_file), &eb)?;
    write_ipc(&out.join(&spans_file), &sb)?;
    let symbols_file = format!("{stem}.symbols.arrow");
    let yb = mimir_schema::symbols_batch(&symbols).map_err(|e| e.to_string())?;
    write_ipc(&out.join(&symbols_file), &yb)?;
    let write_ms = t1.elapsed().as_millis();

    let mut by_kind = BTreeMap::new();
    for e in &w.edges {
        *by_kind.entry(e.kind.as_str().to_string()).or_insert(0) += 1;
    }
    let mut by_vis: BTreeMap<String, usize> = BTreeMap::new();
    let mut by_eff: BTreeMap<String, usize> = BTreeMap::new();
    for n in w.nodes.iter().filter(|n| n.defined_here && n.kind != kind::CFG_GATED_OUT) {
        if let Some(v) = &n.visibility {
            let key = if v.starts_with("pub(in ") { "pub(in …)" } else { v.as_str() };
            *by_vis.entry(key.to_string()).or_insert(0) += 1;
        }
        if let Some(e) = n.effective_visibility {
            *by_eff.entry(e.as_str().to_string()).or_insert(0) += 1;
        }
    }
    let documented_nodes = w.nodes.iter().filter(|n| n.defined_here && n.kind != kind::CFG_GATED_OUT && n.has_doc == Some(true)).count();
    // GATED_BY by what it says: a kept item's own gate / a compiled-out
    // stub's own gate / a gate on something inside an item
    let stub_ids: HashSet<&str> = w.nodes.iter().filter(|n| n.kind == kind::CFG_GATED_OUT).map(|n| n.id.as_str()).collect();
    let (mut gated_by_active, mut gated_by_stubs, mut gated_by_partial) = (0, 0, 0);
    for e in w.edges.iter().filter(|e| e.kind == EdgeKind::GatedBy) {
        match (e.partial == Some(true), stub_ids.contains(e.from.as_str())) {
            (true, _) => gated_by_partial += 1,
            (false, true) => gated_by_stubs += 1,
            (false, false) => gated_by_active += 1,
        }
    }
    let stats = CrateStats {
        crate_name,
        package,
        version,
        hash: inv.short_hash(),
        origin: w.local.origin.as_str().into(),
        crate_types: inv.crate_types.clone(),
        test_harness: inv.is_test,
        nodes: w.nodes.len(),
        nodes_defined: w.nodes.iter().filter(|n| n.defined_here).count(),
        edges: w.edges.len(),
        edge_sites: w.edges.iter().map(|e| e.count as usize).sum(),
        spans: spans.len(),
        edges_by_kind: by_kind,
        cross_crate_edges: w.cross_crate,
        cross_repo_edges: w.cross_repo,
        unresolved_versions: w.unresolved.clone(),
        version_sources: w.version_sources.clone(),
        rustc_release: w.rustc_release.clone(),
        moniker_collisions: w.collisions,
        collision_samples: w.collision_samples.clone(),
        walk_ms,
        write_ms,
        nodes_file,
        edges_file,
        spans_file,
        semhash: mimir_driver::unit_semhash_of_env(&inv),
        stable_crate_id: stable_crate_id_hex(tcx.stable_crate_id(LOCAL_CRATE).as_u64()),
        crate_ids,
        symbol_mangling: format!("{:?}", tcx.sess.opts.get_symbol_mangling_version()).to_lowercase(),
        symbols: symbols.len(),
        symbols_file,
        symbol_errors,
        symbols_ms,
        nodes_by_visibility: by_vis,
        nodes_by_effective_visibility: by_eff,
        api_items: w.x.api_items,
        api_documented: w.x.api_documented,
        documented_nodes,
        feature_nodes: w.x.feature_nodes,
        features_active: w.active_features.len(),
        feature_refs: w.x.feature_refs,
        feature_enables_unresolved: std::mem::take(&mut w.x.feature_enables_unresolved),
        gated_by_active,
        gated_out_stubs: w.x.gated_out_stubs,
        gated_by_stubs,
        gated_by_partial,
        cfg_scan_files: w.x.cfg_scan_files,
        cfg_scan_failed: std::mem::take(&mut w.x.cfg_scan_failed),
        cfg_scan_unresolved_mods: std::mem::take(&mut w.x.cfg_scan_unresolved_mods),
        feature_cfg_mentions: w.x.feature_cfg_mentions,
        feature_cfg_attributed: w.x.feature_cfg_attributed,
        feature_cfg_partial: w.x.feature_cfg_partial,
        cfg_scan_ms: w.x.cfg_scan_ms,
        table_pass: w.tables.enabled,
        table_pass_error: w.tables.error.take(),
        table_sites: w.tables.n_sites,
        table_sites_unresolved: w.tables.n_unresolved,
        tables: w.tables.names.iter().cloned().collect(),
        table_summaries: w.tables.n_summaries,
        table_bodies: w.tables.bodies,
        table_body_visits: w.tables.visits,
        table_sidecars: w.tables.sidecars,
        table_sidecars_stale: std::mem::take(&mut w.tables.stale),
        table_annotations: w.tables.annotations,
        table_annotations_bad: std::mem::take(&mut w.tables.bad_annotations),
        tables_ms: w.tables.ms,
        mir_facts: if mir_st.mode == "off" { 0 } else { mimir_driver::MIR_FACTS },
        mir_error,
        mir_mode: mir_st.mode.to_string(),
        mir_bodies: mir_st.bodies,
        mir_nested_bodies: mir_st.nested,
        mir_blocks: mir_st.blocks,
        mir_statements: mir_st.statements,
        mir_missing: mir_st.missing,
        mir_payload_bytes: mir_st.payload_bytes,
        mir_ms: mir_st.ms,
    };
    let tmp = out.join(format!("{stem}.stats.json.tmp"));
    std::fs::write(&tmp, serde_json::to_vec_pretty(&stats).map_err(|e| e.to_string())?)
        .map_err(|e| e.to_string())?;
    std::fs::rename(&tmp, out.join(format!("{stem}.stats.json"))).map_err(|e| e.to_string())?;
    Ok(())
}

fn write_ipc(path: &Path, batch: &arrow_array::RecordBatch) -> Result<(), String> {
    let tmp = path.with_extension("arrow.tmp");
    {
        let f = std::fs::File::create(&tmp).map_err(|e| e.to_string())?;
        let mut w = arrow_ipc::writer::FileWriter::try_new(std::io::BufWriter::new(f), &batch.schema())
            .map_err(|e| e.to_string())?;
        w.write(batch).map_err(|e| e.to_string())?;
        w.finish().map_err(|e| e.to_string())?;
    }
    std::fs::rename(&tmp, path).map_err(|e| e.to_string())
}

impl<'tcx> Walker<'tcx> {
    // ------------------------------------------------------------------ crates

    fn crate_info(&mut self, cnum: CrateNum) -> Arc<CrateInfo> {
        if let Some(c) = self.crate_cache.get(&cnum) {
            return c.clone();
        }
        let info = Arc::new(self.compute_crate_info(cnum));
        self.crate_cache.insert(cnum, info.clone());
        info
    }

    fn compute_crate_info(&mut self, cnum: CrateNum) -> CrateInfo {
        if cnum == LOCAL_CRATE {
            return CrateInfo {
                package: self.local.package.clone(),
                version: self.local.version.clone(),
                origin: self.local.origin,
            };
        }
        let name = self.tcx.crate_name(cnum).to_string();
        let src = self.tcx.used_crate_source(cnum);
        let paths: Vec<PathBuf> = src.paths().cloned().collect();
        if paths.iter().any(|p| p.starts_with(&self.sysroot)) || self.lang_crates.contains(&name) {
            *self.version_sources.entry("lang".into()).or_default() += 1;
            // rust-analyzer's version field for the toolchain's crates
            let version = moniker::lang_version(&name);
            return CrateInfo { package: name, version, origin: Origin::Lang };
        }
        if let Some(dir) = &self.index_dir {
            for p in &paths {
                if let Some(sc) = artifact_key_of(p).and_then(|k| read_sidecar(dir, &k)) {
                    *self.version_sources.entry("sidecar".into()).or_default() += 1;
                    // what the unit itself said when it was compiled; a sidecar
                    // from before it said so: by name and version
                    // a member of THIS build, whichever build compiled it
                    // (a shared target dir hands one artifact to many)
                    let local = sc.local_in(self.versions.as_ref());
                    return CrateInfo {
                        package: sc.package,
                        version: sc.version,
                        origin: if local { Origin::Local } else { Origin::Library },
                    };
                }
            }
        }
        // direct dep under an ambiguous name: --extern NAME=PATH ↔ loaded file
        if let Some(m) = &self.versions {
            let key = format!("{} {}", self.local.package, self.local.version);
            if let Some(deps) = m.extern_deps.get(&key) {
                for (ext, p) in &self.externs {
                    let canon = |x: &Path| std::fs::canonicalize(x).unwrap_or_else(|_| x.to_path_buf());
                    if paths.iter().any(|x| x == p || canon(x) == canon(p)) {
                        if let Some((pn, pv)) = deps.get(ext) {
                            *self.version_sources.entry("extern".into()).or_default() += 1;
                            let local = m.packages.iter().any(|x| x.local && &x.name == pn && &x.version == pv);
                            return CrateInfo {
                                package: pn.clone(),
                                version: pv.clone(),
                                origin: if local { Origin::Local } else { Origin::Library },
                            };
                        }
                    }
                }
            }
        }
        if let Some(m) = &self.versions {
            let hits = m.by_crate(&name);
            if hits.len() == 1 {
                *self.version_sources.entry("map".into()).or_default() += 1;
                let p = hits[0];
                return CrateInfo {
                    package: p.name.clone(),
                    version: p.version.clone(),
                    origin: if p.local { Origin::Local } else { Origin::Library },
                };
            }
        }
        *self.version_sources.entry("unresolved".into()).or_default() += 1;
        self.unresolved.push(name.clone());
        CrateInfo { package: name, version: "?".into(), origin: Origin::Library }
    }

    // --------------------------------------------------------------- monikers

    fn name_of(&self, did: DefId) -> String {
        self.tcx.opt_item_name(did).map(|s| s.to_string()).unwrap_or_else(|| "_".into())
    }

    /// The name as it goes into a DESCRIPTOR: rustc's DefPath disambiguator is
    /// non-zero exactly when two defs share (parent, namespace, name) — `const
    /// _`, hygienic macro-generated twins, synthetic `impl Trait` params — and
    /// then it is appended as `$n` (`$` cannot occur in a Rust ident, so no
    /// clash with a real name). Unique names are untouched (coderoom-equal).
    fn desc_name(&self, did: DefId) -> String {
        let name = self.name_of(did);
        let dis = self.tcx.def_key(did).disambiguated_data.disambiguator;
        // a `#[test]` fn shares its name with the harness's marker static
        if dis == 0 || did.as_local().is_some_and(|l| self.test_fns.contains(&l)) {
            name
        } else {
            format!("{name}${dis}")
        }
    }

    /// Descriptor chain of `did` (scope + own descriptor), cached.
    fn desc(&mut self, did: DefId) -> Arc<str> {
        if let Some(d) = self.desc_cache.get(&did) {
            return d.clone();
        }
        let d: Arc<str> = self.compute_desc(did).into();
        self.desc_cache.insert(did, d.clone());
        d
    }

    fn compute_desc(&mut self, did: DefId) -> String {
        if did.is_crate_root() {
            return if did.is_local() { self.local.root_prefix.clone() } else { String::new() };
        }
        let tcx = self.tcx;
        let parent = tcx.parent(did);
        let mut s = String::new();
        let dk = tcx.def_kind(did);
        let name = self.desc_name(did);
        match dk {
            DefKind::Mod => {
                s.push_str(&self.desc(parent));
                moniker::push_descriptor(&mut s, Suffix::Namespace, &name);
            }
            DefKind::Struct
            | DefKind::Enum
            | DefKind::Union
            | DefKind::Trait
            | DefKind::TraitAlias
            | DefKind::TyAlias
            | DefKind::ForeignTy
            | DefKind::Variant => {
                s.push_str(&self.desc(parent));
                moniker::push_descriptor(&mut s, Suffix::Type, &name);
            }
            DefKind::Fn => {
                s.push_str(&self.desc(parent));
                moniker::push_descriptor(&mut s, Suffix::Method, &name);
            }
            DefKind::Const { .. } | DefKind::Static { .. } | DefKind::Field => {
                s.push_str(&self.desc(parent));
                moniker::push_descriptor(&mut s, Suffix::Term, &name);
            }
            DefKind::Macro(kinds) => {
                s.push_str(&self.desc(parent));
                // rust-analyzer: bang macros `name!`, derive / attribute macros `Name:`
                let suf = if kinds.contains(MacroKinds::BANG) { Suffix::Macro } else { Suffix::Meta };
                moniker::push_descriptor(&mut s, suf, &name);
            }
            DefKind::TyParam | DefKind::ConstParam | DefKind::LifetimeParam => {
                s.push_str(&self.desc(parent));
                moniker::push_descriptor(&mut s, Suffix::TypeParameter, &name);
            }
            DefKind::AssocFn | DefKind::AssocConst { .. } | DefKind::AssocTy => {
                // trait: `Trait#m().`; impl: rust-analyzer's `impl#[Self][Trait]m().`
                s.push_str(&self.desc(parent));
                let suf = match dk {
                    DefKind::AssocFn => Suffix::Method,
                    DefKind::AssocTy => Suffix::Type,
                    _ => Suffix::Term,
                };
                moniker::push_descriptor(&mut s, suf, &name);
            }
            DefKind::Impl { .. } => {
                s.push_str(&self.desc(parent));
                let (self_name, trait_name) = self.impl_names(did);
                moniker::push_impl(&mut s, &self_name, trait_name.as_deref());
            }
            // transparent: Ctor, ForeignMod, Use, ExternCrate, GlobalAsm,
            // AnonConst, InlineConst, Closure, OpaqueTy, SyntheticCoroutineBody …
            // — except anonymous scopes (closures, anon consts), which add a
            // `$n:` meta descriptor (n = rustc's disambiguator), so items in
            // sibling scopes (tracing's `__CALLSITE`, thread_local!'s
            // `__RUST_STD_INTERNAL_VAL`) stay apart.
            _ => {
                s.push_str(&self.desc(parent));
                let dis = tcx.def_key(did).disambiguated_data.disambiguator;
                if matches!(dk, DefKind::AnonConst | DefKind::Closure) {
                    moniker::push_descriptor(&mut s, Suffix::Meta, &format!("${dis}"));
                }
            }
        }
        s
    }

    /// `(Self, Some(Trait))` names for rust-analyzer's `impl#[Self][Trait]`.
    ///
    /// rust-analyzer's spelling: an ADT self type is its bare name (`String`),
    /// any other self type prints (`i32`, `T`); the trait is its bare name, with
    /// its args when it has any beyond Self, the self type written `Self`
    /// (``[`Add<Self>`]``, ``[`Into<U>`]``). One deviation, where rust-analyzer
    /// itself emits duplicate symbols: an ADT instantiated with concrete args
    /// (`impl X for NonZero<u8>` / `NonZero<u16>` …, macro-generated families)
    /// prints with its args.
    fn impl_names(&mut self, impl_id: DefId) -> (String, Option<String>) {
        let tcx = self.tcx;
        let self_ty = tcx.type_of(impl_id).instantiate_identity().skip_normalization();
        let self_name = match self_ty.kind() {
            ty::Adt(adt, args) if args.iter().all(is_param_arg) => self.desc_name(adt.did()),
            ty::Foreign(d) => self.desc_name(*d),
            _ => plain_ty(self_ty),
        };
        let trait_name = if tcx.impl_is_of_trait(impl_id) {
            let tr = tcx.impl_trait_ref(impl_id).instantiate_identity().skip_normalization();
            let base = tcx.item_name(tr.def_id).to_string();
            let args: Vec<String> = tr
                .args
                .iter()
                .skip(1)
                .filter_map(|a| match a.kind() {
                    GenericArgKind::Type(t) if t == self_ty => Some("Self".to_string()),
                    GenericArgKind::Type(t) => Some(plain_ty(t)),
                    GenericArgKind::Const(c) => Some({
                        let _g = NoTrimmedGuard::new();
                        mimir_driver::shorten_paths(&c.to_string())
                    }),
                    GenericArgKind::Lifetime(r) => Some(r.to_string()),
                })
                .collect();
            Some(if args.is_empty() { base } else { format!("{base}<{}>", args.join(", ")) })
        } else {
            None
        };
        (self_name, trait_name)
    }

    fn moniker(&mut self, did: DefId) -> String {
        let ci = self.crate_info(did.krate);
        if did.is_crate_root() {
            let d = if did.is_local() {
                format!("{}{}", self.local.root_prefix, moniker::CRATE_ROOT_DESCRIPTOR)
            } else {
                moniker::CRATE_ROOT_DESCRIPTOR.to_string()
            };
            return moniker::moniker(&ci.package, &ci.version, &d);
        }
        let d = self.desc(did);
        moniker::moniker(&ci.package, &ci.version, &d)
    }

    // ------------------------------------------------------------ node kinds

    fn node_kind(&self, did: DefId) -> Option<(&'static str, Level)> {
        let tcx = self.tcx;
        if did.is_crate_root() {
            return Some((kind::CRATE, Level::Crate));
        }
        let dk = tcx.def_kind(did);
        Some(match dk {
            DefKind::Mod => (kind::MOD, Level::Module),
            DefKind::Struct => (kind::STRUCT, Level::Item),
            DefKind::Enum => (kind::ENUM, Level::Item),
            DefKind::Union => (kind::UNION, Level::Item),
            DefKind::Trait => (kind::TRAIT, Level::Item),
            DefKind::TraitAlias => (kind::TRAIT_ALIAS, Level::Item),
            DefKind::TyAlias => (kind::TYPE, Level::Item),
            DefKind::ForeignTy => (kind::FOREIGN_TYPE, Level::Item),
            DefKind::Fn => {
                if tcx.is_foreign_item(did) {
                    (kind::FFI, Level::Item)
                } else {
                    (kind::FN, Level::Item)
                }
            }
            DefKind::Const { .. } => (kind::CONST, Level::Item),
            DefKind::Static { nested: false, .. } => {
                if tcx.is_foreign_item(did) {
                    (kind::FFI, Level::Item)
                } else {
                    (kind::STATIC, Level::Item)
                }
            }
            DefKind::Macro(_) => (kind::MACRO, Level::Item),
            DefKind::Impl { .. } => (kind::IMPL, Level::Item),
            DefKind::AssocFn => (kind::METHOD, Level::Part),
            DefKind::AssocConst { .. } => (kind::ASSOC_CONST, Level::Part),
            DefKind::AssocTy => {
                if tcx.is_impl_trait_in_trait(did) {
                    return None;
                }
                (kind::ASSOC_TYPE, Level::Part)
            }
            DefKind::Field => (kind::FIELD, Level::Part),
            DefKind::Variant => (kind::VARIANT, Level::Part),
            DefKind::TyParam | DefKind::ConstParam => (kind::GENERIC, Level::Part),
            _ => return None,
        })
    }

    fn node_parent(&self, did: DefId) -> Option<DefId> {
        let mut p = self.tcx.opt_parent(did);
        while let Some(x) = p {
            if self.node_kind(x).is_some() {
                return Some(x);
            }
            p = self.tcx.opt_parent(x);
        }
        None
    }

    /// Nearest node (self or ancestor) — the `from` of edges out of bodies.
    fn node_or_ancestor(&self, did: DefId) -> Option<DefId> {
        if self.node_kind(did).is_some() { Some(did) } else { self.node_parent(did) }
    }

    // ------------------------------------------------------------ symbols

    /// rustc's symbol name for every LOCAL item that is codegen'd as itself:
    /// non-generic fns / methods (impl not generic either), statics, and
    /// foreign fns / statics (their link name — the FFI border). Generic items
    /// have no symbol of their own (their instances are the binary layer's
    /// `mono_instance` nodes). Each computation runs under `catch_unwind`; a
    /// panic skips that item and is counted, never fakes a row.
    fn walk_symbols(&mut self) -> (Vec<SymbolRow>, usize) {
        let tcx = self.tcx;
        let mut rows = Vec::new();
        let mut errors = 0usize;
        let ids: Vec<LocalDefId> = tcx.iter_local_def_id().collect();
        for did in ids {
            let d = did.to_def_id();
            let dk = tcx.def_kind(d);
            if !matches!(dk, DefKind::Fn | DefKind::AssocFn | DefKind::Static { nested: false, .. }) {
                continue;
            }
            if self.is_test_marker(did) || self.is_harness_item(did) {
                continue;
            }
            // trait methods (declared or provided) are generic over Self
            if dk == DefKind::AssocFn && tcx.trait_of_assoc(d).is_some() {
                continue;
            }
            if tcx.generics_of(d).requires_monomorphization(tcx) {
                continue;
            }
            let id = self.moniker(d);
            if !self.node_ids.contains(&id) {
                continue;
            }
            let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
                tcx.symbol_name(Instance::mono(tcx, d)).name.to_string()
            }));
            let Ok(sym) = res else {
                errors += 1;
                continue;
            };
            rows.push(SymbolRow {
                id,
                symbol: sym,
                demangled: None,
                role: if tcx.is_foreign_item(d) { role::IMPORT.into() } else { role::ITEM.into() },
                krate: self.local.package.clone(),
                version: self.local.version.clone(),
                binary: None,
                section: None,
                addr: None,
                size: None,
                provenance: Provenance::SourceOnly,
                valid_from_sha: self.local.sha.clone(),
                valid_to_sha: None,
            });
        }
        (rows, errors)
    }

    /// The local crate + every crate this compile loaded, with the
    /// StableCrateId rustc mangles into their symbols.
    fn crate_ids(&mut self) -> Vec<CrateId> {
        let tcx = self.tcx;
        let mut out = Vec::new();
        let mut cnums = vec![LOCAL_CRATE];
        cnums.extend(tcx.crates(()).iter().copied());
        for cnum in cnums {
            let ci = self.crate_info(cnum);
            out.push(CrateId {
                crate_name: tcx.crate_name(cnum).to_string(),
                stable_crate_id: stable_crate_id_hex(tcx.stable_crate_id(cnum).as_u64()),
                package: ci.package.clone(),
                version: ci.version.clone(),
                origin: ci.origin.as_str().to_string(),
                prefix: if cnum == LOCAL_CRATE { self.local.root_prefix.clone() } else { String::new() },
            });
        }
        out
    }

    // ---------------------------------------------------------------- spans

    fn file_lines(&self, span: Span, local: bool) -> (Option<String>, Option<u32>, Option<u32>) {
        if span.is_dummy() {
            return (None, None, None);
        }
        let span = span.source_callsite();
        let sm = self.tcx.sess.source_map();
        let lo = sm.lookup_char_pos(span.lo());
        let hi = sm.lookup_char_pos(span.hi());
        let file = match &lo.file.name {
            FileName::Real(_) if local => Some(self.rel_path(&lo.file.name.prefer_local_unconditionally().to_string())),
            FileName::Real(_) => Some(lo.file.name.prefer_remapped_unconditionally().to_string()),
            other => Some(other.prefer_remapped_unconditionally().to_string()),
        };
        (file, Some(lo.line as u32), Some(hi.line as u32))
    }

    fn rel_path(&self, p: &str) -> String {
        let path = Path::new(p);
        if let Some(g) = mimir_driver::out_dir_relative(path, std::env::var_os("OUT_DIR").as_deref().map(Path::new)) {
            return g;
        }
        if path.is_absolute() {
            if self.local.origin == Origin::Local {
                if let Some(r) = self.local.workspace_root.as_ref().and_then(|w| path.strip_prefix(w).ok()) {
                    return r.to_string_lossy().into_owned();
                }
            }
            if let Some(r) = self.local.manifest_dir.as_ref().and_then(|m| path.strip_prefix(m).ok()) {
                return r.to_string_lossy().into_owned();
            }
        }
        p.to_string()
    }

    /// The item's own `#[cfg(…)]`s — the ones rustc evaluated TRUE and kept as
    /// `CfgTrace` (an item whose cfg is false is not here at all). A foreign
    /// item also carries the cfg of its `extern` block (the block is not a
    /// node, so the gate would otherwise sit on nothing).
    fn cfg_of(&self, did: LocalDefId) -> Option<Gate> {
        let tcx = self.tcx;
        let mut traces: Vec<&(CfgEntry, Span)> = Vec::new();
        if let Some(parent) = tcx.opt_local_parent(did) {
            if tcx.def_kind(parent) == DefKind::ForeignMod {
                if let Some(v) = rustc_attr_ir::find_attr!(tcx, parent, CfgTrace(v) => v) {
                    traces.extend(v.iter());
                }
            }
        }
        if let Some(v) = rustc_attr_ir::find_attr!(tcx, did, CfgTrace(v) => v) {
            traces.extend(v.iter());
        }
        if traces.is_empty() {
            return None;
        }
        let sm = tcx.sess.source_map();
        let mut parts = Vec::new();
        let mut sites = Vec::new();
        for (entry, sp) in traces {
            // canonical text from rustc's own parse; a predicate this printer
            // has no form for (`version(…)`) falls back to the source text
            let text = cfg_entry_text(entry).map(|t| format!("cfg({t})")).or_else(|| {
                let s = sm.span_to_snippet(*sp).ok()?;
                let s = s.trim();
                let s = s.strip_prefix("#![").or_else(|| s.strip_prefix("#[")).and_then(|x| x.strip_suffix(']')).unwrap_or(s);
                Some(match mimir_driver::cfg_scan::parse_predicate(s) {
                    Some((t, _)) => t,
                    None => s.split_whitespace().collect::<Vec<_>>().join(" "),
                })
            });
            let Some(text) = text.filter(|t| !t.is_empty()) else { continue };
            parts.push(text);
            let mut here = Vec::new();
            cfg_entry_features(entry, false, &mut here);
            sites.push((*sp, mimir_driver::cfg_scan::merge_features(here)));
        }
        if parts.is_empty() {
            return None;
        }
        Some(Gate { text: parts.join(" && "), sites })
    }

    // ------------------------------------------------- visibility and docs

    /// `(visibility, effective_visibility)` of a local def; `(None, None)` for
    /// a kind rustc gives no visibility (impl blocks, generic params …).
    fn visibility_of(&self, did: LocalDefId) -> (Option<String>, Option<EffectiveVisibility>) {
        let tcx = self.tcx;
        let d = did.to_def_id();
        if !has_visibility(tcx.def_kind(d)) {
            return (None, None);
        }
        let nominal = match tcx.visibility(d) {
            ty::Visibility::Public => visibility::PUB.to_string(),
            ty::Visibility::Restricted(m) => {
                let m = m.to_def_id();
                if m == tcx.parent_module_from_def_id(did).to_def_id() {
                    // its own module only: no qualifier, `pub(self)`, and at
                    // the crate root also `pub(crate)` — one meaning, one value
                    visibility::PRIVATE.to_string()
                } else if m.is_crate_root() {
                    visibility::PUB_CRATE.to_string()
                } else {
                    let path = tcx.def_path(m).to_string_no_crate_verbose();
                    visibility::pub_in(path.trim_start_matches("::"))
                }
            }
        };
        let effective = match tcx.effective_visibilities(()).public_at_level(did) {
            Some(EffLevel::Direct) => EffectiveVisibility::Direct,
            Some(EffLevel::Reexported) => EffectiveVisibility::Reexported,
            Some(EffLevel::Reachable) => EffectiveVisibility::Reachable,
            Some(EffLevel::ReachableThroughImplTrait) => EffectiveVisibility::ReachableImplTrait,
            None => EffectiveVisibility::Internal,
        };
        (Some(nominal), Some(effective))
    }

    /// First line of the item's doc comment (`///`, `//!`, `/** */`, `#[doc = "…"]`).
    fn doc_of(&self, did: LocalDefId) -> Option<String> {
        let tcx = self.tcx;
        tcx.hir_attrs(tcx.local_def_id_to_hir_id(did))
            .iter()
            .filter_map(|a| a.doc_str())
            .find_map(|s| mimir_schema::doc_summary(s.as_str()))
    }

    // ---------------------------------------------------------------- emit

    fn base_row(&mut self, did: DefId, k: &str, level: Level) -> NodeRow {
        let ci = self.crate_info(did.krate);
        let parent = self.node_parent(did).map(|p| self.moniker(p));
        NodeRow {
            id: self.moniker(did),
            kind: k.to_string(),
            level,
            name: if did.is_crate_root() {
                self.tcx.crate_name(did.krate).to_string()
            } else {
                self.name_of(did)
            },
            parent,
            repo: if did.is_local() { self.local.repo.clone() } else { None },
            krate: ci.package.clone(),
            version: ci.version.clone(),
            file: None,
            start_line: None,
            end_line: None,
            cfg: None,
            origin: ci.origin,
            provenance: Provenance::SourceOnly,
            valid_from_sha: self.local.sha.clone(),
            valid_to_sha: None,
            ty: None,
            is_test: false,
            defined_here: did.is_local(),
            visibility: None,
            effective_visibility: None,
            doc: None,
            has_doc: None,
            ignored: None,
            doc_hidden: None,
            link_name: None,
        }
    }

    fn push_node(&mut self, row: NodeRow) {
        if self.node_ids.insert(row.id.clone()) {
            self.nodes.push(row);
        } else if row.defined_here
            && row.kind != kind::IMPL
            // generics of merged inherent impl blocks
            && !(row.kind == kind::GENERIC && row.parent.as_deref().is_some_and(|p| p.ends_with(']')))
        {
            self.collisions += 1;
            if self.collision_samples.len() < 20 {
                let first = self.nodes.iter().find(|n| n.id == row.id);
                self.collision_samples.push(format!(
                    "{} @{}:{} first @{}:{}",
                    row.id,
                    row.file.as_deref().unwrap_or("?"),
                    row.start_line.unwrap_or(0),
                    first.and_then(|n| n.file.as_deref()).unwrap_or("?"),
                    first.and_then(|n| n.start_line).unwrap_or(0),
                ));
            }
        }
    }

    /// One SEMANTIC edge per `(from, to, kind)`; every distinct site
    /// `(file, line)` bumps its `count` and, when positioned, adds a span row.
    fn push_edge(&mut self, from: String, to: String, kind: EdgeKind, file: Option<String>, line: Option<u32>) {
        let idx = match self.edge_index.get(&(from.clone(), to.clone(), kind)) {
            Some(i) => *i,
            None => {
                let i = self.edges.len();
                self.edge_index.insert((from.clone(), to.clone(), kind), i);
                self.edges.push(EdgeRow {
                    from: from.clone(),
                    to: to.clone(),
                    kind,
                    count: 0,
                    provenance: Provenance::SourceOnly,
                    valid_from_sha: self.local.sha.clone(),
                    valid_to_sha: None,
                    negated: None,
                    partial: None,
                    access: None,
                });
                i
            }
        };
        if self.edge_sites.insert((idx, file.clone(), line)) {
            self.edges[idx].count += 1;
            if let (Some(file), Some(line)) = (file, line) {
                self.edge_spans.push(SpanRow {
                    target: SPAN_EDGE,
                    key: edge_key(&from, &to, kind),
                    file,
                    start_line: line,
                    end_line: line,
                });
            }
        }
    }

    /// Edge to an arbitrary DefId target; extern targets get a reference stub
    /// node (defined_here = false) so the dump is self-describing.
    fn edge_to(&mut self, from: DefId, to: DefId, kind: EdgeKind, site: Option<Span>) {
        let Some(to) = self.node_or_ancestor(to) else { return };
        let fm = self.moniker(from);
        let tm = self.moniker(to);
        if !to.is_local() {
            self.cross_crate += 1;
            let tci = self.crate_info(to.krate);
            if self.local.origin == Origin::Local && tci.origin != Origin::Local {
                self.cross_repo += 1;
            }
            self.stub(to);
        }
        let (file, line) = match site {
            Some(sp) => {
                let (f, l, _) = self.file_lines(sp, true);
                (f, l)
            }
            None => (None, None),
        };
        self.push_edge(fm, tm, kind, file, line);
    }

    fn stub(&mut self, did: DefId) {
        if did.is_local() || !self.stub_done.insert(did) {
            return;
        }
        let (k, level) = self.node_kind(did).unwrap_or(("item", Level::Item));
        let mut row = self.base_row(did, k, level);
        if !did.is_crate_root() {
            let (f, s, e) = self.file_lines(self.tcx.def_span(did), false);
            row.file = f;
            row.start_line = s;
            row.end_line = e;
        }
        self.push_node(row);
    }

    // ------------------------------------------------------------- passes

    /// `#[test]` fns: in a `--test` build each has a sibling const carrying
    /// `#[rustc_test_marker]` with the same name (clippy's idiom).
    fn collect_tests(&mut self) {
        let tcx = self.tcx;
        let mut marked: HashSet<(DefId, String)> = HashSet::new();
        for did in tcx.iter_local_def_id() {
            if self.is_test_marker(did) {
                marked.insert((tcx.parent(did.to_def_id()), self.name_of(did.to_def_id())));
            }
        }
        if marked.is_empty() {
            return;
        }
        for did in tcx.iter_local_def_id() {
            if tcx.def_kind(did) == DefKind::Fn
                && marked.contains(&(tcx.parent(did.to_def_id()), self.name_of(did.to_def_id())))
            {
                self.test_fns.insert(did);
            }
        }
    }

    /// The `#[rustc_test_marker]` const/static the test harness generates per
    /// `#[test]` fn (a `TestDescAndFn`).
    fn is_test_marker(&self, did: LocalDefId) -> bool {
        let tcx = self.tcx;
        matches!(tcx.def_kind(did), DefKind::Const { .. } | DefKind::Static { .. })
            && rustc_attr_ir::find_attr!(tcx, did, RustcTestMarker(_))
    }

    /// Items the `--test` harness synthesises (its `main`, re-export modules):
    /// not the program, never nodes.
    fn is_harness_item(&self, did: LocalDefId) -> bool {
        let sp = self.tcx.source_span(did);
        sp.from_expansion()
            && matches!(
                sp.ctxt().outer_expn_data().kind,
                ExpnKind::AstPass(AstPass::TestHarness)
            )
    }

    /// The symbol a foreign fn / static is LINKED as (schema 4): rustc's own
    /// symbol name of the item, which is the item's name unless `#[link_name]`
    /// says otherwise. Under `catch_unwind` like every symbol-name query here:
    /// a panic leaves the column null, never a made-up name.
    fn link_name_of(&self, d: DefId) -> Option<String> {
        let tcx = self.tcx;
        std::panic::catch_unwind(AssertUnwindSafe(|| tcx.symbol_name(Instance::mono(tcx, d)).name.to_string())).ok()
    }

    fn walk_defs(&mut self) {
        let tcx = self.tcx;
        let ids: Vec<LocalDefId> = tcx.iter_local_def_id().collect();
        for did in ids {
            let d = did.to_def_id();
            let Some((k, level)) = self.node_kind(d) else { continue };
            if self.is_test_marker(did) || self.is_harness_item(did) {
                continue;
            }
            if !d.is_crate_root() && tcx.opt_item_name(d).is_none() && !matches!(tcx.def_kind(d), DefKind::Impl { .. }) {
                continue;
            }
            let mut row = self.base_row(d, k, level);
            let span = match tcx.def_kind(d) {
                DefKind::Mod => tcx.hir_get_module(LocalModId::new_unchecked(did)).0.spans.inner_span,
                _ => tcx.source_span(did),
            };
            let (f, s, e) = self.file_lines(span, true);
            row.file = f;
            row.start_line = s;
            row.end_line = e;
            let gate = self.cfg_of(did);
            row.cfg = gate.as_ref().map(|g| g.text.clone());
            row.is_test = self.test_fns.contains(&did);
            row.ty = self.printed_ty(d);
            let (vis, eff) = self.visibility_of(did);
            row.visibility = vis;
            row.effective_visibility = eff;
            if k != kind::GENERIC {
                row.doc = self.doc_of(did);
                row.has_doc = Some(row.doc.is_some());
                row.doc_hidden = Some(tcx.is_doc_hidden(d));
            }
            if row.is_test {
                row.ignored = Some(rustc_attr_ir::find_attr!(tcx, did, Ignore { .. }));
            }
            if k == kind::FFI {
                row.link_name = self.link_name_of(d);
            }
            // the nameable API in rustc's `missing_docs` sense: impl blocks
            // and the items of a trait impl are not documented on their own
            let in_trait_impl = tcx.opt_parent(d).is_some_and(|p| matches!(tcx.def_kind(p), DefKind::Impl { of_trait: true }));
            if eff.is_some_and(EffectiveVisibility::is_public_api) && !in_trait_impl {
                self.x.api_items += 1;
                self.x.api_documented += usize::from(row.has_doc == Some(true));
            }
            let id = row.id.clone();
            let parent = row.parent.clone();
            self.push_node(row);
            if let Some(p) = parent {
                self.push_edge(p, id.clone(), EdgeKind::Contains, None, None);
            }
            if let Some(g) = &gate {
                self.gate_edges(&id, g);
            }
            match tcx.def_kind(d) {
                DefKind::Impl { .. } => self.impl_edges(d),
                DefKind::Fn | DefKind::AssocFn => self.fn_parts(did, &id),
                DefKind::Field => {
                    let fty = tcx.type_of(d).instantiate_identity().skip_normalization();
                    for adt in adts_in(fty) {
                        self.edge_to(d, adt, EdgeKind::UsesType, None);
                    }
                }
                _ => {}
            }
            // EXPANDS: an item produced by a macro expansion → that macro
            if level == Level::Item && span.from_expansion() {
                if let Some(mac) = span.ctxt().outer_expn_data().macro_def_id {
                    self.edge_to(d, mac, EdgeKind::Expands, None);
                }
            }
        }
    }

    fn printed_ty(&self, did: DefId) -> Option<String> {
        let tcx = self.tcx;
        match tcx.def_kind(did) {
            DefKind::Field | DefKind::Const { .. } | DefKind::Static { .. } | DefKind::TyAlias | DefKind::AssocConst { .. } => {
                Some(plain_ty(tcx.type_of(did).instantiate_identity().skip_normalization()))
            }
            _ => None,
        }
    }

    fn impl_edges(&mut self, impl_id: DefId) {
        let tcx = self.tcx;
        if let Some(t) = tcx.impl_opt_trait_id(impl_id) {
            self.edge_to(impl_id, t, EdgeKind::Implements, None);
        }
        let self_ty = tcx.type_of(impl_id).instantiate_identity().skip_normalization();
        if let ty::Adt(adt, _) = peel_refs(self_ty).kind() {
            self.edge_to(impl_id, adt.did(), EdgeKind::ForType, None);
        }
    }

    /// A PART row of this crate under `parent` (a ret / a local): identity
    /// columns, the printed type, no span (the caller sets it).
    fn part_row(&mut self, id: String, k: &str, name: String, parent: &str, ty: String) -> NodeRow {
        let ci = self.crate_info(LOCAL_CRATE);
        let mut row = NodeRow::new(id, k, Level::Part, name, ci.package.clone(), ci.version.clone(), ci.origin);
        row.parent = Some(parent.to_string());
        row.repo = self.local.repo.clone();
        row.valid_from_sha = self.local.sha.clone();
        row.ty = Some(ty);
        row
    }

    /// A fn's `let` bindings as `local` PART nodes (see [`kind::LOCAL`]), at
    /// most [`mimir_schema::LOCALS_PER_FN`].
    fn fn_locals(&mut self, fn_id: &str, locals: &[(String, String, Span)]) {
        let mut seen = HashSet::new();
        for (name, ty, span) in locals {
            if seen.len() >= mimir_schema::LOCALS_PER_FN {
                break;
            }
            let (f, s, e) = self.file_lines(*span, true);
            let Some(line) = s else { continue };
            if !seen.insert((name.clone(), line)) {
                continue;
            }
            let mut desc = String::new();
            moniker::push_descriptor(&mut desc, Suffix::Term, &format!("{name}@{line}"));
            let mut row = self.part_row(format!("{fn_id}{desc}"), kind::LOCAL, name.clone(), fn_id, ty.clone());
            (row.file, row.start_line, row.end_line) = (f, s, e);
            let id = row.id.clone();
            self.push_node(row);
            self.push_edge(fn_id.to_string(), id, EdgeKind::Contains, None, None);
        }
    }

    /// Params (with types) as PART nodes + USES_TYPE fn → ADTs of the signature.
    fn fn_parts(&mut self, did: LocalDefId, fn_id: &str) {
        let tcx = self.tcx;
        let d = did.to_def_id();
        let sig = tcx.fn_sig(d).instantiate_identity().skip_normalization().skip_binder();
        let idents = tcx.fn_arg_idents(d);
        let ci = self.crate_info(LOCAL_CRATE);
        let mut adts: Vec<DefId> = Vec::new();
        for (i, input) in sig.inputs().iter().enumerate() {
            let ident = idents.get(i).copied().flatten();
            let pname = ident
                .map(|id| id.name.to_string())
                .filter(|n| !n.is_empty() && n != "_")
                .unwrap_or_else(|| format!("_{i}"));
            let mut desc = String::new();
            moniker::push_descriptor(&mut desc, Suffix::Parameter, &pname);
            let pid = format!("{fn_id}{desc}");
            let (f, s, e) = match ident {
                Some(id) => self.file_lines(id.span, true),
                None => (None, None, None),
            };
            let row = NodeRow {
                id: pid.clone(),
                kind: kind::PARAM.into(),
                level: Level::Part,
                name: pname,
                parent: Some(fn_id.to_string()),
                repo: self.local.repo.clone(),
                krate: ci.package.clone(),
                version: ci.version.clone(),
                file: f,
                start_line: s,
                end_line: e,
                cfg: None,
                origin: ci.origin,
                provenance: Provenance::SourceOnly,
                valid_from_sha: self.local.sha.clone(),
                valid_to_sha: None,
                ty: Some(plain_ty(*input)),
                is_test: false,
                defined_here: true,
                visibility: None,
                effective_visibility: None,
                doc: None,
                has_doc: None,
                ignored: None,
                doc_hidden: None,
                link_name: None,
            };
            self.push_node(row);
            self.push_edge(fn_id.to_string(), pid, EdgeKind::Contains, None, None);
            adts.extend(adts_in(*input));
        }
        adts.extend(adts_in(sig.output()));
        // the return type: a part under the fn (none for `()`)
        if !sig.output().is_unit() {
            let mut desc = String::new();
            moniker::push_descriptor(&mut desc, Suffix::Term, "->");
            let mut row = self.part_row(format!("{fn_id}{desc}"), kind::RET, "->".to_string(), fn_id, plain_ty(sig.output()));
            if let Some(decl) = tcx.hir_fn_decl_by_hir_id(tcx.local_def_id_to_hir_id(did)) {
                (row.file, row.start_line, row.end_line) = self.file_lines(decl.output.span(), true);
            }
            let rid = row.id.clone();
            self.push_node(row);
            self.push_edge(fn_id.to_string(), rid, EdgeKind::Contains, None, None);
        }
        let mut seen = HashSet::new();
        for a in adts {
            if seen.insert(a) {
                self.edge_to(d, a, EdgeKind::UsesType, None);
            }
        }
    }

    fn walk_crate_deps(&mut self) {
        let tcx = self.tcx;
        let root = LOCAL_CRATE.as_def_id();
        for &cnum in tcx.crates(()) {
            if tcx.extern_crate(cnum).is_some_and(|e| e.is_direct()) {
                self.edge_to(root, cnum.as_def_id(), EdgeKind::DependsOn, None);
            }
        }
    }

    /// Remember who calls what — as written and as resolved — for the table
    /// pass (only when it will run).
    fn note_calls(&mut self, owner: LocalDefId, calls: &[(DefId, DefId)]) {
        if !self.tables.enabled {
            return;
        }
        for (written, resolved) in calls {
            for did in [written, resolved] {
                let v = self.tables.callers.entry(*did).or_default();
                if v.last() != Some(&owner) {
                    v.push(owner);
                }
            }
        }
    }

    /// [`Self::walk_bodies`]' call collection alone (no edges): what the
    /// table pass needs when the crate is not dumped.
    fn collect_calls(&mut self) {
        let tcx = self.tcx;
        let owners: Vec<LocalDefId> = tcx.hir_body_owners().collect();
        for owner in owners {
            if tcx.is_typeck_child(owner.to_def_id()) || self.is_test_marker(owner) || self.is_harness_item(owner) {
                continue;
            }
            let mut v = BodyVisitor { tcx, typeck: tcx.typeck(owner), root: owner, typing_env: TypingEnv::post_analysis(tcx, owner.to_def_id()), found: Vec::new(), calls: Vec::new(), locals: Vec::new(), callees: Default::default() };
            v.visit_body(tcx.hir_body_owned_by(owner));
            self.note_calls(owner, &v.calls);
        }
    }

    /// [`Self::init_tables`], never taking the dump down with it.
    fn init_tables_guarded(&mut self) {
        if let Err(p) = std::panic::catch_unwind(AssertUnwindSafe(|| self.init_tables())) {
            let msg = p.downcast_ref::<String>().cloned().or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default();
            self.tables.enabled = false;
            self.tables.error = Some(format!("panic at start: {msg}"));
            self.table_pass_failed(&format!("panic at start: {msg}"));
        }
    }

    /// The table pass, never taking the rest of the dump down with it: a
    /// panic in it loses the TOUCHES_TABLE edges of this crate and is written
    /// into the stats (`table_pass_error`).
    fn walk_tables_guarded(&mut self) {
        let (nodes, edges, spans) = (self.nodes.len(), self.edges.len(), self.edge_spans.len());
        if let Err(p) = std::panic::catch_unwind(AssertUnwindSafe(|| self.walk_tables(true))) {
            let msg = p.downcast_ref::<String>().cloned().or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default();
            // half-written rows of the pass go: every row it adds is appended
            for n in self.nodes.drain(nodes..) {
                self.node_ids.remove(&n.id);
            }
            for e in self.edges.drain(edges..) {
                self.edge_index.remove(&(e.from, e.to, e.kind));
            }
            self.edge_sites.retain(|(i, _, _)| *i < edges);
            self.edge_spans.truncate(spans);
            self.tables.error = Some(format!("panic: {msg}"));
            self.table_pass_failed(&format!("panic: {msg}"));
        }
    }

    fn walk_bodies(&mut self) {
        let tcx = self.tcx;
        let owners: Vec<LocalDefId> = tcx.hir_body_owners().collect();
        for owner in owners {
            if tcx.is_typeck_child(owner.to_def_id())
                || self.is_test_marker(owner)
                || self.is_harness_item(owner)
            {
                continue;
            }
            let Some(from) = self.node_or_ancestor(owner.to_def_id()) else { continue };
            let typeck = tcx.typeck(owner);
            let body = tcx.hir_body_owned_by(owner);
            let mut v = BodyVisitor {
                tcx,
                typeck,
                root: owner,
                typing_env: TypingEnv::post_analysis(tcx, owner.to_def_id()),
                found: Vec::new(),
                calls: Vec::new(),
                locals: Vec::new(),
                callees: Default::default(),
            };
            v.visit_body(body);
            self.note_calls(owner, &v.calls);
            if from == owner.to_def_id() && matches!(tcx.def_kind(from), DefKind::Fn | DefKind::AssocFn) && !v.locals.is_empty() {
                let fn_id = self.moniker(from);
                self.fn_locals(&fn_id, &v.locals);
            }
            let is_test = from.as_local().is_some_and(|l| self.test_fns.contains(&l));
            for (target, kind, span) in v.found {
                self.edge_to(from, target, kind, Some(span));
                if is_test && kind == EdgeKind::Calls {
                    let local_target = target.is_local() || self.crate_info(target.krate).origin == Origin::Local;
                    if local_target {
                        self.edge_to(from, target, EdgeKind::Tests, Some(span));
                    }
                }
            }
        }
    }
}

/// The def kinds rustc computes a visibility for (`tcx.visibility` panics on
/// the rest — generic params, impl blocks, closures …).
fn has_visibility(dk: DefKind) -> bool {
    matches!(
        dk,
        DefKind::Mod
            | DefKind::Struct
            | DefKind::Union
            | DefKind::Enum
            | DefKind::Variant
            | DefKind::Trait
            | DefKind::TyAlias
            | DefKind::ForeignTy
            | DefKind::TraitAlias
            | DefKind::AssocTy
            | DefKind::Fn
            | DefKind::Const { .. }
            | DefKind::Static { nested: false, .. }
            | DefKind::AssocFn
            | DefKind::AssocConst { .. }
            | DefKind::Macro(_)
            | DefKind::Field
    )
}

/// One parsed cfg predicate in canonical spelling (`all(feature = "a",
/// not(unix))`) — the spelling `cfg_scan` gives a compiled-out item's
/// predicate, so the same attribute reads the same on both sides. `None` for
/// a form with no canonical spelling here (`version(…)`).
fn cfg_entry_text(e: &CfgEntry) -> Option<String> {
    let list = |v: &[CfgEntry]| v.iter().map(cfg_entry_text).collect::<Option<Vec<_>>>().map(|p| p.join(", "));
    Some(match e {
        CfgEntry::All(v, _) => format!("all({})", list(v)?),
        CfgEntry::Any(v, _) => format!("any({})", list(v)?),
        CfgEntry::Not(inner, _) => format!("not({})", cfg_entry_text(inner)?),
        CfgEntry::Bool(b, _) => b.to_string(),
        CfgEntry::NameValue { name, value: Some(v), .. } => format!("{name} = {:?}", v.as_str()),
        CfgEntry::NameValue { name, value: None, .. } => name.to_string(),
        CfgEntry::Version(..) => return None,
    })
}

/// Every `feature = "…"` of a parsed cfg predicate with its polarity.
fn cfg_entry_features(e: &CfgEntry, negated: bool, out: &mut Vec<(String, bool)>) {
    match e {
        CfgEntry::All(v, _) | CfgEntry::Any(v, _) => v.iter().for_each(|x| cfg_entry_features(x, negated, out)),
        CfgEntry::Not(inner, _) => cfg_entry_features(inner, !negated, out),
        CfgEntry::NameValue { name, value: Some(v), .. } if *name == rustc_span::sym::feature => out.push((v.to_string(), negated)),
        _ => {}
    }
}

/// Human type string for the `ty` column (`Vec<Point>`): printed with FULL
/// paths and cut by [`mimir_driver::shorten_paths`]. Never the compiler's own
/// trimming — its trimmed_def_paths query makes rustc ICE at the end of a
/// session that emitted no diagnostic (toml_edit, dwarf 2026-10-17); the
/// guard test `no_trimmed_paths` keeps it so.
fn plain_ty(t: Ty<'_>) -> String {
    let _g = NoTrimmedGuard::new();
    mimir_driver::shorten_paths(&t.to_string())
}

fn is_param_arg(a: ty::GenericArg<'_>) -> bool {
    match a.kind() {
        GenericArgKind::Type(t) => matches!(t.kind(), ty::Param(_)),
        GenericArgKind::Lifetime(_) => true,
        GenericArgKind::Const(c) => matches!(c.kind(), ty::ConstKind::Param(_)),
    }
}

fn peel_refs(mut t: Ty<'_>) -> Ty<'_> {
    while let ty::Ref(_, inner, _) = t.kind() {
        t = *inner;
    }
    t
}

fn adts_in(t: Ty<'_>) -> Vec<DefId> {
    let mut v = Vec::new();
    for arg in t.walk() {
        if let GenericArgKind::Type(ty) = arg.kind() {
            if let ty::Adt(adt, _) = ty.kind() {
                if !v.contains(&adt.did()) {
                    v.push(adt.did());
                }
            }
        }
    }
    v
}

/// Walks one typeck root's body (closures and inline consts included — they
/// share its typeck results) and collects CALLS + READS_FIELD targets.
struct BodyVisitor<'a, 'tcx> {
    tcx: TyCtxt<'tcx>,
    typeck: &'a ty::TypeckResults<'tcx>,
    root: LocalDefId,
    typing_env: TypingEnv<'tcx>,
    found: Vec<(DefId, EdgeKind, Span)>,
    /// every call: (the callee as written, as resolved)
    calls: Vec<(DefId, DefId)>,
    /// every `let` binding: (name, printed type, its ident's span)
    locals: Vec<(String, String, Span)>,
    /// the callee paths of the calls seen (`f` in `f(x)`): a path to a fn
    /// that is NOT one of them names the fn as a VALUE (`each_addr(a, f)`)
    callees: std::collections::HashSet<hir::HirId>,
}

impl<'a, 'tcx> BodyVisitor<'a, 'tcx> {
    /// A trait method with concrete args → the impl method rustc would pick
    /// (`Instance::try_resolve`); anything generic / dyn / shim stays the
    /// trait's own method.
    fn resolve(&self, did: DefId, hir_id: hir::HirId) -> DefId {
        let tcx = self.tcx;
        if tcx.trait_of_assoc(did).is_none() {
            return did;
        }
        let Some(args) = self.typeck.node_args_opt(hir_id) else { return did };
        if args.has_non_region_param() || args.has_infer() || args.references_error() || args.has_escaping_bound_vars() {
            return did;
        }
        let args = tcx.erase_and_anonymize_regions(args);
        match Instance::try_resolve(tcx, self.typing_env, did, args) {
            Ok(Some(inst)) => match inst.def {
                ty::InstanceKind::Item(d) => d,
                _ => did,
            },
            _ => did,
        }
    }
}

impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> {
    type NestedFilter = nested_filter::OnlyBodies;

    fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_nested_body(&mut self, id: hir::BodyId) {
        // only bodies that share THIS typeck root (closures, inline consts);
        // anon consts etc. are their own roots, walked on their own.
        let owner = self.tcx.hir_body_owner_def_id(id);
        if self.tcx.typeck_root_def_id_local(owner) == self.root {
            let body = self.tcx.hir_body(id);
            self.visit_body(body);
        }
    }

    fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
        if !l.span.from_expansion() {
            l.pat.each_binding(|_, hir_id, _, ident| {
                if ident.name.as_str().starts_with('_') || ident.span.from_expansion() {
                    return;
                }
                if let Some(t) = self.typeck.node_type_opt(hir_id) {
                    self.locals.push((ident.name.to_string(), plain_ty(t), ident.span));
                }
            });
        }
        intravisit::walk_local(self, l);
    }

    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
        match e.kind {
            hir::ExprKind::Call(callee, _) => {
                let mut named = false;
                if let hir::ExprKind::Path(ref qpath) = callee.kind {
                    self.callees.insert(callee.hir_id);
                    if let Res::Def(DefKind::Fn | DefKind::AssocFn, did) =
                        self.typeck.qpath_res(qpath, callee.hir_id)
                    {
                        named = true;
                        let t = self.resolve(did, callee.hir_id);
                        self.calls.push((did, t));
                        self.found.push((t, EdgeKind::Calls, e.span));
                    }
                }
                if !named {
                    // a call through a VALUE: a closure, a `Box<dyn Fn…>`, a
                    // generic `F: Fn…` is an overloaded call of the `Fn*`
                    // trait method (resolved to the impl when the type is
                    // concrete); a fn pointer implements `Fn`, so its call is
                    // recorded as `Fn::call` — the reader widens both
                    if let Some(did) = self.typeck.type_dependent_def_id(e.hir_id) {
                        let t = self.resolve(did, e.hir_id);
                        self.calls.push((did, t));
                        self.found.push((t, EdgeKind::Calls, e.span));
                    } else if self.typeck.expr_ty_adjusted_opt(callee).is_some_and(|t| matches!(t.kind(), ty::FnPtr(..)))
                        && let Some(call) = self.tcx.lang_items().fn_trait().and_then(|tr| self.tcx.associated_item_def_ids(tr).first().copied())
                    {
                        self.found.push((call, EdgeKind::Calls, e.span));
                    }
                }
            }
            // a fn named as a VALUE (`each_addr(addr, TcpStream::connect)`,
            // `.map(Self::parse)`, a fn pointer stored): whoever receives it
            // calls it, so the body that names it reaches it — a CALLS edge
            // (the call through the generic / pointer has no resolvable callee)
            hir::ExprKind::Path(ref qpath) if !self.callees.contains(&e.hir_id) => {
                if let Res::Def(DefKind::Fn | DefKind::AssocFn, did) = self.typeck.qpath_res(qpath, e.hir_id) {
                    let t = self.resolve(did, e.hir_id);
                    self.found.push((t, EdgeKind::Calls, e.span));
                }
            }
            hir::ExprKind::MethodCall(..) => {
                if let Some(did) = self.typeck.type_dependent_def_id(e.hir_id) {
                    let t = self.resolve(did, e.hir_id);
                    self.calls.push((did, t));
                    self.found.push((t, EdgeKind::Calls, e.span));
                }
            }
            hir::ExprKind::Field(base, _) => {
                if let (Some(idx), Some(bt)) =
                    (self.typeck.opt_field_index(e.hir_id), self.typeck.expr_ty_adjusted_opt(base))
                {
                    if let ty::Adt(adt, _) = peel_refs(bt).kind() {
                        if adt.is_struct() || adt.is_union() {
                            if let Some(f) = adt.non_enum_variant().fields.get(idx) {
                                self.found.push((f.did, EdgeKind::ReadsField, e.span));
                            }
                        }
                    }
                }
            }
            _ => {}
        }
        intravisit::walk_expr(self, e);
    }
}