1use std::cmp::Ordering;
6use std::collections::{BTreeMap, BTreeSet};
7use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11use crate::jigsaw::plugin_set::PluginFile;
12use crate::model::macro_decl::MacroKey;
13use crate::model::names::{
14 ApplicationName, Arity, BehaviourName, ConfKey, FieldName, FunctionName, Mfa, ModuleName,
15 RecordName, TypeName, ValidatorName, vocabulary,
16};
17use crate::model::schema_files::PartialRef;
18use crate::model::snapshot::{FunArity, HeaderRef};
19use crate::model::verdict::IndirectCallForm;
20
21vocabulary!(
22 #[derive(PartialOrd, Ord, Hash)]
24 pub enum NotChecked: "not-checked reason" {
25 Internal => "internal",
26 Dynamic => "dynamic",
27 UnresolvedAtBuild => "unresolved_at_build",
28 Elixir => "elixir",
29 Untracked => "untracked",
30 UnresolvedInclude => "unresolved_include",
31 ConditionalSite => "conditional_site",
32 }
33);
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub enum CallForm {
39 Direct,
40 FunRef,
41 Apply,
42 Indirect(IndirectCallForm),
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
47pub struct RecordConstruction {
48 pub unset: BTreeSet<FieldName>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
52pub enum SiteDetail {
53 Plain,
54 Call(CallForm),
55 Construction(RecordConstruction),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct ReferenceSite {
60 pub file: PluginFile,
61 pub line: u32,
62 pub detail: SiteDetail,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
67pub enum Reference {
68 Call(Mfa),
69 Record {
70 includer: PluginFile,
71 name: RecordName,
72 },
73 Macro {
74 includer: PluginFile,
75 key: MacroKey,
76 },
77 Header(HeaderRef),
78 Behaviour {
79 implementer: ModuleName,
80 behaviour: BehaviourName,
81 },
82 Type {
83 module: ModuleName,
84 name: TypeName,
85 arity: Arity,
86 },
87 Partial {
88 partial: PartialRef,
89 keys: BTreeSet<ConfKey>,
90 },
91 Validator(ValidatorName),
92 App(ApplicationName),
93}
94
95impl fmt::Display for Reference {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::Call(mfa) => write!(f, "{mfa}"),
99 Self::Record { name, .. } => write!(f, "#{name}{{}}"),
100 Self::Macro { key, .. } => write!(f, "?{key}"),
101 Self::Header(header) => write!(f, "{header}"),
102 Self::Behaviour { behaviour, .. } => write!(f, "{behaviour}"),
103 Self::Type {
104 module,
105 name,
106 arity,
107 } => write!(f, "{module}:{name}/{arity}"),
108 Self::Partial { partial, .. } => write!(f, "{partial}"),
109 Self::Validator(name) => write!(f, "{name}"),
110 Self::App(app) => write!(f, "{app}"),
111 }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
118pub struct DynamicCall {
119 pub module: Option<ModuleName>,
120 pub function: Option<FunctionName>,
121 pub arity: Option<Arity>,
122}
123
124impl fmt::Display for DynamicCall {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match &self.module {
127 Some(module) => write!(f, "{module}")?,
128 None => f.write_str("_")?,
129 }
130 f.write_str(":")?;
131 match &self.function {
132 Some(function) => write!(f, "{function}")?,
133 None => f.write_str("_")?,
134 }
135 f.write_str("/")?;
136 match self.arity {
137 Some(arity) => write!(f, "{arity}"),
138 None => f.write_str("?"),
139 }
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
146pub enum UncheckedSymbol {
147 UnresolvedAtBuild(Reference),
148 Dynamic(DynamicCall),
149}
150
151impl UncheckedSymbol {
152 #[must_use]
153 pub fn reason(&self) -> NotChecked {
154 match self {
155 Self::UnresolvedAtBuild(_) => NotChecked::UnresolvedAtBuild,
156 Self::Dynamic(_) => NotChecked::Dynamic,
157 }
158 }
159}
160
161impl fmt::Display for UncheckedSymbol {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::UnresolvedAtBuild(reference) => write!(f, "{reference}"),
165 Self::Dynamic(call) => write!(f, "{call}"),
166 }
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Hash)]
173pub struct UncheckedSite {
174 pub symbol: UncheckedSymbol,
175 pub file: PluginFile,
176 pub line: u32,
177}
178
179impl UncheckedSite {
180 #[must_use]
181 pub fn reason(&self) -> NotChecked {
182 self.symbol.reason()
183 }
184}
185
186impl Ord for UncheckedSite {
187 fn cmp(&self, other: &Self) -> Ordering {
188 (self.reason(), &self.file, self.line, &self.symbol).cmp(&(
189 other.reason(),
190 &other.file,
191 other.line,
192 &other.symbol,
193 ))
194 }
195}
196
197impl PartialOrd for UncheckedSite {
198 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
199 Some(self.cmp(other))
200 }
201}
202
203#[derive(Debug, Clone, Default)]
206pub struct ReferenceSet {
207 external: BTreeMap<Reference, BTreeSet<ReferenceSite>>,
208 host_includes: BTreeMap<PluginFile, BTreeSet<HeaderRef>>,
209 exports: BTreeMap<ModuleName, BTreeSet<FunArity>>,
210 unresolved_includes: BTreeSet<PluginFile>,
211 not_checked: BTreeMap<NotChecked, usize>,
212 unchecked: BTreeSet<UncheckedSite>,
213}
214
215impl ReferenceSet {
216 #[must_use]
217 pub fn new() -> Self {
218 Self::default()
219 }
220
221 pub fn add(&mut self, reference: Reference, site: ReferenceSite) {
222 self.external.entry(reference).or_default().insert(site);
223 }
224
225 pub fn count(&mut self, reason: NotChecked, n: usize) {
226 if n > 0 {
227 *self.not_checked.entry(reason).or_default() += n;
228 }
229 }
230
231 pub fn add_unchecked(&mut self, site: UncheckedSite) {
232 self.unchecked.insert(site);
233 }
234
235 pub fn set_host_includes(&mut self, file: PluginFile, headers: BTreeSet<HeaderRef>) {
236 if !headers.is_empty() {
237 self.host_includes.insert(file, headers);
238 }
239 }
240
241 pub fn set_exports(&mut self, module: ModuleName, exports: BTreeSet<FunArity>) {
242 self.exports.insert(module, exports);
243 }
244
245 pub fn mark_unresolved_include(&mut self, file: PluginFile) {
246 self.unresolved_includes.insert(file);
247 }
248
249 #[must_use]
250 pub fn external(&self) -> &BTreeMap<Reference, BTreeSet<ReferenceSite>> {
251 &self.external
252 }
253
254 #[must_use]
255 pub fn not_checked(&self) -> &BTreeMap<NotChecked, usize> {
256 &self.not_checked
257 }
258
259 #[must_use]
260 pub fn unchecked(&self) -> &BTreeSet<UncheckedSite> {
261 &self.unchecked
262 }
263
264 #[must_use]
267 pub fn host_includes(&self, file: &PluginFile) -> Option<&BTreeSet<HeaderRef>> {
268 self.host_includes.get(file)
269 }
270
271 #[must_use]
272 pub fn exports_of(&self, module: &ModuleName) -> Option<&BTreeSet<FunArity>> {
273 self.exports.get(module)
274 }
275
276 #[must_use]
277 pub fn has_unresolved_include(&self, file: &PluginFile) -> bool {
278 self.unresolved_includes.contains(file)
279 }
280}