boa_engine/builtins/function/
bound.rs1use 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#[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 pub fn create(
50 target_function: JsObject,
51 this: JsValue,
52 args: Vec<JsValue>,
53 context: &mut Context,
54 ) -> JsResult<JsObject> {
55 let proto = target_function.__get_prototype_of__(context)?;
57
58 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 #[must_use]
82 pub const fn this(&self) -> &JsValue {
83 &self.this
84 }
85
86 #[must_use]
88 pub const fn target_function(&self) -> &JsObject {
89 &self.target_function
90 }
91
92 #[must_use]
94 pub fn args(&self) -> &[JsValue] {
95 self.args.as_slice()
96 }
97}
98
99#[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 let target = bound_function.target_function();
117 context
118 .vm
119 .stack
120 .calling_convention_set_function(argument_count, target.clone().into());
121
122 let bound_this = bound_function.this();
124 context
125 .vm
126 .stack
127 .calling_convention_set_this(argument_count, bound_this.clone());
128
129 let bound_args = bound_function.args();
131
132 context
134 .vm
135 .stack
136 .calling_convention_insert_arguments(argument_count, bound_args);
137
138 Ok(target.__call__(bound_args.len() + argument_count))
140}
141
142#[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 let target = bound_function.target_function();
164
165 let bound_args = bound_function.args();
169
170 context
172 .vm
173 .stack
174 .calling_convention_insert_arguments(argument_count, bound_args);
175
176 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 context.vm.stack.push(new_target);
186 Ok(target.__construct__(bound_args.len() + argument_count))
187}