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

nordisk/nornir

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