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

nordisk/nornir

src/mimir_lens/room_feed/corpus.rs

//! The calibration corpus, from the code room: every function of one repo
//! with its OWN text — the room says where an item starts and ends (the
//! driver's spans), the checkout holds the lines.
//!
//! The extent is the ITEM's, never an embedding chunk's: an `impl` block is
//! one chunk, so a corpus cut at chunk borders would hand most functions a
//! block average. [`crate::mimir_calibrate`] measures similarity on these.

use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

use anyhow::Result;
use mimir_room::{EdgeSel, MaskSpec, Room};
use mimir_schema::{EdgeKind, Origin, kind as nk};

use crate::mimir_calibrate::CorpusFn;

/// The functions lifted out, and what was left behind (each with its reason).
#[derive(Debug, Clone, Default)]
pub struct Corpus {
    pub fns: Vec<CorpusFn>,
    /// Callable items of the repo the room holds.
    pub callables: usize,
    /// Produced by a macro expansion: their span is the invocation, not a body.
    pub macro_generated: usize,
    /// No span row (nothing says where they are).
    pub without_span: usize,
    /// Their file is not in the checkout, or is shorter than the span says.
    pub unreadable: usize,
    /// The files those lie in, each with how many functions it cost.
    pub unreadable_files: BTreeMap<String, usize>,
    /// Shorter than `min_lines` or longer than `max_lines`.
    pub outside_size: usize,
}

/// Lift every `fn` / `method` the repo defines out of `checkout` (the tree the
/// room's file paths are relative to).
///
/// `min_lines` drops functions too small to carry any signal (a 3-line getter
/// embeds close to every other 3-line getter). `max_lines` drops the giants a
/// 120-line chunk window never gives a vector of their own; `0` disables it.
///
/// `signature` is `fn name(param: Type, …)` from the room's parameter nodes,
/// in name order, WITHOUT a return type (the driver does not record one yet);
/// `signature_key` is the sorted parameter types — "same shape" for the hard
/// negatives. `callees` are the names of the items the function CALLS.
pub fn from_room(room: &Room, repo: &str, checkout: &Path, min_lines: usize, max_lines: usize) -> Result<Corpus> {
    let mut ords: Vec<u64> = Vec::new();
    for k in [nk::FN, nk::METHOD] {
        let spec = MaskSpec { repos: vec![repo.to_string()], node_kinds: vec![k.to_string()], origins: vec![Origin::Local], defined_only: true, ..Default::default() };
        ords.extend_from_slice(room.mask(&spec)?.value.ordinals());
    }
    let callables = room.mask_of(&ords);
    let out_edges = |kind: EdgeKind| -> Result<Vec<mimir_room::EdgeOut>> { Ok(room.edges(&EdgeSel { kinds: vec![kind], from: Some(&callables), to: None, cap: None, heaviest_first: false })?.value.edges) };

    let mut params: BTreeMap<u64, Vec<(String, String)>> = BTreeMap::new();
    for e in out_edges(EdgeKind::Contains)? {
        let p = room.node(e.to);
        if p.kind == Some(nk::PARAM) {
            params.entry(e.from).or_default().push((p.name.unwrap_or("_").to_string(), p.ty.unwrap_or("_").to_string()));
        }
    }
    let mut callees: BTreeMap<u64, BTreeSet<String>> = BTreeMap::new();
    for e in out_edges(EdgeKind::Calls)? {
        if let Some(name) = room.node(e.to).name {
            callees.entry(e.from).or_default().insert(name.to_string());
        }
    }
    let generated: BTreeSet<u64> = out_edges(EdgeKind::Expands)?.into_iter().map(|e| e.from).collect();

    let mut corpus = Corpus { callables: callables.len(), ..Default::default() };
    let mut files: BTreeMap<String, Option<Vec<String>>> = BTreeMap::new();
    for &o in callables.ordinals() {
        if generated.contains(&o) {
            corpus.macro_generated += 1;
            continue;
        }
        let n = room.node(o);
        let (Some(file), Some(start), Some(end)) = (n.file, n.start_line, n.end_line) else {
            corpus.without_span += 1;
            continue;
        };
        let (start, end) = (start as usize, end as usize);
        let lines = files.entry(file.to_string()).or_insert_with(|| std::fs::read_to_string(checkout.join(file)).ok().map(|t| t.lines().map(str::to_string).collect()));
        let Some(lines) = lines.as_ref().filter(|l| start >= 1 && end >= start && end <= l.len()) else {
            corpus.unreadable += 1;
            *corpus.unreadable_files.entry(file.to_string()).or_default() += 1;
            continue;
        };
        let span = end - start + 1;
        if span < min_lines || (max_lines != 0 && span > max_lines) {
            corpus.outside_size += 1;
            continue;
        }
        let container = n
            .parent
            .filter(|p| matches!(room.node(*p).kind, Some(nk::IMPL | nk::TRAIT)))
            .map(|p| room.display_path(p).rsplit("::").next().unwrap_or("").to_string())
            .unwrap_or_default();
        let mut ps = params.remove(&o).unwrap_or_default();
        ps.sort();
        let name = n.name.unwrap_or("").to_string();
        let mut types: Vec<&str> = ps.iter().map(|(_, t)| t.as_str()).collect();
        types.sort_unstable();
        corpus.fns.push(CorpusFn {
            file: file.to_string(),
            container,
            signature: format!("fn {name}({})", ps.iter().map(|(p, t)| format!("{p}: {t}")).collect::<Vec<_>>().join(", ")),
            signature_key: format!("fn({})", types.join(", ")),
            name,
            start_line: start,
            end_line: end,
            text: lines[start - 1..end].join("\n"),
            callees: callees.remove(&o).unwrap_or_default(),
        });
    }
    Ok(corpus)
}