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}