Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / reference.rs
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}