use pedant_types::{CandidateInput, ResolutionCertainty, ResolutionGap, ResolutionReportBuilder};
use crate::ir::cfg::RustCfgCondition;
use crate::ir::sites::{ReferenceOrigin, ReferenceSite};
use crate::resolution::rust::limits::ResolutionLimits;
use crate::resolution::rust::snapshot::RustResolutionSnapshot;
use super::corpus::Corpus;
use super::error::RustResolutionError;
use super::imports::Imports;
use super::lookup::{Outcome, Target, resolve_path};
use super::promotion::{Promoted, Promotion, PromotionSite};
use super::references::ReferenceEntry;
struct Denotation {
candidates: Box<[usize]>,
gap: Option<ResolutionGap>,
enumerated: bool,
}
pub(super) fn write<P: Promotion>(
builder: &mut ResolutionReportBuilder,
corpus: &Corpus<'_>,
answers: (&Imports, &P),
request: (&RustResolutionSnapshot, &[ReferenceEntry], ResolutionLimits),
) -> Result<(), RustResolutionError> {
let (snapshot, entries, limits) = request;
for (index, entry) in entries.iter().enumerate() {
write_one(builder, corpus, answers, (snapshot, (index, entry), limits))?;
}
Ok(())
}
fn write_one<P: Promotion>(
builder: &mut ResolutionReportBuilder,
corpus: &Corpus<'_>,
answers: (&Imports, &P),
request: (
&RustResolutionSnapshot,
(usize, &ReferenceEntry),
ResolutionLimits,
),
) -> Result<(), RustResolutionError> {
let (imports, promotion) = answers;
let (snapshot, (index, entry), limits) = request;
let Some(site) = reference_site(corpus, snapshot, entry) else {
return Ok(());
};
let denotation = match promoted_site(corpus, entry).and_then(|at| promotion.promote(at)) {
Some(proved) => promoted(proved),
None => denote(corpus, imports, (index, entry), site),
};
check_fan_out(&denotation, limits)?;
let condition = effective(corpus, entry, site);
let conditional = is_conditional(corpus, &condition, &denotation.candidates);
let certainty = certainty_of(&denotation.candidates, conditional || denotation.enumerated);
builder.set_resolution(
&entry.handle,
candidates(corpus, &denotation.candidates, certainty),
gaps(&denotation, conditional),
)?;
Ok(())
}
fn promoted_site(corpus: &Corpus<'_>, entry: &ReferenceEntry) -> Option<PromotionSite> {
let node = corpus.graph.nodes.get(entry.node)?;
Some(PromotionSite::new(node.unit, node.file, &entry.span))
}
fn promoted(proved: Promoted) -> Denotation {
Denotation {
candidates: proved.candidates,
gap: None,
enumerated: proved.enumerated,
}
}
fn reference_site<'a>(
corpus: &Corpus<'_>,
snapshot: &'a RustResolutionSnapshot,
entry: &ReferenceEntry,
) -> Option<&'a ReferenceSite> {
corpus
.graph
.file_ir(snapshot, entry.node)?
.reference_sites
.get(entry.site)
}
fn denote(
corpus: &Corpus<'_>,
imports: &Imports,
stated: (usize, &ReferenceEntry),
site: &ReferenceSite,
) -> Denotation {
let (index, entry) = stated;
match site.origin() {
ReferenceOrigin::MacroInvocation => refused(ResolutionGap::MacroExpansion),
ReferenceOrigin::MethodCall => method(corpus, entry.node, site),
ReferenceOrigin::Import => imported(corpus, imports, (index, entry), site),
ReferenceOrigin::ModuleDeclaration
| ReferenceOrigin::ExpressionPath
| ReferenceOrigin::CallPath
| ReferenceOrigin::TypeMention
| ReferenceOrigin::Implementation => from_outcome(
corpus,
&resolve_path(corpus, imports.bindings(), entry.node, site.segments()),
),
}
}
fn imported(
corpus: &Corpus<'_>,
imports: &Imports,
stated: (usize, &ReferenceEntry),
site: &ReferenceSite,
) -> Denotation {
let (index, entry) = stated;
match imports.outcome(index) {
Some(outcome) => from_outcome(corpus, outcome),
None => from_outcome(
corpus,
&resolve_path(corpus, imports.bindings(), entry.node, site.segments()),
),
}
}
fn method(corpus: &Corpus<'_>, node: usize, site: &ReferenceSite) -> Denotation {
let Some(receiver) = site.receiver() else {
return refused(ResolutionGap::DynamicDispatch);
};
let found: Box<[usize]> = corpus
.index
.associated(receiver, &site.text)
.iter()
.filter(|slot| corpus.holds(node, **slot))
.copied()
.collect();
match found.is_empty() {
true => refused(ResolutionGap::MissingDefinition),
false => Denotation {
candidates: found,
gap: None,
enumerated: false,
},
}
}
fn from_outcome(corpus: &Corpus<'_>, outcome: &Outcome) -> Denotation {
match outcome {
Outcome::External => refused(ResolutionGap::ExternalDefinition),
Outcome::Missing => refused(ResolutionGap::MissingDefinition),
Outcome::Unsupported => refused(ResolutionGap::UnsupportedSyntax),
Outcome::Found(targets) => found(corpus, targets),
}
}
fn found(corpus: &Corpus<'_>, targets: &[Target]) -> Denotation {
let mut candidates: Vec<usize> = targets
.iter()
.filter_map(|target| slot_of(corpus, target))
.collect();
candidates.sort_unstable();
candidates.dedup();
match candidates.is_empty() {
true => refused(ResolutionGap::ExternalDefinition),
false => Denotation {
candidates: candidates.into_boxed_slice(),
gap: None,
enumerated: false,
},
}
}
fn slot_of(corpus: &Corpus<'_>, target: &Target) -> Option<usize> {
match target {
Target::Definition(slot) => Some(*slot),
Target::Module(node) => corpus.index.module_slot(*node),
}
}
fn refused(gap: ResolutionGap) -> Denotation {
Denotation {
candidates: Box::from([]),
gap: Some(gap),
enumerated: false,
}
}
fn effective(
corpus: &Corpus<'_>,
entry: &ReferenceEntry,
site: &ReferenceSite,
) -> RustCfgCondition {
let node = corpus.graph.nodes.get(entry.node);
let inherited = node
.map(|found| found.condition.clone())
.unwrap_or_default();
let activation = node
.and_then(|found| corpus.units.conditions.get(found.unit))
.cloned()
.unwrap_or_default();
inherited.and(&activation).and(site.condition())
}
fn is_conditional(corpus: &Corpus<'_>, condition: &RustCfgCondition, candidates: &[usize]) -> bool {
!condition.is_empty()
|| candidates.iter().any(|slot| {
corpus
.index
.slot(*slot)
.is_some_and(|found| !found.condition.is_empty())
})
}
fn certainty_of(candidates: &[usize], conditional: bool) -> ResolutionCertainty {
match (candidates.len(), conditional) {
(1, false) => ResolutionCertainty::Resolved,
_ => ResolutionCertainty::Possible,
}
}
fn candidates(
corpus: &Corpus<'_>,
slots: &[usize],
certainty: ResolutionCertainty,
) -> Box<[CandidateInput]> {
slots
.iter()
.filter_map(|slot| corpus.index.slot(*slot))
.map(|slot| CandidateInput::new(slot.handle.clone(), certainty))
.collect()
}
fn gaps(denotation: &Denotation, conditional: bool) -> Box<[ResolutionGap]> {
let dynamic = denotation
.enumerated
.then_some(ResolutionGap::DynamicDispatch);
let ambiguous = (denotation.candidates.len() > 1).then_some(ResolutionGap::Ambiguous);
let stated = conditional.then_some(ResolutionGap::ConditionalCompilation);
denotation
.gap
.into_iter()
.chain(dynamic)
.chain(ambiguous)
.chain(stated)
.collect()
}
fn check_fan_out(
denotation: &Denotation,
limits: ResolutionLimits,
) -> Result<(), RustResolutionError> {
let ceiling = limits.max_candidates_per_reference;
match u64::try_from(denotation.candidates.len()).unwrap_or(u64::MAX) > u64::from(ceiling) {
true => Err(RustResolutionError::LimitExceeded { limit: ceiling }),
false => Ok(()),
}
}