use crate::ir::sites::{ReferenceOrigin, ReferenceSite};
use crate::resolution::rust::snapshot::RustResolutionSnapshot;
use super::bindings::Bindings;
use super::corpus::Corpus;
use super::error::RustResolutionError;
use super::lookup::{Outcome, Target, resolve_path};
use super::references::ReferenceEntry;
const MAX_ROUNDS: u32 = 8;
pub(super) struct Imports {
bindings: Bindings,
outcomes: Box<[Option<Outcome>]>,
}
impl Imports {
pub(super) fn bindings(&self) -> &Bindings {
&self.bindings
}
pub(super) fn outcome(&self, entry: usize) -> Option<&Outcome> {
self.outcomes.get(entry).and_then(Option::as_ref)
}
}
enum Binding {
Named(Box<str>),
Glob,
Absent,
}
struct Resolved {
node: usize,
binding: Binding,
outcome: Outcome,
}
pub(super) fn bind(
corpus: &Corpus<'_>,
snapshot: &RustResolutionSnapshot,
entries: &[ReferenceEntry],
) -> Result<Imports, RustResolutionError> {
let mut bindings = Bindings::default();
for _ in 0..MAX_ROUNDS {
let round: Box<[Option<Resolved>]> = entries
.iter()
.map(|entry| resolve_entry(corpus, &bindings, snapshot, entry))
.collect();
if !apply(&mut bindings, &round) {
return Ok(Imports {
bindings,
outcomes: outcomes(round),
});
}
}
Err(RustResolutionError::ImportsNotConverged { rounds: MAX_ROUNDS })
}
fn apply(bindings: &mut Bindings, round: &[Option<Resolved>]) -> bool {
round.iter().flatten().fold(false, |changed, resolved| {
changed | record(bindings, resolved)
})
}
fn record(bindings: &mut Bindings, resolved: &Resolved) -> bool {
match (&resolved.outcome, &resolved.binding) {
(Outcome::Found(targets), Binding::Named(name)) => {
bindings.bind(resolved.node, name, targets)
}
(Outcome::Found(targets), Binding::Glob) => glob(bindings, resolved.node, targets),
_ => false,
}
}
fn glob(bindings: &mut Bindings, node: usize, targets: &[Target]) -> bool {
modules_of(targets).iter().fold(false, |changed, module| {
changed | bindings.bind_glob(node, *module)
})
}
fn outcomes(round: Box<[Option<Resolved>]>) -> Box<[Option<Outcome>]> {
round
.into_vec()
.into_iter()
.map(|found| found.map(|resolved| resolved.outcome))
.collect()
}
fn resolve_entry(
corpus: &Corpus<'_>,
bindings: &Bindings,
snapshot: &RustResolutionSnapshot,
entry: &ReferenceEntry,
) -> Option<Resolved> {
let site = import_site(corpus, snapshot, entry)?;
Some(Resolved {
node: entry.node,
binding: binding_of(site),
outcome: resolve_path(corpus, bindings, entry.node, site.segments()),
})
}
fn binding_of(site: &ReferenceSite) -> Binding {
match (site.is_glob(), bound_name(site)) {
(true, _) => Binding::Glob,
(false, Some(name)) => Binding::Named(name),
(false, None) => Binding::Absent,
}
}
fn import_site<'a>(
corpus: &Corpus<'_>,
snapshot: &'a RustResolutionSnapshot,
entry: &ReferenceEntry,
) -> Option<&'a ReferenceSite> {
let site = corpus
.graph
.file_ir(snapshot, entry.node)?
.reference_sites
.get(entry.site)?;
(site.origin() == ReferenceOrigin::Import).then_some(site)
}
fn bound_name(site: &ReferenceSite) -> Option<Box<str>> {
match site.alias() {
Some(alias) => Some(Box::from(alias)),
None => site.segments().last().cloned(),
}
}
fn modules_of(targets: &[Target]) -> Box<[usize]> {
targets
.iter()
.filter_map(|target| match target {
Target::Module(node) => Some(*node),
Target::Definition(_) => None,
})
.collect()
}