1use std::collections::{BTreeMap, BTreeSet};
13use std::fmt;
14use std::marker::PhantomData;
15
16use backhopper_core::{ApplicationName, BehaviourName, Mfa, ModuleName};
17use serde::{Deserialize, Serialize};
18
19use crate::call::{FunctionSig, UnresolvedCallSite};
20use crate::loc::Loc;
21use crate::relation::{Relation, VertexSet};
22use crate::state::{Building, Built, Functions, Mode, Phase};
23use crate::vertex::Vertex;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27#[non_exhaustive]
28pub enum DeprecationTier {
29 Now,
30 Next,
31 Eventual,
32}
33
34impl fmt::Display for DeprecationTier {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 let s = match self {
37 Self::Now => "now",
38 Self::Next => "next_release",
39 Self::Eventual => "eventual",
40 };
41 f.write_str(s)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct Deprecation {
47 pub tier: DeprecationTier,
48 pub message: Option<String>,
49}
50
51#[derive(Debug, Clone, Default, PartialEq, Eq)]
52pub struct ModuleSummary {
53 pub application: Option<ApplicationName>,
54 pub exports: BTreeSet<FunctionSig>,
55 pub locals: BTreeSet<FunctionSig>,
56 pub on_load: Option<FunctionSig>,
57 pub behaviours: BTreeSet<BehaviourName>,
58 pub callbacks_required: BTreeSet<FunctionSig>,
59 pub callbacks_optional: BTreeSet<FunctionSig>,
60}
61
62#[derive(Debug, Clone)]
63pub struct CallGraph<M: Mode, P: Phase = Built> {
64 mode: PhantomData<M>,
65 phase: PhantomData<P>,
66
67 modules: BTreeMap<ModuleName, ModuleSummary>,
68 def_at: BTreeMap<Mfa, Loc>,
69 deprecated: BTreeMap<Mfa, Deprecation>,
70 on_load_functions: BTreeSet<Mfa>,
71
72 local_calls: Relation,
73 external_calls: Relation,
74 module_edges: Relation,
75 application_edges: Relation,
76 behaviour_edges: Relation,
77
78 unresolved: BTreeSet<UnresolvedCallSite>,
79 builtins: VertexSet,
80}
81
82impl<M: Mode, P: Phase> CallGraph<M, P> {
83 fn empty() -> Self {
84 Self {
85 mode: PhantomData,
86 phase: PhantomData,
87 modules: BTreeMap::new(),
88 def_at: BTreeMap::new(),
89 deprecated: BTreeMap::new(),
90 on_load_functions: BTreeSet::new(),
91 local_calls: Relation::new(),
92 external_calls: Relation::new(),
93 module_edges: Relation::new(),
94 application_edges: Relation::new(),
95 behaviour_edges: Relation::new(),
96 unresolved: BTreeSet::new(),
97 builtins: VertexSet::new(),
98 }
99 }
100}
101
102impl<M: Mode> CallGraph<M, Building> {
103 pub fn new() -> Self {
104 Self::empty()
105 }
106
107 pub fn set_builtins(&mut self, b: VertexSet) -> &mut Self {
108 self.builtins = b;
109 self
110 }
111
112 pub fn insert_module(&mut self, name: ModuleName, summary: ModuleSummary) {
113 let app_vertex = summary.application.clone().map(Vertex::Application);
114 for b in &summary.behaviours {
115 self.behaviour_edges
116 .insert(Vertex::Module(name.clone()), Vertex::Behaviour(b.clone()));
117 if let Some(a) = &app_vertex {
118 self.application_edges
119 .insert(a.clone(), Vertex::Behaviour(b.clone()));
120 }
121 }
122 self.modules.insert(name, summary);
123 }
124
125 pub fn record_definition(&mut self, mfa: Mfa, loc: Loc) {
126 self.def_at.insert(mfa, loc);
127 }
128
129 pub fn record_on_load(&mut self, mfa: Mfa) {
130 self.on_load_functions.insert(mfa);
131 }
132
133 pub fn record_deprecation(&mut self, mfa: Mfa, d: Deprecation) {
134 self.deprecated.insert(mfa, d);
135 }
136
137 pub fn insert_local_call(&mut self, caller: Mfa, callee: Mfa) {
138 self.local_calls
139 .insert(Vertex::Function(caller), Vertex::Function(callee));
140 }
141
142 pub fn insert_external_call(&mut self, caller: Mfa, callee: Mfa) {
143 let caller_mod = caller.module.clone();
144 let callee_mod = callee.module.clone();
145 self.external_calls
146 .insert(Vertex::Function(caller), Vertex::Function(callee));
147 if caller_mod != callee_mod {
148 let caller_app = self
149 .modules
150 .get(&caller_mod)
151 .and_then(|m| m.application.clone());
152 let callee_app = self
153 .modules
154 .get(&callee_mod)
155 .and_then(|m| m.application.clone());
156 self.module_edges
157 .insert(Vertex::Module(caller_mod), Vertex::Module(callee_mod));
158 if let (Some(a), Some(b)) = (caller_app, callee_app)
159 && a != b
160 {
161 self.application_edges
162 .insert(Vertex::Application(a), Vertex::Application(b));
163 }
164 }
165 }
166
167 pub fn insert_unresolved(&mut self, site: UnresolvedCallSite) {
168 self.unresolved.insert(site);
169 }
170
171 pub fn finish(self) -> CallGraph<M, Built> {
172 CallGraph {
173 mode: PhantomData,
174 phase: PhantomData,
175 modules: self.modules,
176 def_at: self.def_at,
177 deprecated: self.deprecated,
178 on_load_functions: self.on_load_functions,
179 local_calls: self.local_calls,
180 external_calls: self.external_calls,
181 module_edges: self.module_edges,
182 application_edges: self.application_edges,
183 behaviour_edges: self.behaviour_edges,
184 unresolved: self.unresolved,
185 builtins: self.builtins,
186 }
187 }
188}
189
190impl<M: Mode> Default for CallGraph<M, Building> {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196impl<M: Mode> CallGraph<M, Built> {
197 pub fn modules(&self) -> impl Iterator<Item = (&ModuleName, &ModuleSummary)> {
198 self.modules.iter()
199 }
200
201 pub fn module(&self, name: &ModuleName) -> Option<&ModuleSummary> {
202 self.modules.get(name)
203 }
204
205 pub fn def_at(&self, mfa: &Mfa) -> Option<&Loc> {
206 self.def_at.get(mfa)
207 }
208
209 pub fn deprecation(&self, mfa: &Mfa) -> Option<&Deprecation> {
210 self.deprecated.get(mfa)
211 }
212
213 pub fn deprecated_mfas(&self) -> impl Iterator<Item = (&Mfa, &Deprecation)> {
214 self.deprecated.iter()
215 }
216
217 pub fn on_load_functions(&self) -> &BTreeSet<Mfa> {
218 &self.on_load_functions
219 }
220
221 pub fn builtins(&self) -> &VertexSet {
222 &self.builtins
223 }
224
225 pub fn module_edges(&self) -> &Relation {
226 &self.module_edges
227 }
228
229 pub fn application_edges(&self) -> &Relation {
230 &self.application_edges
231 }
232
233 pub fn behaviour_edges(&self) -> &Relation {
234 &self.behaviour_edges
235 }
236
237 pub fn unresolved_calls(&self) -> &BTreeSet<UnresolvedCallSite> {
238 &self.unresolved
239 }
240
241 pub fn defined_functions(&self) -> VertexSet {
242 self.modules
243 .iter()
244 .flat_map(|(m, s)| {
245 s.exports
246 .iter()
247 .chain(s.locals.iter())
248 .map(move |f| function_vertex(m, f))
249 })
250 .collect()
251 }
252}
253
254fn function_vertex(module: &ModuleName, sig: &FunctionSig) -> Vertex {
255 Vertex::Function(Mfa::new(module.clone(), sig.name.clone(), sig.arity))
256}
257
258impl CallGraph<Functions, Built> {
259 pub fn local_calls(&self) -> &Relation {
260 &self.local_calls
261 }
262
263 pub fn external_calls(&self) -> &Relation {
264 &self.external_calls
265 }
266
267 pub fn all_calls(&self) -> Relation {
268 self.local_calls.union(&self.external_calls)
269 }
270}