use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::jigsaw::plugin_set::PluginFile;
use crate::model::macro_decl::MacroKey;
use crate::model::names::{
ApplicationName, Arity, BehaviourName, ConfKey, FieldName, FunctionName, Mfa, ModuleName,
RecordName, TypeName, ValidatorName, vocabulary,
};
use crate::model::schema_files::PartialRef;
use crate::model::snapshot::{FunArity, HeaderRef};
use crate::model::verdict::IndirectCallForm;
vocabulary!(
#[derive(PartialOrd, Ord, Hash)]
pub enum NotChecked: "not-checked reason" {
Internal => "internal",
Dynamic => "dynamic",
UnresolvedAtBuild => "unresolved_at_build",
Elixir => "elixir",
Untracked => "untracked",
UnresolvedInclude => "unresolved_include",
ConditionalSite => "conditional_site",
}
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CallForm {
Direct,
FunRef,
Apply,
Indirect(IndirectCallForm),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RecordConstruction {
pub unset: BTreeSet<FieldName>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SiteDetail {
Plain,
Call(CallForm),
Construction(RecordConstruction),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ReferenceSite {
pub file: PluginFile,
pub line: u32,
pub detail: SiteDetail,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Reference {
Call(Mfa),
Record {
includer: PluginFile,
name: RecordName,
},
Macro {
includer: PluginFile,
key: MacroKey,
},
Header(HeaderRef),
Behaviour {
implementer: ModuleName,
behaviour: BehaviourName,
},
Type {
module: ModuleName,
name: TypeName,
arity: Arity,
},
Partial {
partial: PartialRef,
keys: BTreeSet<ConfKey>,
},
Validator(ValidatorName),
App(ApplicationName),
}
impl fmt::Display for Reference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Call(mfa) => write!(f, "{mfa}"),
Self::Record { name, .. } => write!(f, "#{name}{{}}"),
Self::Macro { key, .. } => write!(f, "?{key}"),
Self::Header(header) => write!(f, "{header}"),
Self::Behaviour { behaviour, .. } => write!(f, "{behaviour}"),
Self::Type {
module,
name,
arity,
} => write!(f, "{module}:{name}/{arity}"),
Self::Partial { partial, .. } => write!(f, "{partial}"),
Self::Validator(name) => write!(f, "{name}"),
Self::App(app) => write!(f, "{app}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DynamicCall {
pub module: Option<ModuleName>,
pub function: Option<FunctionName>,
pub arity: Option<Arity>,
}
impl fmt::Display for DynamicCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.module {
Some(module) => write!(f, "{module}")?,
None => f.write_str("_")?,
}
f.write_str(":")?;
match &self.function {
Some(function) => write!(f, "{function}")?,
None => f.write_str("_")?,
}
f.write_str("/")?;
match self.arity {
Some(arity) => write!(f, "{arity}"),
None => f.write_str("?"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum UncheckedSymbol {
UnresolvedAtBuild(Reference),
Dynamic(DynamicCall),
}
impl UncheckedSymbol {
#[must_use]
pub fn reason(&self) -> NotChecked {
match self {
Self::UnresolvedAtBuild(_) => NotChecked::UnresolvedAtBuild,
Self::Dynamic(_) => NotChecked::Dynamic,
}
}
}
impl fmt::Display for UncheckedSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnresolvedAtBuild(reference) => write!(f, "{reference}"),
Self::Dynamic(call) => write!(f, "{call}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UncheckedSite {
pub symbol: UncheckedSymbol,
pub file: PluginFile,
pub line: u32,
}
impl UncheckedSite {
#[must_use]
pub fn reason(&self) -> NotChecked {
self.symbol.reason()
}
}
impl Ord for UncheckedSite {
fn cmp(&self, other: &Self) -> Ordering {
(self.reason(), &self.file, self.line, &self.symbol).cmp(&(
other.reason(),
&other.file,
other.line,
&other.symbol,
))
}
}
impl PartialOrd for UncheckedSite {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Default)]
pub struct ReferenceSet {
external: BTreeMap<Reference, BTreeSet<ReferenceSite>>,
host_includes: BTreeMap<PluginFile, BTreeSet<HeaderRef>>,
exports: BTreeMap<ModuleName, BTreeSet<FunArity>>,
unresolved_includes: BTreeSet<PluginFile>,
not_checked: BTreeMap<NotChecked, usize>,
unchecked: BTreeSet<UncheckedSite>,
}
impl ReferenceSet {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, reference: Reference, site: ReferenceSite) {
self.external.entry(reference).or_default().insert(site);
}
pub fn count(&mut self, reason: NotChecked, n: usize) {
if n > 0 {
*self.not_checked.entry(reason).or_default() += n;
}
}
pub fn add_unchecked(&mut self, site: UncheckedSite) {
self.unchecked.insert(site);
}
pub fn set_host_includes(&mut self, file: PluginFile, headers: BTreeSet<HeaderRef>) {
if !headers.is_empty() {
self.host_includes.insert(file, headers);
}
}
pub fn set_exports(&mut self, module: ModuleName, exports: BTreeSet<FunArity>) {
self.exports.insert(module, exports);
}
pub fn mark_unresolved_include(&mut self, file: PluginFile) {
self.unresolved_includes.insert(file);
}
#[must_use]
pub fn external(&self) -> &BTreeMap<Reference, BTreeSet<ReferenceSite>> {
&self.external
}
#[must_use]
pub fn not_checked(&self) -> &BTreeMap<NotChecked, usize> {
&self.not_checked
}
#[must_use]
pub fn unchecked(&self) -> &BTreeSet<UncheckedSite> {
&self.unchecked
}
#[must_use]
pub fn host_includes(&self, file: &PluginFile) -> Option<&BTreeSet<HeaderRef>> {
self.host_includes.get(file)
}
#[must_use]
pub fn exports_of(&self, module: &ModuleName) -> Option<&BTreeSet<FunArity>> {
self.exports.get(module)
}
#[must_use]
pub fn has_unresolved_include(&self, file: &PluginFile) -> bool {
self.unresolved_includes.contains(file)
}
}