boa/environment/
declarative_environment_record.rs

1//! # Declarative Records
2//!
3//! Each declarative Environment Record is associated with an ECMAScript program scope containing variable,
4//! `constant`, `let`, `class`, `module`, `import`, and/or function declarations.
5//! A declarative Environment Record binds the set of identifiers defined by the declarations contained within its scope.
6//! More info:  [ECMA-262 sec-declarative-environment-records](https://tc39.es/ecma262/#sec-declarative-environment-records)
7
8use crate::{
9    environment::{
10        environment_record_trait::EnvironmentRecordTrait,
11        lexical_environment::{Environment, EnvironmentType},
12    },
13    gc::{Finalize, Trace},
14    object::JsObject,
15    BoaProfiler, Context, JsResult, JsValue,
16};
17use gc::{Gc, GcCell};
18use rustc_hash::FxHashMap;
19
20/// Declarative Bindings have a few properties for book keeping purposes, such as mutability (const vs let).
21/// Can it be deleted? and strict mode.
22///
23/// So we need to create a struct to hold these values.
24/// From this point onwards, a binding is referring to one of these structures.
25#[derive(Trace, Finalize, Debug, Clone)]
26pub struct DeclarativeEnvironmentRecordBinding {
27    pub value: Option<JsValue>,
28    pub can_delete: bool,
29    pub mutable: bool,
30    pub strict: bool,
31}
32
33/// A declarative Environment Record binds the set of identifiers defined by the
34/// declarations contained within its scope.
35#[derive(Debug, Trace, Finalize, Clone)]
36pub struct DeclarativeEnvironmentRecord {
37    pub env_rec: GcCell<FxHashMap<Box<str>, DeclarativeEnvironmentRecordBinding>>,
38    pub outer_env: Option<Environment>,
39}
40
41impl DeclarativeEnvironmentRecord {
42    pub fn new(env: Option<Environment>) -> DeclarativeEnvironmentRecord {
43        let _timer = BoaProfiler::global().start_event("new_declarative_environment", "env");
44        DeclarativeEnvironmentRecord {
45            env_rec: GcCell::new(FxHashMap::default()),
46            outer_env: env,
47        }
48    }
49}
50
51impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
52    /// `9.1.1.1.1 HasBinding ( N )`
53    ///
54    /// More information:
55    ///  - [ECMAScript reference][spec]
56    ///
57    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
58    fn has_binding(&self, name: &str, _context: &mut Context) -> JsResult<bool> {
59        // 1. If envRec has a binding for the name that is the value of N, return true.
60        // 2. Return false.
61        Ok(self.env_rec.borrow().contains_key(name))
62    }
63
64    /// `9.1.1.1.2 CreateMutableBinding ( N, D )`
65    ///
66    /// More information:
67    ///  - [ECMAScript reference][spec]
68    ///
69    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d
70    fn create_mutable_binding(
71        &self,
72        name: &str,
73        deletion: bool,
74        allow_name_reuse: bool,
75        _context: &mut Context,
76    ) -> JsResult<()> {
77        // 1. Assert: envRec does not already have a binding for N.
78        if !allow_name_reuse {
79            assert!(
80                !self.env_rec.borrow().contains_key(name),
81                "Identifier {} has already been declared",
82                name
83            );
84        }
85
86        // 2. Create a mutable binding in envRec for N and record that it is uninitialized.
87        //    If D is true, record that the newly created binding may be deleted by a subsequent DeleteBinding call.
88        self.env_rec.borrow_mut().insert(
89            name.into(),
90            DeclarativeEnvironmentRecordBinding {
91                value: None,
92                can_delete: deletion,
93                mutable: true,
94                strict: false,
95            },
96        );
97
98        // 3. Return NormalCompletion(empty).
99        Ok(())
100    }
101
102    /// `9.1.1.1.3 CreateImmutableBinding ( N, S )`
103    ///
104    /// More information:
105    ///  - [ECMAScript reference][spec]
106    ///
107    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s
108    fn create_immutable_binding(
109        &self,
110        name: &str,
111        strict: bool,
112        _context: &mut Context,
113    ) -> JsResult<()> {
114        // 1. Assert: envRec does not already have a binding for N.
115        assert!(
116            !self.env_rec.borrow().contains_key(name),
117            "Identifier {} has already been declared",
118            name
119        );
120
121        // 2. Create an immutable binding in envRec for N and record that it is uninitialized.
122        //    If S is true, record that the newly created binding is a strict binding.
123        self.env_rec.borrow_mut().insert(
124            name.into(),
125            DeclarativeEnvironmentRecordBinding {
126                value: None,
127                can_delete: true,
128                mutable: false,
129                strict,
130            },
131        );
132
133        // 3. Return NormalCompletion(empty).
134        Ok(())
135    }
136
137    /// `9.1.1.1.4 InitializeBinding ( N, V )`
138    ///
139    /// More information:
140    ///  - [ECMAScript reference][spec]
141    ///
142    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v
143    fn initialize_binding(
144        &self,
145        name: &str,
146        value: JsValue,
147        _context: &mut Context,
148    ) -> JsResult<()> {
149        if let Some(ref mut record) = self.env_rec.borrow_mut().get_mut(name) {
150            if record.value.is_none() {
151                // 2. Set the bound value for N in envRec to V.
152                // 3. Record that the binding for N in envRec has been initialized.
153                record.value = Some(value);
154
155                // 4. Return NormalCompletion(empty).
156                return Ok(());
157            }
158        }
159
160        // 1. Assert: envRec must have an uninitialized binding for N.
161        panic!("record must have binding for {}", name);
162    }
163
164    /// `9.1.1.1.5 SetMutableBinding ( N, V, S )`
165    ///
166    /// More information:
167    ///  - [ECMAScript reference][spec]
168    ///
169    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s
170    #[allow(clippy::else_if_without_else)]
171    fn set_mutable_binding(
172        &self,
173        name: &str,
174        value: JsValue,
175        mut strict: bool,
176        context: &mut Context,
177    ) -> JsResult<()> {
178        // 1. If envRec does not have a binding for N, then
179        if self.env_rec.borrow().get(name).is_none() {
180            // a. If S is true, throw a ReferenceError exception.
181            if strict {
182                return Err(context.construct_reference_error(format!("{} not found", name)));
183            }
184
185            // b. Perform envRec.CreateMutableBinding(N, true).
186            self.create_mutable_binding(name, true, false, context)?;
187            // c. Perform envRec.InitializeBinding(N, V).
188            self.initialize_binding(name, value, context)?;
189
190            // d. Return NormalCompletion(empty).
191            return Ok(());
192        }
193
194        let (binding_strict, binding_value_is_none, binding_mutable) = {
195            let env_rec = self.env_rec.borrow();
196            let binding = env_rec.get(name).unwrap();
197            (binding.strict, binding.value.is_none(), binding.mutable)
198        };
199
200        // 2. If the binding for N in envRec is a strict binding, set S to true.
201        if binding_strict {
202            strict = true;
203        }
204
205        // 3. If the binding for N in envRec has not yet been initialized, throw a ReferenceError exception.
206        if binding_value_is_none {
207            return Err(
208                context.construct_reference_error(format!("{} has not been initialized", name))
209            );
210        // 4. Else if the binding for N in envRec is a mutable binding, change its bound value to V.
211        } else if binding_mutable {
212            let mut env_rec = self.env_rec.borrow_mut();
213            let binding = env_rec.get_mut(name).unwrap();
214            binding.value = Some(value);
215        // 5. Else,
216        // a. Assert: This is an attempt to change the value of an immutable binding.
217        // b. If S is true, throw a TypeError exception.
218        } else if strict {
219            return Err(context
220                .construct_type_error(format!("Cannot mutate an immutable binding {}", name)));
221        }
222
223        // 6. Return NormalCompletion(empty).
224        Ok(())
225    }
226
227    /// `9.1.1.1.6 GetBindingValue ( N, S )`
228    ///
229    /// More information:
230    ///  - [ECMAScript reference][spec]
231    ///
232    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s
233    fn get_binding_value(
234        &self,
235        name: &str,
236        _strict: bool,
237        context: &mut Context,
238    ) -> JsResult<JsValue> {
239        // 1. Assert: envRec has a binding for N.
240        // 2. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception.
241        // 3. Return the value currently bound to N in envRec.
242        if let Some(binding) = self.env_rec.borrow().get(name) {
243            if let Some(ref val) = binding.value {
244                Ok(val.clone())
245            } else {
246                context.throw_reference_error(format!("{} is an uninitialized binding", name))
247            }
248        } else {
249            panic!("Cannot get binding value for {}", name);
250        }
251    }
252
253    /// `9.1.1.1.7 DeleteBinding ( N )`
254    ///
255    /// More information:
256    ///  - [ECMAScript reference][spec]
257    ///
258    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n
259    fn delete_binding(&self, name: &str, _context: &mut Context) -> JsResult<bool> {
260        // 1. Assert: envRec has a binding for the name that is the value of N.
261        // 2. If the binding for N in envRec cannot be deleted, return false.
262        // 3. Remove the binding for N from envRec.
263        // 4. Return true.
264        match self.env_rec.borrow().get(name) {
265            Some(binding) => {
266                if binding.can_delete {
267                    self.env_rec.borrow_mut().remove(name);
268                    Ok(true)
269                } else {
270                    Ok(false)
271                }
272            }
273            None => panic!("env_rec has no binding for {}", name),
274        }
275    }
276
277    /// `9.1.1.1.8 HasThisBinding ( )`
278    ///
279    /// More information:
280    ///  - [ECMAScript reference][spec]
281    ///
282    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasthisbinding
283    fn has_this_binding(&self) -> bool {
284        // 1. Return false.
285        false
286    }
287
288    fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> {
289        Ok(JsValue::undefined())
290    }
291
292    /// `9.1.1.1.9 HasSuperBinding ( )`
293    ///
294    /// More information:
295    ///  - [ECMAScript reference][spec]
296    ///
297    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hassuperbinding
298    fn has_super_binding(&self) -> bool {
299        // 1. Return false.
300        false
301    }
302
303    /// `9.1.1.1.10 WithBaseObject ( )`
304    ///
305    /// More information:
306    ///  - [ECMAScript reference][spec]
307    ///
308    /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-withbaseobject
309    fn with_base_object(&self) -> Option<JsObject> {
310        None
311    }
312
313    fn get_outer_environment_ref(&self) -> Option<&Environment> {
314        self.outer_env.as_ref()
315    }
316
317    fn set_outer_environment(&mut self, env: Environment) {
318        self.outer_env = Some(env);
319    }
320
321    fn get_environment_type(&self) -> EnvironmentType {
322        EnvironmentType::Declarative
323    }
324}
325
326impl From<DeclarativeEnvironmentRecord> for Environment {
327    fn from(env: DeclarativeEnvironmentRecord) -> Environment {
328        Gc::new(Box::new(env))
329    }
330}