use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use pedant_types::SourceRecord;
use crate::resolution::confinement::{ConfinementFault, canonical_present};
use crate::resolution::path_normalization::RelativePathError;
use crate::resolution::read::{ByteCeilings, ReadFault};
use crate::resolution::rust::edition::CargoEdition;
use crate::resolution::rust::fault::RustSourceFault;
use crate::resolution::rust::inventory::RustFileInventory;
use crate::resolution::rust::limits::{ResolutionLimits, admits_depth};
use crate::resolution::rust::paths;
use crate::resolution::snapshot_rules::SnapshotRules;
use crate::resolution::snapshot_store::{Interned, SnapshotStoreOf};
use crate::resolution::supply::SourceSupply;
use super::declaration::{ModuleDeclaration, declarations_by_scope};
use super::error::{
ClosureSite, ResolutionLimit, RustSnapshotError, SourceClosureError, SourceClosureFailure,
SourceClosureFailureKind,
};
use super::failure::{at_path, failure, limit_failure, read_failure};
use super::source::RustSource;
struct StoredSource {
source: RustSource,
declarations: BTreeMap<u32, Arc<[ModuleDeclaration]>>,
}
pub(super) struct SourceStore {
store: SnapshotStoreOf<RustSnapshotRules>,
empty: Arc<[ModuleDeclaration]>,
}
impl SourceStore {
pub(super) fn new(root: &Path, limits: ResolutionLimits) -> Self {
Self {
store: SnapshotStoreOf::new(RustSnapshotRules {
root: root.to_path_buf(),
limits,
}),
empty: Arc::from(Vec::new()),
}
}
pub(super) fn root(&self) -> &Path {
self.store.rules().root()
}
pub(super) fn limits(&self) -> ResolutionLimits {
self.store.rules().limits
}
pub(super) fn canonical_inside(
&self,
path: &Path,
site: &ClosureSite,
) -> Result<PathBuf, SourceClosureFailure> {
canonical_present(self.root(), path).map_err(|fault| match fault {
ConfinementFault::Unreadable(source) => failure(
site.read_kind(),
(site, self.relative_of(path)),
format!("{site}: the source could not be resolved: {source}"),
),
ConfinementFault::OutOfRoot { canonical } => {
outside_root(site, self.relative_of(path), &paths::path_text(&canonical))
}
})
}
pub(super) fn intern<P: SourceSupply<RustFileInventory, RustSourceFault>>(
&mut self,
provider: &mut P,
canonical: &Path,
site: &ClosureSite,
edition: CargoEdition,
) -> Result<Arc<str>, SourceClosureFailure> {
let ceiling = self.limits().max_syntax_depth;
let interned = self
.store
.intern(provider, canonical, site, |path, record| {
retained(record, (site, path), (edition, ceiling))
})?;
match interned {
Interned::Admitted(placement) => Ok(placement.path),
Interned::Held(placement) => {
self.check_held_edition(placement.index, edition, (site, &placement.path))?;
Ok(placement.path)
}
}
}
fn check_held_edition(
&self,
index: usize,
edition: CargoEdition,
at: (&ClosureSite, &str),
) -> Result<(), SourceClosureFailure> {
let (site, relative) = at;
let stored = self
.store
.stored(index)
.ok_or_else(|| missing_stored_source(site, relative))?;
compatible(stored.source.legacy_callable_trait_error(), edition, at)
}
pub(super) fn declarations(
&self,
request: (&str, u32),
site: &ClosureSite,
) -> Result<Arc<[ModuleDeclaration]>, SourceClosureFailure> {
let (path, scope) = request;
let stored = self
.store
.stored_at(path)
.ok_or_else(|| missing_stored_source(site, path))?;
Ok(stored
.declarations
.get(&scope)
.map(Arc::clone)
.unwrap_or_else(|| Arc::clone(&self.empty)))
}
pub(super) fn reached(&self) -> Box<[Arc<str>]> {
self.store.keys()
}
pub(super) fn finish(self) -> Box<[RustSource]> {
self.store
.finish()
.into_vec()
.into_iter()
.map(|stored| stored.source)
.collect()
}
pub(super) fn relative_of(&self, path: &Path) -> Option<Box<str>> {
paths::relative_text(self.root(), path).ok()
}
}
struct RustSnapshotRules {
root: PathBuf,
limits: ResolutionLimits,
}
impl ByteCeilings for RustSnapshotRules {
fn source_bytes(&self) -> u64 {
self.limits.source_bytes()
}
fn total_bytes(&self) -> u64 {
self.limits.total_bytes()
}
}
impl SnapshotRules for RustSnapshotRules {
type Inventory = RustFileInventory;
type Fault = RustSourceFault;
type Refusal = SourceClosureFailure;
type Site = ClosureSite;
type Stored = StoredSource;
fn root(&self) -> &Path {
&self.root
}
fn max_source_files(&self) -> u32 {
self.limits.max_source_files
}
fn stored_path(stored: &StoredSource) -> &str {
stored.source.path()
}
fn unrelative(
&self,
error: RelativePathError,
(site, canonical): (&ClosureSite, &Path),
) -> SourceClosureFailure {
let source = paths::unrelative(error, &self.root, canonical);
failure(
SourceClosureFailureKind::OutOfRoot,
(site, None),
format!("{site}: the source path is not repository-relative: {source}"),
)
}
fn source_files(&self, at: (&ClosureSite, &str)) -> SourceClosureFailure {
crossed(
ResolutionLimit::SourceFiles,
self.max_source_files().into(),
at,
)
}
fn unnormalized(&self, at: (&ClosureSite, &str)) -> SourceClosureFailure {
let (site, relative) = at;
at_path(
SourceClosureFailureKind::OutOfRoot,
at,
format!("{site}: {relative} is not a normalized repository-relative path"),
)
}
fn refused(&self, fault: RustSourceFault, at: (&ClosureSite, &str)) -> SourceClosureFailure {
refusal(fault, at)
}
fn oversized(&self, fault: ReadFault, at: (&ClosureSite, &str)) -> SourceClosureFailure {
refusal(RustSourceFault::of_read(fault, self.limits), at)
}
}
fn retained(
record: &SourceRecord<RustFileInventory>,
at: (&ClosureSite, &Arc<str>),
admits: (CargoEdition, u32),
) -> Result<StoredSource, SourceClosureFailure> {
let (site, relative) = at;
let (edition, max_syntax_depth) = admits;
within_depth(record.facts().syntax_depth(), max_syntax_depth, at)?;
compatible(
record.facts().legacy_callable_trait_error(),
edition,
(site, &**relative),
)?;
let source = RustSource::of_record(relative, record);
Ok(StoredSource {
declarations: declarations_by_scope(source.ir()),
source,
})
}
fn within_depth(
depth: u32,
ceiling: u32,
at: (&ClosureSite, &Arc<str>),
) -> Result<(), SourceClosureFailure> {
let (site, relative) = at;
match admits_depth(depth, ceiling) {
true => Ok(()),
false => Err(limit_failure(
ResolutionLimit::SyntaxDepth,
(site, Some(Box::from(&**relative))),
ceiling.into(),
)),
}
}
fn refusal(fault: RustSourceFault, at: (&ClosureSite, &str)) -> SourceClosureFailure {
let (site, relative) = at;
match fault {
RustSourceFault::Unreadable(source) => read_failure(site, relative, source),
RustSourceFault::OutOfRoot { path, landing } => outside_root(site, Some(path), &landing),
RustSourceFault::SourceFiles { ceiling } => {
crossed(ResolutionLimit::SourceFiles, ceiling, at)
}
RustSourceFault::SourceBytes { ceiling } => {
crossed(ResolutionLimit::SourceFileBytes, ceiling, at)
}
RustSourceFault::TotalBytes { ceiling } => {
crossed(ResolutionLimit::TotalSourceBytes, ceiling, at)
}
RustSourceFault::SyntaxDepth { ceiling } => {
crossed(ResolutionLimit::SyntaxDepth, ceiling, at)
}
RustSourceFault::InvalidUtf8 { reason } => at_path(
SourceClosureFailureKind::InvalidUtf8,
at,
format!("{relative} is not valid UTF-8: {reason}"),
),
RustSourceFault::Unparsed { reason } => at_path(
SourceClosureFailureKind::SourceParse,
at,
format!("{relative} is not valid Rust: {reason}"),
),
RustSourceFault::Refused { path } => at_path(
site.read_kind(),
at,
format!("{path} was already refused by the provider reading for this snapshot"),
),
}
}
fn outside_root(site: &ClosureSite, path: Option<Box<str>>, landing: &str) -> SourceClosureFailure {
let message = match &path {
Some(relative) => {
format!("{site}: {relative} resolves outside the repository root, at {landing}")
}
None => format!("{site}: the source resolves outside the repository root, at {landing}"),
};
failure(SourceClosureFailureKind::OutOfRoot, (site, path), message)
}
fn crossed(limit: ResolutionLimit, ceiling: u64, at: (&ClosureSite, &str)) -> SourceClosureFailure {
let (site, relative) = at;
limit_failure(limit, (site, Some(Box::from(relative))), ceiling)
}
fn compatible(
legacy_error: Option<&str>,
edition: CargoEdition,
at: (&ClosureSite, &str),
) -> Result<(), SourceClosureFailure> {
let (site, relative) = at;
match (legacy_error, edition.permits_bare_callable_traits()) {
(None, _) | (Some(_), true) => Ok(()),
(Some(strict_error), false) => Err(failure(
SourceClosureFailureKind::SourceParse,
(site, Some(Box::from(relative))),
format!("{relative} is not valid Rust: {strict_error}"),
)),
}
}
fn missing_stored_source(site: &ClosureSite, path: &str) -> SourceClosureFailure {
failure(
SourceClosureFailureKind::MissingStoredSource,
(site, Some(Box::from(path))),
format!("{site}: {path} is instantiated but holds no stored source"),
)
}
pub(super) fn refuse(
store: &SourceStore,
failures: Vec<SourceClosureFailure>,
) -> RustSnapshotError {
RustSnapshotError::SourceClosure(SourceClosureError::new(
store.reached(),
failures.into_boxed_slice(),
))
}