crates/mimir-room/src/attrs.rs
//! What a node IS beyond its id: every payload column (typed, or by name for
//! the ones a producer added after this build), its place in the CONTAINS
//! ladder as a readable path, and its positions — the node's span and the
//! sites of an edge. Positions come from `code_span_events`, never from the
//! node payload (moving code changes spans only).
use mimir_schema::{EdgeKind, Level, kind as nk, moniker};
use serde::Serialize;
use crate::Room;
use crate::snapshot::{ExtraData, NONE, NONE8, Site, kind_index};
/// A node payload value read by column name.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum AttrValue<'a> {
Str(&'a str),
Int(i64),
Bool(bool),
}
impl<'a> AttrValue<'a> {
pub fn as_str(&self) -> Option<&'a str> {
match self {
AttrValue::Str(s) => Some(s),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
AttrValue::Int(i) => Some(*i),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
AttrValue::Bool(b) => Some(*b),
_ => None,
}
}
}
/// A position: a node's extent, or one site of an edge (`start_line ==
/// end_line`). `repo` = the repo whose dump wrote it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub struct Span<'a> {
pub repo: &'a str,
pub file: &'a str,
pub start_line: u32,
pub end_line: u32,
}
/// One source file of the room.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct FileView<'a> {
pub repo: &'a str,
pub path: &'a str,
/// Nodes whose (first) span lies in it.
pub nodes: u64,
}
/// The typed columns, by the name the schema gives them.
const TYPED: [&str; 12] = ["kind", "level", "name", "parent", "crate", "version", "cfg", "origin", "ty", "is_test", "defined_here", "repo"];
impl Room {
/// Does the log's node payload carry column `name` at all? `false` =
/// the producer does not write it yet (the answer that needs it is
/// MISSING an input, not empty).
pub fn has_attr(&self, name: &str) -> bool {
TYPED.contains(&name) || self.snap.nodes.extras.iter().any(|e| e.name == name)
}
/// The payload columns readable with [`Room::attr`]: the typed ones and
/// every further column the log's node schema has.
pub fn attr_names(&self) -> Vec<&str> {
TYPED.iter().copied().chain(self.snap.nodes.extras.iter().map(|e| e.name.as_str())).collect()
}
/// One payload value of a node by column name — the typed columns and,
/// without any change here, whatever column a producer adds later
/// (`visibility`, `doc_lines` …). `None` = null for this node, or no
/// such column ([`Room::has_attr`] tells which).
pub fn attr(&self, ordinal: u64, name: &str) -> Option<AttrValue<'_>> {
let t = &self.snap.nodes;
let i = ordinal as usize;
if i >= t.len() {
return None;
}
let v = self.node(ordinal);
match name {
"kind" => v.kind.map(AttrValue::Str),
"level" => v.level.map(|l| AttrValue::Str(l.as_str())),
"name" => v.name.map(AttrValue::Str),
"parent" => v.parent.map(|p| AttrValue::Str(self.moniker(p))),
"crate" => v.krate.map(AttrValue::Str),
"version" => v.version.map(AttrValue::Str),
"cfg" => v.cfg.map(AttrValue::Str),
"origin" => v.origin.map(|o| AttrValue::Str(o.as_str())),
"ty" => v.ty.map(AttrValue::Str),
"repo" => v.repo.map(AttrValue::Str),
"is_test" => Some(AttrValue::Bool(v.is_test)),
"defined_here" => Some(AttrValue::Bool(v.defined)),
_ => {
let col = t.extras.iter().find(|e| e.name == name)?;
match &col.data {
ExtraData::Str(d) => (d[i] != NONE).then(|| AttrValue::Str(t.strs[d[i] as usize].as_str())),
ExtraData::Int(d) => (d[i] != i64::MIN).then_some(AttrValue::Int(d[i])),
ExtraData::Bool(d) => (d[i] < 2).then_some(AttrValue::Bool(d[i] == 1)),
}
}
}
}
/// The self type of an impl block (its FOR_TYPE target), when it is a
/// node of the room.
pub fn impl_self(&self, impl_ordinal: u64) -> Option<u64> {
let v = &self.snap.impl_self;
v.binary_search_by_key(&(impl_ordinal as u32), |e| e.0).ok().map(|p| v[p].1 as u64)
}
/// What a node is called inside a path: its name; for an impl block the
/// name of its self type (`Table`, so a method reads `Table::open`).
fn segment(&self, ordinal: u64) -> Option<std::borrow::Cow<'_, str>> {
let t = &self.snap.nodes;
let i = ordinal as usize;
let is_impl = t.kind[i] != NONE && t.kinds[t.kind[i] as usize] == nk::IMPL;
if is_impl {
if let Some(ty) = self.impl_self(ordinal).and_then(|o| self.node(o).name) {
return Some(ty.into());
}
// an impl for a type that is no node (`&T`, a tuple, a primitive):
// the moniker's `impl#[Self]` slot
if let Some(s) = impl_slot(self.moniker(ordinal)) {
return Some(s.into());
}
}
(t.name[i] != NONE).then(|| t.strs[t.name[i] as usize].as_str().into())
}
/// The readable path of a node: the names up its `parent` chain, crate
/// first — `skade::store::Table::open`. An impl block reads as its self
/// type. When the chain does not reach a crate node (a reference stub
/// whose module is not in the room) the moniker's own descriptors are
/// read instead; a node with neither is its moniker.
pub fn display_path(&self, ordinal: u64) -> String {
let t = &self.snap.nodes;
// the room's own names, borrowed: one String for the whole path
let mut segs: Vec<std::borrow::Cow<'_, str>> = Vec::with_capacity(8);
let mut cur = ordinal as usize;
let mut top_is_crate = false;
// a cycle in `parent` would be a producer bug; never loop on it
for _ in 0..64 {
let Some(seg) = self.segment(cur as u64) else { break };
segs.push(seg);
top_is_crate = t.level[cur] != NONE8 && Level::ALL[t.level[cur] as usize] == Level::Crate;
if t.parent[cur] == NONE {
break;
}
cur = t.parent[cur] as usize;
}
if !top_is_crate && let Some(p) = moniker_path(self.moniker(ordinal)) {
return p;
}
if segs.is_empty() {
return self.moniker(ordinal).to_string();
}
segs.reverse();
segs.join("::")
}
/// The nearest enclosing module (or the crate root) of a node: the
/// first ancestor — the node itself included — at level `module` or
/// `crate`.
pub fn module_of(&self, ordinal: u64) -> Option<u64> {
let t = &self.snap.nodes;
let mut cur = ordinal as usize;
for _ in 0..64 {
if t.level[cur] != NONE8 && matches!(Level::ALL[t.level[cur] as usize], Level::Module | Level::Crate) {
return Some(cur as u64);
}
if t.parent[cur] == NONE {
return None;
}
cur = t.parent[cur] as usize;
}
None
}
fn span_at(&self, file: u32, start: u32, end: u32) -> Span<'_> {
let f = &self.snap.nodes.files[file as usize];
Span { repo: &f.repo, file: &f.path, start_line: start, end_line: end }
}
/// The node's span (its first one, in (file, line) order, when several
/// units position the same id differently — [`Room::spans_of`] has all).
pub fn span(&self, ordinal: u64) -> Option<Span<'_>> {
let t = &self.snap.nodes;
let i = ordinal as usize;
(i < t.len() && t.span_file[i] != NONE).then(|| self.span_at(t.span_file[i], t.span_start[i], t.span_end[i]))
}
/// Every span of the node.
pub fn spans_of(&self, ordinal: u64) -> Vec<Span<'_>> {
let t = &self.snap.nodes;
let mut out: Vec<Span<'_>> = self.span(ordinal).into_iter().collect();
let o = ordinal as u32;
let from = t.more_spans.partition_point(|s| s.0 < o);
out.extend(t.more_spans[from..].iter().take_while(|s| s.0 == o).map(|s| self.span_at(s.1, s.2, s.3)));
out
}
/// The positioned sites of the edge `from → to` of `kind` (call-site
/// lines …), in (file, line) order. Empty for an edge without a
/// positioned site (CONTAINS, a site inside a macro expansion).
pub fn sites(&self, from: u64, to: u64, kind: EdgeKind) -> Vec<Span<'_>> {
let s = &self.snap.sites;
let key = (kind_index(kind), from as u32, to as u32);
let at = s.partition_point(|x: &Site| (x.kind, x.from, x.to) < key);
s[at..].iter().take_while(|x| (x.kind, x.from, x.to) == key).map(|x| self.span_at(x.file, x.line, x.line)).collect()
}
/// How many sites carry the edge (the `count` column: positioned or
/// not). `None` = the room holds no such edge.
pub fn site_count(&self, from: u64, to: u64, kind: EdgeKind) -> anyhow::Result<Option<u32>> {
let present = {
let g = self.graphs();
match g.a.get(&kind) {
Some(m) => m.get(from, to)?.is_some(),
None => false,
}
};
Ok(present.then(|| self.count_of(kind, from, to)))
}
/// The site count of an edge KNOWN to exist (1 unless recorded otherwise).
pub(crate) fn count_of(&self, kind: EdgeKind, from: u64, to: u64) -> u32 {
let key = (from << 32) | to;
match self.snap.counts.get(&kind) {
Some(v) => v.binary_search_by_key(&key, |e| e.0).map_or(1, |p| v[p].1),
None => 1,
}
}
/// The flags (`E_NEGATED` / `E_PARTIAL`) of an edge known to exist.
pub(crate) fn flags_of(&self, kind: EdgeKind, from: u64, to: u64) -> u8 {
let key = (from << 32) | to;
match self.snap.edge_flags.get(&kind) {
Some(v) => v.binary_search_by_key(&key, |e| e.0).map_or(0, |p| v[p].1),
None => 0,
}
}
/// How an edge touches its table (`TOUCHES_TABLE`'s `access`, the union
/// over the rows that carry the edge); `None` for an edge with no
/// `access` — every other kind, and a log written before mimir-schema 4.
pub fn access_of(&self, kind: EdgeKind, from: u64, to: u64) -> Option<mimir_schema::Access> {
let key = (from << 32) | to;
let v = self.snap.edge_access.get(&kind)?;
v.binary_search_by_key(&key, |e| e.0).ok().and_then(|p| mimir_schema::Access::from_bits(v[p].1))
}
/// Was the edge column `name` added to this lake IN PLACE (the lake was
/// created by an older `mimir-schema`)? Then a repo not consumed since
/// has edges that say nothing for it: the ABSENCE of a value — of every
/// `TOUCHES_TABLE` edge, for `access` — is not yet a fact about the code.
/// `false` = every edge row of the lake was written with the column.
pub fn edge_column_added_in_place(&self, name: &str) -> bool {
self.snap.edge_columns_added.iter().any(|c| c == name)
}
fn file_index(&self) -> &[u32] {
self.snap.file_idx.get_or_init(|| {
let t = &self.snap.nodes;
let mut v: Vec<u32> = (0..t.len() as u32).filter(|i| t.span_file[*i as usize] != NONE).collect();
v.sort_unstable_by_key(|i| (t.span_file[*i as usize], t.span_start[*i as usize], t.span_end[*i as usize], *i));
v
})
}
/// Every file a node span lies in, by (repo, path).
pub fn files(&self) -> Vec<FileView<'_>> {
let t = &self.snap.nodes;
let mut n = vec![0u64; t.files.len()];
for f in t.span_file.iter().filter(|f| **f != NONE) {
n[*f as usize] += 1;
}
t.files.iter().zip(n).map(|(f, nodes)| FileView { repo: &f.repo, path: &f.path, nodes }).collect()
}
/// The nodes whose span lies in `path` (in `repo`, or in any repo of
/// the scope when `None`), in line order — outer items before the items
/// they contain.
pub fn nodes_in_file(&self, repo: Option<&str>, path: &str) -> Vec<u64> {
let t = &self.snap.nodes;
let idx = self.file_index();
let mut out = Vec::new();
for (f, _) in t.files.iter().enumerate().filter(|(_, f)| f.path == path && repo.is_none_or(|r| f.repo == r)) {
let f = f as u32;
let at = idx.partition_point(|i| t.span_file[*i as usize] < f);
out.extend(idx[at..].iter().take_while(|i| t.span_file[**i as usize] == f).map(|i| *i as u64));
}
out
}
}
/// One SCIP descriptor: its name and suffix character class.
fn descriptors(d: &str) -> Vec<(String, char)> {
let mut out = Vec::new();
let mut it = d.chars().peekable();
// a name: a simple identifier or a backtick-quoted one (`` ` `` doubled)
fn name(it: &mut std::iter::Peekable<std::str::Chars<'_>>) -> String {
let mut s = String::new();
if it.peek() == Some(&'`') {
it.next();
while let Some(c) = it.next() {
if c == '`' {
if it.peek() == Some(&'`') {
it.next();
s.push('`');
} else {
break;
}
} else {
s.push(c);
}
}
} else {
while let Some(c) = it.peek().copied() {
if c == '_' || c == '+' || c == '-' || c == '$' || c.is_ascii_alphanumeric() {
s.push(c);
it.next();
} else {
break;
}
}
}
s
}
while let Some(c) = it.peek().copied() {
match c {
'[' | '(' => {
it.next();
let n = name(&mut it);
it.next(); // `]` / `)`
out.push((n, c));
}
_ => {
let n = name(&mut it);
match it.next() {
Some('(') => {
// `name().`
it.next();
it.next();
out.push((n, 'm'));
}
Some(s) => out.push((n, s)),
None => {
if !n.is_empty() {
out.push((n, ' '));
}
}
}
}
}
}
out
}
/// The `Self` slot of a moniker ending in an impl scope (`…impl#[Self]` or
/// `…impl#[Self][Trait]`).
fn impl_slot(m: &str) -> Option<String> {
let (_, _, d) = moniker::parse(m)?;
let ds = descriptors(d);
let at = ds.iter().rposition(|(n, s)| n == "impl" && *s == '#')?;
ds.get(at + 1).filter(|(_, s)| *s == '[').map(|(n, _)| n.clone())
}
/// `crate::module::Type::item` out of a moniker this workspace's formatter
/// wrote; `None` for any other id.
fn moniker_path(m: &str) -> Option<String> {
let (package, _, d) = moniker::parse(m)?;
let ds = descriptors(d);
let mut segs = vec![package.replace('-', "_")];
let mut i = 0;
while i < ds.len() {
let (n, s) = &ds[i];
if i == 0 && n == "crate" && *s == '/' {
i += 1;
continue;
}
if n == "impl" && *s == '#' {
// `impl#[Self][Trait]`: the self type stands for the block
if let Some((ty, '[')) = ds.get(i + 1).map(|(n, s)| (n, *s)) {
segs.push(ty.clone());
i += 2;
if ds.get(i).is_some_and(|(_, s)| *s == '[') && ds.get(i + 1).is_some_and(|(_, s)| *s != '[') {
// the trait slot (a type parameter of an impl item follows an item, never the block)
i += 1;
}
continue;
}
}
segs.push(n.clone());
i += 1;
}
Some(segs.join("::"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_moniker_reads_as_a_path() {
let m = |d: &str| moniker::moniker("my-crate", "0.3.1", d);
assert_eq!(moniker_path(&m("crate/")).as_deref(), Some("my_crate"));
assert_eq!(moniker_path(&m("store/Table#open().")).as_deref(), Some("my_crate::store::Table::open"));
assert_eq!(moniker_path(&m("store/impl#[Table]open().")).as_deref(), Some("my_crate::store::Table::open"));
assert_eq!(moniker_path(&m("impl#[Dog][Greet]greet().")).as_deref(), Some("my_crate::Dog::greet"));
assert_eq!(moniker_path(&m("impl#[i32][`Add<Self>`]add().(rhs)")).as_deref(), Some("my_crate::i32::add::rhs"));
assert_eq!(moniker_path(&m("a/MAX.")).as_deref(), Some("my_crate::a::MAX"));
assert_eq!(moniker_path(&m("a/vec!")).as_deref(), Some("my_crate::a::vec"));
assert_eq!(moniker_path(&m("a/f().[T]")).as_deref(), Some("my_crate::a::f::T"));
assert_eq!(moniker_path("k a"), None, "not a moniker of ours");
assert_eq!(impl_slot(&m("store/impl#[Table]")).as_deref(), Some("Table"));
assert_eq!(impl_slot(&m("store/impl#[`&T`][Show]")).as_deref(), Some("&T"));
assert_eq!(impl_slot(&m("store/Table#")), None);
}
}