boa/environment/
object_environment_record.rs

1//! # Object Records
2//!
3//! Each object Environment Record is associated with an object called its binding object.
4//! An object Environment Record binds the set of string identifier names that directly
5//! correspond to the property names of its binding object.
6//! Property keys that are not strings in the form of an `IdentifierName` are not included in the set of bound identifiers.
7//! More info:  [Object Records](https://tc39.es/ecma262/#sec-object-environment-records)
8
9use gc::Gc;
10
11use crate::{
12    environment::{
13        environment_record_trait::EnvironmentRecordTrait,
14        lexical_environment::{Environment, EnvironmentType},
15    },
16    gc::{Finalize, Trace},
17    object::JsObject,
18    property::PropertyDescriptor,
19    symbol::WellKnownSymbols,
20    Context, JsResult, JsValue,
21};
22
23#[derive(Debug, Trace, Finalize, Clone)]
24pub struct ObjectEnvironmentRecord {
25    pub bindings: JsObject,
26    pub with_environment: bool,
27    pub outer_env: Option<Environment>,
28}
29
30impl ObjectEnvironmentRecord {
31    pub fn new(object: JsObject, environment: Option<Environment>) -> ObjectEnvironmentRecord {
32        ObjectEnvironmentRecord {
33            bindings: object,
34            outer_env: environment,
35            /// Object Environment Records created for with statements (13.11)
36            /// can provide their binding object as an implicit this value for use in function calls.
37            /// The capability is controlled by a withEnvironment Boolean value that is associated
38            /// with each object Environment Record. By default, the value of withEnvironment is false
39            /// for any object Environment Record.
40            with_environment: false,
41        }
42    }
43}
44
45impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
46    /// `9.1.1.2.1 HasBinding ( N )`
47    ///
48    /// More information:
49    ///  - [ECMAScript reference][spec]
50    ///
51    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
52    fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
53        // 1. Let bindingObject be envRec.[[BindingObject]].
54        // 2. Let foundBinding be ? HasProperty(bindingObject, N).
55        // 3. If foundBinding is false, return false.
56        if !self.bindings.has_property(name, context)? {
57            return Ok(false);
58        }
59
60        // 4. If envRec.[[IsWithEnvironment]] is false, return true.
61        if !self.with_environment {
62            return Ok(true);
63        }
64
65        // 5. Let unscopables be ? Get(bindingObject, @@unscopables).
66        // 6. If Type(unscopables) is Object, then
67        if let Some(unscopables) = self
68            .bindings
69            .get(WellKnownSymbols::unscopables(), context)?
70            .as_object()
71        {
72            // a. Let blocked be ! ToBoolean(? Get(unscopables, N)).
73            // b. If blocked is true, return false.
74            if unscopables.get(name, context)?.to_boolean() {
75                return Ok(false);
76            }
77        }
78
79        // 7. Return true.
80        Ok(true)
81    }
82
83    /// `9.1.1.2.2 CreateMutableBinding ( N, D )`
84    ///
85    /// More information:
86    ///  - [ECMAScript reference][spec]
87    ///
88    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d
89    fn create_mutable_binding(
90        &self,
91        name: &str,
92        deletion: bool,
93        _allow_name_reuse: bool,
94        context: &mut Context,
95    ) -> JsResult<()> {
96        // 1. Let bindingObject be envRec.[[BindingObject]].
97        // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }).
98        self.bindings.define_property_or_throw(
99            name,
100            PropertyDescriptor::builder()
101                .value(JsValue::undefined())
102                .writable(true)
103                .enumerable(true)
104                .configurable(deletion),
105            context,
106        )?;
107        Ok(())
108    }
109
110    /// `9.1.1.2.3 CreateImmutableBinding ( N, S )`
111    ///
112    /// More information:
113    ///  - [ECMAScript reference][spec]
114    ///
115    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s
116    fn create_immutable_binding(
117        &self,
118        _name: &str,
119        _strict: bool,
120        _context: &mut Context,
121    ) -> JsResult<()> {
122        Ok(())
123    }
124
125    /// `9.1.1.2.4 InitializeBinding ( N, V )`
126    ///
127    /// More information:
128    ///  - [ECMAScript reference][spec]
129    ///
130    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
131    fn initialize_binding(
132        &self,
133        name: &str,
134        value: JsValue,
135        context: &mut Context,
136    ) -> JsResult<()> {
137        // 1. Return ? envRec.SetMutableBinding(N, V, false).
138        self.set_mutable_binding(name, value, false, context)
139    }
140
141    /// `9.1.1.2.5 SetMutableBinding ( N, V, S )`
142    ///
143    /// More information:
144    ///  - [ECMAScript reference][spec]
145    ///
146    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s
147    fn set_mutable_binding(
148        &self,
149        name: &str,
150        value: JsValue,
151        strict: bool,
152        context: &mut Context,
153    ) -> JsResult<()> {
154        // 1. Let bindingObject be envRec.[[BindingObject]].
155        // 2. Let stillExists be ? HasProperty(bindingObject, N).
156        let still_exists = self.bindings.has_property(name, context)?;
157
158        // 3. If stillExists is false and S is true, throw a ReferenceError exception.
159        if !still_exists && strict {
160            return Err(context.construct_reference_error("Binding already exists"));
161        }
162
163        // 4. Return ? Set(bindingObject, N, V, S).
164        self.bindings.set(name, value, strict, context)?;
165        Ok(())
166    }
167
168    /// `9.1.1.2.6 GetBindingValue ( N, S )`
169    ///
170    /// More information:
171    ///  - [ECMAScript reference][spec]
172    ///
173    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s
174    fn get_binding_value(
175        &self,
176        name: &str,
177        strict: bool,
178        context: &mut Context,
179    ) -> JsResult<JsValue> {
180        // 1. Let bindingObject be envRec.[[BindingObject]].
181        // 2. Let value be ? HasProperty(bindingObject, N).
182        // 3. If value is false, then
183        if !self.bindings.__has_property__(&name.into(), context)? {
184            // a. If S is false, return the value undefined; otherwise throw a ReferenceError exception.
185            if !strict {
186                return Ok(JsValue::undefined());
187            } else {
188                return context.throw_reference_error(format!("{} has no binding", name));
189            }
190        }
191
192        // 4. Return ? Get(bindingObject, N).
193        self.bindings.get(name, context)
194    }
195
196    /// `9.1.1.2.7 DeleteBinding ( N )`
197    ///
198    /// More information:
199    ///  - [ECMAScript reference][spec]
200    ///
201    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n
202    fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
203        // 1. Let bindingObject be envRec.[[BindingObject]].
204        // 2. Return ? bindingObject.[[Delete]](N).
205        self.bindings.__delete__(&name.into(), context)
206    }
207
208    /// `9.1.1.2.8 HasThisBinding ( )`
209    ///
210    /// More information:
211    ///  - [ECMAScript reference][spec]
212    ///
213    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasthisbinding
214    fn has_this_binding(&self) -> bool {
215        // 1. Return false.
216        false
217    }
218
219    /// `9.1.1.2.9 HasSuperBinding ( )`
220    ///
221    /// More information:
222    ///  - [ECMAScript reference][spec]
223    ///
224    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding
225    fn has_super_binding(&self) -> bool {
226        // 1. Return false.
227        false
228    }
229
230    /// `9.1.1.2.10 WithBaseObject ( )`
231    ///
232    /// More information:
233    ///  - [ECMAScript reference][spec]
234    ///
235    /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding
236    fn with_base_object(&self) -> Option<JsObject> {
237        // 1. If envRec.[[IsWithEnvironment]] is true, return envRec.[[BindingObject]].
238        // 2. Otherwise, return undefined.
239        if self.with_environment {
240            Some(self.bindings.clone())
241        } else {
242            None
243        }
244    }
245
246    fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> {
247        Ok(JsValue::undefined())
248    }
249
250    fn get_outer_environment_ref(&self) -> Option<&Environment> {
251        self.outer_env.as_ref()
252    }
253
254    fn set_outer_environment(&mut self, env: Environment) {
255        self.outer_env = Some(env);
256    }
257
258    fn get_environment_type(&self) -> EnvironmentType {
259        EnvironmentType::Function
260    }
261}
262
263impl From<ObjectEnvironmentRecord> for Environment {
264    fn from(env: ObjectEnvironmentRecord) -> Environment {
265        Gc::new(Box::new(env))
266    }
267}