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