src/mimir_lens/room_feed/graph.rs
//! The code room as a lens reads it: one room over every consumed repo of
//! the workspace, the request's repo filter as node sets, and the few derived
//! sets several lenses share (callable items, the test-reachable set, call
//! sites per item).
use std::collections::{BTreeMap, BTreeSet};
use mimir_room::{Answer, EdgeOut, EdgeSel, Evidence, Mask, MaskSpec, Room, Scope};
use mimir_schema::{EdgeKind, Origin, kind as nk};
use super::{Basis, CALLS_LOWER_BOUND, Query};
use crate::code_room::{Reach, Refusal, State};
/// The room of one request.
pub struct Graph {
pub room: Room,
/// The repos the request covers: its filter, or every consumed repo.
pub repos: Vec<String>,
scope: String,
workspace: String,
/// Where the consumer's clones are (`<builds>/mimir_v2/clones/<repo>`): a
/// lens that reads a finding's source text (the literal-body filter).
pub layout: crate::code_room::Layout,
}
/// One callable item (`fn` / `method`) defined in the request's repos.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Callable {
pub ordinal: u64,
pub repo: String,
/// The file its span lies in (`None` = no span row).
pub file: Option<String>,
pub line: u32,
/// The last line of its span (`line` when the room has none).
pub end_line: u32,
/// A `#[test]` / bench fn, or an item inside `#[cfg(test)]` code.
pub is_test: bool,
/// CALLS sites arriving at it, from anywhere in the room.
pub call_sites: u32,
/// In the test-reachable set (`false` everywhere when the scope has no test).
pub covered: bool,
}
thread_local! {
/// As-of rooms this thread folded ([`room_uncached`]): what a test reads
/// to prove a fold was kept rather than repeated.
static UNCACHED: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
/// How many uncached rooms this thread has folded so far.
pub fn uncached_builds() -> u64 {
UNCACHED.with(|n| n.get())
}
/// A room over `scope`, built for this one question and NOT kept in the
/// process cache — the time-travel rooms a diff needs must not push the
/// workspace's own room out of it.
pub fn room_uncached(reach: &Reach<'_>, scope: &Scope) -> Result<Room, Refusal> {
UNCACHED.with(|n| n.set(n.get() + 1));
let log = reach.code_room()?.log()?;
// on a thread of its own, on the lake's runtime: callable from any thread
std::thread::scope(|s| s.spawn(|| log.block_on(Room::build_uncached(&log, scope))).join().unwrap_or_else(|p| std::panic::resume_unwind(p)))
.map_err(|e| Refusal::new(State::Internal, format!("code room at {scope:?}: {e:#}")))
}
/// Does a `cfg` predicate, as written, require `test` (`test`,
/// `all(test, feature = "x")`) — and not merely mention it under `not(…)`?
pub fn cfg_requires_test(cfg: &str) -> bool {
let mentions = cfg.split(|c: char| !(c.is_alphanumeric() || c == '_')).any(|t| t == "test");
mentions && !cfg.replace(' ', "").contains("not(test")
}
/// A room query failed (the engine, not the data).
pub fn internal(e: anyhow::Error) -> Refusal {
Refusal::new(State::Internal, format!("code room query failed: {e:#}"))
}
impl Basis {
/// Take a room answer: its evidence becomes feeds (one per edge kind
/// walked) and gaps (everything the room said was MISSING).
pub fn room<T>(&mut self, a: Answer<T>) -> T {
self.evidence(&a.evidence);
a.value
}
pub fn evidence(&mut self, ev: &Evidence) {
for (k, n) in &ev.kinds {
self.feed(format!("code_edges:{k}"), *n as usize, format!("code room {}", ev.scope));
}
for m in &ev.missing {
// an edge kind without edges is already a missing feed row: keep the why
if m.input.strip_suffix(" edges").is_some_and(|k| EdgeKind::parse(k).is_some()) {
self.note(format!("{}: {}", m.input, m.why));
continue;
}
match &m.repo {
Some(r) if !self.repos.is_empty() && !self.repos.contains(r) => {}
Some(r) => self.gap(format!("{} [{r}]: {}", m.input, m.why)),
None => self.gap(format!("{}: {}", m.input, m.why)),
}
}
for n in &ev.notes {
// the sub-query's own emptiness is not the lens's, and how the room
// looked up linked ring items is not the reader's concern
if !n.starts_with("empty answer") && !n.starts_with("capped") && !n.starts_with("ring off, but linked ring items") {
self.note(n.trim_end_matches('.').to_string());
}
}
}
}
impl Graph {
/// Open the room of this request, universe ring off: the room of the ONE
/// repo asked for when the lens reads nothing that arrives from the other
/// repos ([`crate::mimir_lens::REPO_SCOPED`]), else every consumed repo.
/// Refuses by state: no room in reach, the lake held by another process,
/// nothing consumed yet, the repo asked for not consumed.
pub fn open(reach: &Reach<'_>, q: &Query<'_>, basis: &mut Basis) -> Result<Self, Refusal> {
let handle = reach.code_room()?;
let room = handle.room(&crate::mimir_lens::scope_of(q.lens, q.repo))?;
let consumed: Vec<String> = room.scope().repos.keys().cloned().collect();
if consumed.is_empty() {
return Err(Refusal::new(
State::NotConsumed,
format!("code room of `{}`: no repo has been consumed yet (the log holds no consumer offset)", handle.workspace()),
));
}
if !q.repo.is_empty() && !consumed.iter().any(|r| r == q.repo) {
return Err(Refusal::new(
State::NotConsumed,
format!("code room of `{}`: repo `{}` is not consumed yet (consumed: {})", handle.workspace(), q.repo, consumed.join(", ")),
));
}
let scope = room.scope().to_string();
basis.scope = Some(scope.clone());
let stats = room.stats();
for input in &stats.inputs {
if input.table == "code_node_events" {
basis.feed("code_nodes", input.open_rows as usize, format!("code room {scope}"));
}
}
let repos = if q.repo.is_empty() { consumed } else { vec![q.repo.to_string()] };
basis.repos = repos.clone();
Ok(Self { room, repos, scope, workspace: handle.workspace().to_string(), layout: handle.layout().clone() })
}
/// The workspace the room is of (answers name the verb that fills a gap).
pub fn workspace(&self) -> &str {
&self.workspace
}
pub fn root(&self) -> String {
format!("code room {}", self.scope)
}
/// A mask of the request's repos with `spec`'s other fields.
pub fn mask(&self, mut spec: MaskSpec) -> Result<Mask, Refusal> {
spec.repos = self.repos.clone();
Ok(self.room.mask(&spec).map_err(internal)?.value)
}
/// Source definitions of the request's repos' OWN crates, of these kinds
/// (empty = every kind).
pub fn local(&self, kinds: &[&str]) -> Result<Mask, Refusal> {
self.mask(MaskSpec { node_kinds: kinds.iter().map(|k| k.to_string()).collect(), origins: vec![Origin::Local], defined_only: true, ..Default::default() })
}
/// The callable items (`fn` + `method`) defined in the request's repos.
pub fn callables(&self) -> Result<Mask, Refusal> {
// a kind no node has is reported by the room as missing — here it only
// means "this repo defines no method", which is no gap
let mut ords: Vec<u64> = Vec::new();
for k in [nk::FN, nk::METHOD] {
ords.extend_from_slice(self.local(&[k])?.ordinals());
}
Ok(self.room.mask_of(&ords))
}
/// `(repo, file)` of a node's span.
pub fn file_of(&self, o: u64) -> Option<(String, String)> {
self.room.span(o).map(|s| (s.repo.to_string(), s.file.to_string()))
}
/// The edges of `kinds` between two node sets (`None` = any node). A kind
/// the room holds no edge of is a MISSING feed of the answer.
pub fn edges(&self, basis: &mut Basis, kinds: &[EdgeKind], from: Option<&Mask>, to: Option<&Mask>) -> Result<Vec<EdgeOut>, Refusal> {
let sel = EdgeSel { kinds: kinds.to_vec(), from, to, cap: None, heaviest_first: false };
Ok(basis.room(self.room.edges(&sel).map_err(internal)?).edges)
}
/// [`Self::edges`] for the kinds whose absence is a fact about the code
/// (GATED_BY, ENABLES, TESTS: the driver writes them whenever there is
/// something to write): recorded with their count, never as missing.
pub fn edges_if_any(&self, basis: &mut Basis, kind: EdgeKind, from: Option<&Mask>, to: Option<&Mask>) -> Result<Vec<EdgeOut>, Refusal> {
let sel = EdgeSel { kinds: vec![kind], from, to, cap: None, heaviest_first: false };
let a = self.room.edges(&sel).map_err(internal)?;
let total = a.evidence.kinds.first().map_or(0, |k| k.1 as usize);
basis.fact(format!("code_edges:{kind}"), total, self.root());
Ok(a.value.edges)
}
/// Call SITES arriving at each node of `targets` (the `count` column
/// summed over its CALLS in-edges), and the P6 lower-bound note.
pub fn call_sites_into(&self, basis: &mut Basis, targets: &Mask) -> Result<BTreeMap<u64, u32>, Refusal> {
basis.lower_bound(CALLS_LOWER_BOUND);
let mut out: BTreeMap<u64, u32> = BTreeMap::new();
for e in self.edges(basis, &[EdgeKind::Calls], None, Some(targets))? {
*out.entry(e.to).or_default() += e.count;
}
Ok(out)
}
/// The test-reachable set: everything a `#[test]` / bench fn of the
/// request's repos reaches along CALLS, plus the direct TESTS targets.
/// `None` = the scope holds no test node (no coverage input at all).
pub fn test_reach(&self, basis: &mut Basis) -> Result<Option<BTreeSet<u64>>, Refusal> {
let tests = self.mask(MaskSpec { is_test: Some(true), ..Default::default() })?;
basis.fact("code_nodes:is_test", tests.len(), self.root());
if tests.is_empty() {
return Ok(None);
}
basis.lower_bound(CALLS_LOWER_BOUND);
let walk = basis.room(self.room.reach(&tests, &[EdgeKind::Calls], None, None).map_err(internal)?);
let mut covered: BTreeSet<u64> = walk.reached.into_iter().collect();
// TESTS: what a test names directly
covered.extend(self.edges_if_any(basis, EdgeKind::Tests, Some(&tests), None)?.into_iter().map(|e| e.to));
Ok(Some(covered))
}
/// Is the node inside `#[cfg(test)]` code — itself or a container gated by
/// a `cfg` that requires `test`?
pub fn in_test_code(&self, o: u64) -> bool {
let mut cur = Some(o);
for _ in 0..64 {
let Some(c) = cur else { break };
let n = self.room.node(c);
if n.cfg.is_some_and(cfg_requires_test) {
return true;
}
cur = n.parent;
}
false
}
/// Is the node `#[doc(hidden)]` — itself, or a container it sits in (a
/// hidden module hides what is in it)? The attribute is on the node that
/// carries it (`doc_hidden`, mimir-schema 3); a node consumed before the
/// column existed says nothing and counts as not hidden.
pub fn doc_hidden(&self, o: u64) -> bool {
let mut cur = Some(o);
for _ in 0..64 {
let Some(c) = cur else { break };
if self.room.attr(c, "doc_hidden").and_then(|v| v.as_bool()) == Some(true) {
return true;
}
cur = self.room.node(c).parent;
}
false
}
/// Every callable of the request's repos with its call sites and whether
/// a test reaches it. The second value says whether the scope holds any
/// test at all (without one, `covered` carries no information).
pub fn callable_facts(&self, basis: &mut Basis) -> Result<(Vec<Callable>, bool), Refusal> {
let callables = self.callables()?;
basis.feed("code_nodes:fn+method", callables.len(), self.root());
let sites = self.call_sites_into(basis, &callables)?;
let covered = self.test_reach(basis)?;
let out = callables
.ordinals()
.iter()
.map(|&o| {
let n = self.room.node(o);
Callable {
ordinal: o,
repo: n.repo.unwrap_or("").to_string(),
file: n.file.map(str::to_string),
line: n.start_line.unwrap_or(0),
end_line: n.end_line.or(n.start_line).unwrap_or(0),
is_test: n.is_test || self.in_test_code(o),
call_sites: sites.get(&o).copied().unwrap_or(0),
covered: covered.as_ref().is_some_and(|c| c.contains(&o)),
}
})
.collect();
Ok((out, covered.is_some()))
}
/// The impure items the room knows — `std::process::Command::new`, the
/// libc / nix exec and system wrappers, `std::process::exit` …: every node
/// with a candidate name whose resolved path the villa classifier accepts.
/// `ordinal → (path, node kind)`.
pub fn impure_items(&self) -> Result<BTreeMap<u64, (String, String)>, Refusal> {
use crate::mimir_lens::villa;
let mut impure = BTreeMap::new();
for name in villa::IMPURE_NAMES {
let hits = self.room.lookup(&mimir_room::NameQuery::new(*name).mode(mimir_room::NameMatch::Name)).map_err(internal)?.value;
for o in hits {
let kind = self.room.node(o).kind.unwrap_or("").to_string();
let path = self.room.display_path(o);
if villa::classify(&path, &kind).is_some() {
impure.insert(o, (path, kind));
}
}
}
Ok(impure)
}
}