use std::sync::atomic::{AtomicU64, Ordering};
use super::*;
static FACADE_SYNTHESIS_NODES_LOADED: AtomicU64 = AtomicU64::new(0);
pub fn facade_synthesis_nodes_loaded() -> u64 {
FACADE_SYNTHESIS_NODES_LOADED.load(Ordering::Relaxed)
}
pub(super) fn resolve_facade_reference_queue(project_root: &Path, full_rebuild: bool) -> usize {
let Ok(mut db) = DatabaseConnection::open(facade_database_path(project_root)) else {
return 0;
};
let queries = QueryBuilder::new(db.get_db());
let mut resolver = crate::resolution::index::create_resolver(
project_root.to_string_lossy().into_owned(),
queries,
);
crate::utils::debug_rss("resolve:before resolve_and_persist_batched");
let result = resolver.resolve_and_persist_batched(None, 1000);
crate::utils::debug_rss("resolve:after resolve_and_persist_batched");
let chained = resolver.resolve_chained_calls_via_conformance();
crate::utils::debug_rss("resolve:after chained");
let inherited_this = resolver.resolve_deferred_this_member_refs();
crate::utils::debug_rss("resolve:after deferred_this");
let resolved = result.stats.resolved as usize + chained + inherited_this;
drop(resolver);
crate::utils::debug_rss("resolve:after drop(resolver)");
let synthesized = synthesize_facade_dynamic_edges(project_root, full_rebuild);
crate::utils::debug_rss("resolve:after synthesize");
let removed = dedupe_facade_persisted_edges(project_root);
resolved.saturating_add(synthesized).saturating_sub(removed)
}
pub(super) fn dedupe_facade_persisted_edges(project_root: &Path) -> usize {
let Ok(conn) = open_facade_database(project_root) else {
return 0;
};
conn.execute(
r#"
DELETE FROM edges
WHERE id NOT IN (
SELECT MIN(id)
FROM edges
GROUP BY
source,
target,
kind,
COALESCE(provenance, ''),
CASE
WHEN kind = 'calls' THEN ''
ELSE COALESCE(metadata, '')
END
)
"#,
[],
)
.unwrap_or_default()
}
pub(super) fn synthesize_facade_dynamic_edges(project_root: &Path, full_rebuild: bool) -> usize {
let Ok(mut db) = DatabaseConnection::open(facade_database_path(project_root)) else {
return 0;
};
if full_rebuild {
let mut queries = QueryBuilder::new(db.get_db());
let nodes = queries.get_all_nodes().unwrap_or_default();
let files = queries
.get_all_files()
.unwrap_or_default()
.into_iter()
.map(|file| file.path)
.collect::<Vec<_>>();
let mut ctx = FacadeSynthesisContext::new(project_root.to_path_buf(), nodes, files);
return synthesize_callback_edges(&mut queries, &mut ctx);
}
let mut queries = QueryBuilder::new(db.get_db());
let mut ctx = match DbBackedSynthesisContext::open(project_root) {
Some(ctx) => ctx,
None => {
let nodes = queries.get_all_nodes().unwrap_or_default();
FACADE_SYNTHESIS_NODES_LOADED.fetch_add(nodes.len() as u64, Ordering::Relaxed);
let files = queries
.get_all_files()
.unwrap_or_default()
.into_iter()
.map(|file| file.path)
.collect::<Vec<_>>();
let mut ctx = FacadeSynthesisContext::new(project_root.to_path_buf(), nodes, files);
return synthesize_callback_edges(&mut queries, &mut ctx);
}
};
synthesize_callback_edges(&mut queries, &mut ctx)
}
struct DbBackedSynthesisContext {
project_root: PathBuf,
db: DatabaseConnection,
}
impl DbBackedSynthesisContext {
fn open(project_root: &Path) -> Option<Self> {
let db = DatabaseConnection::open(facade_database_path(project_root)).ok()?;
Some(Self {
project_root: project_root.to_path_buf(),
db,
})
}
fn queries(&mut self) -> QueryBuilder<'_> {
QueryBuilder::new(self.db.get_db())
}
}
impl ResolutionContext for DbBackedSynthesisContext {
fn get_nodes_in_file(&mut self, file_path: &str) -> Vec<Node> {
self.queries()
.get_nodes_by_file(file_path)
.unwrap_or_default()
}
fn get_nodes_by_name(&mut self, name: &str) -> Vec<Node> {
self.queries().get_nodes_by_name(name).unwrap_or_default()
}
fn get_nodes_by_qualified_name(&mut self, qualified_name: &str) -> Vec<Node> {
self.queries()
.get_nodes_by_qualified_name_exact(qualified_name)
.unwrap_or_default()
}
fn get_nodes_by_kind(&mut self, kind: NodeKind) -> Vec<Node> {
self.queries().get_nodes_by_kind(kind).unwrap_or_default()
}
fn file_exists(&mut self, file_path: &str) -> bool {
if self
.queries()
.get_all_file_paths()
.unwrap_or_default()
.iter()
.any(|path| path == file_path)
{
return true;
}
self.project_root.join(file_path).is_file()
}
fn read_file(&mut self, file_path: &str) -> Option<String> {
fs::read_to_string(self.project_root.join(file_path)).ok()
}
fn get_project_root(&self) -> String {
self.project_root.to_string_lossy().into_owned()
}
fn get_all_files(&mut self) -> Vec<String> {
self.queries().get_all_file_paths().unwrap_or_default()
}
fn get_nodes_by_lower_name(&mut self, lower_name: &str) -> Vec<Node> {
self.queries()
.get_nodes_by_lower_name(lower_name)
.unwrap_or_default()
}
fn get_import_mappings(&mut self, file_path: &str, language: Language) -> Vec<ImportMapping> {
self.read_file(file_path)
.map(|content| extract_import_mappings(file_path, &content, language))
.unwrap_or_default()
}
fn get_node_by_id(&mut self, id: &str) -> Option<Node> {
self.queries().get_node_by_id(id).unwrap_or(None)
}
fn get_re_exports(&mut self, _file_path: &str, _language: Language) -> Vec<ReExport> {
Vec::new()
}
fn list_directories(&mut self, relative_path: &str) -> Vec<String> {
let path = self.project_root.join(relative_path);
fs::read_dir(path)
.ok()
.into_iter()
.flat_map(|entries| entries.filter_map(Result::ok))
.filter(|entry| entry.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect()
}
}
pub(super) struct FacadeSynthesisContext {
project_root: PathBuf,
nodes: Vec<Node>,
files: Vec<String>,
}
impl FacadeSynthesisContext {
fn new(project_root: PathBuf, nodes: Vec<Node>, files: Vec<String>) -> Self {
Self {
project_root,
nodes,
files,
}
}
}
impl ResolutionContext for FacadeSynthesisContext {
fn get_nodes_in_file(&mut self, file_path: &str) -> Vec<Node> {
self.nodes
.iter()
.filter(|node| node.file_path == file_path)
.cloned()
.collect()
}
fn get_nodes_by_name(&mut self, name: &str) -> Vec<Node> {
self.nodes
.iter()
.filter(|node| node.name == name)
.cloned()
.collect()
}
fn get_nodes_by_qualified_name(&mut self, qualified_name: &str) -> Vec<Node> {
self.nodes
.iter()
.filter(|node| node.qualified_name == qualified_name)
.cloned()
.collect()
}
fn get_nodes_by_kind(&mut self, kind: NodeKind) -> Vec<Node> {
self.nodes
.iter()
.filter(|node| node.kind == kind)
.cloned()
.collect()
}
fn file_exists(&mut self, file_path: &str) -> bool {
self.files.iter().any(|path| path == file_path)
|| self.project_root.join(file_path).is_file()
}
fn read_file(&mut self, file_path: &str) -> Option<String> {
fs::read_to_string(self.project_root.join(file_path)).ok()
}
fn get_project_root(&self) -> String {
self.project_root.to_string_lossy().into_owned()
}
fn get_all_files(&mut self) -> Vec<String> {
self.files.clone()
}
fn get_nodes_by_lower_name(&mut self, lower_name: &str) -> Vec<Node> {
self.nodes
.iter()
.filter(|node| node.name.eq_ignore_ascii_case(lower_name))
.cloned()
.collect()
}
fn get_import_mappings(&mut self, file_path: &str, language: Language) -> Vec<ImportMapping> {
self.read_file(file_path)
.map(|content| extract_import_mappings(file_path, &content, language))
.unwrap_or_default()
}
fn get_node_by_id(&mut self, id: &str) -> Option<Node> {
self.nodes.iter().find(|node| node.id == id).cloned()
}
fn get_re_exports(&mut self, _file_path: &str, _language: Language) -> Vec<ReExport> {
Vec::new()
}
fn list_directories(&mut self, relative_path: &str) -> Vec<String> {
let path = self.project_root.join(relative_path);
fs::read_dir(path)
.ok()
.into_iter()
.flat_map(|entries| entries.filter_map(Result::ok))
.filter(|entry| entry.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect()
}
}