diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/jsparagus-scope/src/free_name_tracker.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/jsparagus-scope/src/free_name_tracker.rs')
-rw-r--r-- | third_party/rust/jsparagus-scope/src/free_name_tracker.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus-scope/src/free_name_tracker.rs b/third_party/rust/jsparagus-scope/src/free_name_tracker.rs new file mode 100644 index 0000000000..e376e5a955 --- /dev/null +++ b/third_party/rust/jsparagus-scope/src/free_name_tracker.rs @@ -0,0 +1,129 @@ +//! 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<SourceAtomSetIndex>, + + /// Names used in the current scope, and nested non-script scopes. + /// The name might be defined in the current scope. + used_names: HashSet<SourceAtomSetIndex>, + + /// Names defined in the current scope. + def_names: HashSet<SourceAtomSetIndex>, +} + +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); + } + } +} |