use super::{
ContextStage, Phase2Stats, Pipeline, PipelineError, PipelineResult, ResolveStage,
SymbolLookupCache, UnresolvedRelationship, WriteStage,
};
use crate::RelationKind;
use crate::parsing::ParserFactory;
use crate::storage::DocumentIndex;
use std::sync::Arc;
use std::time::Instant;
impl Pipeline {
pub fn run_phase2(
&self,
unresolved: Vec<UnresolvedRelationship>,
symbol_cache: Arc<SymbolLookupCache>,
index: Arc<DocumentIndex>,
) -> PipelineResult<Phase2Stats> {
self.run_phase2_with_progress(unresolved, symbol_cache, index, None)
}
pub fn run_phase2_with_progress(
&self,
unresolved: Vec<UnresolvedRelationship>,
symbol_cache: Arc<SymbolLookupCache>,
index: Arc<DocumentIndex>,
progress: Option<Arc<crate::io::status_line::ProgressBar>>,
) -> PipelineResult<Phase2Stats> {
let start = Instant::now();
let total_relationships = unresolved.len();
if unresolved.is_empty() {
return Ok(Phase2Stats {
total_relationships: 0,
defines_resolved: 0,
calls_resolved: 0,
other_resolved: 0,
unresolved: 0,
elapsed: start.elapsed(),
});
}
populate_reexport_aliases(&symbol_cache, &index);
let factory = Arc::new(ParserFactory::new(Arc::clone(&self.settings)));
let context_stage = ContextStage::new(
Arc::clone(&symbol_cache),
Arc::clone(&index),
factory,
Arc::clone(&self.settings),
);
let mut write_stage = WriteStage::new(Arc::clone(&index));
let (defines, others): (Vec<_>, Vec<_>) = unresolved
.into_iter()
.partition(|rel| rel.kind == RelationKind::Defines);
let mut stats = Phase2Stats {
total_relationships,
..Default::default()
};
tracing::info!(
target: "pipeline",
"Phase 2 Pass 1: Resolving {} Defines relationships",
defines.len()
);
if !defines.is_empty() {
let contexts = context_stage.build_contexts(defines);
let behaviors = context_stage.behaviors();
let resolve_stage = ResolveStage::new(Arc::clone(&symbol_cache), behaviors);
for ctx in contexts {
let rel_count = ctx.unresolved_rels.len() as u64;
let (batch, resolve_stats) = resolve_stage.resolve(&ctx);
stats.defines_resolved += resolve_stats.defines_resolved;
write_stage.write(batch);
if let Some(ref prog) = progress {
prog.set_progress(prog.current() + rel_count);
prog.add_extra1(resolve_stats.defines_resolved as u64);
let skipped = rel_count.saturating_sub(resolve_stats.defines_resolved as u64);
prog.add_extra2(skipped);
}
}
write_stage
.commit()
.map_err(|e| PipelineError::Index(crate::IndexError::General(e.to_string())))?;
}
tracing::info!(
target: "pipeline",
"Phase 2 Pass 2: Resolving {} Calls/other relationships",
others.len()
);
if !others.is_empty() {
let inheritance_resolvers = context_stage.build_inheritance_resolvers(&others);
let contexts = context_stage.build_contexts(others);
let behaviors = context_stage.behaviors();
let resolve_stage = ResolveStage::new(Arc::clone(&symbol_cache), behaviors)
.with_inheritance_resolvers(inheritance_resolvers);
for ctx in contexts {
let rel_count = ctx.unresolved_rels.len() as u64;
let (batch, resolve_stats) = resolve_stage.resolve(&ctx);
stats.calls_resolved += resolve_stats.calls_resolved;
stats.other_resolved += resolve_stats.resolved - resolve_stats.calls_resolved;
write_stage.write(batch);
if let Some(ref prog) = progress {
prog.set_progress(prog.current() + rel_count);
prog.add_extra1(resolve_stats.resolved as u64);
let skipped = rel_count.saturating_sub(resolve_stats.resolved as u64);
prog.add_extra2(skipped);
}
}
write_stage
.flush()
.map_err(|e| PipelineError::Index(crate::IndexError::General(e.to_string())))?;
}
stats.unresolved = stats.total_relationships
- stats.defines_resolved
- stats.calls_resolved
- stats.other_resolved;
stats.elapsed = start.elapsed();
tracing::info!(
target: "pipeline",
"Phase 2 complete: resolved {}/{} ({} Defines, {} Calls, {} other) in {:?}",
stats.defines_resolved + stats.calls_resolved + stats.other_resolved,
stats.total_relationships,
stats.defines_resolved,
stats.calls_resolved,
stats.other_resolved,
stats.elapsed
);
Ok(stats)
}
pub(super) fn run_phase2_maybe_bar(
&self,
unresolved: Vec<UnresolvedRelationship>,
symbol_cache: Arc<SymbolLookupCache>,
index: Arc<DocumentIndex>,
show_progress: bool,
) -> PipelineResult<Phase2Stats> {
use crate::io::status_line::{
ProgressBar, ProgressBarOptions, ProgressBarStyle, StatusLine,
};
if !show_progress || unresolved.is_empty() {
return self.run_phase2(unresolved, symbol_cache, index);
}
let options = ProgressBarOptions::default()
.with_style(ProgressBarStyle::VerticalSolid)
.with_width(28)
.with_label("LINKS")
.show_rate(false); let phase2_bar = Arc::new(ProgressBar::with_options(
unresolved.len() as u64,
"relationships",
"resolved",
"skipped",
options,
));
let phase2_status = StatusLine::new(Arc::clone(&phase2_bar));
let stats = self.run_phase2_with_progress(
unresolved,
symbol_cache,
index,
Some(phase2_bar.clone()),
)?;
drop(phase2_status);
eprintln!("{phase2_bar}");
Ok(stats)
}
}
fn populate_reexport_aliases(cache: &SymbolLookupCache, index: &DocumentIndex) {
let python = crate::parsing::LanguageId::new("python");
let mut entries: Vec<(String, Vec<crate::parsing::Import>)> = Vec::new();
for file_id in cache.file_ids() {
let symbol_ids = cache.symbols_in_file(file_id);
let Some(first) = symbol_ids.first().and_then(|&id| cache.get_ref(id)) else {
continue;
};
if first.language_id.as_ref() != Some(&python) {
continue;
}
let Some(module_path) = first.module_path.as_deref().map(String::from) else {
continue;
};
drop(first);
let imports = index.get_imports_for_file(file_id).unwrap_or_default();
if !imports.is_empty() {
entries.push((module_path, imports));
}
}
if entries.is_empty() {
return;
}
cache.populate_module_aliases(&entries, &python);
tracing::debug!(
target: "pipeline",
"Re-export pre-pass: {} python modules with imports scanned",
entries.len()
);
}