⭐️ A friendly language for building type-safe, scalable systems!
at main 443 lines 15 kB view raw
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}