//! Code to track free names in scopes, to compute which bindings are closed //! over. //! //! A binding is closed-over if it is defined in a scope in one `JSScript` and //! can be used in another `JSScript` (a nested function or direct eval). //! //! In our single pass over the AST, we compute which bindings are closed-over //! using a `FreeNameTracker` for each scope. use ast::source_atom_set::SourceAtomSetIndex; use std::collections::hash_map::RandomState; use std::collections::hash_set::{Difference, Intersection}; use std::collections::HashSet; /// Tracks free names in a scope. /// /// Usage: /// /// * Create this when entering a scope. /// /// * Call `.note_use(name)` when a name is used and `.note_def(name)` when /// one is defined. /// /// * Call `.propagate_from_inner_script(inner)` or /// `.propagate_from_inner_non_script(inner)` when leaving an inner /// scope. These methods do the real work of marking bindings as /// closed-over. /// /// The gist of the algorithm is tracking name *uses* and propagating them /// outward to the scope where the name is *defined*. When we propagate a use /// out of a function, we put it in `closed_over_names`; when that use is /// matched with a binding, the binding is closed-over. /// /// **Why we do all the work when exiting a scope** - It would save a lot of /// work if we could mark a def as closed-over as soon as we see a matching /// use. We could deal with each use as we encounter it. We wouldn't have to /// track them. The problem is hoisting. Bindings can be used anywhere in a /// scope, even before their definition. We can't mark a def that we haven't /// seen yet. The end of scope is the first point where we have definitely seen /// all uses and defs in that scope. /// #[derive(Debug)] pub struct FreeNameTracker { /// Names closed over in inner script-scopes, that's not yet /// defined up to (excluding) the current scope. /// The name might be defined in the current scope. /// /// The name defined in this or outer scope is marked "closed over", and /// no more propagated to outer scope. closed_over_names: HashSet, /// Names used in the current scope, and nested non-script scopes. /// The name might be defined in the current scope. used_names: HashSet, /// Names defined in the current scope. def_names: HashSet, } impl FreeNameTracker { pub fn new() -> Self { Self { closed_over_names: HashSet::new(), used_names: HashSet::new(), def_names: HashSet::new(), } } fn note_closed_over(&mut self, atom: SourceAtomSetIndex) { self.closed_over_names.insert(atom); } /// Note that the given name is used in this scope. pub fn note_use(&mut self, atom: SourceAtomSetIndex) { self.used_names.insert(atom); } pub fn note_def(&mut self, atom: SourceAtomSetIndex) { self.def_names.insert(atom); } pub fn is_used_or_closed_over(&self, atom: SourceAtomSetIndex) -> bool { self.used_names.contains(&atom) || self.closed_over_names.contains(&atom) } /// Names closed over in inner script-scopes, that's not yet defined. pub fn closed_over_freevars(&self) -> Difference<'_, SourceAtomSetIndex, RandomState> { self.closed_over_names.difference(&self.def_names) } /// Names defined in this scope and closed over by inner script-scopes. pub fn defined_and_closed_over_vars( &self, ) -> Intersection<'_, SourceAtomSetIndex, RandomState> { self.def_names.intersection(&self.closed_over_names) } /// Names used in this and inner non-script scopes, that's not yet defined. pub fn used_freevars(&self) -> Difference<'_, SourceAtomSetIndex, RandomState> { self.used_names.difference(&self.def_names) } pub fn is_closed_over_def(&self, atom: &SourceAtomSetIndex) -> bool { return self.def_names.contains(atom) && self.closed_over_names.contains(atom); } /// Propagate free closed over names and used names from the inner /// non-script scope. pub fn propagate_from_inner_non_script(&mut self, inner: &FreeNameTracker) { for atom in inner.closed_over_freevars() { self.note_closed_over(*atom); } for atom in inner.used_freevars() { self.note_use(*atom); } } #[allow(dead_code)] /// Propagate free closed over names and used names from the inner /// script scope, converting everything into free closed over names pub fn propagate_from_inner_script(&mut self, inner: &FreeNameTracker) { for atom in inner.closed_over_freevars() { self.note_closed_over(*atom); } for atom in inner.used_freevars() { self.note_closed_over(*atom); } } }