use std::sync::Arc;
use pedant_core::resolution::rust::{RustResolutionSnapshot, RustTargetResolution};
use pedant_types::{
ResolutionRecord, ResolutionReport, ResolutionUnit, SymbolDefinition, SymbolReference,
};
use crate::edge::{DependencyEvidence, EdgeDraft, GraphEdgeKind, GraphEdgeOrigin};
use crate::error::GraphBuildError;
use crate::graph::CodeGraph;
use crate::id::{GraphNodeId, GraphReferenceId, position};
use crate::limits::GraphLimits;
use crate::node::{GraphNodeKind, GraphNodeLocation, NodeDraft};
use crate::reference::{GraphReferenceKind, ReferenceDraft};
use super::index::{ProjectionCapacity, ProjectionState};
use super::mapping::{self, Vocabulary};
use super::validation;
struct ProjectedReference<'a> {
id: GraphReferenceId,
source: GraphNodeId,
kind: GraphReferenceKind,
record: &'a ResolutionRecord,
}
struct Inputs<'a> {
snapshot: &'a RustResolutionSnapshot,
report: &'a ResolutionReport,
vocabulary: Vocabulary,
resolved: Box<[(&'a SymbolReference, &'a ResolutionRecord)]>,
}
pub(crate) fn project(
snapshot: &RustResolutionSnapshot,
resolution: &RustTargetResolution,
limits: GraphLimits,
) -> Result<CodeGraph, GraphBuildError> {
validation::check_root_target(snapshot, resolution)?;
validation::check_snapshot_identity(snapshot, resolution)?;
let report = resolution.report();
let inputs = Inputs {
snapshot,
report,
vocabulary: Vocabulary::new(),
resolved: validation::resolved_references(report)?,
};
let mut state = ProjectionState::new(limits, capacity(&inputs));
let units = project_units(&mut state, &inputs, resolution)?;
project_sources(&mut state, &inputs)?;
project_definitions(&mut state, &inputs)?;
project_containment(&mut state, &inputs)?;
validation::check_containment_forest(&state, units)?;
let references = project_references(&mut state, &inputs)?;
project_dependencies(&mut state, &inputs)?;
project_candidates(&mut state, &references)?;
Ok(state.finish(inputs.report.tier()))
}
fn capacity(inputs: &Inputs<'_>) -> ProjectionCapacity {
ProjectionCapacity {
units: inputs.report.units().len(),
definitions: inputs.report.definitions().len(),
references: inputs.resolved.len(),
edges: inputs
.resolved
.iter()
.fold(inputs.snapshot.edges().len(), |total, (_, record)| {
total.saturating_add(record.candidates().len())
}),
}
}
fn project_units(
state: &mut ProjectionState,
inputs: &Inputs<'_>,
resolution: &RustTargetResolution,
) -> Result<usize, GraphBuildError> {
for unit in inputs.report.units() {
let reported = unit.id().index();
let snapshot_unit = validation::stated_binding(resolution, unit)?;
let instance = validation::snapshot_instance(inputs.snapshot, (snapshot_unit, reported))?;
let container = state.insert_node(NodeDraft {
language: unit.language(),
name: Arc::from(unit.name()),
kind: inputs.vocabulary.unit_root(instance.kind()),
location: None,
})?;
validation::bind_unit(state, container, (snapshot_unit, reported))?;
}
Ok(state.node_count())
}
fn project_sources(
state: &mut ProjectionState,
inputs: &Inputs<'_>,
) -> Result<(), GraphBuildError> {
for unit in inputs.report.units() {
let reported = unit.id().index();
let snapshot_unit = validation::bound_snapshot_unit(state, reported)?;
let container = validation::unit_container(state, reported)?;
let instance = validation::snapshot_instance(inputs.snapshot, (snapshot_unit, reported))?;
for path in instance.sources() {
let node = validation::qualified_source(
state,
(snapshot_unit, reported),
path,
file_draft(unit, path),
)?;
state.contain(container, node);
}
}
Ok(())
}
fn file_draft(unit: &ResolutionUnit, path: &Arc<str>) -> NodeDraft {
NodeDraft {
language: unit.language(),
name: Arc::clone(path),
kind: GraphNodeKind::File,
location: Some(GraphNodeLocation::File {
path: Arc::clone(path),
}),
}
}
fn project_definitions(
state: &mut ProjectionState,
inputs: &Inputs<'_>,
) -> Result<(), GraphBuildError> {
for definition in inputs.report.definitions() {
let reported = definition.unit().index();
let snapshot_unit = validation::bound_snapshot_unit(state, reported)?;
let file = validation::source_node(
state,
(snapshot_unit, reported),
SymbolDefinition::span(definition).file(),
)?;
let node = state.insert_node(definition_draft(&inputs.vocabulary, definition, file))?;
state.bind_definition(node);
}
Ok(())
}
fn definition_draft(
vocabulary: &Vocabulary,
definition: &SymbolDefinition,
file: GraphNodeId,
) -> NodeDraft {
NodeDraft {
language: definition.language(),
name: Arc::from(definition.name()),
kind: vocabulary.definition(definition.kind()),
location: Some(GraphNodeLocation::Span {
file,
span: SymbolDefinition::span(definition).clone(),
}),
}
}
fn project_containment(
state: &mut ProjectionState,
inputs: &Inputs<'_>,
) -> Result<(), GraphBuildError> {
for definition in inputs.report.definitions() {
let child = validation::definition_node(state, definition.id().index())?;
let parent = match definition.parent() {
Some(parent) => validation::definition_node(state, parent.index())?,
None => validation::unit_container(state, definition.unit().index())?,
};
state.contain(parent, child);
}
Ok(())
}
fn project_references<'a>(
state: &mut ProjectionState,
inputs: &Inputs<'a>,
) -> Result<Box<[ProjectedReference<'a>]>, GraphBuildError> {
let mut projected = Vec::with_capacity(inputs.resolved.len());
for (reference, record) in inputs.resolved.iter().copied() {
let source = reference_source(state, reference)?;
let kind = mapping::reference_kind(reference.kind());
let id = state.insert_reference(reference_draft((source, kind), reference, record))?;
projected.push(ProjectedReference {
id,
source,
kind,
record,
});
}
Ok(projected.into_boxed_slice())
}
fn reference_draft(
site: (GraphNodeId, GraphReferenceKind),
reference: &SymbolReference,
record: &ResolutionRecord,
) -> ReferenceDraft {
let (source, kind) = site;
ReferenceDraft {
source,
language: reference.language(),
kind,
text: Arc::from(reference.text()),
span: SymbolReference::span(reference).clone(),
gaps: record.gaps().into(),
}
}
fn reference_source(
state: &ProjectionState,
reference: &SymbolReference,
) -> Result<GraphNodeId, GraphBuildError> {
let reported = reference.unit().index();
let snapshot_unit = validation::bound_snapshot_unit(state, reported)?;
let file = validation::source_node(
state,
(snapshot_unit, reported),
SymbolReference::span(reference).file(),
)?;
match reference.enclosing_definition() {
Some(enclosing) => validation::definition_node(state, enclosing.index()),
None => Ok(file),
}
}
fn project_dependencies(
state: &mut ProjectionState,
inputs: &Inputs<'_>,
) -> Result<(), GraphBuildError> {
for (index, edge) in inputs.snapshot.edges().iter().enumerate() {
let stated = (position(index), edge);
let source = validation::snapshot_container(state, edge.source(), stated)?;
let target = validation::snapshot_container(state, edge.target(), stated)?;
let (certainty, predicate) = mapping::activation(edge.activation());
state.insert_edge(EdgeDraft {
source,
target,
kind: GraphEdgeKind::DependsOn,
certainty,
origin: GraphEdgeOrigin::Dependency {
evidence: DependencyEvidence::new(
Arc::from(edge.name()),
mapping::dependency_kind(edge.kind()),
predicate,
),
},
})?;
}
Ok(())
}
fn project_candidates(
state: &mut ProjectionState,
references: &[ProjectedReference<'_>],
) -> Result<(), GraphBuildError> {
for projected in references {
for candidate in projected.record.candidates() {
let target = validation::definition_node(state, candidate.definition().index())?;
state.insert_edge(EdgeDraft {
source: projected.source,
target,
kind: mapping::candidate_edge_kind(projected.kind),
certainty: mapping::certainty(candidate.certainty()),
origin: GraphEdgeOrigin::Reference {
reference: projected.id,
},
})?;
}
}
Ok(())
}