|
1 | 1 | use crate::HashMap; |
| 2 | +use crate::HashSet; |
2 | 3 | use crate::SVec; |
3 | 4 | use crate::namespace::{DefineContext, Namespace}; |
4 | 5 | use crate::symbol::{SymbolId, SymbolKind}; |
@@ -52,6 +53,14 @@ pub fn scope_kind_of(kind: &SymbolKind) -> Option<ScopeKind> { |
52 | 53 | } |
53 | 54 | } |
54 | 55 |
|
| 56 | +/// A symbol whose scope-tree bindings must go away with it. |
| 57 | +pub struct DroppedSymbol { |
| 58 | + /// Scope the symbol was inserted into (`Symbol::scope`). |
| 59 | + pub scope: ScopeId, |
| 60 | + pub name: StrId, |
| 61 | + pub id: SymbolId, |
| 62 | +} |
| 63 | + |
55 | 64 | /// An explicit `import pkg::name` binding local to a scope. |
56 | 65 | #[derive(Debug, Clone)] |
57 | 66 | pub struct ImportBinding { |
@@ -254,6 +263,47 @@ impl ScopeArena { |
254 | 263 | .push(symbol); |
255 | 264 | } |
256 | 265 |
|
| 266 | + /// Removes the tree's own copies of dropped `SymbolId`s. Without this a |
| 267 | + /// lookup dereferences an id the symbol table no longer holds. |
| 268 | + fn drop_symbols(&mut self, symbols: &[DroppedSymbol]) { |
| 269 | + if symbols.is_empty() { |
| 270 | + return; |
| 271 | + } |
| 272 | + |
| 273 | + for symbol in symbols { |
| 274 | + let name = resource_table::canonical_str_id(symbol.name); |
| 275 | + |
| 276 | + if let Some(scope) = self.scopes.get_mut(symbol.scope.0 as usize) |
| 277 | + && let Some(ids) = scope.locals.get_mut(&name) |
| 278 | + { |
| 279 | + ids.retain(|x| *x != symbol.id); |
| 280 | + if ids.is_empty() { |
| 281 | + scope.locals.remove(&name); |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + // The inner scope a symbol opens is the child named after it. The |
| 286 | + // owner check matters for ifdef-exclusive declarations, which share |
| 287 | + // that child. |
| 288 | + if let Some(&owned) = self.intern.get(&(symbol.scope.0, name)) |
| 289 | + && let Some(scope) = self.scopes.get_mut(owned as usize) |
| 290 | + && scope.owner == Some(symbol.id) |
| 291 | + { |
| 292 | + scope.owner = None; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + // An import binds the id in the importing file's scope, so there is no |
| 297 | + // reverse route and this has to scan. `apply_import` re-adds them. |
| 298 | + let dropped: HashSet<SymbolId> = symbols.iter().map(|x| x.id).collect(); |
| 299 | + for scope in &mut self.scopes { |
| 300 | + scope.imports.retain(|_, bindings| { |
| 301 | + bindings.retain(|x| !dropped.contains(&x.symbol)); |
| 302 | + !bindings.is_empty() |
| 303 | + }); |
| 304 | + } |
| 305 | + } |
| 306 | + |
257 | 307 | fn add_import( |
258 | 308 | &mut self, |
259 | 309 | scope: ScopeId, |
@@ -587,6 +637,10 @@ pub fn add_local(scope: ScopeId, name: StrId, symbol: SymbolId) { |
587 | 637 | SCOPE_ARENA.with(|f| f.borrow_mut().add_local(scope, name, symbol)) |
588 | 638 | } |
589 | 639 |
|
| 640 | +pub fn drop_symbols(symbols: &[DroppedSymbol]) { |
| 641 | + SCOPE_ARENA.with(|f| f.borrow_mut().drop_symbols(symbols)) |
| 642 | +} |
| 643 | + |
590 | 644 | pub fn set_kind_owner(scope: ScopeId, kind: ScopeKind, owner: SymbolId) { |
591 | 645 | SCOPE_ARENA.with(|f| f.borrow_mut().set_kind_owner(scope, kind, owner)) |
592 | 646 | } |
@@ -841,6 +895,25 @@ pub fn wildcards_get(scope: ScopeId) -> SVec<WildcardImport> { |
841 | 895 | }) |
842 | 896 | } |
843 | 897 |
|
| 898 | +/// Every symbol the tree binds, for asserting a drop left no dangling id. |
| 899 | +#[cfg(test)] |
| 900 | +pub(crate) fn bound_symbols() -> Vec<SymbolId> { |
| 901 | + SCOPE_ARENA.with(|f| { |
| 902 | + f.borrow() |
| 903 | + .scopes |
| 904 | + .iter() |
| 905 | + .flat_map(|s| { |
| 906 | + s.locals |
| 907 | + .values() |
| 908 | + .flatten() |
| 909 | + .chain(s.imports.values().flatten().map(|x| &x.symbol)) |
| 910 | + .chain(s.owner.iter()) |
| 911 | + .copied() |
| 912 | + }) |
| 913 | + .collect() |
| 914 | + }) |
| 915 | +} |
| 916 | + |
844 | 917 | pub fn mixin_get(scope: ScopeId) -> SVec<Mixin> { |
845 | 918 | SCOPE_ARENA.with(|f| { |
846 | 919 | f.borrow() |
@@ -895,6 +968,46 @@ mod tests { |
895 | 968 | assert_eq!(owner_of(a), Some(SymbolId(42))); |
896 | 969 | } |
897 | 970 |
|
| 971 | + #[test] |
| 972 | + fn drop_symbols_removes_locals_and_owner() { |
| 973 | + clear(); |
| 974 | + let prj = intern_child(ScopeId(0), name("prj"), ScopeKind::Project); |
| 975 | + let owned = intern_child(prj, name("Pkg"), ScopeKind::Package); |
| 976 | + set_kind_owner(owned, ScopeKind::Package, SymbolId(1)); |
| 977 | + add_local(prj, name("Pkg"), SymbolId(1)); |
| 978 | + add_local(prj, name("Mod"), SymbolId(2)); |
| 979 | + |
| 980 | + drop_symbols(&[DroppedSymbol { |
| 981 | + scope: prj, |
| 982 | + name: name("Pkg"), |
| 983 | + id: SymbolId(1), |
| 984 | + }]); |
| 985 | + |
| 986 | + assert!(locals_get(prj, name("Pkg")).is_empty()); |
| 987 | + assert_eq!(owner_of(owned), None); |
| 988 | + assert_eq!(locals_get(prj, name("Mod")).as_slice(), [SymbolId(2)]); |
| 989 | + } |
| 990 | + |
| 991 | + #[test] |
| 992 | + fn drop_symbols_keeps_an_owner_claimed_by_another_symbol() { |
| 993 | + clear(); |
| 994 | + let prj = intern_child(ScopeId(0), name("prj"), ScopeKind::Project); |
| 995 | + let owned = intern_child(prj, name("Pkg"), ScopeKind::Package); |
| 996 | + // Two ifdef-exclusive declarations share the scope they open. |
| 997 | + set_kind_owner(owned, ScopeKind::Package, SymbolId(2)); |
| 998 | + add_local(prj, name("Pkg"), SymbolId(1)); |
| 999 | + add_local(prj, name("Pkg"), SymbolId(2)); |
| 1000 | + |
| 1001 | + drop_symbols(&[DroppedSymbol { |
| 1002 | + scope: prj, |
| 1003 | + name: name("Pkg"), |
| 1004 | + id: SymbolId(1), |
| 1005 | + }]); |
| 1006 | + |
| 1007 | + assert_eq!(locals_get(prj, name("Pkg")).as_slice(), [SymbolId(2)]); |
| 1008 | + assert_eq!(owner_of(owned), Some(SymbolId(2))); |
| 1009 | + } |
| 1010 | + |
898 | 1011 | #[test] |
899 | 1012 | fn intern_namespace_builds_and_dedups_the_chain() { |
900 | 1013 | clear(); |
|
0 commit comments