Fork of daniellemaywood.uk/gleam — Wasm codegen work
30 kB
843 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Gleam contributors
3
4use std::collections::{HashMap, HashSet};
5
6use crate::ast::{Publicity, SrcSpan};
7use bimap::{BiMap, Overwritten};
8use ecow::EcoString;
9use petgraph::{
10 Directed, Direction,
11 stable_graph::{NodeIndex, StableGraph},
12};
13
14/// Describes one of a number of situations where references can be generated.
15/// See each variant for an explanation.
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub enum ReferenceKind {
18 /// A type or value which is referenced using the qualified syntax, along with
19 /// some information about the qualifier which is used for tracking module name
20 /// references. For example:
21 /// ```gleam
22 /// import gleam/option.{Some}
23 ///
24 /// pub fn main() -> option.Option(Int) {
25 /// // ^^^^^^ `module_location` covers this
26 /// Some(1)
27 /// }
28 /// ```
29 ///
30 /// Here, `module_alias` is `option`, as that's what's being used as a
31 /// qualifier.
32 ///
33 ///
34 /// ```gleam
35 /// import gleam/int as integer
36 ///
37 /// pub fn main() {
38 /// integer.add(1, 2)
39 /// //^^^^^^^ `module_location` covers this
40 /// }
41 /// ```
42 ///
43 /// In this case, `module_alias` is `integer`, due to the aliased import.
44 ///
45 Qualified {
46 module_alias: EcoString,
47 module_location: SrcSpan,
48 },
49 /// A type or value is being referenced using unqualified syntax. This may
50 /// be due to being imported unqualified, or because it's from the same
51 /// module. For example:
52 ///
53 /// ```gleam
54 /// import gleam/option.{None}
55 ///
56 /// pub fn main() {
57 /// none()
58 /// //^^^^ Unqualified
59 /// }
60 ///
61 /// fn none() {
62 /// None
63 /// //^^^^ Unqualified
64 /// }
65 /// ```
66 ///
67 Unqualified,
68 /// A value or type is being referenced inside an unqualified import. For
69 /// example:
70 /// ```gleam
71 /// import gleam/option.{None}
72 /// // ^^^^ Import
73 /// import gleam/dynamic/decode.{type Dynamic}
74 /// // ^^^^^^^ Import
75 /// ```
76 /// The contained span is the location of `as ...` part, if it exists,
77 /// otherwise it will just be empty. For example:
78 /// ```gleam
79 /// import gleam/option.{None as Nothing}
80 /// // ^^^^^^^^^^^ Alias span
81 /// ```
82 Import(SrcSpan),
83 /// The original definition location of a type or value. This also counts as
84 /// a reference for renaming and "find references" purposes. For example:
85 ///
86 /// ```gleam
87 /// pub type Wibble {
88 /// // ^^^^^^ Definition
89 /// Wibble(Int)
90 /// //^^^^^^ Definition
91 /// }
92 ///
93 /// pub fn extract(w: Wibble) {
94 /// // ^^^^^^^ Definition
95 /// let Wibble(x) = w
96 /// x
97 /// }
98 /// ```
99 Definition,
100 /// A value or type is being referenced using unqualified syntax, with a
101 /// name other than its original definition. This can be due to importing it
102 /// using an alias, or due to referencing it through a type alias. For example:
103 ///
104 /// ```gleam
105 /// import gleam/option.{None as Nothing, type Option as Maybe}
106 ///
107 /// pub fn nothing() -> Maybe(_) {
108 /// // ^^^^^ Alias
109 /// Nothing
110 /// //^^^^^^^ Alias
111 /// }
112 /// ```
113 ///
114 Alias,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
118pub struct Reference {
119 pub location: SrcSpan,
120 pub kind: ReferenceKind,
121}
122
123/// A reference to a module name. This is similar to a `Reference`, which covers
124/// types and values, but it is separate because we care about slightly different
125/// pieces of information when, for example, renaming modules vs. renaming types
126/// or values.
127///
128#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
129pub enum ModuleNameReference {
130 /// The location of a module name in a `ModuleSelect`, when the module name
131 /// is not aliased. For example:
132 /// ```gleam
133 /// import gleam/option
134 ///
135 /// pub fn main() -> option.Option(_) {
136 /// // ^^^^^^ ModuleSelect
137 /// option.None
138 /// //^^^^^^ ModuleSelect
139 /// }
140 /// ```
141 ///
142 ModuleSelect(SrcSpan),
143 /// The location of a module name in a `ModuleSelect`, when the module name
144 /// *is* aliased. For example:
145 /// ```gleam
146 /// import gleam/option as maybe
147 ///
148 /// pub fn main() -> maybe.Option(_) {
149 /// // ^^^^^ AliasedModuleSelect
150 /// maybe.None
151 /// //^^^^^ AliasedModuleSelect
152 /// }
153 /// ```
154 ///
155 AliasedModuleSelect(SrcSpan),
156 /// The location of a module name in an `import` statement, when the module
157 /// name is not aliased. For example:
158 /// ```gleam
159 /// import gleam/option.{None, Some}
160 /// // ^^^^^^^^^^^^ Import ^ `import_end`
161 /// ```
162 ///
163 Import {
164 module_location: SrcSpan,
165 import_end: u32,
166 },
167 /// The location of a module name in an `import` statement, when the module
168 /// name *is* aliased. Also stores the location of the alias (including the
169 /// `as` keyword), and what the alias is. For example:
170 /// ```gleam
171 /// import gleam/option.{None, Some} as maybe
172 /// // ^^^^^^^^^^^^ `module_location`
173 /// // ^^^^^^^^ `alias_location`
174 /// ```
175 /// In this example, `alias` would be `maybe`.
176 ///
177 AliasedImport {
178 module_location: SrcSpan,
179 alias_location: SrcSpan,
180 alias: EcoString,
181 },
182}
183
184pub type ReferenceMap = HashMap<(EcoString, EcoString), Vec<Reference>>;
185
186/// A use of a record field label: a labelled argument in a record constructor
187/// call or pattern, a record update argument, or a `record.field` access.
188#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
189pub struct LabelReference {
190 /// The location of the label. For a shorthand (`label:`) this spans the
191 /// whole `label:`, mirroring how variable references record label
192 /// shorthands. For everything else it is just the label itself.
193 pub location: SrcSpan,
194 pub syntax: LabelSyntax,
195}
196
197/// The syntax used to write a record field label where it is used.
198#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199pub enum LabelSyntax {
200 /// The label is written out on its own, as in `Wibble(label: value)` or
201 /// `record.label`, so renaming it is a simple replacement.
202 Longhand,
203 /// The label shorthand syntax (`label:`), which stands for both the label
204 /// and a variable with the same name. Renaming the field then has to
205 /// expand it so the value keeps its name: `wibble:` becomes
206 /// `wobble: wibble`.
207 Shorthand,
208}
209
210/// The location at which a record field label is defined in one of the
211/// variants of a custom type.
212#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
213pub struct LabelDefinition {
214 pub location: SrcSpan,
215 /// The name of the variant this label is defined in.
216 pub variant: EcoString,
217}
218
219/// Identifies a record field label within a custom type, used to look up the
220/// references to that label.
221#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
222pub struct RecordLabel {
223 /// The module the type this label belongs to is defined in.
224 pub type_module: EcoString,
225 /// The name of the type this label belongs to.
226 pub type_name: EcoString,
227 pub label: EcoString,
228}
229
230#[derive(Debug, Clone)]
231pub struct EntityInformation {
232 pub origin: SrcSpan,
233 pub kind: EntityKind,
234}
235
236/// Information about an "Entity". This determines how we warn about an entity
237/// being unused.
238#[derive(Debug, Clone, Eq, PartialEq)]
239pub enum EntityKind {
240 Function,
241 Constant,
242 Constructor,
243 Type,
244 ImportedModule { module_name: EcoString },
245 ModuleAlias { module: EcoString },
246 ImportedConstructor { module: EcoString },
247 ImportedType { module: EcoString },
248 ImportedValue { module: EcoString },
249}
250
251/// Like `ast::Layer`, this type differentiates between different scopes. For example,
252/// there can be a `wibble` value, a `wibble` module and a `wibble` type in the same
253/// scope all at once!
254///
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256enum EntityLayer {
257 /// An entity which exists in the type layer: a custom type, type variable
258 /// or type alias.
259 Type,
260 /// An entity which exists in the value layer: a constant, function or
261 /// custom type variant constructor.
262 Value,
263 /// An entity which has been shadowed. This allows us to keep track of unused
264 /// imports even if they have been shadowed by another value in the current
265 /// module.
266 /// This extra variant is needed because we used `Entity` as a key in a hashmap,
267 /// and so a duplicate key would not be able to exist.
268 /// We also would not want to get this shadowed entity when performing a lookup
269 /// of a named entity; we only want it to register it as an entity in the
270 /// `unused` function.
271 Shadowed,
272 /// The name of an imported module. Modules are separate to values!
273 Module,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash)]
277pub struct Entity {
278 pub name: EcoString,
279 layer: EntityLayer,
280}
281
282#[derive(Debug, Default)]
283pub struct ReferenceTracker {
284 /// A call-graph which tracks which values are referenced by which other value,
285 /// used for dead code detection.
286 graph: StableGraph<(), (), Directed>,
287 entities: BiMap<Entity, NodeIndex>,
288 current_node: NodeIndex,
289 public_entities: HashSet<Entity>,
290 entity_information: HashMap<Entity, EntityInformation>,
291
292 /// The locations of the references to each value in this module, used for
293 /// renaming and go-to reference.
294 pub value_references: ReferenceMap,
295 /// The locations of the references to each type in this module, used for
296 /// renaming and go-to reference.
297 pub type_references: ReferenceMap,
298 /// The locations of the references to each imported module, used for
299 /// renaming and go-to reference.
300 pub module_references: HashMap<EcoString, Vec<ModuleNameReference>>,
301 /// The locations of the references to each record field label in this
302 /// module, used for renaming and go-to reference.
303 pub label_references: HashMap<RecordLabel, Vec<LabelReference>>,
304 /// The locations at which each record field label is defined, one per
305 /// variant that defines it, used for renaming and go-to definition.
306 pub label_definitions: HashMap<RecordLabel, Vec<LabelDefinition>>,
307
308 /// Maps a module's canonical name to the node of the import it was brought
309 /// in by. Every import is inserted here (aliased or not), keyed by its full
310 /// module name, _except_ imports with a discarded alias (`as _name`), which
311 /// have no reachable node and so are absent.
312 ///
313 /// We need this to keep track of references made to imports by unqualified
314 /// values/types: when an unqualified item is used we want to add an edge
315 /// pointing to the import it comes from, so that if the item is used the
316 /// import won't be marked as unused:
317 ///
318 /// ```gleam
319 /// import wibble/wobble.{used}
320 ///
321 /// pub fn main() {
322 /// used
323 /// }
324 /// ```
325 ///
326 /// And each imported entity carries around the _name of the module_ and not
327 /// just the alias (here it would be `wibble/wobble` and not just `wobble`),
328 /// so this map is keyed by that full name rather than the local name.
329 ///
330 module_name_to_node: HashMap<EcoString, NodeIndex>,
331}
332
333impl ReferenceTracker {
334 pub fn new() -> Self {
335 Self::default()
336 }
337
338 fn get_or_create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex {
339 let entity = Entity { name, layer };
340
341 match self.entities.get_by_left(&entity) {
342 Some(index) => *index,
343 None => {
344 let index = self.graph.add_node(());
345 _ = self.entities.insert(entity, index);
346 index
347 }
348 }
349 }
350
351 fn create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex {
352 let entity = Entity { name, layer };
353 let index = self.graph.add_node(());
354
355 self.create_node_and_maybe_shadow(entity, index);
356
357 index
358 }
359
360 fn create_node_and_maybe_shadow(&mut self, entity: Entity, index: NodeIndex) {
361 match self.entities.insert(entity, index) {
362 Overwritten::Neither => {}
363 Overwritten::Left(mut entity, index)
364 | Overwritten::Right(mut entity, index)
365 | Overwritten::Pair(mut entity, index)
366 | Overwritten::Both((mut entity, index), _) => {
367 // If an entity with the same name as this already exists,
368 // we still need to keep track of its usage! Thought it cannot
369 // be referenced anymore, it still might have been used before this
370 // point, or need to be marked as unused.
371 // To do this, we keep track of a "Shadowed" entity in `entity_information`.
372 if let Some(information) = self.entity_information.get(&entity) {
373 entity.layer = EntityLayer::Shadowed;
374 _ = self
375 .entity_information
376 .insert(entity.clone(), information.clone());
377 _ = self.entities.insert(entity, index);
378 }
379 }
380 }
381 }
382
383 /// Add a call-graph edge from the current node to the entity with the given
384 /// name and layer, creating its node if it doesn't exist yet. If the target
385 /// is used then the current node counts as used too.
386 ///
387 fn add_reference_edge(&mut self, name: EcoString, layer: EntityLayer) {
388 let target = self.get_or_create_node(name, layer);
389 _ = self.graph.add_edge(self.current_node, target, ());
390 }
391
392 /// This function exists because of a specific edge-case where constants
393 /// can shadow imported values. For example:
394 /// ```gleam
395 /// import math.{pi}
396 ///
397 /// pub const pi = pi
398 /// ```
399 /// Here, the new `pi` constant shadows the imported `pi` value, but it still
400 /// references it, so it should not be marked as unused.
401 /// In order for this to work, we must first set the `current_function` field
402 /// so that the `pi` value is referenced by the public `pi` constant.
403 /// However, we can't insert the `pi` constant into the name scope yet, since
404 /// then it would count as referencing itself. We first need to set `current_function`,
405 /// then once we have analysed the right-hand-side of the constant, we can
406 /// register it in the scope using `register_constant`.
407 ///
408 pub fn begin_constant(&mut self) {
409 self.current_node = self.graph.add_node(());
410 }
411
412 pub fn register_constant(&mut self, name: EcoString, location: SrcSpan, publicity: Publicity) {
413 let entity = Entity {
414 name,
415 layer: EntityLayer::Value,
416 };
417 self.create_node_and_maybe_shadow(entity.clone(), self.current_node);
418 match publicity {
419 Publicity::Public | Publicity::Internal { .. } => {
420 let _ = self.public_entities.insert(entity.clone());
421 }
422 Publicity::Private => {}
423 }
424
425 _ = self.entity_information.insert(
426 entity,
427 EntityInformation {
428 kind: EntityKind::Constant,
429 origin: location,
430 },
431 );
432 }
433
434 pub fn register_value(
435 &mut self,
436 name: EcoString,
437 kind: EntityKind,
438 location: SrcSpan,
439 publicity: Publicity,
440 ) {
441 self.current_node = self.create_node(name.clone(), EntityLayer::Value);
442 self.register_module_reference_from_imported_entity(&kind);
443
444 let entity = Entity {
445 name,
446 layer: EntityLayer::Value,
447 };
448 match publicity {
449 Publicity::Public | Publicity::Internal { .. } => {
450 let _ = self.public_entities.insert(entity.clone());
451 }
452 Publicity::Private => {}
453 }
454
455 _ = self.entity_information.insert(
456 entity,
457 EntityInformation {
458 kind,
459 origin: location,
460 },
461 );
462 }
463
464 pub fn set_current_node(&mut self, name: EcoString) {
465 self.current_node = self.get_or_create_node(name, EntityLayer::Value);
466 }
467
468 pub fn register_type(
469 &mut self,
470 name: EcoString,
471 kind: EntityKind,
472 location: SrcSpan,
473 publicity: Publicity,
474 ) {
475 self.current_node = self.create_node(name.clone(), EntityLayer::Type);
476 self.register_module_reference_from_imported_entity(&kind);
477
478 let entity = Entity {
479 name,
480 layer: EntityLayer::Type,
481 };
482 match publicity {
483 Publicity::Public | Publicity::Internal { .. } => {
484 let _ = self.public_entities.insert(entity.clone());
485 }
486 Publicity::Private => {}
487 }
488
489 _ = self.entity_information.insert(
490 entity,
491 EntityInformation {
492 kind,
493 origin: location,
494 },
495 );
496 }
497
498 pub fn register_aliased_module(
499 &mut self,
500 used_name: EcoString,
501 module_name: EcoString,
502 alias_location: SrcSpan,
503 import_location: SrcSpan,
504 module_location: SrcSpan,
505 ) {
506 // We first record a node for the module being aliased.
507 self.register_module(
508 used_name.clone(),
509 module_name.clone(),
510 import_location,
511 module_location,
512 Some(alias_location),
513 );
514
515 // Then we create a node for the alias, as the alias itself might be
516 // unused!
517 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module);
518 // Also we want to register the fact that if this alias is used then the
519 // import is used: so we add a reference from the alias to the import
520 // we've just added.
521 self.register_module_reference_by_module_name(module_name.clone());
522
523 // Finally we can add information for this alias:
524 let entity = Entity {
525 name: used_name,
526 layer: EntityLayer::Module,
527 };
528 _ = self.entity_information.insert(
529 entity,
530 EntityInformation {
531 kind: EntityKind::ModuleAlias {
532 module: module_name,
533 },
534 origin: alias_location,
535 },
536 );
537 }
538
539 pub fn register_module(
540 &mut self,
541 used_name: EcoString,
542 module_name: EcoString,
543 location: SrcSpan,
544 module_location: SrcSpan,
545 alias_location: Option<SrcSpan>,
546 ) {
547 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module);
548 let _ = self
549 .module_name_to_node
550 .insert(module_name.clone(), self.current_node);
551
552 let reference = if let Some(alias_location) = alias_location {
553 ModuleNameReference::AliasedImport {
554 module_location,
555 alias_location,
556 alias: used_name.clone(),
557 }
558 } else {
559 ModuleNameReference::Import {
560 module_location,
561 import_end: location.end,
562 }
563 };
564
565 self.register_module_name_reference(module_name.clone(), reference);
566
567 let entity = Entity {
568 name: used_name,
569 layer: EntityLayer::Module,
570 };
571
572 _ = self.entity_information.insert(
573 entity,
574 EntityInformation {
575 kind: EntityKind::ImportedModule { module_name },
576 origin: location,
577 },
578 );
579 }
580
581 fn register_module_reference_from_imported_entity(&mut self, entity_kind: &EntityKind) {
582 match entity_kind {
583 EntityKind::Function
584 | EntityKind::Constant
585 | EntityKind::Constructor
586 | EntityKind::Type
587 | EntityKind::ImportedModule { .. }
588 | EntityKind::ModuleAlias { .. } => (),
589
590 EntityKind::ImportedConstructor { module }
591 | EntityKind::ImportedType { module }
592 | EntityKind::ImportedValue { module } => {
593 self.register_module_reference_by_module_name(module.clone());
594 }
595 }
596 }
597
598 pub fn register_value_reference(
599 &mut self,
600 module: EcoString,
601 name: EcoString,
602 referenced_name: &EcoString,
603 location: SrcSpan,
604 kind: ReferenceKind,
605 ) {
606 match &kind {
607 ReferenceKind::Qualified {
608 module_alias,
609 module_location,
610 } => {
611 let last_module_segment = module.split('/').next_back().unwrap_or(&module);
612 let reference = if last_module_segment == module_alias {
613 ModuleNameReference::ModuleSelect(*module_location)
614 } else {
615 ModuleNameReference::AliasedModuleSelect(*module_location)
616 };
617 self.register_module_name_reference(module.clone(), reference);
618 }
619 ReferenceKind::Import(_) | ReferenceKind::Definition => {}
620 ReferenceKind::Alias | ReferenceKind::Unqualified => {
621 self.add_reference_edge(referenced_name.clone(), EntityLayer::Value);
622 }
623 }
624
625 self.value_references
626 .entry((module, name))
627 .or_default()
628 .push(Reference { location, kind });
629 }
630
631 pub fn register_type_reference(
632 &mut self,
633 module: EcoString,
634 name: EcoString,
635 referenced_name: &EcoString,
636 location: SrcSpan,
637 kind: ReferenceKind,
638 ) {
639 match &kind {
640 ReferenceKind::Qualified {
641 module_alias,
642 module_location,
643 } => {
644 let last_module_segment = module.split('/').next_back().unwrap_or(&module);
645 let reference = if last_module_segment == module_alias {
646 ModuleNameReference::ModuleSelect(*module_location)
647 } else {
648 ModuleNameReference::AliasedModuleSelect(*module_location)
649 };
650 self.register_module_name_reference(module.clone(), reference);
651 }
652 ReferenceKind::Import(_) | ReferenceKind::Definition => {}
653 ReferenceKind::Alias | ReferenceKind::Unqualified => {
654 self.register_type_reference_in_call_graph(referenced_name.clone());
655 }
656 }
657
658 self.type_references
659 .entry((module, name))
660 .or_default()
661 .push(Reference { location, kind });
662 }
663
664 /// Register a reference to a module name written explicitly in the source
665 /// code, when the module name or local alias appears. This is separate from
666 /// the call-graph edges added by `register_module_reference_by_alias`
667 /// and `register_module_reference_by_module_name`, which track references
668 /// created implicitly, for example when using unqualified imports.
669 ///
670 pub fn register_module_name_reference(
671 &mut self,
672 module: EcoString,
673 reference: ModuleNameReference,
674 ) {
675 self.module_references
676 .entry(module)
677 .or_default()
678 .push(reference);
679 }
680
681 /// Registers a use of a record field label.
682 ///
683 /// `type_` is the `(module, name)` of the type the label belongs to, as
684 /// returned by [`Type::named_type_name`].
685 pub fn register_label_reference(
686 &mut self,
687 type_: (EcoString, EcoString),
688 label: EcoString,
689 location: SrcSpan,
690 syntax: LabelSyntax,
691 ) {
692 let (type_module, type_name) = type_;
693 self.label_references
694 .entry(RecordLabel {
695 type_module,
696 type_name,
697 label,
698 })
699 .or_default()
700 .push(LabelReference { location, syntax });
701 }
702
703 /// Registers the definition of a record field label in one of the
704 /// variants of a custom type.
705 ///
706 /// `type_` is the `(module, name)` of the type being defined, as returned
707 /// by [`Type::named_type_name`].
708 pub fn register_label_definition(
709 &mut self,
710 type_: (EcoString, EcoString),
711 label: EcoString,
712 location: SrcSpan,
713 variant: EcoString,
714 ) {
715 let (type_module, type_name) = type_;
716 self.label_definitions
717 .entry(RecordLabel {
718 type_module,
719 type_name,
720 label,
721 })
722 .or_default()
723 .push(LabelDefinition { location, variant });
724 }
725
726 /// Like `register_type_reference`, but doesn't modify `self.type_references`.
727 /// This is used when we define a constructor for a custom type. The constructor
728 /// doesn't actually "reference" its type, but if the constructor is used, the
729 /// type should also be considered used. The best way to represent this relationship
730 /// is to make a connection between them in the call graph.
731 ///
732 pub fn register_type_reference_in_call_graph(&mut self, name: EcoString) {
733 self.add_reference_edge(name, EntityLayer::Type);
734 }
735
736 /// Add a call-graph edge to a module resolved by its local name: the
737 /// alias, or the last segment of the module path when there is no alias.
738 /// Use this for references that name the module as written at the use site,
739 /// such as a qualified `wobble.thing`.
740 ///
741 pub fn register_module_reference_by_alias(&mut self, name: EcoString) {
742 self.add_reference_edge(name, EntityLayer::Module);
743 }
744
745 /// Add a call-graph edge to an import resolved by its canonical module name
746 /// via `module_name_to_node`. Use this for references that carry the full
747 /// module name rather than the local alias, such as an unqualified item
748 /// that remembers which module it came from.
749 ///
750 fn register_module_reference_by_module_name(&mut self, name: EcoString) {
751 // If the module has no node then it was imported with a discarded
752 // alias, meaning there's no import for the reference to point at.
753 let Some(target) = self.module_name_to_node.get(&name).copied() else {
754 return;
755 };
756 _ = self.graph.add_edge(self.current_node, target, ());
757 }
758
759 pub fn unused(&self) -> HashMap<Entity, EntityInformation> {
760 let mut unused_values = HashMap::with_capacity(self.entities.len());
761
762 for (entity, information) in self.entity_information.iter() {
763 _ = unused_values.insert(entity.clone(), information.clone());
764 }
765 for entity in self.public_entities.iter() {
766 if let Some(index) = self.entities.get_by_left(entity) {
767 self.mark_entity_as_used(&mut unused_values, entity, *index);
768 }
769 }
770
771 for (entity, _) in self.entities.iter() {
772 let Some(index) = self.entities.get_by_left(entity) else {
773 continue;
774 };
775
776 if self.public_entities.contains(entity) {
777 self.mark_entity_as_used(&mut unused_values, entity, *index);
778 } else {
779 // If the entity is not public, we still want to mark referenced
780 // imports as used.
781 self.mark_referenced_imports_as_used(&mut unused_values, entity, *index);
782 }
783 }
784
785 unused_values
786 }
787
788 fn mark_entity_as_used(
789 &self,
790 unused: &mut HashMap<Entity, EntityInformation>,
791 entity: &Entity,
792 index: NodeIndex,
793 ) {
794 if unused.remove(entity).is_some() {
795 for node in self.graph.neighbors_directed(index, Direction::Outgoing) {
796 if let Some(entity) = self.entities.get_by_right(&node) {
797 self.mark_entity_as_used(unused, entity, node);
798 }
799 }
800 }
801 }
802
803 fn mark_referenced_imports_as_used(
804 &self,
805 unused: &mut HashMap<Entity, EntityInformation>,
806 entity: &Entity,
807 index: NodeIndex,
808 ) {
809 // If the entity is a module there's no way it can reference other
810 // modules so we just ignore it.
811 // This also means that module aliases do not count as using a module!
812 if entity.layer == EntityLayer::Module {
813 return;
814 }
815
816 for node in self.graph.neighbors_directed(index, Direction::Outgoing) {
817 // We only want to mark referenced modules as used, so if the node
818 // is not a module we just skip it.
819 let Some(
820 module @ Entity {
821 layer: EntityLayer::Module,
822 ..
823 },
824 ) = self.entities.get_by_right(&node)
825 else {
826 continue;
827 };
828
829 // If the value appears in the module import list, it doesn't count
830 // as using it!
831 let is_imported_type = self
832 .type_references
833 .contains_key(&(module.name.clone(), entity.name.clone()));
834 let is_imported_value = self
835 .value_references
836 .contains_key(&(module.name.clone(), entity.name.clone()));
837 let appears_in_module_import_list = is_imported_type || is_imported_value;
838 if !(appears_in_module_import_list) {
839 self.mark_entity_as_used(unused, module, node);
840 }
841 }
842 }
843}