summaryrefslogtreecommitdiffstats
path: root/third_party/rust/jsparagus-scope/src/free_name_tracker.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/jsparagus-scope/src/free_name_tracker.rs
parentInitial commit. (diff)
downloadfirefox-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.rs129
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);
+ }
+ }
+}