Skip to main content

boa_engine/builtins/function/
bound.rs

1use boa_gc::{Finalize, Trace};
2
3use crate::{
4    Context, JsExpect, JsObject, JsResult, JsValue,
5    object::{
6        JsData,
7        internal_methods::{
8            CallValue, InternalMethodCallContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
9        },
10    },
11};
12
13/// Binds a `Function Object` when `bind` is called.
14#[derive(Debug, Trace, Finalize)]
15pub struct BoundFunction {
16    target_function: JsObject,
17    this: JsValue,
18    args: Vec<JsValue>,
19}
20
21impl JsData for BoundFunction {
22    fn internal_methods(&self) -> &'static InternalObjectMethods {
23        static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods {
24            __call__: bound_function_exotic_call,
25            __construct__: bound_function_exotic_construct,
26            ..ORDINARY_INTERNAL_METHODS
27        };
28
29        static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods {
30            __call__: bound_function_exotic_call,
31            ..ORDINARY_INTERNAL_METHODS
32        };
33
34        if self.target_function.is_constructor() {
35            &CONSTRUCTOR_METHODS
36        } else {
37            &FUNCTION_METHODS
38        }
39    }
40}
41
42impl BoundFunction {
43    /// Abstract operation `BoundFunctionCreate`
44    ///
45    /// More information:
46    ///  - [ECMAScript reference][spec]
47    ///
48    /// [spec]: https://tc39.es/ecma262/#sec-boundfunctioncreate
49    pub fn create(
50        target_function: JsObject,
51        this: JsValue,
52        args: Vec<JsValue>,
53        context: &mut Context,
54    ) -> JsResult<JsObject> {
55        // 1. Let proto be ? targetFunction.[[GetPrototypeOf]]().
56        let proto = target_function.__get_prototype_of__(context)?;
57
58        // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]].
59        // 3. Let obj be ! MakeBasicObject(internalSlotsList).
60        // 4. Set obj.[[Prototype]] to proto.
61        // 5. Set obj.[[Call]] as described in 10.4.1.1.
62        // 6. If IsConstructor(targetFunction) is true, then
63        // a. Set obj.[[Construct]] as described in 10.4.1.2.
64        // 7. Set obj.[[BoundTargetFunction]] to targetFunction.
65        // 8. Set obj.[[BoundThis]] to boundThis.
66        // 9. Set obj.[[BoundArguments]] to boundArgs.
67        // 10. Return obj.
68        Ok(JsObject::from_proto_and_data_with_shared_shape(
69            context.root_shape(),
70            proto,
71            Self {
72                target_function,
73                this,
74                args,
75            },
76        )
77        .upcast())
78    }
79
80    /// Get a reference to the bound function's this.
81    #[must_use]
82    pub const fn this(&self) -> &JsValue {
83        &self.this
84    }
85
86    /// Get a reference to the bound function's target function.
87    #[must_use]
88    pub const fn target_function(&self) -> &JsObject {
89        &self.target_function
90    }
91
92    /// Get a reference to the bound function's args.
93    #[must_use]
94    pub fn args(&self) -> &[JsValue] {
95        self.args.as_slice()
96    }
97}
98
99/// Internal method `[[Call]]` for Bound Function Exotic Objects
100///
101/// More information:
102///  - [ECMAScript reference][spec]
103///
104/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist
105#[allow(clippy::unnecessary_wraps)]
106fn bound_function_exotic_call(
107    obj: &JsObject,
108    argument_count: usize,
109    context: &mut InternalMethodCallContext<'_>,
110) -> JsResult<CallValue> {
111    let bound_function = obj.downcast_ref::<BoundFunction>().js_expect(
112        "bound function exotic method should only be callable from bound function objects",
113    )?;
114
115    // 1. Let target be F.[[BoundTargetFunction]].
116    let target = bound_function.target_function();
117    context
118        .vm
119        .stack
120        .calling_convention_set_function(argument_count, target.clone().into());
121
122    // 2. Let boundThis be F.[[BoundThis]].
123    let bound_this = bound_function.this();
124    context
125        .vm
126        .stack
127        .calling_convention_set_this(argument_count, bound_this.clone());
128
129    // 3. Let boundArgs be F.[[BoundArguments]].
130    let bound_args = bound_function.args();
131
132    // 4. Let args be the list-concatenation of boundArgs and argumentsList.
133    context
134        .vm
135        .stack
136        .calling_convention_insert_arguments(argument_count, bound_args);
137
138    // 5. Return ? Call(target, boundThis, args).
139    Ok(target.__call__(bound_args.len() + argument_count))
140}
141
142/// Internal method `[[Construct]]` for Bound Function Exotic Objects
143///
144/// More information:
145///  - [ECMAScript reference][spec]
146///
147/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget
148#[allow(clippy::unnecessary_wraps)]
149fn bound_function_exotic_construct(
150    function_object: &JsObject,
151    argument_count: usize,
152    context: &mut InternalMethodCallContext<'_>,
153) -> JsResult<CallValue> {
154    let new_target = context.vm.stack.pop();
155
156    debug_assert!(new_target.is_object(), "new.target should be an object");
157
158    let bound_function = function_object.downcast_ref::<BoundFunction>().js_expect(
159        "bound function exotic method should only be callable from bound function objects",
160    )?;
161
162    // 1. Let target be F.[[BoundTargetFunction]].
163    let target = bound_function.target_function();
164
165    // 2. Assert: IsConstructor(target) is true.
166
167    // 3. Let boundArgs be F.[[BoundArguments]].
168    let bound_args = bound_function.args();
169
170    // 4. Let args be the list-concatenation of boundArgs and argumentsList.
171    context
172        .vm
173        .stack
174        .calling_convention_insert_arguments(argument_count, bound_args);
175
176    // 5. If SameValue(F, newTarget) is true, set newTarget to target.
177    let function_object: JsValue = function_object.clone().into();
178    let new_target = if JsValue::same_value(&function_object, &new_target) {
179        target.clone().into()
180    } else {
181        new_target
182    };
183
184    // 6. Return ? Construct(target, args, newTarget).
185    context.vm.stack.push(new_target);
186    Ok(target.__construct__(bound_args.len() + argument_count))
187}