1use std::collections::HashMap;
2
3use crate::ast::{Publicity, SrcSpan};
4use bimap::{BiMap, Overwritten};
5use ecow::EcoString;
6use petgraph::{
7 Directed, Direction,
8 stable_graph::{NodeIndex, StableGraph},
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ReferenceKind {
13 Qualified,
14 Unqualified,
15 Import,
16 Definition,
17 Alias,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Reference {
22 pub location: SrcSpan,
23 pub kind: ReferenceKind,
24}
25
26pub type ReferenceMap = HashMap<(EcoString, EcoString), Vec<Reference>>;
27
28#[derive(Debug, Clone)]
29pub struct EntityInformation {
30 pub origin: SrcSpan,
31 pub kind: EntityKind,
32}
33
34/// Information about an "Entity". This determines how we warn about an entity
35/// being unused.
36#[derive(Debug, Clone, Eq, PartialEq)]
37pub enum EntityKind {
38 Function,
39 Constant,
40 Constructor,
41 Type,
42 ImportedModule { module_name: EcoString },
43 ModuleAlias { module: EcoString },
44 ImportedConstructor { module: EcoString },
45 ImportedType { module: EcoString },
46 ImportedValue { module: EcoString },
47}
48
49/// Like `ast::Layer`, this type differentiates between different scopes. For example,
50/// there can be a `wibble` value, a `wibble` module and a `wibble` type in the same
51/// scope all at once!
52///
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54enum EntityLayer {
55 /// An entity which exists in the type layer: a custom type, type variable
56 /// or type alias.
57 Type,
58 /// An entity which exists in the value layer: a constant, function or
59 /// custom type variant constructor.
60 Value,
61 /// An entity which has been shadowed. This allows us to keep track of unused
62 /// imports even if they have been shadowed by another value in the current
63 /// module.
64 /// This extra variant is needed because we used `Entity` as a key in a hashmap,
65 /// and so a duplicate key would not be able to exist.
66 /// We also would not want to get this shadowed entity when performing a lookup
67 /// of a named entity; we only want it to register it as an entity in the
68 /// `unused` function.
69 Shadowed,
70 /// The name of an imported module. Modules are separate to values!
71 Module,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75pub struct Entity {
76 pub name: EcoString,
77 layer: EntityLayer,
78}
79
80#[derive(Debug, Default)]
81pub struct ReferenceTracker {
82 /// A call-graph which tracks which values are referenced by which other value,
83 /// used for dead code detection.
84 graph: StableGraph<(), (), Directed>,
85 entities: BiMap<Entity, NodeIndex>,
86 current_node: NodeIndex,
87 public_entities: Vec<Entity>,
88 entity_information: HashMap<Entity, EntityInformation>,
89
90 /// The locations of the references to each value in this module, used for
91 /// renaming and go-to reference.
92 pub value_references: ReferenceMap,
93 /// The locations of the references to each type in this module, used for
94 /// renaming and go-to reference.
95 pub type_references: ReferenceMap,
96
97 /// This map is used to access the nodes of modules that were not
98 /// aliased, given their name.
99 /// We need this to keep track of references made to imports by unqualified
100 /// values/types: when an unqualified item is used we want to add an edge
101 /// pointing to the import it comes from, so that if the item is used the
102 /// import won't be marked as unused:
103 ///
104 /// ```gleam
105 /// import wibble/wobble.{used}
106 ///
107 /// pub fn main() {
108 /// used
109 /// }
110 /// ```
111 ///
112 /// And each imported entity carries around the _name of the module_ and not
113 /// just the alias (here it would be `wibble/wobble` and not just `wobble`).
114 ///
115 module_name_to_node: HashMap<EcoString, NodeIndex>,
116}
117
118impl ReferenceTracker {
119 pub fn new() -> Self {
120 Self::default()
121 }
122
123 fn get_or_create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex {
124 let entity = Entity { name, layer };
125
126 match self.entities.get_by_left(&entity) {
127 Some(index) => *index,
128 None => {
129 let index = self.graph.add_node(());
130 _ = self.entities.insert(entity, index);
131 index
132 }
133 }
134 }
135
136 fn create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex {
137 let entity = Entity { name, layer };
138 let index = self.graph.add_node(());
139
140 self.create_node_and_maybe_shadow(entity, index);
141
142 index
143 }
144
145 fn create_node_and_maybe_shadow(&mut self, entity: Entity, index: NodeIndex) {
146 match self.entities.insert(entity, index) {
147 Overwritten::Neither => {}
148 Overwritten::Left(mut entity, index)
149 | Overwritten::Right(mut entity, index)
150 | Overwritten::Pair(mut entity, index)
151 | Overwritten::Both((mut entity, index), _) => {
152 // If an entity with the same name as this already exists,
153 // we still need to keep track of its usage! Thought it cannot
154 // be referenced anymore, it still might have been used before this
155 // point, or need to be marked as unused.
156 // To do this, we keep track of a "Shadowed" entity in `entity_information`.
157 if let Some(information) = self.entity_information.get(&entity) {
158 entity.layer = EntityLayer::Shadowed;
159 _ = self
160 .entity_information
161 .insert(entity.clone(), information.clone());
162 _ = self.entities.insert(entity, index);
163 }
164 }
165 }
166 }
167
168 /// This function exists because of a specific edge-case where constants
169 /// can shadow imported values. For example:
170 /// ```gleam
171 /// import math.{pi}
172 ///
173 /// pub const pi = pi
174 /// ```
175 /// Here, the new `pi` constant shadows the imported `pi` value, but it still
176 /// references it, so it should not be marked as unused.
177 /// In order for this to work, we must first set the `current_function` field
178 /// so that the `pi` value is referenced by the public `pi` constant.
179 /// However, we can't insert the `pi` constant into the name scope yet, since
180 /// then it would count as referencing itself. We first need to set `current_function`,
181 /// then once we have analysed the right-hand-side of the constant, we can
182 /// register it in the scope using `register_constant`.
183 ///
184 pub fn begin_constant(&mut self) {
185 self.current_node = self.graph.add_node(());
186 }
187
188 pub fn register_constant(&mut self, name: EcoString, location: SrcSpan, publicity: Publicity) {
189 let entity = Entity {
190 name,
191 layer: EntityLayer::Value,
192 };
193 self.create_node_and_maybe_shadow(entity.clone(), self.current_node);
194 match publicity {
195 Publicity::Public | Publicity::Internal { .. } => {
196 self.public_entities.push(entity.clone());
197 }
198 Publicity::Private => {}
199 }
200
201 _ = self.entity_information.insert(
202 entity,
203 EntityInformation {
204 kind: EntityKind::Constant,
205 origin: location,
206 },
207 );
208 }
209
210 pub fn register_value(
211 &mut self,
212 name: EcoString,
213 kind: EntityKind,
214 location: SrcSpan,
215 publicity: Publicity,
216 ) {
217 self.current_node = self.create_node(name.clone(), EntityLayer::Value);
218 self.register_module_reference_from_imported_entity(&kind);
219
220 let entity = Entity {
221 name,
222 layer: EntityLayer::Value,
223 };
224 match publicity {
225 Publicity::Public | Publicity::Internal { .. } => {
226 self.public_entities.push(entity.clone());
227 }
228 Publicity::Private => {}
229 }
230
231 _ = self.entity_information.insert(
232 entity,
233 EntityInformation {
234 kind,
235 origin: location,
236 },
237 );
238 }
239
240 pub fn set_current_node(&mut self, name: EcoString) {
241 self.current_node = self.get_or_create_node(name, EntityLayer::Value);
242 }
243
244 pub fn register_type(
245 &mut self,
246 name: EcoString,
247 kind: EntityKind,
248 location: SrcSpan,
249 publicity: Publicity,
250 ) {
251 self.current_node = self.create_node(name.clone(), EntityLayer::Type);
252 self.register_module_reference_from_imported_entity(&kind);
253
254 let entity = Entity {
255 name,
256 layer: EntityLayer::Type,
257 };
258 match publicity {
259 Publicity::Public | Publicity::Internal { .. } => {
260 self.public_entities.push(entity.clone());
261 }
262 Publicity::Private => {}
263 }
264
265 _ = self.entity_information.insert(
266 entity,
267 EntityInformation {
268 kind,
269 origin: location,
270 },
271 );
272 }
273
274 pub fn register_aliased_module(
275 &mut self,
276 used_name: EcoString,
277 module_name: EcoString,
278 alias_location: SrcSpan,
279 import_location: SrcSpan,
280 ) {
281 // We first record a node for the module being aliased. We use its entire
282 // name to identify it in this case and keep track of the node it's
283 // associated with.
284 self.register_module(module_name.clone(), module_name.clone(), import_location);
285
286 // Then we create a node for the alias, as the alias itself might be
287 // unused!
288 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module);
289 // Also we want to register the fact that if this alias is used then the
290 // import is used: so we add a reference from the alias to the import
291 // we've just added.
292 self.register_module_reference(module_name.clone());
293
294 // Finally we can add information for this alias:
295 let entity = Entity {
296 name: used_name,
297 layer: EntityLayer::Module,
298 };
299 _ = self.entity_information.insert(
300 entity,
301 EntityInformation {
302 kind: EntityKind::ModuleAlias {
303 module: module_name,
304 },
305 origin: alias_location,
306 },
307 );
308 }
309
310 pub fn register_module(
311 &mut self,
312 used_name: EcoString,
313 module_name: EcoString,
314 location: SrcSpan,
315 ) {
316 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module);
317 let _ = self
318 .module_name_to_node
319 .insert(module_name.clone(), self.current_node);
320
321 let entity = Entity {
322 name: used_name,
323 layer: EntityLayer::Module,
324 };
325
326 _ = self.entity_information.insert(
327 entity,
328 EntityInformation {
329 kind: EntityKind::ImportedModule { module_name },
330 origin: location,
331 },
332 );
333 }
334
335 fn register_module_reference_from_imported_entity(&mut self, entity_kind: &EntityKind) {
336 match entity_kind {
337 EntityKind::Function
338 | EntityKind::Constant
339 | EntityKind::Constructor
340 | EntityKind::Type
341 | EntityKind::ImportedModule { .. }
342 | EntityKind::ModuleAlias { .. } => (),
343
344 EntityKind::ImportedConstructor { module }
345 | EntityKind::ImportedType { module }
346 | EntityKind::ImportedValue { module } => {
347 self.register_module_reference(module.clone())
348 }
349 }
350 }
351
352 pub fn register_value_reference(
353 &mut self,
354 module: EcoString,
355 name: EcoString,
356 referenced_name: &EcoString,
357 location: SrcSpan,
358 kind: ReferenceKind,
359 ) {
360 match kind {
361 ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {}
362 ReferenceKind::Alias | ReferenceKind::Unqualified => {
363 let target = self.get_or_create_node(referenced_name.clone(), EntityLayer::Value);
364 _ = self.graph.add_edge(self.current_node, target, ());
365 }
366 }
367
368 self.value_references
369 .entry((module, name))
370 .or_default()
371 .push(Reference { location, kind });
372 }
373
374 pub fn register_type_reference(
375 &mut self,
376 module: EcoString,
377 name: EcoString,
378 referenced_name: &EcoString,
379 location: SrcSpan,
380 kind: ReferenceKind,
381 ) {
382 match kind {
383 ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {}
384 ReferenceKind::Alias | ReferenceKind::Unqualified => {
385 self.register_type_reference_in_call_graph(referenced_name.clone())
386 }
387 }
388
389 self.type_references
390 .entry((module, name))
391 .or_default()
392 .push(Reference { location, kind });
393 }
394
395 /// Like `register_type_reference`, but doesn't modify `self.type_references`.
396 /// This is used when we define a constructor for a custom type. The constructor
397 /// doesn't actually "reference" its type, but if the constructor is used, the
398 /// type should also be considered used. The best way to represent this relationship
399 /// is to make a connection between them in the call graph.
400 ///
401 pub fn register_type_reference_in_call_graph(&mut self, name: EcoString) {
402 let target = self.get_or_create_node(name, EntityLayer::Type);
403 _ = self.graph.add_edge(self.current_node, target, ());
404 }
405
406 pub fn register_module_reference(&mut self, name: EcoString) {
407 let target = match self.module_name_to_node.get(&name) {
408 Some(target) => *target,
409 None => self.get_or_create_node(name, EntityLayer::Module),
410 };
411 _ = self.graph.add_edge(self.current_node, target, ());
412 }
413
414 pub fn unused(&self) -> HashMap<Entity, EntityInformation> {
415 let mut unused_values = HashMap::with_capacity(self.entities.len());
416
417 for (entity, information) in self.entity_information.iter() {
418 _ = unused_values.insert(entity.clone(), information.clone());
419 }
420 for entity in self.public_entities.iter() {
421 if let Some(index) = self.entities.get_by_left(entity) {
422 self.mark_entity_as_used(&mut unused_values, entity, *index);
423 }
424 }
425
426 unused_values
427 }
428
429 fn mark_entity_as_used(
430 &self,
431 unused: &mut HashMap<Entity, EntityInformation>,
432 entity: &Entity,
433 index: NodeIndex,
434 ) {
435 if unused.remove(entity).is_some() {
436 for node in self.graph.neighbors_directed(index, Direction::Outgoing) {
437 if let Some(entity) = self.entities.get_by_right(&node) {
438 self.mark_entity_as_used(unused, entity, node);
439 }
440 }
441 }
442 }
443}