use std::sync::Arc;
use pedant_syntax::go::GoReferenceKind;
use pedant_types::{ReferenceKind, ResolutionGap};
use crate::resolution::go::binding_fact::GoBindingRecord;
use crate::resolution::go::facts::GoSourceFacts;
use crate::resolution::go::import_fact::GoImportRecord;
use crate::resolution::go::reference_fact::GoReferenceRecord;
use crate::resolution::go::unit::GoResolutionUnit;
use super::answer::Answer;
use super::corpus::{Corpus, UnitSource, conditional};
use super::denotation::Denotation;
use super::dispatch;
use super::error::GoResolutionError;
use super::implementations::Implementations;
use super::imports::{FileImports, ImportTarget, target};
use super::index::{self, Index, Slot};
use super::lookup::{self, Outcome};
use super::methods::{self, Member};
use super::relations;
use super::scopes;
use super::types::{self, Site};
struct FileSite<'a> {
unit: usize,
path: &'a Arc<str>,
facts: &'a GoSourceFacts,
imports: &'a FileImports<'a>,
conditional: bool,
}
impl<'a> FileSite<'a> {
fn of(unit: usize, held: UnitSource<'a>) -> Self {
Self {
unit,
path: held.path,
facts: held.source.facts(),
imports: held.imports,
conditional: conditional(held.source),
}
}
}
pub(super) fn of_unit(
index: &Index,
corpus: &Corpus<'_>,
implementations: &Implementations,
unit: (usize, &GoResolutionUnit),
) -> Result<Box<[Denotation]>, GoResolutionError> {
let (position, held) = unit;
let mut denoted = Vec::new();
for source in corpus.sources_of(held)?.iter() {
of_file(
index,
corpus,
implementations,
&FileSite::of(position, *source),
&mut denoted,
);
}
denoted.extend(relations::of_unit(index, implementations, position));
Ok(denoted.into_boxed_slice())
}
fn of_file(
index: &Index,
corpus: &Corpus<'_>,
implementations: &Implementations,
site: &FileSite<'_>,
denoted: &mut Vec<Denotation>,
) {
let imported = site
.facts
.imports()
.iter()
.map(|import| imported(index, corpus, site, import));
let occurrences = site
.facts
.references()
.iter()
.filter_map(|fact| denote(index, implementations, site, fact));
denoted.extend(imported.chain(occurrences));
}
fn imported(
index: &Index,
corpus: &Corpus<'_>,
site: &FileSite<'_>,
import: &GoImportRecord,
) -> Denotation {
let found = target(corpus, import);
let package = found
.unit
.and_then(|unit| index.unit(unit))
.map(|tables| tables.package);
Denotation {
kind: ReferenceKind::Import,
text: Arc::from(import.path()),
span: index::site(site.path, import.span()),
enclosing: None,
candidates: package.into_iter().collect(),
gap: package
.is_none()
.then_some(ResolutionGap::ExternalDefinition),
conditional: site.conditional,
possible_only: false,
}
}
fn denote(
index: &Index,
implementations: &Implementations,
site: &FileSite<'_>,
fact: &GoReferenceRecord,
) -> Option<Denotation> {
match (fact.kind(), fact.qualifier()) {
(GoReferenceKind::Call, _) => Some(bare_call(index, site, fact)),
(GoReferenceKind::QualifiedCall, Some(written)) => Some(qualified(
index,
implementations,
site,
(fact, written),
ReferenceKind::Call,
)),
(GoReferenceKind::Selector, Some(written)) => Some(qualified(
index,
implementations,
site,
(fact, written),
ReferenceKind::Value,
)),
(GoReferenceKind::QualifiedCall | GoReferenceKind::Selector, None) => None,
(GoReferenceKind::Type, Some(written)) => Some(qualified(
index,
implementations,
site,
(fact, written),
ReferenceKind::Type,
)),
(GoReferenceKind::Type, None) => plain(index, site, fact, ReferenceKind::Type),
(GoReferenceKind::Value, _) => plain(index, site, fact, ReferenceKind::Value),
}
}
fn bare_call(index: &Index, site: &FileSite<'_>, fact: &GoReferenceRecord) -> Denotation {
match covered(site, fact, fact.name()) {
true => stated(
index,
site,
fact,
Answer::refused(ReferenceKind::Call, ResolutionGap::DynamicDispatch),
),
false => {
let found = lookup::bare(index, (site.unit, site.imports), fact.name());
stated(
index,
site,
fact,
classified(index, from_outcome(found), ReferenceKind::Call),
)
}
}
}
fn plain(
index: &Index,
site: &FileSite<'_>,
fact: &GoReferenceRecord,
default: ReferenceKind,
) -> Option<Denotation> {
if covered(site, fact, fact.name()) || site.imports.named(fact.name()).is_some() {
return None;
}
let answer = from_outcome(lookup::bare(index, (site.unit, site.imports), fact.name()));
Some(stated(
index,
site,
fact,
classified(index, answer, default),
))
}
fn qualified(
index: &Index,
implementations: &Implementations,
site: &FileSite<'_>,
written: (&GoReferenceRecord, &str),
default: ReferenceKind,
) -> Denotation {
let (fact, qualifier) = written;
let bound = binding_of(site, fact, qualifier);
match package_target(site, bound, qualifier) {
Some(package) => {
let answer = from_outcome(lookup::qualified(index, package, fact.name()));
stated(index, site, fact, classified(index, answer, default))
}
None => member(index, implementations, site, (fact, bound), default),
}
}
fn binding_of<'a>(
site: &'a FileSite<'a>,
fact: &GoReferenceRecord,
qualifier: &str,
) -> Option<&'a GoBindingRecord> {
let base = types::plain_name(qualifier)?;
scopes::binding(site.facts, (fact.scope(), fact.span().start_byte()), base)
}
fn package_target<'a>(
site: &'a FileSite<'a>,
bound: Option<&GoBindingRecord>,
qualifier: &str,
) -> Option<ImportTarget<'a>> {
match bound {
Some(_) => None,
None => site.imports.named(qualifier),
}
}
fn member(
index: &Index,
implementations: &Implementations,
site: &FileSite<'_>,
written: (&GoReferenceRecord, Option<&GoBindingRecord>),
default: ReferenceKind,
) -> Denotation {
let (fact, bound) = written;
let receiver = bound.and_then(|bound| types::from_binding(index, receiver_site(site), bound));
let Some(receiver) = receiver else {
return stated(
index,
site,
fact,
Answer::refused(default, ResolutionGap::UnsupportedSyntax),
);
};
let found = methods::promoted(index, receiver, fact.name());
let answer = answered(index, implementations, (default, fact.name()), &found);
stated(index, site, fact, answer)
}
fn answered(
index: &Index,
implementations: &Implementations,
asked: (ReferenceKind, &str),
found: &[Member],
) -> Answer {
let (default, name) = asked;
let slots: Box<[usize]> = found.iter().map(|member| member.slot).collect();
match (
slots.is_empty(),
found.iter().any(|member| member.multiples),
) {
(true, _) => Answer::refused(default, ResolutionGap::MissingDefinition),
(false, true) => Answer::multiple(default, slots),
(false, false) => dispatch::selected(index, implementations, (default, name), slots),
}
}
fn receiver_site<'a>(site: &'a FileSite<'a>) -> Site<'a> {
Site {
unit: site.unit,
imports: site.imports,
}
}
fn covered(site: &FileSite<'_>, fact: &GoReferenceRecord, name: &str) -> bool {
scopes::binding(site.facts, (fact.scope(), fact.span().start_byte()), name).is_some()
}
fn from_outcome(outcome: Outcome) -> (Box<[usize]>, Option<ResolutionGap>) {
match outcome {
Outcome::Found(found) => (found, None),
Outcome::External => (Box::from([]), Some(ResolutionGap::ExternalDefinition)),
Outcome::Missing => (Box::from([]), Some(ResolutionGap::MissingDefinition)),
}
}
fn classified(
index: &Index,
answer: (Box<[usize]>, Option<ResolutionGap>),
default: ReferenceKind,
) -> Answer {
let (found, gap) = answer;
let converts = found
.iter()
.any(|slot| index.slot(*slot).is_some_and(Slot::is_type));
let kind = match converts {
true => ReferenceKind::Type,
false => default,
};
Answer::found(kind, found, gap)
}
fn stated(
index: &Index,
site: &FileSite<'_>,
fact: &GoReferenceRecord,
answer: Answer,
) -> Denotation {
Denotation {
kind: answer.kind,
text: Arc::from(fact.name()),
span: index::site(site.path, fact.span()),
enclosing: fact
.declaration()
.and_then(|declaration| enclosing(index, site, declaration)),
candidates: answer.candidates,
gap: answer.gap,
conditional: site.conditional,
possible_only: answer.possible_only,
}
}
fn enclosing(index: &Index, site: &FileSite<'_>, declaration: u32) -> Option<usize> {
index.unit(site.unit)?.declared(site.path, declaration)
}