Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions lci-maps/src/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
//! rule 1): a consumer that wants a different order computes it at walk time.

use crate::{
Cost, EdgeKind, MapDescriptor, MapId, MapResponse, MapStatus, NodeKind, Observed, SourceLabel,
Cost, EdgeKind, Evidence, MapDescriptor, MapHit, MapId, MapRequest, MapResponse, MapStatus,
NodeKind, Observed, SnapshotView, SourceLabel,
};
use lci_core::node::NodeId;
use std::{cmp::Reverse, time::Duration};
use std::{
cmp::Reverse,
collections::HashSet,
time::{Duration, Instant},
};

/// How far a witness can be trusted, from the strongest to the weakest
/// source. Consumers read `Compiler` and `Exact` by default and report
Expand Down Expand Up @@ -232,6 +237,66 @@ pub fn cap_edges(edges: &mut Vec<Edge>, cap: usize) {
edges.truncate(cap);
}

/// The seeded answer of a map whose edges carry a SCIP site: every seed's
/// capped edges (`edges_of` gives them and how many there were before the
/// cap), merged, sorted, one hit per destination, cut to the request's limit.
/// Hits score `conf` as a unit interval and cite the site through `detail`.
/// `cap` is only reported, not applied here (`edges_of` applies it): a cap
/// that dropped edges makes the status `Partial`, its note naming `noun` and
/// `cap`.
pub fn scip_ranked_hits(
descriptor: &MapDescriptor,
request: &MapRequest<'_>,
view: &SnapshotView<'_>,
cap: usize,
noun: &str,
edges_of: impl Fn(&NodeId) -> (Expanded, usize),
detail: impl Fn(Site) -> String,
) -> MapResponse {
let started = Instant::now();
let mut edges = Vec::new();
let mut dropped = 0;
for (seed, _) in request.seeds {
let (found, total) = edges_of(seed);
dropped += total - found.edges.len();
edges.extend(found.edges);
}
cap_edges(&mut edges, usize::MAX);
let mut seen = HashSet::new();
edges.retain(|edge| seen.insert(edge.dst.clone()));
edges.truncate(request.limit);
let mut response = MapResponse::empty(descriptor, view, MapStatus::Ok);
if dropped > 0 {
response.status = MapStatus::Partial {
note: Some(format!(
"{dropped} more {noun} than the cap of {cap} were not offered"
)),
warning: None,
};
}
response.cost = Cost {
elapsed_ms: started.elapsed().as_secs_f64() * 1000.0,
nodes_touched: edges.len() as u64,
..Cost::default()
};
response.hits = edges
.into_iter()
.zip(1..)
.map(|(edge, rank)| MapHit {
node: edge.dst,
rank,
score: Some(f32::from(edge.conf) / f32::from(CONF_MAX)),
evidence: vec![Evidence {
via: Some(edge.src),
edge: edge.kind,
label: SourceLabel::Scip,
detail: edge.site.map(&detail),
}],
})
.collect();
response
}

/// The edges a seeded query's answer stands for, as [`crate::Map::expand`]'s
/// default yields them: one per hit that is not the seed itself, witness from
/// the hit's evidence label (the descriptor's first label when the hit has
Expand Down
63 changes: 15 additions & 48 deletions lci-maps/src/scip_impact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@
//! which reads the same file.

use crate::{
Budget, Contribution, Cost, EXPAND_LIMIT, EdgeKind, Evidence, FreshnessModel, Map,
MapDescriptor, MapHit, MapId, MapRequest, MapResponse, MapStatus, SnapshotView, SourceLabel,
edge::{CONF_MAX, Edge, Expanded, MapCard, QuestionType, Site, WitnessClass, cap_edges},
Budget, Contribution, Cost, EXPAND_LIMIT, EdgeKind, FreshnessModel, Map, MapDescriptor, MapId,
MapRequest, MapResponse, SnapshotView, SourceLabel,
edge::{
CONF_MAX, Edge, Expanded, MapCard, QuestionType, Site, WitnessClass, cap_edges,
scip_ranked_hits,
},
impact::ImpactIndex,
live_grep::CostLog,
};
Expand Down Expand Up @@ -319,51 +322,15 @@ impl Map for ImpactMap {
}

async fn query(&self, request: &MapRequest<'_>, view: &SnapshotView<'_>) -> MapResponse {
let started = Instant::now();
let mut edges = Vec::new();
let mut dropped = 0;
for (seed, _) in request.seeds {
let (found, total) = self.edges_of(seed);
dropped += total - found.edges.len();
edges.extend(found.edges);
}
cap_edges(&mut edges, usize::MAX);
let mut seen = HashSet::new();
edges.retain(|edge| seen.insert(edge.dst.clone()));
edges.truncate(request.limit);
let mut response = MapResponse::empty(&self.descriptor, view, MapStatus::Ok);
if dropped > 0 {
response.status = MapStatus::Partial {
note: Some(format!(
"{dropped} more users than the cap of {} were not offered",
self.options.cap
)),
warning: None,
};
}
response.cost = Cost {
elapsed_ms: started.elapsed().as_secs_f64() * 1000.0,
nodes_touched: edges.len() as u64,
..Cost::default()
};
response.hits = edges
.into_iter()
.zip(1..)
.map(|(edge, rank)| MapHit {
node: edge.dst,
rank,
score: Some(f32::from(edge.conf) / f32::from(CONF_MAX)),
evidence: vec![Evidence {
via: Some(edge.src),
edge: edge.kind,
label: SourceLabel::Scip,
detail: edge
.site
.map(|s| format!("references the seed at {}:{}", s.path, s.line + 1)),
}],
})
.collect();
response
scip_ranked_hits(
&self.descriptor,
request,
view,
self.options.cap,
"users",
|seed| self.edges_of(seed),
|s| format!("references the seed at {}:{}", s.path, s.line + 1),
)
}

async fn expand(&self, node: &NodeId, _budget: Budget, _view: &SnapshotView<'_>) -> Expanded {
Expand Down
93 changes: 92 additions & 1 deletion lci-maps/src/scip_impact/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use super::*;
use crate::{
Registry, SnapshotRef,
MapStatus, Registry, SnapshotRef,
audit::audit_edge_sources,
kit_c3::check_expand,
scip_graph::{Range, RawDocument, RawOccurrence},
Expand Down Expand Up @@ -543,3 +543,94 @@ async fn graph_scip_and_scip_impact_over_one_index_corroborate_once() {
);
assert_eq!(reached.n_maps(), 1, "one index, one piece of evidence");
}

// ---- pins: the seeded `query` output -------------------------------------------------

async fn ask(map: &ImpactMap, seeds: &[(NodeId, f32)], limit: usize) -> MapResponse {
let params = serde_json::Value::Null;
map.query(
&MapRequest {
text: None,
seeds,
limit,
budget: budget(),
params: &params,
as_of: None,
},
&view(),
)
.await
}

#[tokio::test]
async fn query_pins_the_partial_note_and_the_first_hit() {
let (ix, target) = crowd(100);
let map = ImpactMap::new(ix, &|_| None, ImpactOptions { cap: 5 });
let node = map.node_of(&target).unwrap().clone();
let response = ask(&map, &[(node.clone(), 1.0)], 100).await;
assert_eq!(
response.status,
MapStatus::Partial {
note: Some("95 more users than the cap of 5 were not offered".to_string()),
warning: None,
}
);
assert_eq!(response.hits.len(), 5);
let hit = &response.hits[0];
assert_eq!(hit.rank, 1);
assert_eq!(hit.score, Some(1.0));
assert_eq!(response.hits[4].rank, 5);
assert_eq!(hit.evidence.len(), 1);
let evidence = &hit.evidence[0];
assert_eq!(evidence.via, Some(node));
assert_eq!(evidence.edge, EdgeKind::CallsOrRefs);
assert_eq!(evidence.label, crate::SourceLabel::Scip);
assert_eq!(
evidence.detail.as_deref(),
Some("references the seed at a.rs:2")
);
}

#[tokio::test]
async fn query_dedupes_a_shared_destination_and_truncates_to_the_limit() {
let map = map();
let foo = map.node_of(&sym("a/Foo#")).unwrap().clone();
let run = method("run");
// Both seeds reach `use_foo` (Foo at a.rs:3 col 15, run at a.rs:3 col 20);
// only Foo reaches the enum E. The seed whose edge wins the dedupe is listed
// second, so only the global sort by site keeps it.
let seeds = [(run, 1.0), (foo.clone(), 1.0)];
let enum_e = NodeId::Symbol(SymbolKey {
language: "rust".into(),
module_path: rust_module_path("a.rs"),
container: Vec::new(),
name: "E".into(),
kind: SymbolKind::Enum,
disambiguator: 0,
});
let all = ask(&map, &seeds, 10).await;
assert_eq!(all.status, MapStatus::Ok);
let dsts: Vec<&NodeId> = all.hits.iter().map(|h| &h.node).collect();
assert_eq!(dsts, vec![&enum_e, &function("use_foo")]);
let use_foo = all
.hits
.iter()
.find(|h| h.node == function("use_foo"))
.expect("use_foo is offered once");
// The first edge in sort order survives: the one with the smaller site.
assert_eq!(use_foo.evidence[0].via, Some(foo));
assert_eq!(
use_foo.evidence[0].detail.as_deref(),
Some("references the seed at a.rs:3")
);
assert_eq!(
all.hits.iter().map(|h| h.rank).collect::<Vec<_>>(),
vec![1, 2]
);

let one = ask(&map, &seeds, 1).await;
assert_eq!(one.hits.len(), 1);
assert_eq!(one.hits[0].rank, 1);
assert_eq!(one.hits[0].node, enum_e);
assert_eq!(one.status, MapStatus::Ok);
}
63 changes: 15 additions & 48 deletions lci-maps/src/scip_implements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,12 @@
//! `graph.scip`.

use crate::{
Budget, Contribution, Cost, EXPAND_LIMIT, EdgeKind, Evidence, FreshnessModel, Map,
MapDescriptor, MapHit, MapId, MapRequest, MapResponse, MapStatus, SnapshotView, SourceLabel,
edge::{CONF_MAX, Edge, Expanded, MapCard, QuestionType, Site, WitnessClass, cap_edges},
Budget, Contribution, Cost, EXPAND_LIMIT, EdgeKind, FreshnessModel, Map, MapDescriptor, MapId,
MapRequest, MapResponse, SnapshotView, SourceLabel,
edge::{
CONF_MAX, Edge, Expanded, MapCard, QuestionType, Site, WitnessClass, cap_edges,
scip_ranked_hits,
},
impact::chain_of,
live_grep::CostLog,
scip_graph::{GraphEdgeKind, ScipGraph, Witness},
Expand Down Expand Up @@ -447,51 +450,15 @@ impl Map for ImplementsMap {
}

async fn query(&self, request: &MapRequest<'_>, view: &SnapshotView<'_>) -> MapResponse {
let started = Instant::now();
let mut edges = Vec::new();
let mut dropped = 0;
for (seed, _) in request.seeds {
let (found, total) = self.edges_of(seed);
dropped += total - found.edges.len();
edges.extend(found.edges);
}
cap_edges(&mut edges, usize::MAX);
let mut seen = HashSet::new();
edges.retain(|edge| seen.insert(edge.dst.clone()));
edges.truncate(request.limit);
let mut response = MapResponse::empty(&self.descriptor, view, MapStatus::Ok);
if dropped > 0 {
response.status = MapStatus::Partial {
note: Some(format!(
"{dropped} more implementations than the cap of {} were not offered",
self.options.cap
)),
warning: None,
};
}
response.cost = Cost {
elapsed_ms: started.elapsed().as_secs_f64() * 1000.0,
nodes_touched: edges.len() as u64,
..Cost::default()
};
response.hits = edges
.into_iter()
.zip(1..)
.map(|(edge, rank)| MapHit {
node: edge.dst,
rank,
score: Some(f32::from(edge.conf) / f32::from(CONF_MAX)),
evidence: vec![Evidence {
via: Some(edge.src),
edge: edge.kind,
label: SourceLabel::Scip,
detail: edge
.site
.map(|s| format!("implements at {}:{}", s.path, s.line + 1)),
}],
})
.collect();
response
scip_ranked_hits(
&self.descriptor,
request,
view,
self.options.cap,
"implementations",
|seed| self.edges_of(seed),
|s| format!("implements at {}:{}", s.path, s.line + 1),
)
}

async fn expand(&self, node: &NodeId, _budget: Budget, _view: &SnapshotView<'_>) -> Expanded {
Expand Down
Loading
Loading