use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::ir::extract::ParseCompatibility;
use crate::resolution::rust::edition::CargoEdition;
use crate::resolution::rust::identity::position;
use crate::resolution::rust::limits::ResolutionLimits;
use crate::resolution::rust::paths;
use super::declaration::{ModuleDeclaration, declarations_by_scope};
use super::error::{
ClosureSite, ResolutionLimit, RustSnapshotError, SourceClosureError, SourceClosureFailure,
SourceClosureFailureKind,
};
use super::failure::{failure, limit_failure};
use super::reader::{ReadBudget, ReadRequest, byte_length, read_source};
use super::source::RustSource;
struct StoredSource {
source: RustSource,
declarations: BTreeMap<u32, Arc<[ModuleDeclaration]>>,
compatibility: ParseCompatibility,
}
pub(super) struct SourceStore {
root: PathBuf,
limits: ResolutionLimits,
indexes: BTreeMap<Arc<str>, usize>,
stored: Vec<StoredSource>,
empty: Arc<[ModuleDeclaration]>,
consumed: u64,
}
impl SourceStore {
pub(super) fn new(root: &Path, limits: ResolutionLimits) -> Self {
Self {
root: root.to_path_buf(),
limits,
indexes: BTreeMap::new(),
stored: Vec::new(),
empty: Arc::from(Vec::new()),
consumed: 0,
}
}
pub(super) fn root(&self) -> &Path {
&self.root
}
pub(super) fn limits(&self) -> ResolutionLimits {
self.limits
}
pub(super) fn canonical_inside(
&self,
path: &Path,
site: &ClosureSite,
) -> Result<PathBuf, SourceClosureFailure> {
let canonical = std::fs::canonicalize(path).map_err(|source| {
failure(
site.read_kind(),
(site, self.relative_of(path)),
format!("{site}: the source could not be resolved: {source}"),
)
})?;
match paths::contains(&self.root, &canonical) {
true => Ok(canonical),
false => Err(failure(
SourceClosureFailureKind::OutOfRoot,
(site, None),
format!("{site}: the source resolves outside the repository root"),
)),
}
}
pub(super) fn intern(
&mut self,
canonical: &Path,
site: &ClosureSite,
edition: CargoEdition,
) -> Result<Arc<str>, SourceClosureFailure> {
let relative = self.relative(canonical, site)?;
if let Some(stored) = self.stored_source(&relative) {
return compatible_path(stored, edition, site, relative);
}
self.check_capacity(site, &relative)?;
let stored = read_source(
&ReadRequest {
canonical,
relative: &relative,
site,
edition,
},
ReadBudget {
limits: self.limits,
consumed: self.consumed,
},
)?;
self.consumed = self.consumed.saturating_add(byte_length(&stored.source));
self.indexes
.insert(Arc::clone(&relative), self.stored.len());
self.stored.push(StoredSource {
declarations: declarations_by_scope(&stored.source.ir),
source: stored.source,
compatibility: stored.compatibility,
});
Ok(relative)
}
pub(super) fn declarations(
&self,
request: (&str, u32),
site: &ClosureSite,
) -> Result<Arc<[ModuleDeclaration]>, SourceClosureFailure> {
let (path, scope) = request;
let stored = self.stored_source(path).ok_or_else(|| {
failure(
SourceClosureFailureKind::MissingStoredSource,
(site, Some(Box::from(path))),
format!("{site}: {path} is instantiated but holds no stored source"),
)
})?;
Ok(stored
.declarations
.get(&scope)
.map(Arc::clone)
.unwrap_or_else(|| Arc::clone(&self.empty)))
}
fn stored_source(&self, path: &str) -> Option<&StoredSource> {
self.indexes
.get(path)
.and_then(|index| self.stored.get(*index))
}
pub(super) fn reached(&self) -> Box<[Box<str>]> {
self.indexes.keys().map(|path| Box::from(&**path)).collect()
}
pub(super) fn finish(self) -> Box<[RustSource]> {
let mut sources: Vec<RustSource> = self
.stored
.into_iter()
.map(|stored| stored.source)
.collect();
sources.sort_by(|left, right| left.path.cmp(&right.path));
sources.into_boxed_slice()
}
fn relative(
&self,
canonical: &Path,
site: &ClosureSite,
) -> Result<Arc<str>, SourceClosureFailure> {
paths::relative_text(&self.root, canonical).map_err(|source| {
failure(
SourceClosureFailureKind::OutOfRoot,
(site, None),
format!("{site}: the source path is not repository-relative: {source}"),
)
})
}
fn check_capacity(
&self,
site: &ClosureSite,
relative: &str,
) -> Result<(), SourceClosureFailure> {
let next = u64::from(position(self.stored.len())).saturating_add(1);
match next > u64::from(self.limits.max_source_files) {
true => Err(limit_failure(
ResolutionLimit::SourceFiles,
(site, Some(Box::from(relative))),
self.limits.max_source_files.into(),
)),
false => Ok(()),
}
}
pub(super) fn relative_of(&self, path: &Path) -> Option<Box<str>> {
paths::relative_text(&self.root, path)
.ok()
.map(|text| Box::from(&*text))
}
}
fn compatible_path(
stored: &StoredSource,
edition: CargoEdition,
site: &ClosureSite,
relative: Arc<str>,
) -> Result<Arc<str>, SourceClosureFailure> {
match (
&stored.compatibility,
edition.permits_bare_callable_traits(),
) {
(ParseCompatibility::AllEditions, _)
| (ParseCompatibility::LegacyCallableTraits { .. }, true) => Ok(relative),
(ParseCompatibility::LegacyCallableTraits { strict_error }, false) => Err(failure(
SourceClosureFailureKind::SourceParse,
(site, Some(Box::from(&*relative))),
format!("{relative} is not valid Rust: {strict_error}"),
)),
}
}
pub(super) fn refuse(
store: &SourceStore,
failures: Vec<SourceClosureFailure>,
) -> RustSnapshotError {
RustSnapshotError::SourceClosure(SourceClosureError::new(
store.reached(),
failures.into_boxed_slice(),
))
}