Skip to main content

pedant_core/resolution/rust/resolve/
target.rs

1//! The snapshot-bound resolution result, and the one core validation boundary
2//! that produces it.
3//!
4//! A report is only meaningful beside the snapshot it describes, so every
5//! resolver entry point returns this wrapper and nothing else. Construction
6//! rebinds the report's units to snapshot units by their stable keys and proves
7//! every site coordinate exists in the exact snapshotted source; a report built
8//! against another snapshot cannot pass.
9
10use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use pedant_types::{
14    ResolutionReport, ResolutionUnit, ResolutionUnitId, SourceSpan, SymbolDefinition,
15    SymbolReference,
16};
17
18use crate::resolution::rust::identity::{PackageId, TargetId, position};
19use crate::resolution::rust::snapshot::{
20    RustResolutionSnapshot, RustResolutionUnit, RustSnapshotUnitId,
21};
22use crate::resolution::rust::warning;
23
24use super::coordinates::LineIndex;
25use super::error::RustResolutionError;
26
27/// The stable key one snapshot unit is identified by across reports.
28pub(super) fn unit_key(unit: &RustResolutionUnit) -> Arc<str> {
29    warning::unit_key(unit)
30}
31
32/// The canonical Cargo crate name one target compiles under.
33pub(super) fn crate_name(unit: &RustResolutionUnit) -> Arc<str> {
34    Arc::from(unit.name().replace('-', "_"))
35}
36
37/// What one report-local unit identifier binds to in the snapshot.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct RustUnitBinding {
40    unit: ResolutionUnitId,
41    snapshot_unit: RustSnapshotUnitId,
42    package: PackageId,
43    target: TargetId,
44}
45
46impl RustUnitBinding {
47    /// The report-local identifier this binding answers for.
48    pub fn unit(&self) -> ResolutionUnitId {
49        self.unit
50    }
51
52    /// The snapshot unit the report unit describes.
53    pub fn snapshot_unit(&self) -> RustSnapshotUnitId {
54        self.snapshot_unit
55    }
56
57    /// The package that declares the bound Cargo target.
58    pub fn package(&self) -> PackageId {
59        self.package
60    }
61
62    /// The Cargo target the report unit compiles.
63    pub fn target(&self) -> TargetId {
64        self.target
65    }
66}
67
68/// One resolution result, bound to the snapshot it describes.
69#[derive(Debug, Clone)]
70pub struct RustTargetResolution {
71    root_target: TargetId,
72    units: Box<[RustUnitBinding]>,
73    report: Arc<ResolutionReport>,
74}
75
76impl RustTargetResolution {
77    /// Bind `report` to `snapshot`, or refuse every disagreement between them.
78    pub fn try_new(
79        snapshot: &RustResolutionSnapshot,
80        report: ResolutionReport,
81    ) -> Result<Self, RustResolutionError> {
82        let units = bind_units(snapshot, report.units())?;
83        validate_sites(snapshot, &report)?;
84        Ok(Self {
85            root_target: snapshot.root_target(),
86            units,
87            report: Arc::new(report),
88        })
89    }
90
91    /// The target this resolution was requested for.
92    pub fn root_target(&self) -> TargetId {
93        self.root_target
94    }
95
96    /// Every report-local unit identifier and the Cargo target it names.
97    pub fn units(&self) -> &[RustUnitBinding] {
98        &self.units
99    }
100
101    /// The binding one report-local unit identifier selects.
102    pub fn unit(&self, unit: ResolutionUnitId) -> Option<&RustUnitBinding> {
103        self.units.get(usize::try_from(unit.index()).ok()?)
104    }
105
106    /// The validated report, shared rather than copied.
107    pub fn report(&self) -> &ResolutionReport {
108        &self.report
109    }
110
111    /// A second handle on the same validated report.
112    pub fn shared_report(&self) -> Arc<ResolutionReport> {
113        Arc::clone(&self.report)
114    }
115}
116
117fn bind_units(
118    snapshot: &RustResolutionSnapshot,
119    units: &[ResolutionUnit],
120) -> Result<Box<[RustUnitBinding]>, RustResolutionError> {
121    if units.len() != snapshot.units().len() {
122        return Err(RustResolutionError::UnitMapping {
123            unit: position(units.len()),
124            reason: Box::from("the report and the snapshot hold different unit counts"),
125        });
126    }
127    let keyed = keyed_units(snapshot);
128    units.iter().map(|unit| bind_unit(&keyed, unit)).collect()
129}
130
131/// Every snapshot unit under its stable key, so binding a report of N units
132/// formats N keys rather than one key per candidate per report unit.
133fn keyed_units(snapshot: &RustResolutionSnapshot) -> BTreeMap<Arc<str>, &RustResolutionUnit> {
134    snapshot
135        .units()
136        .iter()
137        .map(|unit| (unit_key(unit), unit))
138        .collect()
139}
140
141fn bind_unit(
142    keyed: &BTreeMap<Arc<str>, &RustResolutionUnit>,
143    unit: &ResolutionUnit,
144) -> Result<RustUnitBinding, RustResolutionError> {
145    let found = keyed
146        .get(unit.key())
147        .ok_or_else(|| RustResolutionError::UnitMapping {
148            unit: unit.id().index(),
149            reason: Box::from("no snapshot unit carries this key"),
150        })?;
151    Ok(RustUnitBinding {
152        unit: unit.id(),
153        snapshot_unit: found.id(),
154        package: found.package(),
155        target: found.target(),
156    })
157}
158
159/// Prove every stated coordinate against the exact snapshotted source.
160///
161/// Each source is indexed once rather than once per site: a report states many
162/// thousands of sites over the same files, and building the line table per site
163/// rescanned whole sources for coordinates derived from those same tables.
164fn validate_sites(
165    snapshot: &RustResolutionSnapshot,
166    report: &ResolutionReport,
167) -> Result<(), RustResolutionError> {
168    let lines = indexed_sources(snapshot);
169    for definition in report.definitions() {
170        validate_span(snapshot, &lines, SymbolDefinition::span(definition))?;
171    }
172    for reference in report.references() {
173        validate_span(snapshot, &lines, SymbolReference::span(reference))?;
174    }
175    Ok(())
176}
177
178fn indexed_sources(snapshot: &RustResolutionSnapshot) -> BTreeMap<&str, LineIndex> {
179    snapshot
180        .sources()
181        .iter()
182        .map(|source| (source.path(), LineIndex::new(source.text())))
183        .collect()
184}
185
186fn validate_span(
187    snapshot: &RustResolutionSnapshot,
188    lines: &BTreeMap<&str, LineIndex>,
189    span: &SourceSpan,
190) -> Result<(), RustResolutionError> {
191    let stated = snapshot
192        .source(span.file())
193        .zip(lines.get(span.file()))
194        .ok_or_else(|| RustResolutionError::UnknownFile {
195            file: Box::from(span.file()),
196        })?;
197    let (source, index) = stated;
198    for at in [span.start(), span.end()] {
199        if !index.holds(source.text(), at) {
200            return Err(RustResolutionError::InvalidCoordinate {
201                file: Box::from(span.file()),
202                line: at.line(),
203                column: at.column(),
204            });
205        }
206    }
207    Ok(())
208}