boa/environment/
function_environment_record.rs

1//! # Function Environment Records
2//!
3//! A function Environment Record is a declarative Environment Record that is used to represent
4//! the top-level scope of a function and, if the function is not an `ArrowFunction`,
5//! provides a `this` binding.
6//! If a function is not an `ArrowFunction` function and references super,
7//! its function Environment Record also contains the state that is used to perform super method invocations
8//! from within the function.
9//! More info: <https://tc39.es/ecma262/#sec-function-environment-records>
10
11use gc::Gc;
12
13use crate::{
14    environment::{
15        declarative_environment_record::DeclarativeEnvironmentRecord,
16        environment_record_trait::EnvironmentRecordTrait,
17        lexical_environment::{Environment, EnvironmentType, VariableScope},
18    },
19    gc::{empty_trace, Finalize, Trace},
20    object::JsObject,
21    Context, JsResult, JsValue,
22};
23
24/// Different binding status for `this`.
25/// Usually set on a function environment record
26#[derive(Copy, Finalize, Debug, Clone)]
27pub enum BindingStatus {
28    /// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
29    Lexical,
30    /// If initialized the function environment record has already been bound with a `this` value
31    Initialized,
32    /// If uninitialized the function environment record has not been bouned with a `this` value
33    Uninitialized,
34}
35
36unsafe impl Trace for BindingStatus {
37    empty_trace!();
38}
39
40/// <https://tc39.es/ecma262/#table-16>
41#[derive(Debug, Trace, Finalize, Clone)]
42pub struct FunctionEnvironmentRecord {
43    pub declarative_record: DeclarativeEnvironmentRecord,
44    /// This is the this value used for this invocation of the function.
45    pub this_value: JsValue,
46    /// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
47    pub this_binding_status: BindingStatus,
48    /// The function object whose invocation caused this Environment Record to be created.
49    pub function: JsObject,
50    /// If the associated function has super property accesses and is not an ArrowFunction,
51    /// `[[HomeObject]]` is the object that the function is bound to as a method.
52    /// The default value for `[[HomeObject]]` is undefined.
53    pub home_object: JsValue,
54    /// If this Environment Record was created by the `[[Construct]]` internal method,
55    /// `[[NewTarget]]` is the value of the `[[Construct]]` newTarget parameter.
56    /// Otherwise, its value is undefined.
57    pub new_target: JsValue,
58}
59
60impl FunctionEnvironmentRecord {
61    pub fn new(
62        f: JsObject,
63        this: Option<JsValue>,
64        outer: Option<Environment>,
65        binding_status: BindingStatus,
66        new_target: JsValue,
67        context: &mut Context,
68    ) -> JsResult<FunctionEnvironmentRecord> {
69        let mut func_env = FunctionEnvironmentRecord {
70            declarative_record: DeclarativeEnvironmentRecord::new(outer), // the outer environment will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects
71            function: f,
72            this_binding_status: binding_status,
73            home_object: JsValue::undefined(),
74            new_target,
75            this_value: JsValue::undefined(),
76        };
77        // If a `this` value has been passed, bind it to the environment
78        if let Some(v) = this {
79            func_env.bind_this_value(v, context)?;
80        }
81        Ok(func_env)
82    }
83
84    /// `9.1.1.3.1 BindThisValue ( V )`
85    ///
86    /// More information:
87    ///  - [ECMAScript reference][spec]
88    ///
89    /// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue
90    pub fn bind_this_value(&mut self, value: JsValue, context: &mut Context) -> JsResult<JsValue> {
91        match self.this_binding_status {
92            // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
93            BindingStatus::Lexical => {
94                panic!("Cannot bind to an arrow function!");
95            }
96            // 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
97            BindingStatus::Initialized => {
98                context.throw_reference_error("Cannot bind to an initialized function!")
99            }
100            BindingStatus::Uninitialized => {
101                // 3. Set envRec.[[ThisValue]] to V.
102                self.this_value = value.clone();
103                // 4. Set envRec.[[ThisBindingStatus]] to initialized.
104                self.this_binding_status = BindingStatus::Initialized;
105                // 5. Return V.
106                Ok(value)
107            }
108        }
109    }
110
111    /// `9.1.1.3.5 GetSuperBase ( )`
112    ///
113    /// More information:
114    ///  - [ECMAScript reference][spec]
115    ///
116    /// [spec]: https://tc39.es/ecma262/#sec-getsuperbase
117    pub fn get_super_base(&self) -> JsValue {
118        // 1. Let home be envRec.[[FunctionObject]].[[HomeObject]].
119        let home = &self.home_object;
120
121        // 2. If home has the value undefined, return undefined.
122        if home.is_undefined() {
123            JsValue::undefined()
124        } else {
125            // 3. Assert: Type(home) is Object.
126            assert!(home.is_object());
127
128            // 4. Return ? home.[[GetPrototypeOf]]().
129            home.as_object()
130                .expect("home_object must be an Object")
131                .prototype_instance()
132        }
133    }
134}
135
136impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
137    fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
138        self.declarative_record.has_binding(name, context)
139    }
140
141    fn create_mutable_binding(
142        &self,
143        name: &str,
144        deletion: bool,
145        allow_name_reuse: bool,
146        context: &mut Context,
147    ) -> JsResult<()> {
148        self.declarative_record
149            .create_mutable_binding(name, deletion, allow_name_reuse, context)
150    }
151
152    fn create_immutable_binding(
153        &self,
154        name: &str,
155        strict: bool,
156        context: &mut Context,
157    ) -> JsResult<()> {
158        self.declarative_record
159            .create_immutable_binding(name, strict, context)
160    }
161
162    fn initialize_binding(
163        &self,
164        name: &str,
165        value: JsValue,
166        context: &mut Context,
167    ) -> JsResult<()> {
168        self.declarative_record
169            .initialize_binding(name, value, context)
170    }
171
172    fn set_mutable_binding(
173        &self,
174        name: &str,
175        value: JsValue,
176        strict: bool,
177        context: &mut Context,
178    ) -> JsResult<()> {
179        self.declarative_record
180            .set_mutable_binding(name, value, strict, context)
181    }
182
183    fn get_binding_value(
184        &self,
185        name: &str,
186        strict: bool,
187        context: &mut Context,
188    ) -> JsResult<JsValue> {
189        self.declarative_record
190            .get_binding_value(name, strict, context)
191    }
192
193    fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
194        self.declarative_record.delete_binding(name, context)
195    }
196
197    /// `9.1.1.3.2 HasThisBinding ( )`
198    ///
199    /// More information:
200    ///  - [ECMAScript reference][spec]
201    ///
202    /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
203    fn has_this_binding(&self) -> bool {
204        // 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
205        !matches!(self.this_binding_status, BindingStatus::Lexical)
206    }
207
208    /// `9.1.1.3.3 HasSuperBinding ( )`
209    ///
210    /// More information:
211    ///  - [ECMAScript reference][spec]
212    ///
213    /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
214    fn has_super_binding(&self) -> bool {
215        // 1. If envRec.[[ThisBindingStatus]] is lexical, return false.
216        // 2. If envRec.[[FunctionObject]].[[HomeObject]] has the value undefined, return false; otherwise, return true.
217        if let BindingStatus::Lexical = self.this_binding_status {
218            false
219        } else {
220            !self.home_object.is_undefined()
221        }
222    }
223
224    /// `9.1.1.3.4 GetThisBinding ( )`
225    ///
226    /// More information:
227    ///  - [ECMAScript reference][spec]
228    ///
229    /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
230    fn get_this_binding(&self, context: &mut Context) -> JsResult<JsValue> {
231        match self.this_binding_status {
232            // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
233            BindingStatus::Lexical => {
234                panic!("There is no this for a lexical function record");
235            }
236            // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
237            BindingStatus::Uninitialized => {
238                context.throw_reference_error("Uninitialized binding for this function")
239            }
240            // 3. Return envRec.[[ThisValue]].
241            BindingStatus::Initialized => Ok(self.this_value.clone()),
242        }
243    }
244
245    fn with_base_object(&self) -> Option<JsObject> {
246        None
247    }
248
249    fn get_outer_environment_ref(&self) -> Option<&Environment> {
250        self.declarative_record.get_outer_environment_ref()
251    }
252
253    fn set_outer_environment(&mut self, env: Environment) {
254        self.declarative_record.set_outer_environment(env)
255    }
256
257    fn get_environment_type(&self) -> EnvironmentType {
258        EnvironmentType::Function
259    }
260
261    fn recursive_create_mutable_binding(
262        &self,
263        name: &str,
264        deletion: bool,
265        _scope: VariableScope,
266        context: &mut Context,
267    ) -> JsResult<()> {
268        self.create_mutable_binding(name, deletion, false, context)
269    }
270
271    fn recursive_create_immutable_binding(
272        &self,
273        name: &str,
274        deletion: bool,
275        _scope: VariableScope,
276        context: &mut Context,
277    ) -> JsResult<()> {
278        self.create_immutable_binding(name, deletion, context)
279    }
280}
281
282impl From<FunctionEnvironmentRecord> for Environment {
283    fn from(env: FunctionEnvironmentRecord) -> Environment {
284        Gc::new(Box::new(env))
285    }
286}