use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::kit::{AsyncAutoBuilder, AsyncKit, ModuleMeta};
use super::capability::Resolver;
use super::error::ResolveError;
use super::includes_graph::IncludesGraph;
use super::symbol_table::ProjectSymbolTable;
use crate::ir::ExtractResult;
use crate::model::{Edge, Graph};
pub struct ResolverModule;
impl ModuleMeta for ResolverModule {
const NAME: &'static str = "resolver";
fn dependencies() -> &'static [(&'static str, TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for ResolverModule {
type Capability = Arc<dyn Resolver>;
type Error = ResolveError;
fn build<'a>(
_kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { Self::build_cap() })
}
}
impl ResolverModule {
pub(crate) fn build_cap() -> Result<Arc<dyn Resolver>, ResolveError> {
Ok(Arc::new(ResolverCapability))
}
}
struct ResolverCapability;
impl Resolver for ResolverCapability {
fn build_symbol_table(&self, results: &[ExtractResult], project: &str) -> ProjectSymbolTable {
super::build_symbol_table(results, project)
}
fn resolve_all(
&self,
results: &[ExtractResult],
symbol_table: &ProjectSymbolTable,
project: &str,
graph: &mut Graph,
includes_graph: &IncludesGraph,
) -> Vec<Edge> {
super::resolve_all(results, symbol_table, project, graph, includes_graph)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::CallInfo;
use crate::kit::{AsyncKit, ResolverModule};
use crate::model::{Language, Node, NodeLabel};
use crate::resolve::FqnGenerator;
fn fixture_call_foo_to_bar() -> (Vec<ExtractResult>, ProjectSymbolTable, Graph) {
let foo_node = Node::builder(NodeLabel::Function, "foo", "qn")
.language(Language::Rust)
.file_path("a.rs")
.is_exported(true)
.build();
let bar_node = Node::builder(NodeLabel::Function, "bar", "qn")
.language(Language::Rust)
.file_path("a.rs")
.is_exported(true)
.build();
let foo_qn = FqnGenerator::generate("proj", "a.rs", "foo", Language::Rust, None);
let bar_qn = FqnGenerator::generate("proj", "a.rs", "bar", Language::Rust, None);
let mut result = ExtractResult::new("a.rs", Language::Rust);
result.nodes = vec![foo_node, bar_node];
result.calls.push(CallInfo {
caller_qn: Some(foo_qn.clone()),
callee_name: "bar".to_string(),
line: 5,
args: vec![],
});
let results = vec![result];
let table = crate::resolve::build_symbol_table(&results, "proj");
let mut graph = Graph::new();
for r in &results {
for node in &r.nodes {
let qn =
FqnGenerator::generate("proj", &r.file_path, &node.name, Language::Rust, None);
let mut g = node.clone();
g.id = qn.clone();
g.qualified_name = qn;
graph.add_node(g);
}
}
let _ = bar_qn;
(results, table, graph)
}
#[test]
fn build_returns_capability() {
let cap = ResolverModule::build_cap().expect("build_cap");
let table = cap.build_symbol_table(&[], "proj");
assert_eq!(table.symbol_count(), 0, "empty results → empty table");
assert_eq!(table.file_count(), 0);
}
#[test]
fn capability_build_symbol_table_returns_table() {
let (results, _table, _graph) = fixture_call_foo_to_bar();
let cap = ResolverModule::build_cap().expect("build_cap");
let table = cap.build_symbol_table(&results, "proj");
assert_eq!(table.symbol_count(), 2, "foo + bar");
assert_eq!(table.file_count(), 1);
assert!(table.lookup_exact("foo").is_some());
assert!(table.lookup_exact("bar").is_some());
}
#[test]
fn capability_resolve_all_produces_calls_edge() {
let (results, table, mut graph) = fixture_call_foo_to_bar();
let cap = ResolverModule::build_cap().expect("build_cap");
let includes_graph = IncludesGraph::new();
let edges = cap.resolve_all(&results, &table, "proj", &mut graph, &includes_graph);
let calls_count = edges
.iter()
.filter(|e| e.edge_type == crate::model::EdgeType::Calls)
.count();
assert!(
calls_count >= 1,
"should produce at least one CALLS edge, got {edges:?}"
);
assert!(
graph.edge_count() >= 1,
"edges should be added to the graph"
);
}
#[tokio::test]
async fn kit_registration_flow() {
let mut kit = AsyncKit::new();
kit.register::<ResolverModule>()
.expect("register::<ResolverModule>");
let kit = kit.build().await.expect("build");
assert!(kit.contains::<ResolverModule>());
let required = kit
.require::<ResolverModule>()
.expect("require::<ResolverModule>");
let table = required.build_symbol_table(&[], "proj");
assert_eq!(table.symbol_count(), 0);
}
}