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
25 kB 715 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 Import, 77 /// The original definition location of a type or value. This also counts as 78 /// a reference for renaming and "find references" purposes. For example: 79 /// 80 /// ```gleam 81 /// pub type Wibble { 82 /// // ^^^^^^ Definition 83 /// Wibble(Int) 84 /// //^^^^^^ Definition 85 /// } 86 /// 87 /// pub fn extract(w: Wibble) { 88 /// // ^^^^^^^ Definition 89 /// let Wibble(x) = w 90 /// x 91 /// } 92 /// ``` 93 Definition, 94 /// A value or type is being referenced using unqualified syntax, with a 95 /// name other than its original definition. This can be due to importing it 96 /// using an alias, or due to referencing it through a type alias. For example: 97 /// 98 /// ```gleam 99 /// import gleam/option.{None as Nothing, type Option as Maybe} 100 /// 101 /// pub fn nothing() -> Maybe(_) { 102 /// // ^^^^^ Alias 103 /// Nothing 104 /// //^^^^^^^ Alias 105 /// } 106 /// ``` 107 /// 108 Alias, 109} 110 111#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 112pub struct Reference { 113 pub location: SrcSpan, 114 pub kind: ReferenceKind, 115} 116 117/// A reference to a module name. This is similar to a `Reference`, which covers 118/// types and values, but it is separate because we care about slightly different 119/// pieces of information when, for example, renaming modules vs. renaming types 120/// or values. 121/// 122#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 123pub enum ModuleNameReference { 124 /// The location of a module name in a `ModuleSelect`, when the module name 125 /// is not aliased. For example: 126 /// ```gleam 127 /// import gleam/option 128 /// 129 /// pub fn main() -> option.Option(_) { 130 /// // ^^^^^^ ModuleSelect 131 /// option.None 132 /// //^^^^^^ ModuleSelect 133 /// } 134 /// ``` 135 /// 136 ModuleSelect(SrcSpan), 137 /// The location of a module name in a `ModuleSelect`, when the module name 138 /// *is* aliased. For example: 139 /// ```gleam 140 /// import gleam/option as maybe 141 /// 142 /// pub fn main() -> maybe.Option(_) { 143 /// // ^^^^^ AliasedModuleSelect 144 /// maybe.None 145 /// //^^^^^ AliasedModuleSelect 146 /// } 147 /// ``` 148 /// 149 AliasedModuleSelect(SrcSpan), 150 /// The location of a module name in an `import` statement, when the module 151 /// name is not aliased. For example: 152 /// ```gleam 153 /// import gleam/option.{None, Some} 154 /// // ^^^^^^^^^^^^ Import ^ `import_end` 155 /// ``` 156 /// 157 Import { 158 module_location: SrcSpan, 159 import_end: u32, 160 }, 161 /// The location of a module name in an `import` statement, when the module 162 /// name *is* aliased. Also stores the location of the alias (including the 163 /// `as` keyword), and what the alias is. For example: 164 /// ```gleam 165 /// import gleam/option.{None, Some} as maybe 166 /// // ^^^^^^^^^^^^ `module_location` 167 /// // ^^^^^^^^ `alias_location` 168 /// ``` 169 /// In this example, `alias` would be `maybe`. 170 /// 171 AliasedImport { 172 module_location: SrcSpan, 173 alias_location: SrcSpan, 174 alias: EcoString, 175 }, 176} 177 178pub type ReferenceMap = HashMap<(EcoString, EcoString), Vec<Reference>>; 179 180#[derive(Debug, Clone)] 181pub struct EntityInformation { 182 pub origin: SrcSpan, 183 pub kind: EntityKind, 184} 185 186/// Information about an "Entity". This determines how we warn about an entity 187/// being unused. 188#[derive(Debug, Clone, Eq, PartialEq)] 189pub enum EntityKind { 190 Function, 191 Constant, 192 Constructor, 193 Type, 194 ImportedModule { module_name: EcoString }, 195 ModuleAlias { module: EcoString }, 196 ImportedConstructor { module: EcoString }, 197 ImportedType { module: EcoString }, 198 ImportedValue { module: EcoString }, 199} 200 201/// Like `ast::Layer`, this type differentiates between different scopes. For example, 202/// there can be a `wibble` value, a `wibble` module and a `wibble` type in the same 203/// scope all at once! 204/// 205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 206enum EntityLayer { 207 /// An entity which exists in the type layer: a custom type, type variable 208 /// or type alias. 209 Type, 210 /// An entity which exists in the value layer: a constant, function or 211 /// custom type variant constructor. 212 Value, 213 /// An entity which has been shadowed. This allows us to keep track of unused 214 /// imports even if they have been shadowed by another value in the current 215 /// module. 216 /// This extra variant is needed because we used `Entity` as a key in a hashmap, 217 /// and so a duplicate key would not be able to exist. 218 /// We also would not want to get this shadowed entity when performing a lookup 219 /// of a named entity; we only want it to register it as an entity in the 220 /// `unused` function. 221 Shadowed, 222 /// The name of an imported module. Modules are separate to values! 223 Module, 224} 225 226#[derive(Debug, Clone, PartialEq, Eq, Hash)] 227pub struct Entity { 228 pub name: EcoString, 229 layer: EntityLayer, 230} 231 232#[derive(Debug, Default)] 233pub struct ReferenceTracker { 234 /// A call-graph which tracks which values are referenced by which other value, 235 /// used for dead code detection. 236 graph: StableGraph<(), (), Directed>, 237 entities: BiMap<Entity, NodeIndex>, 238 current_node: NodeIndex, 239 public_entities: HashSet<Entity>, 240 entity_information: HashMap<Entity, EntityInformation>, 241 242 /// The locations of the references to each value in this module, used for 243 /// renaming and go-to reference. 244 pub value_references: ReferenceMap, 245 /// The locations of the references to each type in this module, used for 246 /// renaming and go-to reference. 247 pub type_references: ReferenceMap, 248 /// The locations of the references to each imported module, used for 249 /// renaming and go-to reference. 250 pub module_references: HashMap<EcoString, Vec<ModuleNameReference>>, 251 252 /// This map is used to access the nodes of modules that were not 253 /// aliased, given their name. 254 /// We need this to keep track of references made to imports by unqualified 255 /// values/types: when an unqualified item is used we want to add an edge 256 /// pointing to the import it comes from, so that if the item is used the 257 /// import won't be marked as unused: 258 /// 259 /// ```gleam 260 /// import wibble/wobble.{used} 261 /// 262 /// pub fn main() { 263 /// used 264 /// } 265 /// ``` 266 /// 267 /// And each imported entity carries around the _name of the module_ and not 268 /// just the alias (here it would be `wibble/wobble` and not just `wobble`). 269 /// 270 module_name_to_node: HashMap<EcoString, NodeIndex>, 271} 272 273impl ReferenceTracker { 274 pub fn new() -> Self { 275 Self::default() 276 } 277 278 fn get_or_create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex { 279 let entity = Entity { name, layer }; 280 281 match self.entities.get_by_left(&entity) { 282 Some(index) => *index, 283 None => { 284 let index = self.graph.add_node(()); 285 _ = self.entities.insert(entity, index); 286 index 287 } 288 } 289 } 290 291 fn create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex { 292 let entity = Entity { name, layer }; 293 let index = self.graph.add_node(()); 294 295 self.create_node_and_maybe_shadow(entity, index); 296 297 index 298 } 299 300 fn create_node_and_maybe_shadow(&mut self, entity: Entity, index: NodeIndex) { 301 match self.entities.insert(entity, index) { 302 Overwritten::Neither => {} 303 Overwritten::Left(mut entity, index) 304 | Overwritten::Right(mut entity, index) 305 | Overwritten::Pair(mut entity, index) 306 | Overwritten::Both((mut entity, index), _) => { 307 // If an entity with the same name as this already exists, 308 // we still need to keep track of its usage! Thought it cannot 309 // be referenced anymore, it still might have been used before this 310 // point, or need to be marked as unused. 311 // To do this, we keep track of a "Shadowed" entity in `entity_information`. 312 if let Some(information) = self.entity_information.get(&entity) { 313 entity.layer = EntityLayer::Shadowed; 314 _ = self 315 .entity_information 316 .insert(entity.clone(), information.clone()); 317 _ = self.entities.insert(entity, index); 318 } 319 } 320 } 321 } 322 323 /// This function exists because of a specific edge-case where constants 324 /// can shadow imported values. For example: 325 /// ```gleam 326 /// import math.{pi} 327 /// 328 /// pub const pi = pi 329 /// ``` 330 /// Here, the new `pi` constant shadows the imported `pi` value, but it still 331 /// references it, so it should not be marked as unused. 332 /// In order for this to work, we must first set the `current_function` field 333 /// so that the `pi` value is referenced by the public `pi` constant. 334 /// However, we can't insert the `pi` constant into the name scope yet, since 335 /// then it would count as referencing itself. We first need to set `current_function`, 336 /// then once we have analysed the right-hand-side of the constant, we can 337 /// register it in the scope using `register_constant`. 338 /// 339 pub fn begin_constant(&mut self) { 340 self.current_node = self.graph.add_node(()); 341 } 342 343 pub fn register_constant(&mut self, name: EcoString, location: SrcSpan, publicity: Publicity) { 344 let entity = Entity { 345 name, 346 layer: EntityLayer::Value, 347 }; 348 self.create_node_and_maybe_shadow(entity.clone(), self.current_node); 349 match publicity { 350 Publicity::Public | Publicity::Internal { .. } => { 351 let _ = self.public_entities.insert(entity.clone()); 352 } 353 Publicity::Private => {} 354 } 355 356 _ = self.entity_information.insert( 357 entity, 358 EntityInformation { 359 kind: EntityKind::Constant, 360 origin: location, 361 }, 362 ); 363 } 364 365 pub fn register_value( 366 &mut self, 367 name: EcoString, 368 kind: EntityKind, 369 location: SrcSpan, 370 publicity: Publicity, 371 ) { 372 self.current_node = self.create_node(name.clone(), EntityLayer::Value); 373 self.register_module_reference_from_imported_entity(&kind); 374 375 let entity = Entity { 376 name, 377 layer: EntityLayer::Value, 378 }; 379 match publicity { 380 Publicity::Public | Publicity::Internal { .. } => { 381 let _ = self.public_entities.insert(entity.clone()); 382 } 383 Publicity::Private => {} 384 } 385 386 _ = self.entity_information.insert( 387 entity, 388 EntityInformation { 389 kind, 390 origin: location, 391 }, 392 ); 393 } 394 395 pub fn set_current_node(&mut self, name: EcoString) { 396 self.current_node = self.get_or_create_node(name, EntityLayer::Value); 397 } 398 399 pub fn register_type( 400 &mut self, 401 name: EcoString, 402 kind: EntityKind, 403 location: SrcSpan, 404 publicity: Publicity, 405 ) { 406 self.current_node = self.create_node(name.clone(), EntityLayer::Type); 407 self.register_module_reference_from_imported_entity(&kind); 408 409 let entity = Entity { 410 name, 411 layer: EntityLayer::Type, 412 }; 413 match publicity { 414 Publicity::Public | Publicity::Internal { .. } => { 415 let _ = self.public_entities.insert(entity.clone()); 416 } 417 Publicity::Private => {} 418 } 419 420 _ = self.entity_information.insert( 421 entity, 422 EntityInformation { 423 kind, 424 origin: location, 425 }, 426 ); 427 } 428 429 pub fn register_aliased_module( 430 &mut self, 431 used_name: EcoString, 432 module_name: EcoString, 433 alias_location: SrcSpan, 434 import_location: SrcSpan, 435 module_location: SrcSpan, 436 ) { 437 // We first record a node for the module being aliased. 438 self.register_module( 439 used_name.clone(), 440 module_name.clone(), 441 import_location, 442 module_location, 443 Some(alias_location), 444 ); 445 446 // Then we create a node for the alias, as the alias itself might be 447 // unused! 448 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module); 449 // Also we want to register the fact that if this alias is used then the 450 // import is used: so we add a reference from the alias to the import 451 // we've just added. 452 self.register_module_reference(module_name.clone()); 453 454 // Finally we can add information for this alias: 455 let entity = Entity { 456 name: used_name, 457 layer: EntityLayer::Module, 458 }; 459 _ = self.entity_information.insert( 460 entity, 461 EntityInformation { 462 kind: EntityKind::ModuleAlias { 463 module: module_name, 464 }, 465 origin: alias_location, 466 }, 467 ); 468 } 469 470 pub fn register_module( 471 &mut self, 472 used_name: EcoString, 473 module_name: EcoString, 474 location: SrcSpan, 475 module_location: SrcSpan, 476 alias_location: Option<SrcSpan>, 477 ) { 478 self.current_node = self.create_node(used_name.clone(), EntityLayer::Module); 479 let _ = self 480 .module_name_to_node 481 .insert(module_name.clone(), self.current_node); 482 483 let reference = if let Some(alias_location) = alias_location { 484 ModuleNameReference::AliasedImport { 485 module_location, 486 alias_location, 487 alias: used_name.clone(), 488 } 489 } else { 490 ModuleNameReference::Import { 491 module_location, 492 import_end: location.end, 493 } 494 }; 495 496 self.register_module_name_reference(module_name.clone(), reference); 497 498 let entity = Entity { 499 name: used_name, 500 layer: EntityLayer::Module, 501 }; 502 503 _ = self.entity_information.insert( 504 entity, 505 EntityInformation { 506 kind: EntityKind::ImportedModule { module_name }, 507 origin: location, 508 }, 509 ); 510 } 511 512 fn register_module_reference_from_imported_entity(&mut self, entity_kind: &EntityKind) { 513 match entity_kind { 514 EntityKind::Function 515 | EntityKind::Constant 516 | EntityKind::Constructor 517 | EntityKind::Type 518 | EntityKind::ImportedModule { .. } 519 | EntityKind::ModuleAlias { .. } => (), 520 521 EntityKind::ImportedConstructor { module } 522 | EntityKind::ImportedType { module } 523 | EntityKind::ImportedValue { module } => { 524 self.register_module_reference(module.clone()) 525 } 526 } 527 } 528 529 pub fn register_value_reference( 530 &mut self, 531 module: EcoString, 532 name: EcoString, 533 referenced_name: &EcoString, 534 location: SrcSpan, 535 kind: ReferenceKind, 536 ) { 537 match &kind { 538 ReferenceKind::Qualified { 539 module_alias, 540 module_location, 541 } => { 542 let last_module_segment = module.split('/').next_back().unwrap_or(&module); 543 let reference = if last_module_segment == module_alias { 544 ModuleNameReference::ModuleSelect(*module_location) 545 } else { 546 ModuleNameReference::AliasedModuleSelect(*module_location) 547 }; 548 self.register_module_name_reference(module.clone(), reference); 549 } 550 ReferenceKind::Import | ReferenceKind::Definition => {} 551 ReferenceKind::Alias | ReferenceKind::Unqualified => { 552 let target = self.get_or_create_node(referenced_name.clone(), EntityLayer::Value); 553 _ = self.graph.add_edge(self.current_node, target, ()); 554 } 555 } 556 557 self.value_references 558 .entry((module, name)) 559 .or_default() 560 .push(Reference { location, kind }); 561 } 562 563 pub fn register_type_reference( 564 &mut self, 565 module: EcoString, 566 name: EcoString, 567 referenced_name: &EcoString, 568 location: SrcSpan, 569 kind: ReferenceKind, 570 ) { 571 match &kind { 572 ReferenceKind::Qualified { 573 module_alias, 574 module_location, 575 } => { 576 let last_module_segment = module.split('/').next_back().unwrap_or(&module); 577 let reference = if last_module_segment == module_alias { 578 ModuleNameReference::ModuleSelect(*module_location) 579 } else { 580 ModuleNameReference::AliasedModuleSelect(*module_location) 581 }; 582 self.register_module_name_reference(module.clone(), reference); 583 } 584 ReferenceKind::Import | ReferenceKind::Definition => {} 585 ReferenceKind::Alias | ReferenceKind::Unqualified => { 586 self.register_type_reference_in_call_graph(referenced_name.clone()) 587 } 588 } 589 590 self.type_references 591 .entry((module, name)) 592 .or_default() 593 .push(Reference { location, kind }); 594 } 595 596 /// Register a reference to a module in the code. This is separate to 597 /// `register_module_reference`, as references to modules can be created 598 /// implicitly, for example when using unqualified imports. This only register 599 /// explicit references in the source code, when the module name or local 600 /// alias is written. 601 pub fn register_module_name_reference( 602 &mut self, 603 module: EcoString, 604 reference: ModuleNameReference, 605 ) { 606 self.module_references 607 .entry(module) 608 .or_default() 609 .push(reference); 610 } 611 612 /// Like `register_type_reference`, but doesn't modify `self.type_references`. 613 /// This is used when we define a constructor for a custom type. The constructor 614 /// doesn't actually "reference" its type, but if the constructor is used, the 615 /// type should also be considered used. The best way to represent this relationship 616 /// is to make a connection between them in the call graph. 617 /// 618 pub fn register_type_reference_in_call_graph(&mut self, name: EcoString) { 619 let target = self.get_or_create_node(name, EntityLayer::Type); 620 _ = self.graph.add_edge(self.current_node, target, ()); 621 } 622 623 pub fn register_module_reference(&mut self, name: EcoString) { 624 let target = match self.module_name_to_node.get(&name) { 625 Some(target) => *target, 626 None => self.get_or_create_node(name, EntityLayer::Module), 627 }; 628 _ = self.graph.add_edge(self.current_node, target, ()); 629 } 630 631 pub fn unused(&self) -> HashMap<Entity, EntityInformation> { 632 let mut unused_values = HashMap::with_capacity(self.entities.len()); 633 634 for (entity, information) in self.entity_information.iter() { 635 _ = unused_values.insert(entity.clone(), information.clone()); 636 } 637 for entity in self.public_entities.iter() { 638 if let Some(index) = self.entities.get_by_left(entity) { 639 self.mark_entity_as_used(&mut unused_values, entity, *index); 640 } 641 } 642 643 for (entity, _) in self.entities.iter() { 644 let Some(index) = self.entities.get_by_left(entity) else { 645 continue; 646 }; 647 648 if self.public_entities.contains(entity) { 649 self.mark_entity_as_used(&mut unused_values, entity, *index); 650 } else { 651 // If the entity is not public, we still want to mark referenced 652 // imports as used. 653 self.mark_referenced_imports_as_used(&mut unused_values, entity, *index); 654 } 655 } 656 657 unused_values 658 } 659 660 fn mark_entity_as_used( 661 &self, 662 unused: &mut HashMap<Entity, EntityInformation>, 663 entity: &Entity, 664 index: NodeIndex, 665 ) { 666 if unused.remove(entity).is_some() { 667 for node in self.graph.neighbors_directed(index, Direction::Outgoing) { 668 if let Some(entity) = self.entities.get_by_right(&node) { 669 self.mark_entity_as_used(unused, entity, node); 670 } 671 } 672 } 673 } 674 675 fn mark_referenced_imports_as_used( 676 &self, 677 unused: &mut HashMap<Entity, EntityInformation>, 678 entity: &Entity, 679 index: NodeIndex, 680 ) { 681 // If the entity is a module there's no way it can reference other 682 // modules so we just ignore it. 683 // This also means that module aliases do not count as using a module! 684 if entity.layer == EntityLayer::Module { 685 return; 686 } 687 688 for node in self.graph.neighbors_directed(index, Direction::Outgoing) { 689 // We only want to mark referenced modules as used, so if the node 690 // is not a module we just skip it. 691 let Some( 692 module @ Entity { 693 layer: EntityLayer::Module, 694 .. 695 }, 696 ) = self.entities.get_by_right(&node) 697 else { 698 continue; 699 }; 700 701 // If the value appears in the module import list, it doesn't count 702 // as using it! 703 let is_imported_type = self 704 .type_references 705 .contains_key(&(module.name.clone(), entity.name.clone())); 706 let is_imported_value = self 707 .value_references 708 .contains_key(&(module.name.clone(), entity.name.clone())); 709 let appears_in_module_import_list = is_imported_type || is_imported_value; 710 if !(appears_in_module_import_list) { 711 self.mark_entity_as_used(unused, module, node); 712 } 713 } 714 } 715}