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
}
}