Browse Source

Refactor `Function` internal methods and implement `BoundFunction` objects (#1583)

* Refactor `[[Call]]` and `[[Construct]]` to be InternalFunctions

* Implement `BoundFunction` exotic objects

* Implement remaining `Function` builtin methods

* Lift `OrdinaryHasInstance` to be inside JsValue

* Implement `JsValue::InstanceOf` operator inside JsValue

* Implement `JsValue::[as/is]_[callable/constructor]

* Enable mutability for the `Closure` type inside closures
pull/1655/head
jedel1043 3 years ago committed by GitHub
parent
commit
9f845465bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      boa/examples/closures.rs
  2. 88
      boa/src/builtins/array/array_iterator.rs
  3. 155
      boa/src/builtins/array/mod.rs
  4. 45
      boa/src/builtins/array_buffer/mod.rs
  5. 25
      boa/src/builtins/bigint/mod.rs
  6. 16
      boa/src/builtins/boolean/mod.rs
  7. 2
      boa/src/builtins/boolean/tests.rs
  8. 10
      boa/src/builtins/date/mod.rs
  9. 467
      boa/src/builtins/function/mod.rs
  10. 2
      boa/src/builtins/intrinsics.rs
  11. 16
      boa/src/builtins/iterable/mod.rs
  12. 75
      boa/src/builtins/json/mod.rs
  13. 15
      boa/src/builtins/map/map_iterator.rs
  14. 32
      boa/src/builtins/map/mod.rs
  15. 16
      boa/src/builtins/number/mod.rs
  16. 92
      boa/src/builtins/object/for_in_iterator.rs
  17. 23
      boa/src/builtins/object/mod.rs
  18. 24
      boa/src/builtins/reflect/mod.rs
  19. 137
      boa/src/builtins/regexp/mod.rs
  20. 132
      boa/src/builtins/regexp/regexp_string_iterator.rs
  21. 62
      boa/src/builtins/set/mod.rs
  22. 109
      boa/src/builtins/set/set_iterator.rs
  23. 68
      boa/src/builtins/string/mod.rs
  24. 67
      boa/src/builtins/string/string_iterator.rs
  25. 22
      boa/src/builtins/symbol/mod.rs
  26. 84
      boa/src/builtins/typed_array/mod.rs
  27. 2
      boa/src/bytecompiler.rs
  28. 29
      boa/src/class.rs
  29. 18
      boa/src/context.rs
  30. 8
      boa/src/exec/tests.rs
  31. 100
      boa/src/object/internal_methods/bound_function.rs
  32. 314
      boa/src/object/internal_methods/function.rs
  33. 67
      boa/src/object/internal_methods/mod.rs
  34. 338
      boa/src/object/jsobject.rs
  35. 148
      boa/src/object/mod.rs
  36. 111
      boa/src/object/operations.rs
  37. 2
      boa/src/syntax/ast/node/declaration/mod.rs
  38. 16
      boa/src/syntax/ast/node/new/mod.rs
  39. 6
      boa/src/syntax/ast/node/object/mod.rs
  40. 31
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  41. 89
      boa/src/value/mod.rs
  42. 36
      boa/src/value/operations.rs
  43. 13
      boa/src/vm/code_block.rs
  44. 34
      boa/src/vm/mod.rs

15
boa/examples/closures.rs

@ -21,6 +21,7 @@ fn main() -> Result<(), JsValue> {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {}", variable);
println!();
// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
@ -52,7 +53,7 @@ fn main() -> Result<(), JsValue> {
// Now, we execute some operations that return a `Clone` type
let clone_variable = BigStruct {
greeting: JsString::from("Hello from Javascript!"),
greeting: JsString::from("Hello!"),
object,
};
@ -73,7 +74,11 @@ fn main() -> Result<(), JsValue> {
captures.greeting.as_str(),
]);
// We can also mutate the moved data inside the closure.
captures.greeting = format!("{} Hello!", captures.greeting).into();
println!("{}", message);
println!();
// We convert `message` into `Jsvalue` to be able to return it.
Ok(message.into())
@ -100,7 +105,13 @@ fn main() -> Result<(), JsValue> {
assert_eq!(
context.eval("createMessage()")?,
"message from `Boa dev`: Hello from Javascript!".into()
"message from `Boa dev`: Hello!".into()
);
// The data mutates between calls
assert_eq!(
context.eval("createMessage(); createMessage();")?,
"message from `Boa dev`: Hello! Hello! Hello!".into()
);
// We have moved `Clone` variables into a closure and executed that closure

88
boa/src/builtins/array/array_iterator.rs

@ -62,57 +62,53 @@ impl ArrayIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(array_iterator) = object.as_array_iterator_mut() {
let index = array_iterator.next_index;
if array_iterator.done {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let mut array_iterator = this.as_object().map(|obj| obj.borrow_mut());
let array_iterator = array_iterator
.as_mut()
.and_then(|obj| obj.as_array_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?;
let index = array_iterator.next_index;
if array_iterator.done {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() {
if f.is_detached() {
return context.throw_type_error(
"Cannot get value from typed array that has a detached array buffer",
);
}
let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() {
if f.is_detached() {
return context.throw_type_error(
"Cannot get value from typed array that has a detached array buffer",
);
}
f.array_length()
} else {
array_iterator.array.length_of_array_like(context)?
};
f.array_length()
} else {
array_iterator.array.length_of_array_like(context)?
};
if index >= len {
array_iterator.done = true;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
array_iterator.next_index = index + 1;
return match array_iterator.kind {
PropertyNameKind::Key => {
Ok(create_iter_result_object(index.into(), false, context))
}
PropertyNameKind::Value => {
let element_value = array_iterator.array.get(index, context)?;
Ok(create_iter_result_object(element_value, false, context))
}
PropertyNameKind::KeyAndValue => {
let element_value = array_iterator.array.get(index, context)?;
let result =
Array::create_array_from_list([index.into(), element_value], context);
Ok(create_iter_result_object(result.into(), false, context))
}
};
if index >= len {
array_iterator.done = true;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
array_iterator.next_index = index + 1;
match array_iterator.kind {
PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)),
PropertyNameKind::Value => {
let element_value = array_iterator.array.get(index, context)?;
Ok(create_iter_result_object(element_value, false, context))
}
PropertyNameKind::KeyAndValue => {
let element_value = array_iterator.array.get(index, context)?;
let result = Array::create_array_from_list([index.into(), element_value], context);
Ok(create_iter_result_object(result.into(), false, context))
}
}
context.throw_type_error("`this` is not an ArrayIterator")
}
/// Create the %ArrayIteratorPrototype% object

155
boa/src/builtins/array/mod.rs

@ -49,7 +49,7 @@ impl BuiltIn for Array {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
let values_function = Self::values_intrinsic(context);
@ -354,14 +354,12 @@ impl Array {
}
// 7. If IsConstructor(C) is false, throw a TypeError exception.
if let Some(c) = c.as_object() {
if !c.is_constructable() {
return Err(context.construct_type_error("Symbol.species must be a constructor"));
}
if let Some(c) = c.as_constructor() {
// 8. Return ? Construct(C, « 𝔽(length) »).
Ok(
c.construct(&[JsValue::new(length)], &c.clone().into(), context)?
.as_object()
.cloned()
.unwrap(),
)
} else {
@ -412,10 +410,12 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.isarray
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
pub(crate) fn is_array(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
match args.get(0).and_then(|x| x.as_object()) {
Some(object) => Ok(JsValue::new(object.borrow().is_array())),
None => Ok(JsValue::new(false)),
}
Ok(args
.get_or_undefined(0)
.as_object()
.map(|obj| obj.borrow().is_array())
.unwrap_or_default()
.into())
}
/// `Array.of(...items)`
@ -439,10 +439,11 @@ impl Array {
// a. Let A be ? Construct(C, « lenNumber »).
// 5. Else,
// a. Let A be ? ArrayCreate(len).
let a = match this.as_object() {
Some(object) if object.is_constructable() => object
let a = match this.as_constructor() {
Some(constructor) => constructor
.construct(&[len.into()], this, context)?
.as_object()
.cloned()
.ok_or_else(|| {
context.construct_type_error("object constructor didn't return an object")
})?,
@ -683,15 +684,9 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = if let Some(arg) = args
.get(0)
.and_then(JsValue::as_object)
.filter(JsObject::is_callable)
{
arg
} else {
return context.throw_type_error("Array.prototype.forEach: invalid callback function");
};
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.forEach: invalid callback function")
})?;
// 4. Let k be 0.
// 5. Repeat, while k < len,
for k in 0..len {
@ -792,7 +787,7 @@ impl Array {
let func = array.get("join", context)?;
// 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
// 4. Return ? Call(func, array).
if let Some(func) = func.as_object().filter(JsObject::is_callable) {
if let Some(func) = func.as_callable() {
func.call(&array.into(), &[], context)
} else {
crate::builtins::object::Object::to_string(&array.into(), &[], context)
@ -1030,15 +1025,9 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = if let Some(arg) = args
.get(0)
.and_then(JsValue::as_object)
.filter(JsObject::is_callable)
{
arg
} else {
return context.throw_type_error("Array.prototype.every: callback is not callable");
};
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.every: callback is not callable")
})?;
let this_arg = args.get_or_undefined(1);
@ -1088,10 +1077,9 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0);
if !callback.is_function() {
return context.throw_type_error("Array.prototype.map: Callbackfn is not callable");
}
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.map: Callbackfn is not callable")
})?;
// 4. Let A be ? ArraySpeciesCreate(O, len).
let a = Self::array_species_create(&o, len, context)?;
@ -1110,7 +1098,7 @@ impl Array {
let k_value = o.get(k, context)?;
// ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value =
context.call(callback, this_arg, &[k_value, k.into(), this.into()])?;
callback.call(this_arg, &[k_value, k.into(), this.into()], context)?;
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
@ -1297,12 +1285,9 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = match args.get(0).and_then(JsValue::as_object) {
Some(predicate) if predicate.is_callable() => predicate,
_ => {
return context.throw_type_error("Array.prototype.find: predicate is not callable")
}
};
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.find: predicate is not callable")
})?;
let this_arg = args.get_or_undefined(1);
@ -1357,13 +1342,9 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = match args.get(0).and_then(JsValue::as_object) {
Some(predicate) if predicate.is_callable() => predicate,
_ => {
return context
.throw_type_error("Array.prototype.reduce: predicate is not callable")
}
};
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.reduce: predicate is not callable")
})?;
let this_arg = args.get_or_undefined(1);
@ -1469,10 +1450,9 @@ impl Array {
let source_len = o.length_of_array_like(context)?;
// 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
let mapper_function = args.get_or_undefined(0);
if !mapper_function.is_function() {
return context.throw_type_error("flatMap mapper function is not callable");
}
let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("flatMap mapper function is not callable")
})?;
// 4. Let A be ? ArraySpeciesCreate(O, 0).
let a = Self::array_species_create(&o, 0, context)?;
@ -1484,7 +1464,7 @@ impl Array {
source_len as u64,
0,
1,
Some(mapper_function.as_object().unwrap()),
Some(mapper_function.clone()),
args.get_or_undefined(1),
context,
)?;
@ -1574,7 +1554,7 @@ impl Array {
// 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
target_index = Self::flatten_into_array(
target,
&element,
element,
element_len as u64,
target_index,
new_depth,
@ -1999,21 +1979,11 @@ impl Array {
let length = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args
.get(0)
.map(|a| a.to_object(context))
.transpose()?
.ok_or_else(|| {
context.construct_type_error(
"missing argument 0 when calling function Array.prototype.filter",
)
})?;
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.filter: `callback` must be callable")
})?;
let this_arg = args.get_or_undefined(1);
if !callback.is_callable() {
return context.throw_type_error("the callback must be callable");
}
// 4. Let A be ? ArraySpeciesCreate(O, 0).
let a = Self::array_species_create(&o, 0, context)?;
@ -2073,15 +2043,9 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = if let Some(arg) = args
.get(0)
.and_then(JsValue::as_object)
.filter(JsObject::is_callable)
{
arg
} else {
return context.throw_type_error("Array.prototype.some: callback is not callable");
};
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.some: callback is not callable")
})?;
// 4. Let k be 0.
// 5. Repeat, while k < len,
@ -2127,10 +2091,9 @@ impl Array {
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let comparefn = match args.get(0).cloned() {
// todo: change to `is_callable` inside `JsValue`
Some(fun) if fun.is_function() => fun,
None => JsValue::undefined(),
let comparefn = match args.get_or_undefined(0) {
JsValue::Object(ref obj) if obj.is_callable() => Some(obj),
JsValue::Undefined => None,
_ => {
return context.throw_type_error(
"The comparison function must be either a function or undefined",
@ -2157,11 +2120,11 @@ impl Array {
}
// 4. If comparefn is not undefined, then
if !comparefn.is_undefined() {
if let Some(cmp) = comparefn {
let args = [x.clone(), y.clone()];
// a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)).
let v = context
.call(&comparefn, &JsValue::Undefined, &args)?
let v = cmp
.call(&JsValue::Undefined, &args, context)?
.to_number(context)?;
// b. If v is NaN, return +0𝔽.
// c. Return v.
@ -2265,13 +2228,10 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = match args.get(0).and_then(JsValue::as_object) {
Some(callback) if callback.is_callable() => callback,
_ => {
return context
.throw_type_error("Array.prototype.reduce: callback function is not callable")
}
};
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context
.construct_type_error("Array.prototype.reduce: callback function is not callable")
})?;
// 4. If len = 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
@ -2363,14 +2323,11 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = match args.get(0).and_then(JsValue::as_object) {
Some(callback) if callback.is_callable() => callback,
_ => {
return context.throw_type_error(
"Array.prototype.reduceRight: callback function is not callable",
)
}
};
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
context.construct_type_error(
"Array.prototype.reduceRight: callback function is not callable",
)
})?;
// 4. If len is 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
@ -2661,7 +2618,7 @@ impl Array {
FunctionBuilder::native(context, Self::values)
.name("values")
.length(0)
.constructable(false)
.constructor(false)
.build()
}
}

45
boa/src/builtins/array_buffer/mod.rs

@ -40,7 +40,7 @@ impl BuiltIn for ArrayBuffer {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
ConstructorBuilder::with_standard_object(
@ -229,35 +229,38 @@ impl ArrayBuffer {
let ctor = obj.species_constructor(StandardObjects::array_buffer_object, context)?;
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
let new = Self::constructor(&ctor.into(), &[new_len.into()], context)?;
let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?;
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
let new_obj = if let Some(obj) = new.as_object() {
obj
} else {
return context.throw_type_error("ArrayBuffer constructor returned non-object value");
};
let mut new_obj_borrow = new_obj.borrow_mut();
let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() {
b
} else {
return context.throw_type_error("ArrayBuffer constructor returned invalid object");
};
let new_obj = new.as_object().cloned().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned non-object value")
})?;
// TODO: Shared Array Buffer
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
if Self::is_detached_buffer(new_array_buffer) {
return context
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
{
let new_obj = new_obj.borrow();
let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned invalid object")
})?;
// TODO: Shared Array Buffer
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
if new_array_buffer.is_detached_buffer() {
return context
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
}
}
// 20. If SameValue(new, O) is true, throw a TypeError exception.
if JsValue::same_value(&new, this) {
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
}
let mut new_obj_borrow = new_obj.borrow_mut();
let new_array_buffer = new_obj_borrow
.as_array_buffer_mut()
.expect("Already checked that `new_obj` was an `ArrayBuffer`");
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
if new_array_buffer.array_buffer_byte_length < new_len {
return context.throw_type_error("New ArrayBuffer length too small");

25
boa/src/builtins/bigint/mod.rs

@ -53,7 +53,7 @@ impl BuiltIn for BigInt {
.static_method(Self::as_int_n, "asIntN", 2)
.static_method(Self::as_uint_n, "asUintN", 2)
.callable(true)
.constructable(false)
.constructor(false)
.property(
to_string_tag,
Self::NAME,
@ -123,23 +123,20 @@ impl BigInt {
/// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
#[inline]
fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult<JsBigInt> {
match value {
value
// 1. If Type(value) is BigInt, return value.
JsValue::BigInt(ref bigint) => return Ok(bigint.clone()),
.as_bigint()
.cloned()
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
// a. Assert: Type(value.[[BigIntData]]) is BigInt.
// b. Return value.[[BigIntData]].
JsValue::Object(ref object) => {
if let Some(bigint) = object.borrow().as_bigint() {
return Ok(bigint.clone());
}
}
_ => {}
}
// 3. Throw a TypeError exception.
Err(context.construct_type_error("'this' is not a BigInt"))
.or_else(|| {
value
.as_object()
.and_then(|obj| obj.borrow().as_bigint().cloned())
})
// 3. Throw a TypeError exception.
.ok_or_else(|| context.construct_type_error("'this' is not a BigInt"))
}
/// `BigInt.prototype.toString( [radix] )`

16
boa/src/builtins/boolean/mod.rs

@ -83,18 +83,10 @@ impl Boolean {
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
fn this_boolean_value(value: &JsValue, context: &mut Context) -> JsResult<bool> {
match value {
JsValue::Boolean(boolean) => return Ok(*boolean),
JsValue::Object(ref object) => {
let object = object.borrow();
if let Some(boolean) = object.as_boolean() {
return Ok(boolean);
}
}
_ => {}
}
Err(context.construct_type_error("'this' is not a boolean"))
value
.as_boolean()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean()))
.ok_or_else(|| context.construct_type_error("'this' is not a boolean"))
}
/// The `toString()` method returns a string representing the specified `Boolean` object.

2
boa/src/builtins/boolean/tests.rs

@ -60,6 +60,6 @@ fn instances_have_correct_proto_set() {
assert_eq!(
&*bool_instance.as_object().unwrap().prototype(),
&bool_prototype.as_object()
&bool_prototype.as_object().cloned()
);
}

10
boa/src/builtins/date/mod.rs

@ -1924,10 +1924,8 @@ impl Date {
/// [spec]: https://tc39.es/ecma262/#sec-thistimevalue
#[inline]
pub fn this_time_value(value: &JsValue, context: &mut Context) -> JsResult<Date> {
if let JsValue::Object(ref object) = value {
if let Some(date) = object.borrow().as_date() {
return Ok(*date);
}
}
Err(context.construct_type_error("'this' is not a Date"))
value
.as_object()
.and_then(|obj| obj.borrow().as_date().copied())
.ok_or_else(|| context.construct_type_error("'this' is not a Date"))
}

467
boa/src/builtins/function/mod.rs

@ -12,11 +12,14 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
use std::{
any::Any,
borrow::Cow,
fmt,
ops::{Deref, DerefMut},
};
use dyn_clone::DynClone;
use gc::{Gc, GcCell};
use crate::{
builtins::BuiltIn,
@ -24,15 +27,22 @@ use crate::{
environment::lexical_environment::Environment,
gc::{Finalize, Trace},
object::JsObject,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
NativeObject, ObjectData,
},
object::{internal_methods::get_prototype_from_constructor, NativeObject, ObjectData},
property::Attribute,
property::PropertyDescriptor,
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::{object::Object, symbol::WellKnownSymbols};
use crate::{
object::{ConstructorBuilder, FunctionBuilder},
property::PropertyKey,
JsString,
};
use crate::{
object::{Ref, RefMut},
value::IntegerOrInfinity,
};
use super::JsArgs;
@ -66,7 +76,6 @@ pub trait ClosureFunctionSignature:
{
}
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
{
@ -116,80 +125,45 @@ impl ConstructorKind {
matches!(self, Self::Derived)
}
}
// We don't use a standalone `NativeObject` for `Captures` because it doesn't
// guarantee that the internal type implements `Clone`.
// This private trait guarantees that the internal type passed to `Captures`
// implements `Clone`, and `DynClone` allows us to implement `Clone` for
// `Box<dyn CapturesObject>`.
trait CapturesObject: NativeObject + DynClone {}
impl<T: NativeObject + Clone> CapturesObject for T {}
dyn_clone::clone_trait_object!(CapturesObject);
/// Wrapper for `Box<dyn NativeObject + Clone>` that allows passing additional
/// Wrapper for `Gc<GcCell<dyn NativeObject>>` that allows passing additional
/// captures through a `Copy` closure.
///
/// Any type implementing `Trace + Any + Debug + Clone`
/// Any type implementing `Trace + Any + Debug`
/// can be used as a capture context, so you can pass e.g. a String,
/// a tuple or even a full struct.
///
/// You can downcast to any type and handle the fail case as you like
/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
/// fails.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Captures(Box<dyn CapturesObject>);
/// You can cast to `Any` with `as_any`, `as_mut_any` and downcast
/// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original
/// type.
#[derive(Clone, Debug, Trace, Finalize)]
pub struct Captures(Gc<GcCell<Box<dyn NativeObject>>>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(captures: T) -> Self
where
T: NativeObject + Clone,
T: NativeObject,
{
Self(Box::new(captures))
Self(Gc::new(GcCell::new(Box::new(captures))))
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or `None` otherwise.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject + Clone,
{
self.0.deref().as_any().downcast_ref::<T>()
}
/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject + Clone,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject + Clone,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
/// Casts `Captures` to `Any`
///
/// # Panics
///
/// Panics if it's already borrowed as `&mut Any`
pub fn as_any(&self) -> gc::GcCellRef<'_, dyn Any> {
Ref::map(self.0.borrow(), |data| data.deref().as_any())
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject + Clone,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
/// Mutably casts `Captures` to `Any`
///
/// # Panics
///
/// Panics if it's already borrowed as `&mut Any`
pub fn as_mut_any(&self) -> gc::GcCellRefMut<'_, Box<dyn NativeObject>, dyn Any> {
RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any())
}
}
@ -198,21 +172,21 @@ impl Captures {
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Clone, Trace, Finalize)]
#[derive(Trace, Finalize)]
pub enum Function {
Native {
#[unsafe_ignore_trace]
function: NativeFunctionSignature,
constructable: bool,
constructor: bool,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>,
constructable: bool,
constructor: bool,
captures: Captures,
},
Ordinary {
constructable: bool,
constructor: bool,
this_mode: ThisMode,
body: RcStatementList,
params: Box<[FormalParameter]>,
@ -220,7 +194,7 @@ pub enum Function {
},
#[cfg(feature = "vm")]
VmOrdinary {
code: gc::Gc<crate::vm::CodeBlock>,
code: Gc<crate::vm::CodeBlock>,
environment: Environment,
},
}
@ -233,7 +207,6 @@ impl fmt::Debug for Function {
impl Function {
// Adds the final rest parameters to the Environment as an array
#[cfg(not(feature = "vm"))]
pub(crate) fn add_rest_param(
param: &FormalParameter,
index: usize,
@ -277,14 +250,14 @@ impl Function {
.expect("Failed to intialize binding");
}
/// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool {
/// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool {
match self {
Self::Native { constructable, .. } => *constructable,
Self::Closure { constructable, .. } => *constructable,
Self::Ordinary { constructable, .. } => *constructable,
Self::Native { constructor, .. } => *constructor,
Self::Closure { constructor, .. } => *constructor,
Self::Ordinary { constructor, .. } => *constructor,
#[cfg(feature = "vm")]
Self::VmOrdinary { code, .. } => code.constructable,
Self::VmOrdinary { code, .. } => code.constructor,
}
}
}
@ -324,7 +297,7 @@ pub(crate) fn make_builtin_fn<N>(
interpreter.standard_objects().function_object().prototype(),
ObjectData::function(Function::Native {
function,
constructable: false,
constructor: false,
}),
);
let attribute = PropertyDescriptor::builder()
@ -362,38 +335,14 @@ impl BuiltInFunctionObject {
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructable: true,
constructor: true,
}),
);
Ok(this.into())
}
fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Ok(JsValue::undefined())
}
/// `Function.prototype.call`
///
/// The call() method invokes self with the first argument as the `this` value.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
fn call(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display()));
}
let this_arg = args.get_or_undefined(0);
// TODO?: 3. Perform PrepareForTailCall
let start = if !args.is_empty() { 1 } else { 0 };
context.call(this, this_arg, &args[start..])
}
/// `Function.prototype.apply`
/// `Function.prototype.apply ( thisArg, argArray )`
///
/// The apply() method invokes self with the first argument as the `this` value
/// and the rest of the arguments provided as an array (or an array-like object).
@ -405,26 +354,156 @@ impl BuiltInFunctionObject {
/// [spec]: https://tc39.es/ecma262/#sec-function.prototype.apply
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
fn apply(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display()));
}
// 1. Let func be the this value.
// 2. If IsCallable(func) is false, throw a TypeError exception.
let func = this.as_callable().ok_or_else(|| {
context.construct_type_error(format!("{} is not a function", this.display()))
})?;
let this_arg = args.get_or_undefined(0);
let arg_array = args.get_or_undefined(1);
// 3. If argArray is undefined or null, then
if arg_array.is_null_or_undefined() {
// a. Perform PrepareForTailCall().
// TODO?: 3.a. PrepareForTailCall
return context.call(this, this_arg, &[]);
// b. Return ? Call(func, thisArg).
return func.call(this_arg, &[], context);
}
// 4. Let argList be ? CreateListFromArrayLike(argArray).
let arg_list = arg_array.create_list_from_array_like(&[], context)?;
// 5. Perform PrepareForTailCall().
// TODO?: 5. PrepareForTailCall
context.call(this, this_arg, &arg_list)
// 6. Return ? Call(func, thisArg, argList).
func.call(this_arg, &arg_list, context)
}
/// `Function.prototype.bind ( thisArg, ...args )`
///
/// The bind() method creates a new function that, when called, has its
/// this keyword set to the provided value, with a given sequence of arguments
/// preceding any provided when the new function is called.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function.prototype.bind
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
fn bind(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let Target be the this value.
// 2. If IsCallable(Target) is false, throw a TypeError exception.
let target = this.as_callable().ok_or_else(|| {
context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method")
})?;
let this_arg = args.get_or_undefined(0).clone();
let bound_args = args.get(1..).unwrap_or_else(|| &[]).to_vec();
let arg_count = bound_args.len() as i64;
// 3. Let F be ? BoundFunctionCreate(Target, thisArg, args).
let f = BoundFunction::create(target.clone(), this_arg, bound_args, context)?;
// 4. Let L be 0.
let mut l = JsValue::new(0);
// 5. Let targetHasLength be ? HasOwnProperty(Target, "length").
// 6. If targetHasLength is true, then
if target.has_own_property("length", context)? {
// a. Let targetLen be ? Get(Target, "length").
let target_len = target.get("length", context)?;
// b. If Type(targetLen) is Number, then
if target_len.is_number() {
// 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen).
match target_len
.to_integer_or_infinity(context)
.expect("to_integer_or_infinity cannot fail for a number")
{
// i. If targetLen is +∞𝔽, set L to +∞.
IntegerOrInfinity::PositiveInfinity => l = f64::INFINITY.into(),
// ii. Else if targetLen is -∞𝔽, set L to 0.
IntegerOrInfinity::NegativeInfinity => {}
// iii. Else,
IntegerOrInfinity::Integer(target_len) => {
// 2. Assert: targetLenAsInt is finite.
// 3. Let argCount be the number of elements in args.
// 4. Set L to max(targetLenAsInt - argCount, 0).
l = (target_len - arg_count).max(0).into();
}
}
}
}
// 7. Perform ! SetFunctionLength(F, L).
f.define_property_or_throw(
"length",
PropertyDescriptor::builder()
.value(l)
.writable(false)
.enumerable(false)
.configurable(true),
context,
)
.expect("defining the `length` property for a new object should not fail");
// 8. Let targetName be ? Get(Target, "name").
let target_name = target.get("name", context)?;
// 9. If Type(targetName) is not String, set targetName to the empty String.
let target_name = target_name
.as_string()
.map_or(JsString::new(""), Clone::clone);
// 10. Perform SetFunctionName(F, targetName, "bound").
set_function_name(&f, &target_name.into(), Some("bound"), context);
// 11. Return F.
Ok(f.into())
}
/// `Function.prototype.call ( thisArg, ...args )`
///
/// The call() method calls a function with a given this value and arguments provided individually.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function.prototype.call
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
fn call(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let func be the this value.
// 2. If IsCallable(func) is false, throw a TypeError exception.
let func = this.as_callable().ok_or_else(|| {
context.construct_type_error(format!("{} is not a function", this.display()))
})?;
let this_arg = args.get_or_undefined(0);
// 3. Perform PrepareForTailCall().
// TODO?: 3. Perform PrepareForTailCall
// 4. Return ? Call(func, thisArg, args).
func.call(this_arg, args.get(1..).unwrap_or(&[]), context)
}
#[allow(clippy::wrong_self_convention)]
fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let object = this.as_object().map(JsObject::borrow);
let function = object
.as_deref()
.and_then(Object::as_function)
.ok_or_else(|| context.construct_type_error("Not a function"))?;
let name = {
// Is there a case here where if there is no name field on a value
// name should default to None? Do all functions have names set?
let value = this.get_field("name", &mut *context)?;
let value = this
.as_object()
.expect("checked that `this` was an object above")
.get("name", &mut *context)?;
if value.is_null_or_undefined() {
None
} else {
@ -432,23 +511,11 @@ impl BuiltInFunctionObject {
}
};
let function = {
let object = this
.as_object()
.map(|object| object.borrow().as_function().cloned());
if let Some(Some(function)) = object {
function
} else {
return context.throw_type_error("Not a function");
}
};
match (&function, name) {
match (function, name) {
(
Function::Native {
function: _,
constructable: _,
constructor: _,
},
Some(name),
) => Ok(format!("function {}() {{\n [native Code]\n}}", &name).into()),
@ -495,6 +562,22 @@ impl BuiltInFunctionObject {
_ => Ok("TODO".into()),
}
}
/// `Function.prototype [ @@hasInstance ] ( V )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance
fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let F be the this value.
// 2. Return ? OrdinaryHasInstance(F, V).
Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into())
}
fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
Ok(JsValue::undefined())
}
}
impl BuiltIn for BuiltInFunctionObject {
@ -511,9 +594,17 @@ impl BuiltIn for BuiltInFunctionObject {
FunctionBuilder::native(context, Self::prototype)
.name("")
.length(0)
.constructable(false)
.constructor(false)
.build_function_prototype(&function_prototype);
let symbol_has_instance = WellKnownSymbols::has_instance();
let has_instance = FunctionBuilder::native(context, Self::has_instance)
.name("[Symbol.iterator]")
.length(1)
.constructor(false)
.build();
let function_object = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
@ -521,11 +612,137 @@ impl BuiltIn for BuiltInFunctionObject {
)
.name(Self::NAME)
.length(Self::LENGTH)
.method(Self::call, "call", 1)
.method(Self::apply, "apply", 1)
.method(Self::bind, "bind", 1)
.method(Self::call, "call", 1)
.method(Self::to_string, "toString", 0)
.property(symbol_has_instance, has_instance, Attribute::default())
.build();
function_object.into()
}
}
/// Abstract operation `SetFunctionName`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-setfunctionname
fn set_function_name(
function: &JsObject,
name: &PropertyKey,
prefix: Option<&str>,
context: &mut Context,
) {
// 1. Assert: F is an extensible object that does not have a "name" own property.
// 2. If Type(name) is Symbol, then
let mut name = match name {
PropertyKey::Symbol(sym) => {
// a. Let description be name's [[Description]] value.
if let Some(desc) = sym.description() {
// c. Else, set name to the string-concatenation of "[", description, and "]".
Cow::Owned(JsString::concat_array(&["[", &desc, "]"]))
} else {
// b. If description is undefined, set name to the empty String.
Cow::Owned(JsString::new(""))
}
}
PropertyKey::String(string) => Cow::Borrowed(string),
PropertyKey::Index(index) => Cow::Owned(JsString::new(format!("{}", index))),
};
// 3. Else if name is a Private Name, then
// a. Set name to name.[[Description]].
// todo: implement Private Names
// 4. If F has an [[InitialName]] internal slot, then
// a. Set F.[[InitialName]] to name.
// todo: implement [[InitialName]] for builtins
// 5. If prefix is present, then
if let Some(prefix) = prefix {
name = Cow::Owned(JsString::concat_array(&[prefix, " ", &name]));
// b. If F has an [[InitialName]] internal slot, then
// i. Optionally, set F.[[InitialName]] to name.
// todo: implement [[InitialName]] for builtins
}
// 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor { [[Value]]: name,
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }).
function
.define_property_or_throw(
"name",
PropertyDescriptor::builder()
.value(name.into_owned())
.writable(false)
.enumerable(false)
.configurable(true),
context,
)
.expect("defining the `name` property must not fail per the spec");
}
/// Binds a `Function Object` when `bind` is called.
#[derive(Debug, Trace, Finalize)]
pub struct BoundFunction {
target_function: JsObject,
this: JsValue,
args: Vec<JsValue>,
}
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)?;
let is_constructor = target_function.is_constructor();
// 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(
proto,
ObjectData::bound_function(
BoundFunction {
target_function,
this,
args,
},
is_constructor,
),
))
}
/// Get a reference to the bound function's this.
pub fn this(&self) -> &JsValue {
&self.this
}
/// Get a reference to the bound function's target function.
pub fn target_function(&self) -> &JsObject {
&self.target_function
}
/// Get a reference to the bound function's args.
pub fn args(&self) -> &[JsValue] {
self.args.as_slice()
}
}

2
boa/src/builtins/intrinsics.rs

@ -31,7 +31,7 @@ fn create_throw_type_error(context: &mut Context) -> JsObject {
context.standard_objects().function_object().prototype(),
ObjectData::function(Function::Native {
function: throw_type_error,
constructable: false,
constructor: false,
}),
);

16
boa/src/builtins/iterable/mod.rs

@ -121,22 +121,26 @@ impl JsValue {
// a. If hint is async, then
if hint == IteratorHint::Async {
// i. Set method to ? GetMethod(obj, @@asyncIterator).
let method = self.get_method(WellKnownSymbols::async_iterator(), context)?;
// ii. If method is undefined, then
if method.is_undefined() {
if let Some(method) =
self.get_method(WellKnownSymbols::async_iterator(), context)?
{
method.into()
} else {
// ii. If method is undefined, then
// 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?;
let sync_method = self
.get_method(WellKnownSymbols::iterator(), context)?
.map_or(JsValue::Undefined, JsValue::from);
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let _sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
todo!("CreateAsyncFromSyncIterator");
}
method
} else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)?
.map_or(JsValue::Undefined, JsValue::from)
}
};

75
boa/src/builtins/json/mod.rs

@ -97,23 +97,22 @@ impl Json {
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let unfiltered = context.eval(script_string.as_bytes())?;
match args.get(1).cloned().unwrap_or_default().as_object() {
// 11. If IsCallable(reviver) is true, then
Some(obj) if obj.is_callable() => {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = context.construct_object();
// b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
root.create_data_property_or_throw("", unfiltered, context)
.expect("CreateDataPropertyOrThrow should never throw here");
// d. Return ? InternalizeJSONProperty(root, rootName, reviver).
Self::internalize_json_property(root, "".into(), obj, context)
}
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = context.construct_object();
// b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
root.create_data_property_or_throw("", unfiltered, context)
.expect("CreateDataPropertyOrThrow should never throw here");
// d. Return ? InternalizeJSONProperty(root, rootName, reviver).
Self::internalize_json_property(&root, "".into(), obj, context)
} else {
// 12. Else,
// a. Return unfiltered.
_ => Ok(unfiltered),
Ok(unfiltered)
}
}
@ -124,9 +123,9 @@ impl Json {
///
/// [spec]: https://tc39.es/ecma262/#sec-internalizejsonproperty
fn internalize_json_property(
holder: JsObject,
holder: &JsObject,
name: JsString,
reviver: JsObject,
reviver: &JsObject,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let val be ? Get(holder, name).
@ -145,9 +144,9 @@ impl Json {
// 1. Let prop be ! ToString(𝔽(I)).
// 2. Let newElement be ? InternalizeJSONProperty(val, prop, reviver).
let new_element = Self::internalize_json_property(
obj.clone(),
obj,
i.to_string().into(),
reviver.clone(),
reviver,
context,
)?;
@ -174,12 +173,8 @@ impl Json {
let p = p.as_string().unwrap();
// 1. Let newElement be ? InternalizeJSONProperty(val, P, reviver).
let new_element = Self::internalize_json_property(
obj.clone(),
p.clone(),
reviver.clone(),
context,
)?;
let new_element =
Self::internalize_json_property(obj, p.clone(), reviver, context)?;
// 2. If newElement is undefined, then
if new_element.is_undefined() {
@ -196,7 +191,7 @@ impl Json {
}
// 3. Return ? Call(reviver, holder, « name, val »).
reviver.call(&holder.into(), &[name.into(), val], context)
reviver.call(&holder.clone().into(), &[name.into(), val], context)
}
/// `JSON.stringify( value[, replacer[, space]] )`
@ -237,7 +232,7 @@ impl Json {
// a. If IsCallable(replacer) is true, then
if replacer_obj.is_callable() {
// i. Set ReplacerFunction to replacer.
replacer_function = Some(replacer_obj)
replacer_function = Some(replacer_obj.clone())
// b. Else,
} else {
// i. Let isArray be ? IsArray(replacer).
@ -352,7 +347,7 @@ impl Json {
// 12. Return ? SerializeJSONProperty(state, the empty String, wrapper).
Ok(
Self::serialize_json_property(&mut state, JsString::new(""), wrapper, context)?
Self::serialize_json_property(&mut state, JsString::new(""), &wrapper, context)?
.map(|s| s.into())
.unwrap_or_default(),
)
@ -367,7 +362,7 @@ impl Json {
fn serialize_json_property(
state: &mut StateRecord,
key: JsString,
holder: JsObject,
holder: &JsObject,
context: &mut Context,
) -> JsResult<Option<JsString>> {
// 1. Let value be ? Get(holder, key).
@ -390,11 +385,11 @@ impl Json {
// 3. If state.[[ReplacerFunction]] is not undefined, then
if let Some(obj) = &state.replacer_function {
// a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »).
value = obj.call(&holder.into(), &[key.into(), value], context)?
value = obj.call(&holder.clone().into(), &[key.into(), value], context)?
}
// 4. If Type(value) is Object, then
if let Some(obj) = value.as_object() {
if let Some(obj) = value.as_object().cloned() {
// a. If value has a [[NumberData]] internal slot, then
if obj.is_number() {
// i. Set value to ? ToNumber(value).
@ -530,11 +525,11 @@ impl Json {
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonobject
fn serialize_json_object(
state: &mut StateRecord,
value: JsObject,
value: &JsObject,
context: &mut Context,
) -> JsResult<JsString> {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter::new(&value);
let limiter = RecursionLimiter::new(value);
if limiter.live {
return Err(context.construct_type_error("cyclic object value"));
}
@ -566,7 +561,7 @@ impl Json {
// 8. For each element P of K, do
for p in &k {
// a. Let strP be ? SerializeJSONProperty(state, P, value).
let str_p = Self::serialize_json_property(state, p.clone(), value.clone(), context)?;
let str_p = Self::serialize_json_property(state, p.clone(), value, context)?;
// b. If strP is not undefined, then
if let Some(str_p) = str_p {
@ -643,11 +638,11 @@ impl Json {
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonarray
fn serialize_json_array(
state: &mut StateRecord,
value: JsObject,
value: &JsObject,
context: &mut Context,
) -> JsResult<JsString> {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter::new(&value);
let limiter = RecursionLimiter::new(value);
if limiter.live {
return Err(context.construct_type_error("cyclic object value"));
}
@ -673,12 +668,8 @@ impl Json {
// 8. Repeat, while index < len,
while index < len {
// a. Let strP be ? SerializeJSONProperty(state, ! ToString(𝔽(index)), value).
let str_p = Self::serialize_json_property(
state,
index.to_string().into(),
value.clone(),
context,
)?;
let str_p =
Self::serialize_json_property(state, index.to_string().into(), value, context)?;
// b. If strP is undefined, then
if let Some(str_p) = str_p {

15
boa/src/builtins/map/map_iterator.rs

@ -66,16 +66,11 @@ impl MapIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let iterator_object = match this {
JsValue::Object(obj) if obj.borrow().is_map_iterator() => obj,
_ => return context.throw_type_error("`this` is not a MapIterator"),
};
let mut iterator_object = iterator_object.borrow_mut();
let map_iterator = iterator_object
.as_map_iterator_mut()
.expect("checked that obj was a map iterator");
let mut map_iterator = this.as_object().map(|obj| obj.borrow_mut());
let map_iterator = map_iterator
.as_mut()
.and_then(|obj| obj.as_map_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not a MapIterator"))?;
let item_kind = map_iterator.map_iteration_kind;

32
boa/src/builtins/map/mod.rs

@ -50,19 +50,19 @@ impl BuiltIn for Map {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
let get_size = FunctionBuilder::native(context, Self::get_size)
.name("get size")
.length(0)
.constructable(false)
.constructor(false)
.build();
let entries_function = FunctionBuilder::native(context, Self::entries)
.name("entries")
.length(0)
.constructable(false)
.constructor(false)
.build();
let map_object = ConstructorBuilder::with_standard_object(
@ -450,19 +450,16 @@ impl Map {
) -> JsResult<JsValue> {
// 1. Let M be the this value.
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
let map = match this {
JsValue::Object(obj) if obj.is_map() => obj,
_ => return context.throw_type_error("`this` is not a Map"),
};
let map = this
.as_object()
.filter(|obj| obj.is_map())
.ok_or_else(|| context.construct_type_error("`this` is not a Map"))?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = match args.get_or_undefined(0) {
JsValue::Object(obj) if obj.is_callable() => obj,
val => {
let name = val.to_string(context)?;
return context.throw_type_error(format!("{} is not a function", name));
}
};
let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error(format!("{} is not a function", callback.display()))
})?;
let this_arg = args.get_or_undefined(1);
@ -543,10 +540,9 @@ pub(crate) fn add_entries_from_iterable(
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If IsCallable(adder) is false, throw a TypeError exception.
let adder = match adder {
JsValue::Object(obj) if obj.is_callable() => obj,
_ => return context.throw_type_error("property `set` of `NewTarget` is not callable"),
};
let adder = adder.as_callable().ok_or_else(|| {
context.construct_type_error("property `set` of `NewTarget` is not callable")
})?;
// 2. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.get_iterator(context, None, None)?;

16
boa/src/builtins/number/mod.rs

@ -184,18 +184,10 @@ impl Number {
///
/// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue
fn this_number_value(value: &JsValue, context: &mut Context) -> JsResult<f64> {
match *value {
JsValue::Integer(integer) => return Ok(f64::from(integer)),
JsValue::Rational(rational) => return Ok(rational),
JsValue::Object(ref object) => {
if let Some(number) = object.borrow().as_number() {
return Ok(number);
}
}
_ => {}
}
Err(context.construct_type_error("'this' is not a number"))
value
.as_number()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_number()))
.ok_or_else(|| context.construct_type_error("'this' is not a number"))
}
/// `Number.prototype.toExponential( [fractionDigits] )`

92
boa/src/builtins/object/for_in_iterator.rs

@ -62,63 +62,59 @@ impl ForInIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref o) = this {
let mut for_in_iterator = o.borrow_mut();
if let Some(iterator) = for_in_iterator.as_for_in_iterator_mut() {
let mut object = iterator.object.to_object(context)?;
loop {
if !iterator.object_was_visited {
let keys = object.__own_property_keys__(context)?;
for k in keys {
match k {
PropertyKey::String(ref k) => {
iterator.remaining_keys.push_back(k.clone());
}
PropertyKey::Index(i) => {
iterator.remaining_keys.push_back(i.to_string().into());
}
_ => {}
}
let mut iterator = this.as_object().map(|obj| obj.borrow_mut());
let iterator = iterator
.as_mut()
.and_then(|obj| obj.as_for_in_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not a ForInIterator"))?;
let mut object = iterator.object.to_object(context)?;
loop {
if !iterator.object_was_visited {
let keys = object.__own_property_keys__(context)?;
for k in keys {
match k {
PropertyKey::String(ref k) => {
iterator.remaining_keys.push_back(k.clone());
}
iterator.object_was_visited = true;
}
while let Some(r) = iterator.remaining_keys.pop_front() {
if !iterator.visited_keys.contains(&r) {
if let Some(desc) = object
.__get_own_property__(&PropertyKey::from(r.clone()), context)?
{
iterator.visited_keys.insert(r.clone());
if desc.expect_enumerable() {
return Ok(create_iter_result_object(
JsValue::new(r.to_string()),
false,
context,
));
}
}
PropertyKey::Index(i) => {
iterator.remaining_keys.push_back(i.to_string().into());
}
_ => {}
}
let proto = object.prototype().clone();
match proto {
Some(o) => {
object = o;
}
_ => {
}
iterator.object_was_visited = true;
}
while let Some(r) = iterator.remaining_keys.pop_front() {
if !iterator.visited_keys.contains(&r) {
if let Some(desc) =
object.__get_own_property__(&PropertyKey::from(r.clone()), context)?
{
iterator.visited_keys.insert(r.clone());
if desc.expect_enumerable() {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
JsValue::new(r.to_string()),
false,
context,
))
));
}
}
iterator.object = JsValue::new(object.clone());
iterator.object_was_visited = false;
}
} else {
context.throw_type_error("`this` is not a ForInIterator")
}
} else {
context.throw_type_error("`this` is not an ForInIterator")
let proto = object.prototype().clone();
match proto {
Some(o) => {
object = o;
}
_ => {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
}
iterator.object = JsValue::new(object.clone());
iterator.object_was_visited = false;
}
}

23
boa/src/builtins/object/mod.rs

@ -134,9 +134,10 @@ impl Object {
let properties = args.get_or_undefined(1);
let obj = match prototype {
JsValue::Object(_) | JsValue::Null => {
JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary())
}
JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data(
prototype.as_object().cloned(),
ObjectData::ordinary(),
),
_ => {
return context.throw_type_error(format!(
"Object prototype may only be an Object or null: {}",
@ -195,10 +196,7 @@ impl Object {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let object = args
.get(0)
.unwrap_or(&JsValue::undefined())
.to_object(context)?;
let object = args.get_or_undefined(0).to_object(context)?;
let descriptors = context.construct_object();
for key in object.borrow().properties().keys() {
@ -321,7 +319,7 @@ impl Object {
}
};
let mut obj = if let Some(obj) = o.as_object() {
let obj = if let Some(obj) = o.as_object() {
obj
} else {
// 3. If Type(O) is not Object, return O.
@ -379,7 +377,7 @@ impl Object {
context: &mut Context,
) -> JsResult<JsValue> {
let object = args.get_or_undefined(0);
if let Some(object) = object.as_object() {
if let JsValue::Object(object) = object {
let key = args
.get(1)
.unwrap_or(&JsValue::Undefined)
@ -391,7 +389,7 @@ impl Object {
object.define_property_or_throw(key, desc, context)?;
Ok(object.into())
Ok(object.clone().into())
} else {
context.throw_type_error("Object.defineProperty called on non-object")
}
@ -413,10 +411,9 @@ impl Object {
context: &mut Context,
) -> JsResult<JsValue> {
let arg = args.get_or_undefined(0);
let arg_obj = arg.as_object();
if let Some(obj) = arg_obj {
if let JsValue::Object(obj) = arg {
let props = args.get_or_undefined(1);
object_define_properties(&obj, props.clone(), context)?;
object_define_properties(obj, props.clone(), context)?;
Ok(arg.clone())
} else {
context.throw_type_error("Expected an object")

24
boa/src/builtins/reflect/mod.rs

@ -110,12 +110,12 @@ impl Reflect {
.ok_or_else(|| context.construct_type_error("target must be a function"))?;
let args_list = args.get_or_undefined(1);
if !target.is_constructable() {
if !target.is_constructor() {
return context.throw_type_error("target must be a constructor");
}
let new_target = if let Some(new_target) = args.get(2) {
if new_target.as_object().map(|o| o.is_constructable()) != Some(true) {
if new_target.as_object().map(|o| o.is_constructor()) != Some(true) {
return context.throw_type_error("newTarget must be constructor");
}
new_target.clone()
@ -147,7 +147,7 @@ impl Reflect {
let key = args.get_or_undefined(1).to_property_key(context)?;
let prop_desc: JsValue = args
.get(2)
.and_then(|v| v.as_object())
.and_then(|v| v.as_object().cloned())
.ok_or_else(|| context.construct_type_error("property descriptor must be an object"))?
.into();
@ -218,13 +218,17 @@ impl Reflect {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
match args.get(0) {
Some(v) if v.is_object() => (),
_ => return context.throw_type_error("target must be an object"),
if args.get_or_undefined(0).is_object() {
// This function is the same as Object.prototype.getOwnPropertyDescriptor, that why
// it is invoked here.
builtins::object::Object::get_own_property_descriptor(
&JsValue::undefined(),
args,
context,
)
} else {
context.throw_type_error("target must be an object")
}
// This function is the same as Object.prototype.getOwnPropertyDescriptor, that why
// it is invoked here.
builtins::object::Object::get_own_property_descriptor(&JsValue::undefined(), args, context)
}
/// Gets the prototype of an object.
@ -375,7 +379,7 @@ impl Reflect {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let mut target = args
let target = args
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?;

137
boa/src/builtins/regexp/mod.rs

@ -80,42 +80,42 @@ impl BuiltIn for RegExp {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_global = FunctionBuilder::native(context, Self::get_global)
.name("get global")
.constructable(false)
.constructor(false)
.build();
let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case)
.name("get ignoreCase")
.constructable(false)
.constructor(false)
.build();
let get_multiline = FunctionBuilder::native(context, Self::get_multiline)
.name("get multiline")
.constructable(false)
.constructor(false)
.build();
let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all)
.name("get dotAll")
.constructable(false)
.constructor(false)
.build();
let get_unicode = FunctionBuilder::native(context, Self::get_unicode)
.name("get unicode")
.constructable(false)
.constructor(false)
.build();
let get_sticky = FunctionBuilder::native(context, Self::get_sticky)
.name("get sticky")
.constructable(false)
.constructor(false)
.build();
let get_flags = FunctionBuilder::native(context, Self::get_flags)
.name("get flags")
.constructable(false)
.constructor(false)
.build();
let get_source = FunctionBuilder::native(context, Self::get_source)
.name("get source")
.constructable(false)
.constructor(false)
.build();
let regexp_object = ConstructorBuilder::with_standard_object(
context,
@ -197,15 +197,7 @@ impl RegExp {
let flags = args.get_or_undefined(1);
// 1. Let patternIsRegExp be ? IsRegExp(pattern).
let pattern_is_regexp = if let JsValue::Object(obj) = &pattern {
if obj.is_regexp() {
Some(obj)
} else {
None
}
} else {
None
};
let pattern_is_regexp = pattern.as_object().filter(|obj| obj.is_regexp());
// 2. If NewTarget is undefined, then
// 3. Else, let newTarget be NewTarget.
@ -414,7 +406,7 @@ impl RegExp {
}
if JsObject::equals(
&object,
object,
&context.standard_objects().regexp_object().prototype,
) {
return Ok(JsValue::undefined());
@ -703,10 +695,10 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let R be the this value.
// 2. If Type(R) is not Object, throw a TypeError exception.
if !this.is_object() {
return context
.throw_type_error("RegExp.prototype.test method called on incompatible value");
}
let this = this.as_object().ok_or_else(|| {
context
.construct_type_error("RegExp.prototype.test method called on incompatible value")
})?;
// 3. Let string be ? ToString(S).
let arg_str = args
@ -745,19 +737,15 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let R be the this value.
// 2. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]).
let obj = this.as_object().unwrap_or_default();
if !obj.is_regexp() {
return Err(
let obj = this
.as_object()
.filter(|obj| obj.is_regexp())
.ok_or_else(|| {
context.construct_type_error("RegExp.prototype.exec called with invalid value")
);
}
})?;
// 3. Let S be ? ToString(string).
let arg_str = args
.get(0)
.cloned()
.unwrap_or_default()
.to_string(context)?;
let arg_str = args.get_or_undefined(0).to_string(context)?;
// 4. Return ? RegExpBuiltinExec(R, S).
if let Some(v) = Self::abstract_builtin_exec(obj, arg_str, context)? {
@ -774,23 +762,20 @@ impl RegExp {
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpexec
pub(crate) fn abstract_exec(
this: &JsValue,
this: &JsObject,
input: JsString,
context: &mut Context,
) -> JsResult<Option<JsObject>> {
// 1. Assert: Type(R) is Object.
let object = this
.as_object()
.ok_or_else(|| context.construct_type_error("RegExpExec called with invalid value"))?;
// 2. Assert: Type(S) is String.
// 3. Let exec be ? Get(R, "exec").
let exec = this.get_field("exec", context)?;
let exec = this.get("exec", context)?;
// 4. If IsCallable(exec) is true, then
if exec.is_function() {
if let Some(exec) = exec.as_callable() {
// a. Let result be ? Call(exec, R, « S »).
let result = context.call(&exec, this, &[input.into()])?;
let result = exec.call(&this.clone().into(), &[input.into()], context)?;
// b. If Type(result) is neither Object nor Null, throw a TypeError exception.
if !result.is_object() && !result.is_null() {
@ -800,16 +785,16 @@ impl RegExp {
}
// c. Return result.
return Ok(result.as_object());
return Ok(result.as_object().cloned());
}
// 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]).
if !object.is_regexp() {
if !this.is_regexp() {
return Err(context.construct_type_error("RegExpExec called with invalid value"));
}
// 6. Return ? RegExpBuiltinExec(R, S).
Self::abstract_builtin_exec(object, input, context)
Self::abstract_builtin_exec(this, input, context)
}
/// `22.2.5.2.2 RegExpBuiltinExec ( R, S )`
@ -819,7 +804,7 @@ impl RegExp {
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpbuiltinexec
pub(crate) fn abstract_builtin_exec(
this: JsObject,
this: &JsObject,
input: JsString,
context: &mut Context,
) -> JsResult<Option<JsObject>> {
@ -1080,7 +1065,7 @@ impl RegExp {
// 6. Else,
if !global {
// a. Return ? RegExpExec(rx, S).
if let Some(v) = Self::abstract_exec(&JsValue::new(rx), arg_str, context)? {
if let Some(v) = Self::abstract_exec(rx, arg_str, context)? {
Ok(v.into())
} else {
Ok(JsValue::null())
@ -1103,8 +1088,7 @@ impl RegExp {
// f. Repeat,
loop {
// i. Let result be ? RegExpExec(rx, S).
let result =
Self::abstract_exec(&JsValue::new(rx.clone()), arg_str.clone(), context)?;
let result = Self::abstract_exec(rx, arg_str.clone(), context)?;
// ii. If result is null, then
// iii. Else,
@ -1194,27 +1178,20 @@ impl RegExp {
) -> JsResult<JsValue> {
// 1. Let R be the this value.
// 2. If Type(R) is not Object, throw a TypeError exception.
if !this.is_object() {
return context.throw_type_error(
let regexp = this.as_object().ok_or_else(|| {
context.construct_type_error(
"RegExp.prototype.match_all method called on incompatible value",
);
}
)
})?;
// 3. Let S be ? ToString(string).
let arg_str = args
.get(0)
.cloned()
.unwrap_or_default()
.to_string(context)?;
let arg_str = args.get_or_undefined(0).to_string(context)?;
// 4. Let C be ? SpeciesConstructor(R, %RegExp%).
let c = this
.as_object()
.unwrap_or_default()
.species_constructor(StandardObjects::regexp_object, context)?;
let c = regexp.species_constructor(StandardObjects::regexp_object, context)?;
// 5. Let flags be ? ToString(? Get(R, "flags")).
let flags = this.get_field("flags", context)?.to_string(context)?;
let flags = regexp.get("flags", context)?.to_string(context)?;
// 6. Let matcher be ? Construct(C, « R, flags »).
let matcher = c.construct(
@ -1222,12 +1199,15 @@ impl RegExp {
&c.clone().into(),
context,
)?;
let matcher = matcher
.as_object()
.expect("construct must always return an Object");
// 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
let last_index = this.get_field("lastIndex", context)?.to_length(context)?;
let last_index = regexp.get("lastIndex", context)?.to_length(context)?;
// 8. Perform ? Set(matcher, "lastIndex", lastIndex, true).
matcher.set_field("lastIndex", last_index, true, context)?;
matcher.set("lastIndex", last_index, true, context)?;
// 9. If flags contains "g", let global be true.
// 10. Else, let global be false.
@ -1239,7 +1219,11 @@ impl RegExp {
// 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode).
RegExpStringIterator::create_regexp_string_iterator(
&matcher, arg_str, global, unicode, context,
matcher.clone(),
arg_str,
global,
unicode,
context,
)
}
@ -1282,7 +1266,10 @@ impl RegExp {
// 5. Let functionalReplace be IsCallable(replaceValue).
let mut replace_value = args.get_or_undefined(1).clone();
let functional_replace = replace_value.is_function();
let functional_replace = replace_value
.as_object()
.map(|obj| obj.is_callable())
.unwrap_or_default();
// 6. If functionalReplace is false, then
if !functional_replace {
@ -1310,7 +1297,7 @@ impl RegExp {
// 11. Repeat, while done is false,
loop {
// a. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(&JsValue::new(rx.clone()), arg_str.clone(), context)?;
let result = Self::abstract_exec(rx, arg_str.clone(), context)?;
// b. If result is null, set done to true.
// c. Else,
@ -1530,7 +1517,7 @@ impl RegExp {
}
// 6. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(&JsValue::new(rx.clone()), arg_str, context)?;
let result = Self::abstract_exec(rx, arg_str, context)?;
// 7. Let currentLastIndex be ? Get(rx, "lastIndex").
let current_last_index = rx.get("lastIndex", context)?;
@ -1602,10 +1589,14 @@ impl RegExp {
// 10. Let splitter be ? Construct(C, « rx, newFlags »).
let splitter = constructor.construct(
&[rx.into(), new_flags.into()],
&[this.clone(), new_flags.into()],
&constructor.clone().into(),
context,
)?;
let splitter = splitter
.as_object()
// todo: remove when we handle realms on `get_prototype_from_constructor` and make `construct` always return a `JsObject`
.ok_or_else(|| context.construct_type_error("constructor did not return an object"))?;
// 11. Let A be ! ArrayCreate(0).
let a = Array::array_create(0, None, context).unwrap();
@ -1632,7 +1623,7 @@ impl RegExp {
// 16. If size is 0, then
if size == 0 {
// a. Let z be ? RegExpExec(splitter, S).
let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?;
let result = Self::abstract_exec(splitter, arg_str.clone(), context)?;
// b. If z is not null, return A.
if result.is_some() {
@ -1655,18 +1646,16 @@ impl RegExp {
// 19. Repeat, while q < size,
while q < size {
// a. Perform ? Set(splitter, "lastIndex", 𝔽(q), true).
splitter.set_field("lastIndex", JsValue::new(q), true, context)?;
splitter.set("lastIndex", JsValue::new(q), true, context)?;
// b. Let z be ? RegExpExec(splitter, S).
let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?;
let result = Self::abstract_exec(splitter, arg_str.clone(), context)?;
// c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching).
// d. Else,
if let Some(result) = result {
// i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))).
let mut e = splitter
.get_field("lastIndex", context)?
.to_length(context)?;
let mut e = splitter.get("lastIndex", context)?.to_length(context)?;
// ii. Set e to min(e, size).
e = std::cmp::min(e, size);

132
boa/src/builtins/regexp/regexp_string_iterator.rs

@ -23,7 +23,7 @@ use crate::{
// TODO: See todos in create_regexp_string_iterator and next.
#[derive(Debug, Clone, Finalize, Trace)]
pub struct RegExpStringIterator {
matcher: JsValue,
matcher: JsObject,
string: JsString,
global: bool,
unicode: bool,
@ -32,7 +32,7 @@ pub struct RegExpStringIterator {
// TODO: See todos in create_regexp_string_iterator and next.
impl RegExpStringIterator {
fn new(matcher: JsValue, string: JsString, global: bool, unicode: bool) -> Self {
fn new(matcher: JsObject, string: JsString, global: bool, unicode: bool) -> Self {
Self {
matcher,
string,
@ -49,7 +49,7 @@ impl RegExpStringIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-createregexpstringiterator
pub(crate) fn create_regexp_string_iterator(
matcher: &JsValue,
matcher: JsObject,
string: JsString,
global: bool,
unicode: bool,
@ -69,83 +69,71 @@ impl RegExpStringIterator {
let regexp_string_iterator = JsObject::from_proto_and_data(
context.iterator_prototypes().regexp_string_iterator(),
ObjectData::reg_exp_string_iterator(Self::new(
matcher.clone(),
string,
global,
unicode,
)),
ObjectData::reg_exp_string_iterator(Self::new(matcher, string, global, unicode)),
);
Ok(regexp_string_iterator.into())
}
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(iterator) = object.as_regexp_string_iterator_mut() {
if iterator.completed {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
// TODO: This is the code that should be created as a closure in create_regexp_string_iterator.
// i. Let match be ? RegExpExec(R, S).
let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?;
if let Some(m) = m {
// iii. If global is false, then
if !iterator.global {
// 1. Perform ? Yield(match).
// 2. Return undefined.
iterator.completed = true;
return Ok(create_iter_result_object(m.into(), false, context));
}
// iv. Let matchStr be ? ToString(? Get(match, "0")).
let m_str = m.get("0", context)?.to_string(context)?;
// v. If matchStr is the empty String, then
if m_str.is_empty() {
// 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
let this_index = iterator
.matcher
.get_field("lastIndex", context)?
.to_length(context)?;
// 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
let next_index = advance_string_index(
iterator.string.clone(),
this_index,
iterator.unicode,
);
// 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
iterator
.matcher
.set_field("lastIndex", next_index, true, context)?;
}
// vi. Perform ? Yield(match).
Ok(create_iter_result_object(m.into(), false, context))
} else {
// ii. If match is null, return undefined.
iterator.completed = true;
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
} else {
context.throw_type_error("`this` is not a RegExpStringIterator")
let mut iterator = this.as_object().map(|obj| obj.borrow_mut());
let iterator = iterator
.as_mut()
.and_then(|obj| obj.as_regexp_string_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not a RegExpStringIterator"))?;
if iterator.completed {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
// TODO: This is the code that should be created as a closure in create_regexp_string_iterator.
// i. Let match be ? RegExpExec(R, S).
let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?;
if let Some(m) = m {
// iii. If global is false, then
if !iterator.global {
// 1. Perform ? Yield(match).
// 2. Return undefined.
iterator.completed = true;
return Ok(create_iter_result_object(m.into(), false, context));
}
// iv. Let matchStr be ? ToString(? Get(match, "0")).
let m_str = m.get("0", context)?.to_string(context)?;
// v. If matchStr is the empty String, then
if m_str.is_empty() {
// 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
let this_index = iterator
.matcher
.get("lastIndex", context)?
.to_length(context)?;
// 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
let next_index =
advance_string_index(iterator.string.clone(), this_index, iterator.unicode);
// 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
iterator
.matcher
.set("lastIndex", next_index, true, context)?;
}
// vi. Perform ? Yield(match).
Ok(create_iter_result_object(m.into(), false, context))
} else {
context.throw_type_error("`this` is not a RegExpStringIterator")
// ii. If match is null, return undefined.
iterator.completed = true;
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
}

62
boa/src/builtins/set/mod.rs

@ -47,11 +47,11 @@ impl BuiltIn for Set {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
let size_getter = FunctionBuilder::native(context, Self::size_getter)
.constructable(false)
.constructor(false)
.name("get size")
.build();
@ -62,7 +62,7 @@ impl BuiltIn for Set {
let values_function = FunctionBuilder::native(context, Self::values)
.name("values")
.length(0)
.constructable(false)
.constructor(false)
.build();
let set_object = ConstructorBuilder::with_standard_object(
@ -142,9 +142,9 @@ impl Set {
let adder = obj.get("add", context)?;
// 6
if !adder.is_function() {
return context.throw_type_error("'add' of 'newTarget' is not a function");
}
let adder = adder.as_callable().ok_or_else(|| {
context.construct_type_error("'add' of 'newTarget' is not a function")
})?;
// 7
let iterator_record = iterable.clone().get_iterator(context, None, None)?;
@ -158,7 +158,7 @@ impl Set {
let next_value = next.value;
// d, e
if let Err(status) = context.call(&adder, &obj.clone().into(), &[next_value]) {
if let Err(status) = adder.call(&obj.clone().into(), &[next_value], context) {
return iterator_record.close(Err(status), context);
}
@ -337,17 +337,15 @@ impl Set {
let mut index = 0;
while index < Set::get_size(this, context)? {
let arguments = if let JsValue::Object(ref object) = this {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
set.get_index(index)
.map(|value| [value.clone(), value.clone(), this.clone()])
} else {
return context.throw_type_error("'this' is not a Set");
}
} else {
return context.throw_type_error("'this' is not a Set");
};
let arguments = this
.as_object()
.and_then(|obj| {
obj.borrow().as_set_ref().map(|set| {
set.get_index(index)
.map(|value| [value.clone(), value.clone(), this.clone()])
})
})
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))?;
if let Some(arguments) = arguments {
context.call(callback_arg, &this_arg, &arguments)?;
@ -376,14 +374,13 @@ impl Set {
) -> JsResult<JsValue> {
let value = args.get_or_undefined(0);
if let JsValue::Object(ref object) = this {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
return Ok(set.contains(value).into());
}
}
Err(context.construct_type_error("'this' is not a Set"))
this.as_object()
.and_then(|obj| {
obj.borrow()
.as_set_ref()
.map(|set| set.contains(value).into())
})
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))
}
/// `Set.prototype.values( )`
@ -426,15 +423,8 @@ impl Set {
/// Helper function to get the size of the set.
fn get_size(set: &JsValue, context: &mut Context) -> JsResult<usize> {
if let JsValue::Object(ref object) = set {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
Ok(set.size())
} else {
Err(context.construct_type_error("'this' is not a Set"))
}
} else {
Err(context.construct_type_error("'this' is not a Set"))
}
set.as_object()
.and_then(|obj| obj.borrow().as_set_ref().map(|set| set.size()))
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))
}
}

109
boa/src/builtins/set/set_iterator.rs

@ -63,73 +63,62 @@ impl SetIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(set_iterator) = object.as_set_iterator_mut() {
let m = &set_iterator.iterated_set;
let mut index = set_iterator.next_index;
let item_kind = &set_iterator.iteration_kind;
let mut set_iterator = this.as_object().map(|obj| obj.borrow_mut());
if set_iterator.iterated_set.is_undefined() {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let set_iterator = set_iterator
.as_mut()
.and_then(|obj| obj.as_set_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not an SetIterator"))?;
{
let m = &set_iterator.iterated_set;
let mut index = set_iterator.next_index;
let item_kind = &set_iterator.iteration_kind;
if let JsValue::Object(ref object) = m {
if let Some(entries) = object.borrow().as_set_ref() {
let num_entries = entries.size();
while index < num_entries {
let e = entries.get_index(index);
index += 1;
set_iterator.next_index = index;
if let Some(value) = e {
match item_kind {
PropertyNameKind::Value => {
return Ok(create_iter_result_object(
value.clone(),
false,
context,
));
}
PropertyNameKind::KeyAndValue => {
let result = Array::create_array_from_list(
[value.clone(), value.clone()],
context,
);
return Ok(create_iter_result_object(
result.into(),
false,
context,
));
}
PropertyNameKind::Key => {
panic!("tried to collect only keys of Set")
}
}
}
}
} else {
return Err(context.construct_type_error("'this' is not a Set"));
}
} else {
return Err(context.construct_type_error("'this' is not a Set"));
}
set_iterator.iterated_set = JsValue::undefined();
Ok(create_iter_result_object(
if set_iterator.iterated_set.is_undefined() {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
} else {
context.throw_type_error("`this` is not an SetIterator")
));
}
let entries = m.as_object().map(|obj| obj.borrow());
let entries = entries
.as_ref()
.and_then(|obj| obj.as_set_ref())
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))?;
let num_entries = entries.size();
while index < num_entries {
let e = entries.get_index(index);
index += 1;
set_iterator.next_index = index;
if let Some(value) = e {
match item_kind {
PropertyNameKind::Value => {
return Ok(create_iter_result_object(value.clone(), false, context));
}
PropertyNameKind::KeyAndValue => {
let result = Array::create_array_from_list(
[value.clone(), value.clone()],
context,
);
return Ok(create_iter_result_object(result.into(), false, context));
}
PropertyNameKind::Key => {
panic!("tried to collect only keys of Set")
}
}
}
}
} else {
context.throw_type_error("`this` is not an SetIterator")
}
set_iterator.iterated_set = JsValue::undefined();
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
/// Create the %SetIteratorPrototype% object

68
boa/src/builtins/string/mod.rs

@ -225,18 +225,10 @@ impl String {
}
fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult<JsString> {
match this {
JsValue::String(ref string) => return Ok(string.clone()),
JsValue::Object(ref object) => {
let object = object.borrow();
if let Some(string) = object.as_string() {
return Ok(string);
}
}
_ => {}
}
Err(context.construct_type_error("'this' is not a string"))
this.as_string()
.cloned()
.or_else(|| this.as_object().and_then(|obj| obj.borrow().as_string()))
.ok_or_else(|| context.construct_type_error("'this' is not a string"))
}
/// `String.fromCharCode(...codePoints)`
@ -720,10 +712,10 @@ impl String {
}
fn is_regexp_object(value: &JsValue) -> bool {
match value {
JsValue::Object(ref obj) => obj.borrow().is_regexp(),
_ => false,
}
value
.as_object()
.map(|obj| obj.borrow().is_regexp())
.unwrap_or_default()
}
/// `String.prototype.replace( regexp|substr, newSubstr|function )`
@ -759,12 +751,12 @@ impl String {
let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?;
// b. If replacer is not undefined, then
if !replacer.is_undefined() {
if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return context.call(
&replacer,
return replacer.call(
search_value,
&[this.clone(), replace_value.clone()],
context,
);
}
}
@ -776,7 +768,10 @@ impl String {
let search_str = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value.is_function();
let functional_replace = replace_value
.as_object()
.map(|obj| obj.is_callable())
.unwrap_or_default();
// 6. If functionalReplace is false, then
// a. Set replaceValue to ? ToString(replaceValue).
@ -868,7 +863,7 @@ impl String {
// 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() {
// a. Let isRegExp be ? IsRegExp(searchValue).
if let Some(obj) = search_value.as_object() {
if let Some(obj) = search_value.as_object().filter(|obj| obj.is_regexp()) {
// b. If isRegExp is true, then
if obj.is_regexp() {
// i. Let flags be ? Get(searchValue, "flags").
@ -890,9 +885,9 @@ impl String {
let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?;
// d. If replacer is not undefined, then
if !replacer.is_undefined() {
if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return context.call(&replacer, search_value, &[o.into(), replace_value.clone()]);
return replacer.call(search_value, &[o.into(), replace_value.clone()], context);
}
}
@ -903,7 +898,10 @@ impl String {
let search_string = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value.is_function();
let functional_replace = replace_value
.as_object()
.map(|obj| obj.is_callable())
.unwrap_or_default();
// 6. If functionalReplace is false, then
let replace_value_string = if !functional_replace {
@ -1127,9 +1125,9 @@ impl String {
// a. Let matcher be ? GetMethod(regexp, @@match).
let matcher = regexp.get_method(WellKnownSymbols::r#match(), context)?;
// b. If matcher is not undefined, then
if !matcher.is_undefined() {
if let Some(matcher) = matcher {
// i. Return ? Call(matcher, regexp, « O »).
return context.call(&matcher, regexp, &[o.clone()]);
return matcher.call(regexp, &[o.clone()], context);
}
}
@ -1492,9 +1490,9 @@ impl String {
// a. Let splitter be ? GetMethod(separator, @@split).
let splitter = separator.get_method(WellKnownSymbols::split(), context)?;
// b. If splitter is not undefined, then
if !splitter.is_undefined() {
if let Some(splitter) = splitter {
// i. Return ? Call(splitter, separator, « O, limit »).
return context.call(&splitter, separator, &[this.clone(), limit.clone()]);
return splitter.call(separator, &[this.clone(), limit.clone()], context);
}
}
@ -1661,9 +1659,9 @@ impl String {
if !regexp.is_null_or_undefined() {
// a. Let isRegExp be ? IsRegExp(regexp).
// b. If isRegExp is true, then
if regexp.as_object().unwrap_or_default().is_regexp() {
if let Some(regexp_obj) = regexp.as_object().filter(|obj| obj.is_regexp()) {
// i. Let flags be ? Get(regexp, "flags").
let flags = regexp.get_field("flags", context)?;
let flags = regexp_obj.get("flags", context)?;
// ii. Perform ? RequireObjectCoercible(flags).
flags.require_object_coercible(context)?;
@ -1675,13 +1673,11 @@ impl String {
);
}
}
// c. Let matcher be ? GetMethod(regexp, @@matchAll).
let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?;
// d. If matcher is not undefined, then
if !matcher.is_undefined() {
// i. Return ? Call(matcher, regexp, « O »).
return context.call(&matcher, regexp, &[o.clone()]);
if let Some(matcher) = matcher {
return matcher.call(regexp, &[o.clone()], context);
}
}
@ -1757,9 +1753,9 @@ impl String {
// a. Let searcher be ? GetMethod(regexp, @@search).
let searcher = regexp.get_method(WellKnownSymbols::search(), context)?;
// b. If searcher is not undefined, then
if !searcher.is_undefined() {
if let Some(searcher) = searcher {
// i. Return ? Call(searcher, regexp, « O »).
return context.call(&searcher, regexp, &[o.clone()]);
return searcher.call(regexp, &[o.clone()], context);
}
}

67
boa/src/builtins/string/string_iterator.rs

@ -32,42 +32,39 @@ impl StringIterator {
}
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(string_iterator) = object.as_string_iterator_mut() {
if string_iterator.string.is_undefined() {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let native_string = string_iterator.string.to_string(context)?;
let len = native_string.encode_utf16().count() as i32;
let position = string_iterator.next_index;
if position >= len {
string_iterator.string = JsValue::undefined();
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let (_, code_unit_count, _) =
code_point_at(native_string, position).expect("Invalid code point position");
string_iterator.next_index += code_unit_count as i32;
let result_string = crate::builtins::string::String::substring(
&string_iterator.string,
&[position.into(), string_iterator.next_index.into()],
context,
)?;
Ok(create_iter_result_object(result_string, false, context))
} else {
context.throw_type_error("`this` is not an ArrayIterator")
}
} else {
context.throw_type_error("`this` is not an ArrayIterator")
let mut string_iterator = this.as_object().map(|obj| obj.borrow_mut());
let string_iterator = string_iterator
.as_mut()
.and_then(|obj| obj.as_string_iterator_mut())
.ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?;
if string_iterator.string.is_undefined() {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let native_string = string_iterator.string.to_string(context)?;
let len = native_string.encode_utf16().count() as i32;
let position = string_iterator.next_index;
if position >= len {
string_iterator.string = JsValue::undefined();
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let (_, code_unit_count, _) =
code_point_at(native_string, position).expect("Invalid code point position");
string_iterator.next_index += code_unit_count as i32;
let result_string = crate::builtins::string::String::substring(
&string_iterator.string,
&[position.into(), string_iterator.next_index.into()],
context,
)?;
Ok(create_iter_result_object(result_string, false, context))
}
/// Create the %ArrayIteratorPrototype% object

22
boa/src/builtins/symbol/mod.rs

@ -102,12 +102,12 @@ impl BuiltIn for Symbol {
let to_primitive = FunctionBuilder::native(context, Self::to_primitive)
.name("[Symbol.toPrimitive]")
.length(1)
.constructable(false)
.constructor(false)
.build();
let get_description = FunctionBuilder::native(context, Self::get_description)
.name("get description")
.constructable(false)
.constructor(false)
.build();
let symbol_object = ConstructorBuilder::with_standard_object(
@ -141,7 +141,7 @@ impl BuiltIn for Symbol {
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.callable(true)
.constructable(false)
.constructor(false)
.property(
symbol_to_string_tag,
Self::NAME,
@ -190,18 +190,10 @@ impl Symbol {
}
fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> {
match value {
JsValue::Symbol(ref symbol) => return Ok(symbol.clone()),
JsValue::Object(ref object) => {
let object = object.borrow();
if let Some(symbol) = object.as_symbol() {
return Ok(symbol);
}
}
_ => {}
}
Err(context.construct_type_error("'this' is not a Symbol"))
value
.as_symbol()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol()))
.ok_or_else(|| context.construct_type_error("'this' is not a Symbol"))
}
/// `Symbol.prototype.toString()`

84
boa/src/builtins/typed_array/mod.rs

@ -56,7 +56,7 @@ macro_rules! typed_array {
let get_species = FunctionBuilder::native(context, TypedArray::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
ConstructorBuilder::with_standard_object(
@ -146,7 +146,11 @@ macro_rules! typed_array {
// ii. If firstArgument has a [[TypedArrayName]] internal slot, then
if first_argument.is_typed_array() {
// 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument).
TypedArray::initialize_from_typed_array(&o, first_argument, context)?;
TypedArray::initialize_from_typed_array(
&o,
first_argument.clone(),
context,
)?;
} else if first_argument.is_array_buffer() {
// iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then
@ -159,7 +163,7 @@ macro_rules! typed_array {
// 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length).
TypedArray::initialize_from_array_buffer(
&o,
first_argument,
first_argument.clone(),
byte_offset,
length,
context,
@ -177,10 +181,13 @@ macro_rules! typed_array {
first_argument_v.get_method(WellKnownSymbols::replace(), context)?;
// 3. If usingIterator is not undefined, then
if !using_iterator.is_undefined() {
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(firstArgument, usingIterator).
let values =
iterable_to_list(context, first_argument_v, Some(using_iterator))?;
let values = iterable_to_list(
context,
first_argument_v,
Some(using_iterator.into()),
)?;
// b. Perform ? InitializeTypedArrayFromList(O, values).
TypedArray::initialize_from_list(&o, values, context)?;
@ -233,38 +240,38 @@ impl TypedArray {
pub(crate) fn init(context: &mut Context) -> JsObject {
let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]")
.constructable(false)
.constructor(false)
.build();
let get_buffer = FunctionBuilder::native(context, Self::buffer)
.name("get buffer")
.constructable(false)
.constructor(false)
.build();
let get_byte_length = FunctionBuilder::native(context, Self::byte_length)
.name("get byteLength")
.constructable(false)
.constructor(false)
.build();
let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset)
.name("get byteOffset")
.constructable(false)
.constructor(false)
.build();
let get_length = FunctionBuilder::native(context, Self::length)
.name("get length")
.constructable(false)
.constructor(false)
.build();
let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag)
.name("get [Symbol.toStringTag]")
.constructable(false)
.constructor(false)
.build();
let values_function = FunctionBuilder::native(context, Self::values)
.name("values")
.length(0)
.constructable(false)
.constructor(false)
.build();
let object = ConstructorBuilder::with_standard_object(
@ -380,7 +387,7 @@ impl TypedArray {
// 1. Let C be the this value.
// 2. If IsConstructor(C) is false, throw a TypeError exception.
let constructor = match this.as_object() {
Some(obj) if obj.is_constructable() => obj,
Some(obj) if obj.is_constructor() => obj,
_ => {
return context
.throw_type_error("TypedArray.from called on non-constructable value")
@ -409,13 +416,13 @@ impl TypedArray {
let this_arg = args.get_or_undefined(2);
// 6. If usingIterator is not undefined, then
if !using_iterator.is_undefined() {
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(source, usingIterator).
let values = iterable_to_list(context, source.clone(), Some(using_iterator))?;
let values = iterable_to_list(context, source.clone(), Some(using_iterator.into()))?;
// b. Let len be the number of elements in values.
// c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let target_obj = Self::create(&constructor, &[values.len().into()], context)?;
let target_obj = Self::create(constructor, &[values.len().into()], context)?;
// d. Let k be 0.
// e. Repeat, while k < len,
@ -451,7 +458,7 @@ impl TypedArray {
let len = array_like.length_of_array_like(context)?;
// 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let target_obj = Self::create(&constructor, &[len.into()], context)?;
let target_obj = Self::create(constructor, &[len.into()], context)?;
// 11. Let k be 0.
// 12. Repeat, while k < len,
@ -490,14 +497,14 @@ impl TypedArray {
// 2. Let C be the this value.
// 3. If IsConstructor(C) is false, throw a TypeError exception.
let constructor = match this.as_object() {
Some(obj) if obj.is_constructable() => obj,
Some(obj) if obj.is_constructor() => obj,
_ => {
return context.throw_type_error("TypedArray.of called on non-constructable value")
}
};
// 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let new_obj = Self::create(&constructor, &[args.len().into()], context)?;
let new_obj = Self::create(constructor, &[args.len().into()], context)?;
// 5. Let k be 0.
// 6. Repeat, while k < len,
@ -846,7 +853,7 @@ impl TypedArray {
// 3. Return CreateArrayIterator(O, key+value).
Ok(ArrayIterator::create_array_iterator(
o,
o.clone(),
PropertyNameKind::KeyAndValue,
context,
))
@ -1057,7 +1064,7 @@ impl TypedArray {
}
// 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »).
let a = Self::species_create(&obj, o.typed_array_name(), &[captured.into()], context)?;
let a = Self::species_create(obj, o.typed_array_name(), &[captured.into()], context)?;
// 10. Let n be 0.
// 11. For each element e of kept, do
@ -1473,7 +1480,7 @@ impl TypedArray {
// 3. Return CreateArrayIterator(O, key).
Ok(ArrayIterator::create_array_iterator(
o,
o.clone(),
PropertyNameKind::Key,
context,
))
@ -1616,7 +1623,7 @@ impl TypedArray {
};
// 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »).
let a = Self::species_create(&obj, o.typed_array_name(), &[len.into()], context)?;
let a = Self::species_create(obj, o.typed_array_name(), &[len.into()], context)?;
// 6. Let k be 0.
// 7. Repeat, while k < len,
@ -1888,12 +1895,12 @@ impl TypedArray {
// 6. If source is an Object that has a [[TypedArrayName]] internal slot, then
JsValue::Object(source) if source.is_typed_array() => {
// a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
Self::set_typed_array_from_typed_array(&target, target_offset, source, context)?;
Self::set_typed_array_from_typed_array(target, target_offset, source, context)?;
}
// 7. Else,
_ => {
// a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source).
Self::set_typed_array_from_array_like(&target, target_offset, source, context)?;
Self::set_typed_array_from_array_like(target, target_offset, source, context)?;
}
}
@ -2287,7 +2294,7 @@ impl TypedArray {
let count = std::cmp::max(r#final - k, 0) as usize;
// 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »).
let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?;
let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?;
let a_borrow = a.borrow();
let a_array = a_borrow
.as_typed_array()
@ -2658,7 +2665,7 @@ impl TypedArray {
}
// 12. Return obj.
Ok(obj.into())
Ok(obj.clone().into())
}
/// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )`
@ -2732,7 +2739,7 @@ impl TypedArray {
// 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
// 20. Return ? TypedArraySpeciesCreate(O, argumentsList).
Ok(Self::species_create(
&obj,
obj,
o.typed_array_name(),
&[
buffer.clone().into(),
@ -2768,7 +2775,7 @@ impl TypedArray {
// 3. Return CreateArrayIterator(O, value).
Ok(ArrayIterator::create_array_iterator(
o,
o.clone(),
PropertyNameKind::Value,
context,
))
@ -2886,10 +2893,8 @@ impl TypedArray {
}
}
drop(obj_borrow);
// 4. Return newTypedArray.
Ok(obj)
Ok(obj.clone())
}
/// <https://tc39.es/ecma262/#sec-allocatetypedarraybuffer>
@ -2942,10 +2947,10 @@ impl TypedArray {
let len = values.len();
{
let mut o = o.borrow_mut();
let mut o_inner = o.as_typed_array_mut().expect("expected a TypedArray");
let o_inner = o.as_typed_array_mut().expect("expected a TypedArray");
// 2. Perform ? AllocateTypedArrayBuffer(O, len).
TypedArray::allocate_buffer(&mut o_inner, len, context)?;
TypedArray::allocate_buffer(o_inner, len, context)?;
}
// 3. Let k be 0.
@ -3037,10 +3042,6 @@ impl TypedArray {
let src_data_obj = src_array
.viewed_array_buffer()
.expect("Already checked for detached buffer");
let src_data_obj_b = src_data_obj.borrow();
let src_data = src_data_obj_b
.as_array_buffer()
.expect("Already checked for detached buffer");
// 3. Let constructorName be the String value of O.[[TypedArrayName]].
// 4. Let elementType be the Element Type value in Table 73 for constructorName.
@ -3069,6 +3070,11 @@ impl TypedArray {
let buffer_constructor =
src_data_obj.species_constructor(StandardObjects::array_buffer_object, context)?;
let src_data_obj_b = src_data_obj.borrow();
let src_data = src_data_obj_b
.as_array_buffer()
.expect("Already checked for detached buffer");
// 14. If elementType is the same as srcType, then
let data = if constructor_name == src_name {
// a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor).

2
boa/src/bytecompiler.rs

@ -849,7 +849,7 @@ impl ByteCompiler {
let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true);
if let FunctionKind::Arrow = kind {
code.constructable = false;
code.constructor = false;
code.this_mode = ThisMode::Lexical;
}

29
boa/src/class.rs

@ -110,18 +110,18 @@ impl<T: Class> ClassConstructor for T {
));
}
let class_constructor =
if let Some(obj) = context.global_object().get(T::NAME, context)?.as_object() {
obj
} else {
return context.throw_type_error(format!(
"invalid constructor for native class `{}` ",
T::NAME
));
};
let class_constructor = context.global_object().get(T::NAME, context)?;
let class_constructor = if let JsValue::Object(ref obj) = class_constructor {
obj
} else {
return context.throw_type_error(format!(
"invalid constructor for native class `{}` ",
T::NAME
));
};
let class_prototype =
if let Some(obj) = class_constructor.get(PROTOTYPE, context)?.as_object() {
obj
if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? {
obj.clone()
} else {
return context.throw_type_error(format!(
"invalid default prototype for native class `{}`",
@ -131,12 +131,13 @@ impl<T: Class> ClassConstructor for T {
let prototype = this
.as_object()
.and_then(|obj| {
.cloned()
.map(|obj| {
obj.get(PROTOTYPE, context)
.map(|o| o.as_object())
.transpose()
.map(|val| val.as_object().cloned())
})
.transpose()?
.flatten()
.unwrap_or(class_prototype);
let native_instance = Self::constructor(this, args, context)?;

18
boa/src/context.rs

@ -426,7 +426,8 @@ impl Default for Context {
.get("prototype", &mut context)
.expect("prototype must exist")
.as_object()
.expect("prototype must be object");
.expect("prototype must be object")
.clone();
context.typed_array_constructor.constructor = typed_array_constructor_constructor;
context.typed_array_constructor.prototype = typed_array_constructor_prototype;
context.create_intrinsics();
@ -516,10 +517,9 @@ impl Context {
this: &JsValue,
args: &[JsValue],
) -> JsResult<JsValue> {
match *f {
JsValue::Object(ref object) if object.is_callable() => object.call(this, args, self),
_ => self.throw_type_error("Value is not callable"),
}
f.as_callable()
.ok_or_else(|| self.construct_type_error("Value is not callable"))
.and_then(|obj| obj.call(this, args, self))
}
/// Return the global object.
@ -694,7 +694,7 @@ impl Context {
name: N,
params: P,
mut body: StatementList,
constructable: bool,
constructor: bool,
this_mode: ThisMode,
) -> JsResult<JsValue>
where
@ -715,7 +715,7 @@ impl Context {
let params = params.into();
let params_len = params.len();
let func = Function::Ordinary {
constructable,
constructor,
this_mode,
body: RcStatementList::from(body),
params,
@ -782,7 +782,7 @@ impl Context {
let function = FunctionBuilder::native(self, body)
.name(name)
.length(length)
.constructable(true)
.constructor(true)
.build();
self.global_object().insert_property(
@ -825,7 +825,7 @@ impl Context {
let function = FunctionBuilder::closure(self, body)
.name(name)
.length(length)
.constructable(true)
.constructor(true)
.build();
self.global_object().insert_property(

8
boa/src/exec/tests.rs

@ -792,10 +792,12 @@ mod in_operator {
let bar_obj = bar_val.as_object().unwrap();
let foo_val = forward_val(&mut context, "Foo").unwrap();
assert_eq!(
&*bar_obj.prototype(),
&foo_val
*bar_obj.prototype(),
foo_val.as_object().and_then(|obj| obj
.get("prototype", &mut context)
.unwrap()
.as_object()
.and_then(|obj| obj.get("prototype", &mut context).unwrap().as_object())
.cloned())
);
}
}

100
boa/src/object/internal_methods/bound_function.rs

@ -0,0 +1,100 @@
use crate::{object::JsObject, Context, JsResult, JsValue};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for function objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(bound_function_exotic_call),
__construct__: None,
..ORDINARY_INTERNAL_METHODS
};
pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(bound_function_exotic_call),
__construct__: Some(bound_function_exotic_construct),
..ORDINARY_INTERNAL_METHODS
};
/// 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
#[track_caller]
#[inline]
fn bound_function_exotic_call(
obj: &JsObject,
_: &JsValue,
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let obj = obj.borrow();
let bound_function = obj
.as_bound_function()
.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. Let boundThis be F.[[BoundThis]].
let bound_this = bound_function.this();
// 3. Let boundArgs be F.[[BoundArguments]].
let bound_args = bound_function.args();
// 4. Let args be the list-concatenation of boundArgs and argumentsList.
let mut args = bound_args.to_vec();
args.extend_from_slice(arguments_list);
// 5. Return ? Call(target, boundThis, args).
target.call(bound_this, &args, context)
}
/// 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
#[track_caller]
#[inline]
fn bound_function_exotic_construct(
obj: &JsObject,
arguments_list: &[JsValue],
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
let object = obj.borrow();
let bound_function = object
.as_bound_function()
.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 mut args = bound_args.to_vec();
args.extend_from_slice(arguments_list);
// 5. If SameValue(F, newTarget) is true, set newTarget to target.
let new_target = match new_target {
JsValue::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(),
_ => new_target.clone(),
};
// 6. Return ? Construct(target, args, newTarget).
target.construct(&args, &new_target, context)
}

314
boa/src/object/internal_methods/function.rs

@ -0,0 +1,314 @@
use crate::{
builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature},
environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::{Executable, InterpreterState},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
syntax::ast::node::RcStatementList,
Context, JsResult, JsValue,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use crate::{builtins::function::arguments::Arguments, context::StandardObjects};
/// Definitions of the internal object methods for function objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: None,
..ORDINARY_INTERNAL_METHODS
};
pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: Some(function_construct),
..ORDINARY_INTERNAL_METHODS
};
/// Call this object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
fn function_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
call_construct(obj, this, args, context, false)
}
/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
fn function_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
call_construct(obj, new_target, args, context, true)
}
/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ordinarycallbindthis>
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
pub(super) fn call_construct(
obj: &JsObject,
this_target: &JsValue,
args: &[JsValue],
context: &mut Context,
construct: bool,
) -> JsResult<JsValue> {
/// The body of a JavaScript function.
///
/// This is needed for the call method since we cannot mutate the function itself since we
/// already borrow it so we get the function body clone it then drop the borrow and run the body
enum FunctionBody {
BuiltInFunction(NativeFunctionSignature),
BuiltInConstructor(NativeFunctionSignature),
Closure {
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
Ordinary(RcStatementList),
}
let this_function_object = obj.clone();
let mut has_parameter_expressions = false;
let body = if let Some(function) = obj.borrow().as_function() {
if construct && !function.is_constructor() {
let name = obj.get("name", context)?.display().to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
} else {
match function {
Function::Native {
function,
constructor,
} => {
if *constructor || construct {
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(*function)
}
}
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
function: function.clone(),
captures: captures.clone(),
},
Function::Ordinary {
constructor: _,
this_mode,
body,
params,
environment,
} => {
let this = if construct {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = get_prototype_from_constructor(
this_target,
StandardObjects::object_object,
context,
)?;
JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into()
} else {
this_target.clone()
};
// Create a new Function environment whose parent is set to the scope of the function declaration (obj.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object.clone(),
if construct || !this_mode.is_lexical() {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
for param in params.iter() {
has_parameter_expressions =
has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.name() == "arguments";
is_simple_parameter_list = is_simple_parameter_list
&& !param.is_rest_param()
&& param.init().is_none()
}
// Turn local_env into Environment so it can be cloned
let local_env: Environment = local_env.into();
// An arguments object is added when all of the following conditions are met
// - If not in an arrow function (10.2.11.16)
// - If the parameter list does not contain `arguments` (10.2.11.17)
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !this_mode.is_lexical()
&& !arguments_in_parameter_names
&& (has_parameter_expressions
|| (!body.lexically_declared_names().contains("arguments")
&& !body.function_declared_names().contains("arguments")))
{
// Add arguments object
let arguments_obj =
if context.strict() || body.strict() || !is_simple_parameter_list {
Arguments::create_unmapped_arguments_object(args, context)
} else {
Arguments::create_mapped_arguments_object(
obj, params, args, &local_env, context,
)
};
local_env.create_mutable_binding("arguments", false, true, context)?;
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() {
None | Some(JsValue::Undefined) => param
.init()
.map(|init| init.run(context).ok())
.flatten()
.unwrap_or_default(),
Some(value) => value,
};
Function::add_arguments_to_environment(param, value, &local_env, context);
}
if has_parameter_expressions {
// Create a second environment when default parameter expressions are used
// This prevents variables declared in the function body from being
// used in default parameter initializers.
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
let second_env = FunctionEnvironmentRecord::new(
this_function_object,
if construct || !this_mode.is_lexical() {
Some(this)
} else {
None
},
Some(local_env),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
context.push_environment(second_env);
}
FunctionBody::Ordinary(body.clone())
}
#[cfg(feature = "vm")]
Function::VmOrdinary { .. } => {
todo!("vm call")
}
}
}
} else {
return context.throw_type_error("not a function");
};
match body {
FunctionBody::BuiltInConstructor(function) if construct => {
function(this_target, args, context)
}
FunctionBody::BuiltInConstructor(function) => {
function(&JsValue::undefined(), args, context)
}
FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this_target, args, captures, context)
}
FunctionBody::Ordinary(body) => {
let result = body.run(context);
let this = context.get_this_binding();
if has_parameter_expressions {
context.pop_environment();
}
context.pop_environment();
if construct {
// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
// 12. If result.[[Type]] is return, then
if context.executor().get_current_state() == &InterpreterState::Return {
// a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
if let Ok(v) = &result {
if v.is_object() {
return result;
}
}
}
// 13. Else, ReturnIfAbrupt(result).
result?;
// 14. Return ? constructorEnv.GetThisBinding().
this
} else if context.executor().get_current_state() == &InterpreterState::Return {
result
} else {
result?;
Ok(JsValue::undefined())
}
}
}
}

67
boa/src/object/internal_methods/mod.rs

@ -17,6 +17,8 @@ use super::{JsPrototype, PROTOTYPE};
pub(super) mod arguments;
pub(super) mod array;
pub(super) mod bound_function;
pub(super) mod function;
pub(super) mod integer_indexed;
pub(super) mod string;
@ -46,7 +48,7 @@ impl JsObject {
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
#[inline]
pub(crate) fn __set_prototype_of__(
&mut self,
&self,
val: JsPrototype,
context: &mut Context,
) -> JsResult<bool> {
@ -209,6 +211,50 @@ impl JsObject {
let func = self.borrow().data.internal_methods.__own_property_keys__;
func(self, context)
}
/// Internal method `[[Call]]`
///
/// Call this object if it has a `[[Call]]` internal method.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
#[inline]
#[track_caller]
pub(crate) fn __call__(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let func = self.borrow().data.internal_methods.__call__;
func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
self, this, args, context,
)
}
/// Internal method `[[Construct]]`
///
/// Construct a new instance of this object if this object has a `[[Construct]]` internal method.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
#[inline]
#[track_caller]
pub(crate) fn __construct__(
&self,
args: &[JsValue],
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
let func = self.borrow().data.internal_methods.__construct__;
func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
self, args, new_target, context,
)
}
}
/// Definitions of the internal object methods for ordinary objects.
@ -234,6 +280,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
__set__: ordinary_set,
__delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys,
__call__: None,
__construct__: None,
};
/// The internal representation of the internal methods of a `JsObject`.
@ -244,6 +292,7 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
///
/// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`.
#[derive(Clone, Copy)]
#[allow(clippy::type_complexity)]
pub(crate) struct InternalObjectMethods {
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsPrototype>,
pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult<bool>,
@ -259,6 +308,10 @@ pub(crate) struct InternalObjectMethods {
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult<bool>,
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>,
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__:
Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>>,
pub(crate) __construct__:
Option<fn(&JsObject, &[JsValue], &JsValue, &mut Context) -> JsResult<JsValue>>,
}
/// Abstract operation `OrdinaryGetPrototypeOf`.
@ -445,14 +498,12 @@ pub(crate) fn ordinary_has_property(
// 4. Let parent be ? O.[[GetPrototypeOf]]().
let parent = obj.__get_prototype_of__(context)?;
// 5. If parent is not null, then
if let Some(object) = parent {
parent
// 5. If parent is not null, then
// a. Return ? parent.[[HasProperty]](P).
object.__has_property__(key, context)
} else {
.map(|obj| obj.__has_property__(key, context))
// 6. Return false.
Ok(false)
}
.unwrap_or(Ok(false))
}
}
@ -880,7 +931,7 @@ where
// 2. Let proto be ? Get(constructor, "prototype").
if let Some(object) = constructor.as_object() {
if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() {
return Ok(proto);
return Ok(proto.clone());
}
}
// 3. If Type(proto) is not Object, then

338
boa/src/object/jsobject.rs

@ -18,18 +18,6 @@ use std::{
result::Result as StdResult,
};
#[cfg(not(feature = "vm"))]
use crate::{
builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature},
environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::InterpreterState,
syntax::ast::node::RcStatementList,
Executable,
};
/// A wrapper type for an immutably borrowed type T.
pub type Ref<'a, T> = GcCellRef<'a, T>;
@ -122,266 +110,6 @@ impl JsObject {
std::ptr::eq(lhs.as_ref(), rhs.as_ref())
}
/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ordinarycallbindthis>
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
#[cfg(not(feature = "vm"))]
pub(super) fn call_construct(
&self,
this_target: &JsValue,
args: &[JsValue],
context: &mut Context,
construct: bool,
) -> JsResult<JsValue> {
use crate::{builtins::function::arguments::Arguments, context::StandardObjects};
use super::internal_methods::get_prototype_from_constructor;
/// The body of a JavaScript function.
///
/// This is needed for the call method since we cannot mutate the function itself since we
/// already borrow it so we get the function body clone it then drop the borrow and run the body
#[cfg(not(feature = "vm"))]
enum FunctionBody {
BuiltInFunction(NativeFunctionSignature),
BuiltInConstructor(NativeFunctionSignature),
Closure {
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
Ordinary(RcStatementList),
}
let this_function_object = self.clone();
let mut has_parameter_expressions = false;
let body = if let Some(function) = self.borrow().as_function() {
if construct && !function.is_constructable() {
let name = self.get("name", context)?.display().to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
} else {
match function {
Function::Native {
function,
constructable,
} => {
if *constructable || construct {
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(*function)
}
}
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
function: function.clone(),
captures: captures.clone(),
},
Function::Ordinary {
constructable: _,
this_mode,
body,
params,
environment,
} => {
let this = if construct {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = get_prototype_from_constructor(
this_target,
StandardObjects::object_object,
context,
)?;
JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary())
.into()
} else {
this_target.clone()
};
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object.clone(),
if construct || !this_mode.is_lexical() {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
for param in params.iter() {
has_parameter_expressions =
has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.name() == "arguments";
is_simple_parameter_list = is_simple_parameter_list
&& !param.is_rest_param()
&& param.init().is_none()
}
// Turn local_env into Environment so it can be cloned
let local_env: Environment = local_env.into();
// An arguments object is added when all of the following conditions are met
// - If not in an arrow function (10.2.11.16)
// - If the parameter list does not contain `arguments` (10.2.11.17)
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !this_mode.is_lexical()
&& !arguments_in_parameter_names
&& (has_parameter_expressions
|| (!body.lexically_declared_names().contains("arguments")
&& !body.function_declared_names().contains("arguments")))
{
// Add arguments object
let arguments_obj =
if context.strict() || body.strict() || !is_simple_parameter_list {
Arguments::create_unmapped_arguments_object(args, context)
} else {
Arguments::create_mapped_arguments_object(
self, params, args, &local_env, context,
)
};
local_env.create_mutable_binding("arguments", false, true, context)?;
local_env.initialize_binding(
"arguments",
arguments_obj.into(),
context,
)?;
}
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() {
None | Some(JsValue::Undefined) => param
.init()
.map(|init| init.run(context).ok())
.flatten()
.unwrap_or_default(),
Some(value) => value,
};
Function::add_arguments_to_environment(
param, value, &local_env, context,
);
}
if has_parameter_expressions {
// Create a second environment when default parameter expressions are used
// This prevents variables declared in the function body from being
// used in default parameter initializers.
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
let second_env = FunctionEnvironmentRecord::new(
this_function_object,
if construct || !this_mode.is_lexical() {
Some(this)
} else {
None
},
Some(local_env),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
context.push_environment(second_env);
}
FunctionBody::Ordinary(body.clone())
}
#[cfg(feature = "vm")]
Function::VmOrdinary { .. } => {
todo!("vm call")
}
}
}
} else {
return context.throw_type_error("not a function");
};
match body {
FunctionBody::BuiltInConstructor(function) if construct => {
function(this_target, args, context)
}
FunctionBody::BuiltInConstructor(function) => {
function(&JsValue::undefined(), args, context)
}
FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this_target, args, captures, context)
}
FunctionBody::Ordinary(body) => {
let result = body.run(context);
let this = context.get_this_binding();
if has_parameter_expressions {
context.pop_environment();
}
context.pop_environment();
if construct {
// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
// 12. If result.[[Type]] is return, then
if context.executor().get_current_state() == &InterpreterState::Return {
// a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
if let Ok(v) = &result {
if v.is_object() {
return result;
}
}
}
// 13. Else, ReturnIfAbrupt(result).
result?;
// 14. Return ? constructorEnv.GetThisBinding().
this
} else if context.executor().get_current_state() == &InterpreterState::Return {
result
} else {
result?;
Ok(JsValue::undefined())
}
}
}
}
/// Converts an object to a primitive.
///
/// Diverges from the spec to prevent a stack overflow when the object is recursive.
@ -435,14 +163,15 @@ impl JsObject {
};
// 5. For each name in methodNames in List order, do
let this = JsValue::new(self.clone());
for name in &method_names {
// a. Let method be ? Get(O, name).
let method: JsValue = this.get_field(*name, context)?;
let method = self.get(*name, context)?;
// b. If IsCallable(method) is true, then
if method.is_function() {
if let Some(method) = method.as_callable() {
// i. Let result be ? Call(method, O).
let result = context.call(&method, &this, &[])?;
let result = method.call(&self.clone().into(), &[], context)?;
// ii. If Type(result) is not Object, return result.
if !result.is_object() {
return Ok(result);
@ -705,55 +434,6 @@ impl JsObject {
self.borrow().is_native_object()
}
/// Determines if `value` inherits from the instance object inheritance path.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
#[inline]
pub(crate) fn ordinary_has_instance(
&self,
context: &mut Context,
value: &JsValue,
) -> JsResult<bool> {
// 1. If IsCallable(C) is false, return false.
if !self.is_callable() {
return Ok(false);
}
// TODO: 2. If C has a [[BoundTargetFunction]] internal slot, then
// a. Let BC be C.[[BoundTargetFunction]].
// b. Return ? InstanceofOperator(O, BC).
// 3. If Type(O) is not Object, return false.
if let Some(object) = value.as_object() {
// 4. Let P be ? Get(C, "prototype").
// 5. If Type(P) is not Object, throw a TypeError exception.
if let Some(prototype) = self.get("prototype", context)?.as_object() {
// 6. Repeat,
// a. Set O to ? O.[[GetPrototypeOf]]().
// b. If O is null, return false.
let mut object = object.__get_prototype_of__(context)?;
while let Some(object_prototype) = object {
// c. If SameValue(P, O) is true, return true.
if prototype == object_prototype {
return Ok(true);
}
// a. Set O to ? O.[[GetPrototypeOf]]().
object = object_prototype.__get_prototype_of__(context)?;
}
Ok(false)
} else {
Err(context
.construct_type_error("function has non-object prototype in instanceof check"))
}
} else {
Ok(false)
}
}
pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
// 1 is implemented on the method `to_property_descriptor` of value
@ -851,7 +531,7 @@ impl JsObject {
/// [spec]: https://tc39.es/ecma262/#sec-copydataproperties
#[inline]
pub fn copy_data_properties<K>(
&mut self,
&self,
source: &JsValue,
excluded_keys: Vec<K>,
context: &mut Context,
@ -947,7 +627,7 @@ impl JsObject {
#[inline]
#[track_caller]
pub fn is_callable(&self) -> bool {
self.borrow().is_callable()
self.borrow().data.internal_methods.__call__.is_some()
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
@ -958,8 +638,8 @@ impl JsObject {
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
#[track_caller]
pub fn is_constructable(&self) -> bool {
self.borrow().is_constructable()
pub fn is_constructor(&self) -> bool {
self.borrow().data.internal_methods.__construct__.is_some()
}
/// Returns true if the JsObject is the global for a Realm

148
boa/src/object/mod.rs

@ -5,9 +5,10 @@ use crate::{
array::array_iterator::ArrayIterator,
array_buffer::ArrayBuffer,
function::arguments::{Arguments, MappedArguments},
function::{Captures, Function, NativeFunctionSignature},
function::{BoundFunction, Captures, Function, NativeFunctionSignature},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
object::for_in_iterator::ForInIterator,
regexp::regexp_string_iterator::RegExpStringIterator,
set::ordered_set::OrderedSet,
set::set_iterator::SetIterator,
@ -17,7 +18,6 @@ use crate::{
},
context::StandardConstructor,
gc::{Finalize, Trace},
object::internal_methods::arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS,
property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue,
};
@ -27,6 +27,22 @@ use std::{
ops::{Deref, DerefMut},
};
pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut};
pub use operations::IntegrityLevel;
pub use property_map::*;
use self::internal_methods::{
arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS,
array::ARRAY_EXOTIC_INTERNAL_METHODS,
bound_function::{
BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS,
},
function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
string::STRING_EXOTIC_INTERNAL_METHODS,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
};
#[cfg(test)]
mod tests;
@ -35,17 +51,6 @@ mod jsobject;
mod operations;
mod property_map;
use crate::builtins::object::for_in_iterator::ForInIterator;
use internal_methods::InternalObjectMethods;
pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut};
pub use operations::IntegrityLevel;
pub use property_map::*;
use self::internal_methods::{
array::ARRAY_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
string::STRING_EXOTIC_INTERNAL_METHODS, ORDINARY_INTERNAL_METHODS,
};
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
pub static PROTOTYPE: &str = "prototype";
@ -108,6 +113,7 @@ pub enum ObjectKind {
Boolean(bool),
ForInIterator(ForInIterator),
Function(Function),
BoundFunction(BoundFunction),
Set(OrderedSet<JsValue>),
SetIterator(SetIterator),
String(JsString),
@ -207,8 +213,24 @@ impl ObjectData {
/// Create the `Function` object data
pub fn function(function: Function) -> Self {
Self {
internal_methods: if function.is_constructor() {
&CONSTRUCTOR_INTERNAL_METHODS
} else {
&FUNCTION_INTERNAL_METHODS
},
kind: ObjectKind::Function(function),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `BoundFunction` object data
pub fn bound_function(bound_function: BoundFunction, constructor: bool) -> Self {
Self {
kind: ObjectKind::BoundFunction(bound_function),
internal_methods: if constructor {
&BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS
} else {
&BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS
},
}
}
@ -329,6 +351,7 @@ impl Display for ObjectKind {
Self::ArrayBuffer(_) => "ArrayBuffer",
Self::ForInIterator(_) => "ForInIterator",
Self::Function(_) => "Function",
Self::BoundFunction(_) => "BoundFunction",
Self::RegExp(_) => "RegExp",
Self::RegExpStringIterator(_) => "RegExpStringIterator",
Self::Map(_) => "Map",
@ -380,38 +403,6 @@ impl Object {
&self.data.kind
}
/// It determines if Object is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
// todo: functions are not the only objects that are callable.
// todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
pub fn is_callable(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::Function(_),
..
}
)
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
// todo: functions are not the only objects that are constructable.
// todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
pub fn is_constructable(&self) -> bool {
matches!(self.data, ObjectData{kind: ObjectKind::Function(ref f), ..} if f.is_constructable())
}
/// Checks if it an `Array` object.
#[inline]
pub fn is_array(&self) -> bool {
@ -704,6 +695,28 @@ impl Object {
}
}
#[inline]
pub fn as_function_mut(&mut self) -> Option<&mut Function> {
match self.data {
ObjectData {
kind: ObjectKind::Function(ref mut function),
..
} => Some(function),
_ => None,
}
}
#[inline]
pub fn as_bound_function(&self) -> Option<&BoundFunction> {
match self.data {
ObjectData {
kind: ObjectKind::BoundFunction(ref bound_function),
..
} => Some(bound_function),
_ => None,
}
}
/// Checks if it a Symbol object.
#[inline]
pub fn is_symbol(&self) -> bool {
@ -1151,7 +1164,7 @@ impl<'context> FunctionBuilder<'context> {
context,
function: Some(Function::Native {
function,
constructable: false,
constructor: false,
}),
name: JsString::default(),
length: 0,
@ -1168,7 +1181,7 @@ impl<'context> FunctionBuilder<'context> {
context,
function: Some(Function::Closure {
function: Box::new(move |this, args, _, context| function(this, args, context)),
constructable: false,
constructor: false,
captures: Captures::new(()),
}),
name: JsString::default(),
@ -1190,16 +1203,19 @@ impl<'context> FunctionBuilder<'context> {
) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult<JsValue> + Copy + 'static,
C: NativeObject + Clone,
C: NativeObject,
{
Self {
context,
function: Some(Function::Closure {
function: Box::new(move |this, args, mut captures: Captures, context| {
let data = captures.try_downcast_mut::<C>(context)?;
function(this, args, data, context)
function: Box::new(move |this, args, captures: Captures, context| {
let mut captures = captures.as_mut_any();
let captures = captures.downcast_mut::<C>().ok_or_else(|| {
context.construct_type_error("cannot downcast `Captures` to given type")
})?;
function(this, args, captures, context)
}),
constructable: false,
constructor: false,
captures: Captures::new(captures),
}),
name: JsString::default(),
@ -1234,10 +1250,10 @@ impl<'context> FunctionBuilder<'context> {
///
/// The default is `false`.
#[inline]
pub fn constructable(&mut self, yes: bool) -> &mut Self {
pub fn constructor(&mut self, yes: bool) -> &mut Self {
match self.function.as_mut() {
Some(Function::Native { constructable, .. }) => *constructable = yes,
Some(Function::Closure { constructable, .. }) => *constructable = yes,
Some(Function::Native { constructor, .. }) => *constructor = yes,
Some(Function::Closure { constructor, .. }) => *constructor = yes,
_ => unreachable!(),
}
self
@ -1337,7 +1353,7 @@ impl<'context> ObjectInitializer<'context> {
let function = FunctionBuilder::native(self.context, function)
.name(binding.name)
.length(length)
.constructable(false)
.constructor(false)
.build();
self.object.borrow_mut().insert_property(
@ -1383,7 +1399,7 @@ pub struct ConstructorBuilder<'context> {
name: JsString,
length: usize,
callable: bool,
constructable: bool,
constructor: bool,
inherit: Option<JsPrototype>,
custom_prototype: Option<JsPrototype>,
}
@ -1397,7 +1413,7 @@ impl Debug for ConstructorBuilder<'_> {
.field("prototype", &self.prototype)
.field("inherit", &self.inherit)
.field("callable", &self.callable)
.field("constructable", &self.constructable)
.field("constructor", &self.constructor)
.finish()
}
}
@ -1414,7 +1430,7 @@ impl<'context> ConstructorBuilder<'context> {
length: 0,
name: JsString::default(),
callable: true,
constructable: true,
constructor: true,
inherit: None,
custom_prototype: None,
}
@ -1434,7 +1450,7 @@ impl<'context> ConstructorBuilder<'context> {
length: 0,
name: JsString::default(),
callable: true,
constructable: true,
constructor: true,
inherit: None,
custom_prototype: None,
}
@ -1455,7 +1471,7 @@ impl<'context> ConstructorBuilder<'context> {
let function = FunctionBuilder::native(self.context, function)
.name(binding.name)
.length(length)
.constructable(false)
.constructor(false)
.build();
self.prototype.borrow_mut().insert_property(
@ -1484,7 +1500,7 @@ impl<'context> ConstructorBuilder<'context> {
let function = FunctionBuilder::native(self.context, function)
.name(binding.name)
.length(length)
.constructable(false)
.constructor(false)
.build();
self.constructor_object.borrow_mut().insert_property(
@ -1630,8 +1646,8 @@ impl<'context> ConstructorBuilder<'context> {
///
/// Default is `true`
#[inline]
pub fn constructable(&mut self, constructable: bool) -> &mut Self {
self.constructable = constructable;
pub fn constructor(&mut self, constructor: bool) -> &mut Self {
self.constructor = constructor;
self
}
@ -1664,7 +1680,7 @@ impl<'context> ConstructorBuilder<'context> {
// Create the native function
let function = Function::Native {
function: self.constructor_function,
constructable: self.constructable,
constructor: self.constructor,
};
let length = PropertyDescriptor::builder()

111
boa/src/object/operations.rs

@ -266,7 +266,13 @@ impl JsObject {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(this, args, context, false)
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. If IsCallable(F) is false, throw a TypeError exception.
if !self.is_callable() {
return context.throw_type_error("not a function");
}
// 3. Return ? F.[[Call]](V, argumentsList).
self.__call__(this, args, context)
}
/// Construct an instance of this object with the specified arguments.
@ -284,7 +290,10 @@ impl JsObject {
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(new_target, args, context, true)
// 1. If newTarget is not present, set newTarget to F.
// 2. If argumentsList is not present, set argumentsList to a new empty List.
// 3. Return ? F.[[Construct]](argumentsList, newTarget).
self.__construct__(args, new_target, context)
}
/// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
@ -435,7 +444,7 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Let C be ? Get(O, "constructor").
let c = self.clone().get("constructor", context)?;
let c = self.get("constructor", context)?;
// 3. If C is undefined, return defaultConstructor.
if c.is_undefined() {
@ -457,14 +466,9 @@ impl JsObject {
// 7. If IsConstructor(S) is true, return S.
// 8. Throw a TypeError exception.
if let Some(obj) = s.as_object() {
if obj.is_constructable() {
Ok(obj)
} else {
Err(context.construct_type_error("property 'constructor' is not a constructor"))
}
} else {
Err(context.construct_type_error("property 'constructor' is not an object"))
match s.as_object() {
Some(obj) if obj.is_constructor() => Ok(obj.clone()),
_ => Err(context.construct_type_error("property 'constructor' is not a constructor")),
}
}
@ -580,26 +584,20 @@ impl JsValue {
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
let func = self.get_v(key, context)?;
// 3. If func is either undefined or null, return undefined.
if func.is_null_or_undefined() {
return Ok(JsValue::undefined());
}
// 4. If IsCallable(func) is false, throw a TypeError exception.
if !func.is_callable() {
Err(context
.construct_type_error("value returned for property of object is not a function"))
} else {
match &self.get_v(key, context)? {
// 3. If func is either undefined or null, return undefined.
JsValue::Undefined | JsValue::Null => Ok(None),
// 5. Return func.
Ok(func)
JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
// 4. If IsCallable(func) is false, throw a TypeError exception.
_ => Err(context
.construct_type_error("value returned for property of object is not a function")),
}
}
@ -685,4 +683,67 @@ impl JsValue {
// 3. Return ? Call(func, V, argumentsList)
context.call(&func, self, args)
}
/// Abstract operation `OrdinaryHasInstance ( C, O )`
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
pub fn ordinary_has_instance(
function: &JsValue,
object: &JsValue,
context: &mut Context,
) -> JsResult<bool> {
// 1. If IsCallable(C) is false, return false.
let function = if let Some(function) = function.as_callable() {
function
} else {
return Ok(false);
};
// 2. If C has a [[BoundTargetFunction]] internal slot, then
if let Some(bound_function) = function.borrow().as_bound_function() {
// a. Let BC be C.[[BoundTargetFunction]].
// b. Return ? InstanceofOperator(O, BC).
return JsValue::instance_of(
object,
&bound_function.target_function().clone().into(),
context,
);
}
let mut object = if let Some(obj) = object.as_object() {
obj.clone()
} else {
// 3. If Type(O) is not Object, return false.
return Ok(false);
};
// 4. Let P be ? Get(C, "prototype").
let prototype = function.get("prototype", context)?;
let prototype = if let Some(obj) = prototype.as_object() {
obj
} else {
// 5. If Type(P) is not Object, throw a TypeError exception.
return Err(context
.construct_type_error("function has non-object prototype in instanceof check"));
};
// 6. Repeat,
loop {
// a. Set O to ? O.[[GetPrototypeOf]]().
object = match object.__get_prototype_of__(context)? {
Some(obj) => obj,
// b. If O is null, return false.
None => return Ok(false),
};
// c. If SameValue(P, O) is true, return true.
if JsObject::equals(&object, prototype) {
return Ok(true);
}
}
}
}

2
boa/src/syntax/ast/node/declaration/mod.rs

@ -519,7 +519,7 @@ impl DeclarationPatternObject {
// 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment).
// 2. Let restObj be ! OrdinaryObjectCreate(%Object.prototype%).
let mut rest_obj = context.construct_object();
let rest_obj = context.construct_object();
// 3. Perform ? CopyDataProperties(restObj, value, excludedNames).
rest_obj.copy_data_properties(value, excluded_keys.clone(), context)?;

16
boa/src/syntax/ast/node/new/mod.rs

@ -70,13 +70,15 @@ impl Executable for New {
}
}
match func_object {
JsValue::Object(ref object) => {
object.construct(&v_args, &object.clone().into(), context)
}
_ => context
.throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)),
}
func_object
.as_constructor()
.ok_or_else(|| {
context.construct_type_error(format!(
"{} is not a constructor",
self.expr().to_string(),
))
})
.and_then(|cons| cons.construct(&v_args, &cons.clone().into(), context))
}
}

6
boa/src/syntax/ast/node/object/mod.rs

@ -89,7 +89,7 @@ impl Object {
impl Executable for Object {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("object", "exec");
let mut obj = context.construct_object();
let obj = context.construct_object();
// TODO: Implement the rest of the property types.
for property in self.properties().iter() {
@ -141,7 +141,7 @@ impl Executable for Object {
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_get(func.run(context)?.as_object())
.maybe_get(func.run(context)?.as_object().cloned())
.maybe_set(set)
.enumerable(true)
.configurable(true)
@ -159,7 +159,7 @@ impl Executable for Object {
name,
PropertyDescriptor::builder()
.maybe_get(get)
.maybe_set(func.run(context)?.as_object())
.maybe_set(func.run(context)?.as_object().cloned())
.enumerable(true)
.configurable(true)
.build(),

31
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -1,7 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
symbol::WellKnownSymbols,
syntax::ast::{
node::Node,
op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp},
@ -146,35 +145,7 @@ impl Executable for BinOp {
let key = x.to_property_key(context)?;
context.has_property(&y, &key)?
}
CompOp::InstanceOf => {
// <https://tc39.es/ecma262/#sec-instanceofoperator>
// TODO: move to a separate instance_of_operator function
// 1. If Type(target) is not Object, throw a TypeError exception.
if !y.is_object() {
return context.throw_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
y.type_of()
));
}
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
let inst_of_handler =
y.get_method(WellKnownSymbols::has_instance(), context)?;
// 3. If instOfHandler is not undefined, then
if !inst_of_handler.is_undefined() {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).
context.call(&inst_of_handler, &y, &[x])?.to_boolean()
} else if !y.is_callable() {
// 4. If IsCallable(target) is false, throw a TypeError exception.
return Err(context.construct_type_error(
"right-hand side of 'instanceof' is not callable",
));
} else {
// 5. Return ? OrdinaryHasInstance(target, V).
y.ordinary_has_instance(context, &x)?
}
}
CompOp::InstanceOf => x.instance_of(&y, context)?,
}))
}
op::BinOp::Log(op) => Ok(match op {

89
boa/src/value/mod.rs

@ -130,13 +130,40 @@ impl JsValue {
}
#[inline]
pub fn as_object(&self) -> Option<JsObject> {
pub fn as_object(&self) -> Option<&JsObject> {
match *self {
Self::Object(ref o) => Some(o.clone()),
Self::Object(ref o) => Some(o),
_ => None,
}
}
/// It determines if the value is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
pub fn is_callable(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_callable())
}
#[inline]
pub fn as_callable(&self) -> Option<&JsObject> {
self.as_object().filter(|obj| obj.is_callable())
}
/// Returns true if the value is a constructor object
#[inline]
pub fn is_constructor(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_constructor())
}
#[inline]
pub fn as_constructor(&self) -> Option<&JsObject> {
self.as_object().filter(|obj| obj.is_constructor())
}
/// Returns true if the value is a symbol.
#[inline]
pub fn is_symbol(&self) -> bool {
@ -150,12 +177,6 @@ impl JsValue {
}
}
/// Returns true if the value is a function
#[inline]
pub fn is_function(&self) -> bool {
matches!(self, Self::Object(o) if o.is_function())
}
/// Returns true if the value is undefined.
#[inline]
pub fn is_undefined(&self) -> bool {
@ -396,7 +417,7 @@ impl JsValue {
let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?;
// b. If exoticToPrim is not undefined, then
if !exotic_to_prim.is_undefined() {
if let Some(exotic_to_prim) = exotic_to_prim {
// i. If preferredType is not present, let hint be "default".
// ii. Else if preferredType is string, let hint be "string".
// iii. Else,
@ -410,7 +431,7 @@ impl JsValue {
.into();
// iv. Let result be ? Call(exoticToPrim, input, « hint »).
let result = context.call(&exotic_to_prim, self, &[hint])?;
let result = exotic_to_prim.call(self, &[hint], context)?;
// v. If Type(result) is not Object, return result.
// vi. Throw a TypeError exception.
return if result.is_object() {
@ -950,11 +971,13 @@ impl JsValue {
#[inline]
pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
// 1. If Type(Obj) is not Object, throw a TypeError exception.
match self {
JsValue::Object(ref obj) => obj.to_property_descriptor(context),
_ => Err(context
.construct_type_error("Cannot construct a property descriptor from a non-object")),
}
self.as_object()
.ok_or_else(|| {
context.construct_type_error(
"Cannot construct a property descriptor from a non-object",
)
})
.and_then(|obj| obj.to_property_descriptor(context))
}
/// Converts argument to an integer, +∞, or -∞.
@ -1029,42 +1052,10 @@ impl JsValue {
// 3. If argument is a Proxy exotic object, then
// b. Let target be argument.[[ProxyTarget]].
// c. Return ? IsArray(target).
// 4. Return false.
// TODO: handle ProxyObjects
Ok(object.is_array())
} else {
Ok(false)
}
}
/// It determines if the value is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[track_caller]
pub(crate) fn is_callable(&self) -> bool {
if let Self::Object(obj) = self {
obj.is_callable()
} else {
false
}
}
/// Determines if `value` inherits from the instance object inheritance path.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
pub(crate) fn ordinary_has_instance(
&self,
context: &mut Context,
value: &JsValue,
) -> JsResult<bool> {
if let Self::Object(obj) = self {
obj.ordinary_has_instance(context, value)
} else {
// 4. Return false.
Ok(false)
}
}

36
boa/src/value/operations.rs

@ -388,6 +388,42 @@ impl JsValue {
})
}
/// Abstract operation `InstanceofOperator ( V, target )`
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator
#[inline]
pub fn instance_of(&self, target: &JsValue, context: &mut Context) -> JsResult<bool> {
// 1. If Type(target) is not Object, throw a TypeError exception.
if !target.is_object() {
return Err(context.construct_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
target.type_of()
)));
}
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
match target.get_method(WellKnownSymbols::has_instance(), context)? {
// 3. If instOfHandler is not undefined, then
Some(instance_of_handler) => {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).
Ok(instance_of_handler
.call(target, std::slice::from_ref(self), context)?
.to_boolean())
}
None if target.is_callable() => {
// 5. Return ? OrdinaryHasInstance(target, V).
JsValue::ordinary_has_instance(target, self, context)
}
None => {
// 4. If IsCallable(target) is false, throw a TypeError exception.
Err(context.construct_type_error("right-hand side of 'instanceof' is not callable"))
}
}
}
#[inline]
pub fn neg(&self, context: &mut Context) -> JsResult<JsValue> {
Ok(match *self {

13
boa/src/vm/code_block.rs

@ -45,8 +45,8 @@ pub struct CodeBlock {
/// Is this function in strict mode.
pub(crate) strict: bool,
/// Is this function constructable.
pub(crate) constructable: bool,
/// Is this function a constructor.
pub(crate) constructor: bool,
/// [[ThisMode]]
pub(crate) this_mode: ThisMode,
@ -67,7 +67,7 @@ pub struct CodeBlock {
}
impl CodeBlock {
pub fn new(name: JsString, length: u32, strict: bool, constructable: bool) -> Self {
pub fn new(name: JsString, length: u32, strict: bool, constructor: bool) -> Self {
Self {
code: Vec::new(),
literals: Vec::new(),
@ -76,7 +76,7 @@ impl CodeBlock {
name,
length,
strict,
constructable,
constructor,
this_mode: ThisMode::Global,
params: Vec::new().into_boxed_slice(),
}
@ -359,6 +359,7 @@ pub(crate) enum FunctionBody {
},
}
// TODO: this should be modified to not take `exit_on_return` and then moved to `internal_methods`
impl JsObject {
pub(crate) fn call_internal(
&self,
@ -483,8 +484,8 @@ impl JsObject {
let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
if !self.is_constructable() {
return context.throw_type_error("not a constructable function");
if !self.is_constructor() {
return context.throw_type_error("not a constructor function");
}
let body = {

34
boa/src/vm/mod.rs

@ -3,8 +3,8 @@
//! plus an interpreter to execute those instructions
use crate::{
builtins::Array, environment::lexical_environment::VariableScope, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue,
builtins::Array, environment::lexical_environment::VariableScope, BoaProfiler, Context,
JsResult, JsValue,
};
mod call_frame;
@ -215,22 +215,8 @@ impl Context {
Opcode::InstanceOf => {
let target = self.vm.pop();
let v = self.vm.pop();
if !target.is_object() {
return Err(self.construct_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
target.type_of()
)));
};
let handler = target.get_method(WellKnownSymbols::has_instance(), self)?;
if !handler.is_undefined() {
let value = self.call(&handler, &target, &[v.clone()])?.to_boolean();
self.vm.push(value);
}
if !target.is_callable() {
return Err(self
.construct_type_error("right-hand side of 'instanceof' is not callable"));
}
let value = target.ordinary_has_instance(self, &v)?;
let value = v.instance_of(&target, self)?;
self.vm.push(value);
}
Opcode::Void => {
@ -359,7 +345,7 @@ impl Context {
let value = self.vm.pop();
let object = if let Some(object) = value.as_object() {
object
object.clone()
} else {
value.to_object(self)?
};
@ -373,7 +359,7 @@ impl Context {
let value = self.vm.pop();
let key = self.vm.pop();
let object = if let Some(object) = value.as_object() {
object
object.clone()
} else {
value.to_object(self)?
};
@ -389,7 +375,7 @@ impl Context {
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object
object.clone()
} else {
object.to_object(self)?
};
@ -403,7 +389,7 @@ impl Context {
let key = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object
object.clone()
} else {
object.to_object(self)?
};
@ -546,7 +532,7 @@ impl Context {
match self.vm.stack.last() {
None => "<empty>".to_string(),
Some(value) => {
if value.is_function() {
if value.is_callable() {
"[function]".to_string()
} else if value.is_object() {
"[object]".to_string()
@ -591,7 +577,7 @@ impl Context {
"{:04}{:<width$} {}",
i,
"",
if value.is_function() {
if value.is_callable() {
"[function]".to_string()
} else if value.is_object() {
"[object]".to_string()

Loading…
Cancel
Save