Browse Source

Implement new `get_or_undefined` method for `[JsValue]` (#1492)

* Create `JsArgs` trait

* Implement trait `JsArgs` for `[JsValue]`

* Replace calls to `get/unwrap_or` with `get_or_undefined`
pull/1563/head
jedel1043 3 years ago committed by GitHub
parent
commit
2a8d343fdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 48
      boa/src/builtins/array/mod.rs
  2. 16
      boa/src/builtins/bigint/mod.rs
  3. 7
      boa/src/builtins/console/mod.rs
  4. 4
      boa/src/builtins/date/mod.rs
  5. 14
      boa/src/builtins/function/mod.rs
  6. 8
      boa/src/builtins/json/mod.rs
  7. 27
      boa/src/builtins/map/mod.rs
  8. 22
      boa/src/builtins/mod.rs
  9. 10
      boa/src/builtins/number/mod.rs
  10. 45
      boa/src/builtins/object/mod.rs
  11. 27
      boa/src/builtins/reflect/mod.rs
  12. 18
      boa/src/builtins/regexp/mod.rs
  13. 29
      boa/src/builtins/set/mod.rs
  14. 115
      boa/src/builtins/string/mod.rs
  15. 4
      boa/src/builtins/symbol/mod.rs
  16. 3
      boa/src/class.rs

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

@ -25,6 +25,8 @@ use crate::{
}; };
use std::cmp::{max, min, Ordering}; use std::cmp::{max, min, Ordering};
use super::JsArgs;
/// JavaScript `Array` built-in implementation. /// JavaScript `Array` built-in implementation.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct Array; pub(crate) struct Array;
@ -689,8 +691,8 @@ impl Array {
// i. Let kValue be ? Get(O, Pk). // i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?; let k_value = o.get(pk, context)?;
// ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let this_arg = args.get_or_undefined(1);
callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?; callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
} }
// d. Set k to k + 1. // d. Set k to k + 1.
} }
@ -1024,7 +1026,7 @@ impl Array {
return context.throw_type_error("Array.prototype.every: callback is not callable"); return context.throw_type_error("Array.prototype.every: callback is not callable");
}; };
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
// 4. Let k be 0. // 4. Let k be 0.
// 5. Repeat, while k < len, // 5. Repeat, while k < len,
@ -1038,7 +1040,7 @@ impl Array {
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let test_result = callback let test_result = callback
.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// iii. If testResult is false, return false. // iii. If testResult is false, return false.
if !test_result { if !test_result {
@ -1072,7 +1074,7 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O). // 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get(0).cloned().unwrap_or_default(); let callback = args.get_or_undefined(0);
if !callback.is_function() { if !callback.is_function() {
return context.throw_type_error("Array.prototype.map: Callbackfn is not callable"); return context.throw_type_error("Array.prototype.map: Callbackfn is not callable");
} }
@ -1080,7 +1082,7 @@ impl Array {
// 4. Let A be ? ArraySpeciesCreate(O, len). // 4. Let A be ? ArraySpeciesCreate(O, len).
let a = Self::array_species_create(&o, len, context)?; let a = Self::array_species_create(&o, len, context)?;
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
// 5. Let k be 0. // 5. Let k be 0.
// 6. Repeat, while k < len, // 6. Repeat, while k < len,
@ -1094,7 +1096,7 @@ impl Array {
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value = let mapped_value =
context.call(&callback, &this_arg, &[k_value, k.into(), this.into()])?; context.call(callback, this_arg, &[k_value, k.into(), this.into()])?;
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?; a.create_data_property_or_throw(k, mapped_value, context)?;
} }
@ -1158,7 +1160,7 @@ impl Array {
} }
}; };
let search_element = args.get(0).cloned().unwrap_or_default(); let search_element = args.get_or_undefined(0);
// 10. Repeat, while k < len, // 10. Repeat, while k < len,
while k < len { while k < len {
@ -1234,7 +1236,7 @@ impl Array {
IntegerOrInfinity::Integer(n) => len + n, IntegerOrInfinity::Integer(n) => len + n,
}; };
let search_element = args.get(0).cloned().unwrap_or_default(); let search_element = args.get_or_undefined(0);
// 8. Repeat, while k ≥ 0, // 8. Repeat, while k ≥ 0,
while k >= 0 { while k >= 0 {
@ -1246,7 +1248,7 @@ impl Array {
let element_k = o.get(k, context)?; let element_k = o.get(k, context)?;
// ii. Let same be IsStrictlyEqual(searchElement, elementK). // ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k). // iii. If same is true, return 𝔽(k).
if JsValue::strict_equals(&search_element, &element_k) { if JsValue::strict_equals(search_element, &element_k) {
return Ok(JsValue::new(k)); return Ok(JsValue::new(k));
} }
} }
@ -1288,7 +1290,7 @@ impl Array {
} }
}; };
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
// 4. Let k be 0. // 4. Let k be 0.
let mut k = 0; let mut k = 0;
@ -1301,7 +1303,7 @@ impl Array {
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call( .call(
&this_arg, this_arg,
&[k_value.clone(), k.into(), o.clone().into()], &[k_value.clone(), k.into(), o.clone().into()],
context, context,
)? )?
@ -1349,7 +1351,7 @@ impl Array {
} }
}; };
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
// 4. Let k be 0. // 4. Let k be 0.
let mut k = 0; let mut k = 0;
@ -1361,7 +1363,7 @@ impl Array {
let k_value = o.get(pk, context)?; let k_value = o.get(pk, context)?;
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// d. If testResult is true, return 𝔽(k). // d. If testResult is true, return 𝔽(k).
if test_result { if test_result {
@ -1453,7 +1455,7 @@ impl Array {
let source_len = o.length_of_array_like(context)?; let source_len = o.length_of_array_like(context)?;
// 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
let mapper_function = args.get(0).cloned().unwrap_or_default(); let mapper_function = args.get_or_undefined(0);
if !mapper_function.is_function() { if !mapper_function.is_function() {
return context.throw_type_error("flatMap mapper function is not callable"); return context.throw_type_error("flatMap mapper function is not callable");
} }
@ -1469,7 +1471,7 @@ impl Array {
0, 0,
1, 1,
Some(mapper_function.as_object().unwrap()), Some(mapper_function.as_object().unwrap()),
&args.get(1).cloned().unwrap_or_default(), args.get_or_undefined(1),
context, context,
)?; )?;
@ -1624,7 +1626,7 @@ impl Array {
// 10. Else, let final be min(relativeEnd, len). // 10. Else, let final be min(relativeEnd, len).
let final_ = Self::get_relative_end(context, args.get(2), len)?; let final_ = Self::get_relative_end(context, args.get(2), len)?;
let value = args.get(0).cloned().unwrap_or_default(); let value = args.get_or_undefined(0);
// 11. Repeat, while k < final, // 11. Repeat, while k < final,
while k < final_ { while k < final_ {
@ -1695,14 +1697,14 @@ impl Array {
} }
} }
let search_element = args.get(0).cloned().unwrap_or_default(); let search_element = args.get_or_undefined(0);
// 10. Repeat, while k < len, // 10. Repeat, while k < len,
while k < len { while k < len {
// a. Let elementK be ? Get(O, ! ToString(𝔽(k))). // a. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?; let element_k = o.get(k, context)?;
// b. If SameValueZero(searchElement, elementK) is true, return true. // b. If SameValueZero(searchElement, elementK) is true, return true.
if JsValue::same_value_zero(&search_element, &element_k) { if JsValue::same_value_zero(search_element, &element_k) {
return Ok(JsValue::new(true)); return Ok(JsValue::new(true));
} }
// c. Set k to k + 1. // c. Set k to k + 1.
@ -1992,7 +1994,7 @@ impl Array {
"missing argument 0 when calling function Array.prototype.filter", "missing argument 0 when calling function Array.prototype.filter",
) )
})?; })?;
let this_val = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let this_arg = args.get_or_undefined(1);
if !callback.is_callable() { if !callback.is_callable() {
return context.throw_type_error("the callback must be callable"); return context.throw_type_error("the callback must be callable");
@ -2016,7 +2018,7 @@ impl Array {
let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())];
// ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let selected = callback.call(&this_val, &args, context)?.to_boolean(); let selected = callback.call(this_arg, &args, context)?.to_boolean();
// iii. If selected is true, then // iii. If selected is true, then
if selected { if selected {
@ -2078,9 +2080,9 @@ impl Array {
// i. Let kValue be ? Get(O, Pk). // i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
let test_result = callback let test_result = callback
.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// iii. If testResult is true, return true. // iii. If testResult is true, return true.
if test_result { if test_result {

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

@ -13,8 +13,12 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
use crate::{ use crate::{
builtins::BuiltIn, object::ConstructorBuilder, property::Attribute, symbol::WellKnownSymbols, builtins::{BuiltIn, JsArgs},
value::IntegerOrInfinity, BoaProfiler, Context, JsBigInt, JsResult, JsValue, object::ConstructorBuilder,
property::Attribute,
symbol::WellKnownSymbols,
value::IntegerOrInfinity,
BoaProfiler, Context, JsBigInt, JsResult, JsValue,
}; };
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -131,7 +135,7 @@ impl BigInt {
// 1. Let x be ? thisBigIntValue(this value). // 1. Let x be ? thisBigIntValue(this value).
let x = Self::this_bigint_value(this, context)?; let x = Self::this_bigint_value(this, context)?;
let radix = args.get(0).cloned().unwrap_or_default(); let radix = args.get_or_undefined(0);
// 2. If radix is undefined, let radixMV be 10. // 2. If radix is undefined, let radixMV be 10.
let radix_mv = if radix.is_undefined() { let radix_mv = if radix.is_undefined() {
@ -234,10 +238,8 @@ impl BigInt {
fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> { fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> {
use std::convert::TryFrom; use std::convert::TryFrom;
let undefined_value = JsValue::undefined(); let bits_arg = args.get_or_undefined(0);
let bigint_arg = args.get_or_undefined(1);
let bits_arg = args.get(0).unwrap_or(&undefined_value);
let bigint_arg = args.get(1).unwrap_or(&undefined_value);
let bits = bits_arg.to_index(context)?; let bits = bits_arg.to_index(context)?;
let bits = u32::try_from(bits).unwrap_or(u32::MAX); let bits = u32::try_from(bits).unwrap_or(u32::MAX);

7
boa/src/builtins/console/mod.rs

@ -17,7 +17,7 @@
mod tests; mod tests;
use crate::{ use crate::{
builtins::BuiltIn, builtins::{BuiltIn, JsArgs},
object::ObjectInitializer, object::ObjectInitializer,
property::Attribute, property::Attribute,
value::{display::display_obj, JsValue}, value::{display::display_obj, JsValue},
@ -90,7 +90,7 @@ pub fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
} }
/* object, FIXME: how to render this properly? */ /* object, FIXME: how to render this properly? */
'o' | 'O' => { 'o' | 'O' => {
let arg = data.get(arg_index).cloned().unwrap_or_default(); let arg = data.get_or_undefined(arg_index);
formatted.push_str(&format!("{}", arg.display())); formatted.push_str(&format!("{}", arg.display()));
arg_index += 1 arg_index += 1
} }
@ -564,9 +564,8 @@ impl Console {
/// [spec]: https://console.spec.whatwg.org/#dir /// [spec]: https://console.spec.whatwg.org/#dir
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir
pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
logger( logger(
LogMessage::Info(display_obj(args.get(0).unwrap_or(&undefined), true)), LogMessage::Info(display_obj(args.get_or_undefined(0), true)),
context.console(), context.console(),
); );

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

@ -13,6 +13,8 @@ use crate::{
use chrono::{prelude::*, Duration, LocalResult}; use chrono::{prelude::*, Duration, LocalResult};
use std::fmt::Display; use std::fmt::Display;
use super::JsArgs;
/// The number of nanoseconds in a millisecond. /// The number of nanoseconds in a millisecond.
const NANOS_PER_MS: i64 = 1_000_000; const NANOS_PER_MS: i64 = 1_000_000;
/// The number of milliseconds in an hour. /// The number of milliseconds in an hour.
@ -523,7 +525,7 @@ impl Date {
return context.throw_type_error("Date.prototype[@@toPrimitive] called on non object"); return context.throw_type_error("Date.prototype[@@toPrimitive] called on non object");
}; };
let hint = args.get(0).cloned().unwrap_or_default(); let hint = args.get_or_undefined(0);
let try_first = match hint.as_string().map(|s| s.as_str()) { let try_first = match hint.as_string().map(|s| s.as_str()) {
// 3. If hint is "string" or "default", then // 3. If hint is "string" or "default", then

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

@ -26,6 +26,8 @@ use dyn_clone::DynClone;
use sealed::Sealed; use sealed::Sealed;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use super::JsArgs;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -340,10 +342,10 @@ impl BuiltInFunctionObject {
if !this.is_function() { if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display())); return context.throw_type_error(format!("{} is not a function", this.display()));
} }
let this_arg: JsValue = args.get(0).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(0);
// TODO?: 3. Perform PrepareForTailCall // TODO?: 3. Perform PrepareForTailCall
let start = if !args.is_empty() { 1 } else { 0 }; let start = if !args.is_empty() { 1 } else { 0 };
context.call(this, &this_arg, &args[start..]) context.call(this, this_arg, &args[start..])
} }
/// `Function.prototype.apply` /// `Function.prototype.apply`
@ -361,15 +363,15 @@ impl BuiltInFunctionObject {
if !this.is_function() { if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display())); return context.throw_type_error(format!("{} is not a function", this.display()));
} }
let this_arg = args.get(0).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(0);
let arg_array = args.get(1).cloned().unwrap_or_default(); let arg_array = args.get_or_undefined(1);
if arg_array.is_null_or_undefined() { if arg_array.is_null_or_undefined() {
// TODO?: 3.a. PrepareForTailCall // TODO?: 3.a. PrepareForTailCall
return context.call(this, &this_arg, &[]); return context.call(this, this_arg, &[]);
} }
let arg_list = arg_array.create_list_from_array_like(&[], context)?; let arg_list = arg_array.create_list_from_array_like(&[], context)?;
// TODO?: 5. PrepareForTailCall // TODO?: 5. PrepareForTailCall
context.call(this, &this_arg, &arg_list) context.call(this, this_arg, &arg_list)
} }
} }

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

@ -26,6 +26,8 @@ use crate::{
}; };
use serde_json::{self, Value as JSONValue}; use serde_json::{self, Value as JSONValue};
use super::JsArgs;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -156,7 +158,7 @@ impl Json {
let mut property_list = None; let mut property_list = None;
let mut replacer_function = None; let mut replacer_function = None;
let replacer = args.get(1).cloned().unwrap_or_default(); let replacer = args.get_or_undefined(1);
// 4. If Type(replacer) is Object, then // 4. If Type(replacer) is Object, then
if let Some(replacer_obj) = replacer.as_object() { if let Some(replacer_obj) = replacer.as_object() {
@ -212,7 +214,7 @@ impl Json {
} }
} }
let mut space = args.get(2).cloned().unwrap_or_default(); let mut space = args.get_or_undefined(2).clone();
// 5. If Type(space) is Object, then // 5. If Type(space) is Object, then
if let Some(space_obj) = space.as_object() { if let Some(space_obj) = space.as_object() {
@ -264,7 +266,7 @@ impl Json {
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper wrapper
.create_data_property_or_throw("", args.get(0).cloned().unwrap_or_default(), context) .create_data_property_or_throw("", args.get_or_undefined(0).clone(), context)
.expect("CreateDataPropertyOrThrow should never fail here"); .expect("CreateDataPropertyOrThrow should never fail here");
// 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }.

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

@ -26,6 +26,8 @@ use map_iterator::MapIterator;
use self::ordered_map::MapLock; use self::ordered_map::MapLock;
use super::JsArgs;
pub mod ordered_map; pub mod ordered_map;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -245,15 +247,12 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let (key, value) = match args.len() { let key = args.get_or_undefined(0);
0 => (JsValue::undefined(), JsValue::undefined()), let value = args.get_or_undefined(1);
1 => (args[0].clone(), JsValue::undefined()),
_ => (args[0].clone(), args[1].clone()),
};
let size = if let Some(object) = this.as_object() { let size = if let Some(object) = this.as_object() {
if let Some(map) = object.borrow_mut().as_map_mut() { if let Some(map) = object.borrow_mut().as_map_mut() {
map.insert(key, value); map.insert(key.clone(), value.clone());
map.len() map.len()
} else { } else {
return Err(context.construct_type_error("'this' is not a Map")); return Err(context.construct_type_error("'this' is not a Map"));
@ -281,11 +280,11 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let key = args.get(0).cloned().unwrap_or_default(); let key = args.get_or_undefined(0);
let (deleted, size) = if let Some(object) = this.as_object() { let (deleted, size) = if let Some(object) = this.as_object() {
if let Some(map) = object.borrow_mut().as_map_mut() { if let Some(map) = object.borrow_mut().as_map_mut() {
let deleted = map.remove(&key).is_some(); let deleted = map.remove(key).is_some();
(deleted, map.len()) (deleted, map.len())
} else { } else {
return Err(context.construct_type_error("'this' is not a Map")); return Err(context.construct_type_error("'this' is not a Map"));
@ -312,12 +311,12 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let key = args.get(0).cloned().unwrap_or_default(); let key = args.get_or_undefined(0);
if let JsValue::Object(ref object) = this { if let JsValue::Object(ref object) = this {
let object = object.borrow(); let object = object.borrow();
if let Some(map) = object.as_map_ref() { if let Some(map) = object.as_map_ref() {
return Ok(if let Some(result) = map.get(&key) { return Ok(if let Some(result) = map.get(key) {
result.clone() result.clone()
} else { } else {
JsValue::undefined() JsValue::undefined()
@ -361,12 +360,12 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let key = args.get(0).cloned().unwrap_or_default(); let key = args.get_or_undefined(0);
if let JsValue::Object(ref object) = this { if let JsValue::Object(ref object) = this {
let object = object.borrow(); let object = object.borrow();
if let Some(map) = object.as_map_ref() { if let Some(map) = object.as_map_ref() {
return Ok(map.contains_key(&key).into()); return Ok(map.contains_key(key).into());
} }
} }
@ -393,7 +392,7 @@ impl Map {
} }
let callback_arg = &args[0]; let callback_arg = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let this_arg = args.get_or_undefined(1);
let mut index = 0; let mut index = 0;
@ -416,7 +415,7 @@ impl Map {
}; };
if let Some(arguments) = arguments { if let Some(arguments) = arguments {
context.call(callback_arg, &this_arg, &arguments)?; context.call(callback_arg, this_arg, &arguments)?;
} }
index += 1; index += 1;

22
boa/src/builtins/mod.rs

@ -112,3 +112,25 @@ pub fn init(context: &mut Context) {
global_object.borrow_mut().insert(name, property); global_object.borrow_mut().insert(name, property);
} }
} }
pub trait JsArgs {
/// Utility function to `get` a parameter from
/// a `[JsValue]` or default to `JsValue::Undefined`
/// if `get` returns `None`.
///
/// Call this if you are thinking of calling something similar to
/// `args.get(n).cloned().unwrap_or_default()` or
/// `args.get(n).unwrap_or(&undefined)`.
///
/// This returns a reference for efficiency, in case
/// you only need to call methods of `JsValue`, so
/// try to minimize calling `clone`.
fn get_or_undefined(&self, index: usize) -> &JsValue;
}
impl JsArgs for [JsValue] {
fn get_or_undefined(&self, index: usize) -> &JsValue {
const UNDEFINED: &JsValue = &JsValue::Undefined;
self.get(index).unwrap_or(UNDEFINED)
}
}

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

@ -13,8 +13,8 @@
//! [spec]: https://tc39.es/ecma262/#sec-number-object //! [spec]: https://tc39.es/ecma262/#sec-number-object
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
use super::function::make_builtin_fn;
use super::string::is_trimmable_whitespace; use super::string::is_trimmable_whitespace;
use super::{function::make_builtin_fn, JsArgs};
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
@ -392,12 +392,12 @@ impl Number {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let precision = args.get(0).cloned().unwrap_or_default(); let precision = args.get_or_undefined(0);
// 1 & 6 // 1 & 6
let mut this_num = Self::this_number_value(this, context)?; let mut this_num = Self::this_number_value(this, context)?;
// 2 // 2
if precision == JsValue::undefined() { if precision.is_undefined() {
return Self::to_string(this, &[], context); return Self::to_string(this, &[], context);
} }
@ -720,7 +720,7 @@ impl Number {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
if let (Some(val), radix) = (args.get(0), args.get(1)) { if let (Some(val), radix) = (args.get(0), args.get_or_undefined(1)) {
// 1. Let inputString be ? ToString(string). // 1. Let inputString be ? ToString(string).
let input_string = val.to_string(context)?; let input_string = val.to_string(context)?;
@ -745,7 +745,7 @@ impl Number {
} }
// 6. Let R be ℝ(? ToInt32(radix)). // 6. Let R be ℝ(? ToInt32(radix)).
let mut var_r = radix.cloned().unwrap_or_default().to_i32(context)?; let mut var_r = radix.to_i32(context)?;
// 7. Let stripPrefix be true. // 7. Let stripPrefix be true.
let mut strip_prefix = true; let mut strip_prefix = true;

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

@ -14,7 +14,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
use crate::{ use crate::{
builtins::BuiltIn, builtins::{BuiltIn, JsArgs},
object::{ object::{
ConstructorBuilder, IntegrityLevel, JsObject, Object as BuiltinObject, ObjectData, ConstructorBuilder, IntegrityLevel, JsObject, Object as BuiltinObject, ObjectData,
ObjectInitializer, ObjectKind, PROTOTYPE, ObjectInitializer, ObjectKind, PROTOTYPE,
@ -135,12 +135,12 @@ impl Object {
/// [spec]: https://tc39.es/ecma262/#sec-object.create /// [spec]: https://tc39.es/ecma262/#sec-object.create
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let prototype = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let prototype = args.get_or_undefined(0);
let properties = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let properties = args.get_or_undefined(1);
let obj = match prototype { let obj = match prototype {
JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype( JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype(
prototype, prototype.clone(),
ObjectData::ordinary(), ObjectData::ordinary(),
)), )),
_ => { _ => {
@ -152,7 +152,7 @@ impl Object {
}; };
if !properties.is_undefined() { if !properties.is_undefined() {
object_define_properties(&obj, properties, context)?; object_define_properties(&obj, properties.clone(), context)?;
return Ok(obj.into()); return Ok(obj.into());
} }
@ -174,10 +174,7 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let object = args let object = args.get_or_undefined(0).to_object(context)?;
.get(0)
.unwrap_or(&JsValue::undefined())
.to_object(context)?;
if let Some(key) = args.get(1) { if let Some(key) = args.get(1) {
let key = key.to_property_key(context)?; let key = key.to_property_key(context)?;
@ -276,10 +273,10 @@ impl Object {
/// Uses the SameValue algorithm to check equality of objects /// Uses the SameValue algorithm to check equality of objects
pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> { pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
let x = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let x = args.get_or_undefined(0);
let y = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let y = args.get_or_undefined(1);
Ok(JsValue::same_value(&x, &y).into()) Ok(JsValue::same_value(x, y).into())
} }
/// Get the `prototype` of an object. /// Get the `prototype` of an object.
@ -319,7 +316,7 @@ impl Object {
.clone(); .clone();
// 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
let proto = args.get(1).cloned().unwrap_or_default(); let proto = args.get_or_undefined(1);
if !matches!(proto.get_type(), Type::Object | Type::Null) { if !matches!(proto.get_type(), Type::Object | Type::Null) {
return ctx.throw_type_error(format!( return ctx.throw_type_error(format!(
"expected an object or null, got {}", "expected an object or null, got {}",
@ -336,7 +333,7 @@ impl Object {
let status = obj let status = obj
.as_object() .as_object()
.expect("obj was not an object") .expect("obj was not an object")
.__set_prototype_of__(proto, ctx)?; .__set_prototype_of__(proto.clone(), ctx)?;
// 5. If status is false, throw a TypeError exception. // 5. If status is false, throw a TypeError exception.
if !status { if !status {
@ -362,11 +359,11 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let undefined = JsValue::undefined(); let v = args.get_or_undefined(0);
let mut v = args.get(0).unwrap_or(&undefined).clone();
if !v.is_object() { if !v.is_object() {
return Ok(JsValue::new(false)); return Ok(JsValue::new(false));
} }
let mut v = v.clone();
let o = JsValue::new(this.to_object(context)?); let o = JsValue::new(this.to_object(context)?);
loop { loop {
v = Self::get_prototype_of(this, &[v], context)?; v = Self::get_prototype_of(this, &[v], context)?;
@ -385,7 +382,7 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let object = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let object = args.get_or_undefined(0);
if let Some(object) = object.as_object() { if let Some(object) = object.as_object() {
let key = args let key = args
.get(1) .get(1)
@ -419,12 +416,12 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let arg = args.get(0).cloned().unwrap_or_default(); let arg = args.get_or_undefined(0);
let arg_obj = arg.as_object(); let arg_obj = arg.as_object();
if let Some(obj) = arg_obj { if let Some(obj) = arg_obj {
let props = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let props = args.get_or_undefined(1);
object_define_properties(&obj, props, context)?; object_define_properties(&obj, props.clone(), context)?;
Ok(arg) Ok(arg.clone())
} else { } else {
context.throw_type_error("Expected an object") context.throw_type_error("Expected an object")
} }
@ -572,11 +569,7 @@ impl Object {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let to be ? ToObject(target). // 1. Let to be ? ToObject(target).
let to = args let to = args.get_or_undefined(0).to_object(context)?;
.get(0)
.cloned()
.unwrap_or_default()
.to_object(context)?;
// 2. If only one argument was passed, return to. // 2. If only one argument was passed, return to.
if args.len() == 1 { if args.len() == 1 {

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

@ -18,7 +18,7 @@ use crate::{
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };
use super::Array; use super::{Array, JsArgs};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -81,14 +81,14 @@ impl Reflect {
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be a function"))?; .ok_or_else(|| context.construct_type_error("target must be a function"))?;
let this_arg = args.get(1).cloned().unwrap_or_default(); let this_arg = args.get_or_undefined(1);
let args_list = args.get(2).cloned().unwrap_or_default(); let args_list = args.get_or_undefined(2);
if !target.is_callable() { if !target.is_callable() {
return context.throw_type_error("target must be a function"); return context.throw_type_error("target must be a function");
} }
let args = args_list.create_list_from_array_like(&[], context)?; let args = args_list.create_list_from_array_like(&[], context)?;
target.call(&this_arg, &args, context) target.call(this_arg, &args, context)
} }
/// Calls a target function as a constructor with arguments. /// Calls a target function as a constructor with arguments.
@ -108,7 +108,7 @@ impl Reflect {
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be a function"))?; .ok_or_else(|| context.construct_type_error("target must be a function"))?;
let args_list = args.get(1).cloned().unwrap_or_default(); let args_list = args.get_or_undefined(1);
if !target.is_constructable() { if !target.is_constructable() {
return context.throw_type_error("target must be a constructor"); return context.throw_type_error("target must be a constructor");
@ -140,12 +140,11 @@ impl Reflect {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
let target = args let target = args
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; let key = args.get_or_undefined(1).to_property_key(context)?;
let prop_desc: JsValue = args let prop_desc: JsValue = args
.get(2) .get(2)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
@ -170,12 +169,11 @@ impl Reflect {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
let target = args let target = args
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; let key = args.get_or_undefined(1).to_property_key(context)?;
Ok(target.__delete__(&key, context)?.into()) Ok(target.__delete__(&key, context)?.into())
} }
@ -189,14 +187,13 @@ impl Reflect {
/// [spec]: https://tc39.es/ecma262/#sec-reflect.get /// [spec]: https://tc39.es/ecma262/#sec-reflect.get
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
pub(crate) fn get(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn get(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
// 1. If Type(target) is not Object, throw a TypeError exception. // 1. If Type(target) is not Object, throw a TypeError exception.
let target = args let target = args
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
// 2. Let key be ? ToPropertyKey(propertyKey). // 2. Let key be ? ToPropertyKey(propertyKey).
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; let key = args.get_or_undefined(1).to_property_key(context)?;
// 3. If receiver is not present, then // 3. If receiver is not present, then
let receiver = if let Some(receiver) = args.get(2).cloned() { let receiver = if let Some(receiver) = args.get(2).cloned() {
receiver receiver
@ -347,13 +344,12 @@ impl Reflect {
/// [spec]: https://tc39.es/ecma262/#sec-reflect.set /// [spec]: https://tc39.es/ecma262/#sec-reflect.set
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
pub(crate) fn set(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn set(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
let target = args let target = args
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; let key = args.get_or_undefined(1).to_property_key(context)?;
let value = args.get(2).unwrap_or(&undefined); let value = args.get_or_undefined(2);
let receiver = if let Some(receiver) = args.get(3).cloned() { let receiver = if let Some(receiver) = args.get(3).cloned() {
receiver receiver
} else { } else {
@ -377,12 +373,11 @@ impl Reflect {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
let mut target = args let mut target = args
.get(0) .get(0)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let proto = args.get(1).unwrap_or(&undefined); let proto = args.get_or_undefined(1);
if !proto.is_null() && !proto.is_object() { if !proto.is_null() && !proto.is_object() {
return context.throw_type_error("proto must be an object or null"); return context.throw_type_error("proto must be an object or null");
} }

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

@ -23,6 +23,8 @@ use crate::{
use regexp_string_iterator::RegExpStringIterator; use regexp_string_iterator::RegExpStringIterator;
use regress::Regex; use regress::Regex;
use super::JsArgs;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -187,8 +189,8 @@ impl RegExp {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let pattern = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let pattern = args.get_or_undefined(0);
let flags = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let flags = args.get_or_undefined(1);
// 1. Let patternIsRegExp be ? IsRegExp(pattern). // 1. Let patternIsRegExp be ? IsRegExp(pattern).
let pattern_is_regexp = if let JsValue::Object(obj) = &pattern { let pattern_is_regexp = if let JsValue::Object(obj) = &pattern {
@ -233,12 +235,12 @@ impl RegExp {
JsValue::new(regexp.original_flags.clone()), JsValue::new(regexp.original_flags.clone()),
) )
} else { } else {
(JsValue::new(regexp.original_source.clone()), flags) (JsValue::new(regexp.original_source.clone()), flags.clone())
} }
} else { } else {
// a. Let P be pattern. // a. Let P be pattern.
// b. Let F be flags. // b. Let F be flags.
(pattern, flags) (pattern.clone(), flags.clone())
}; };
// 7. Let O be ? RegExpAlloc(newTarget). // 7. Let O be ? RegExpAlloc(newTarget).
@ -275,8 +277,8 @@ impl RegExp {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
fn initialize(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn initialize(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let pattern = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let pattern = args.get_or_undefined(0);
let flags = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let flags = args.get_or_undefined(1);
// 1. If pattern is undefined, let P be the empty String. // 1. If pattern is undefined, let P be the empty String.
// 2. Else, let P be ? ToString(pattern). // 2. Else, let P be ? ToString(pattern).
@ -1282,7 +1284,7 @@ impl RegExp {
let length_arg_str = arg_str.encode_utf16().count(); let length_arg_str = arg_str.encode_utf16().count();
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let mut replace_value = args.get(1).cloned().unwrap_or_default(); let mut replace_value = args.get_or_undefined(1).clone();
let functional_replace = replace_value.is_function(); let functional_replace = replace_value.is_function();
// 6. If functionalReplace is false, then // 6. If functionalReplace is false, then
@ -1619,7 +1621,7 @@ impl RegExp {
let mut length_a = 0; let mut length_a = 0;
// 13. If limit is undefined, let lim be 2^32 - 1; else let lim be ℝ(? ToUint32(limit)). // 13. If limit is undefined, let lim be 2^32 - 1; else let lim be ℝ(? ToUint32(limit)).
let limit = args.get(1).cloned().unwrap_or_default(); let limit = args.get_or_undefined(1);
let lim = if limit.is_undefined() { let lim = if limit.is_undefined() {
u32::MAX u32::MAX
} else { } else {

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

@ -22,6 +22,8 @@ use ordered_set::OrderedSet;
pub mod set_iterator; pub mod set_iterator;
use set_iterator::SetIterator; use set_iterator::SetIterator;
use super::JsArgs;
pub mod ordered_set; pub mod ordered_set;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -139,7 +141,7 @@ impl Set {
// 3 // 3
set.set_data(ObjectData::set(OrderedSet::default())); set.set_data(ObjectData::set(OrderedSet::default()));
let iterable = args.get(0).cloned().unwrap_or_default(); let iterable = args.get_or_undefined(0);
// 4 // 4
if iterable.is_null_or_undefined() { if iterable.is_null_or_undefined() {
return Ok(set); return Ok(set);
@ -154,7 +156,7 @@ impl Set {
} }
// 7 // 7
let iterator_record = get_iterator(context, iterable)?; let iterator_record = get_iterator(context, iterable.clone())?;
// 8.a // 8.a
let mut next = iterator_record.next(context)?; let mut next = iterator_record.next(context)?;
@ -206,14 +208,15 @@ impl Set {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let mut value = args.get(0).cloned().unwrap_or_default(); let value = args.get_or_undefined(0);
if let Some(object) = this.as_object() { if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() { if let Some(set) = object.borrow_mut().as_set_mut() {
if value.as_number().map(|n| n == -0f64).unwrap_or(false) { set.add(if value.as_number().map(|n| n == -0f64).unwrap_or(false) {
value = JsValue::Integer(0); JsValue::Integer(0)
} } else {
set.add(value); value.clone()
});
} else { } else {
return context.throw_type_error("'this' is not a Set"); return context.throw_type_error("'this' is not a Set");
} }
@ -263,11 +266,11 @@ impl Set {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let value = args.get(0).cloned().unwrap_or_default(); let value = args.get_or_undefined(0);
let res = if let Some(object) = this.as_object() { let res = if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() { if let Some(set) = object.borrow_mut().as_set_mut() {
set.delete(&value) set.delete(value)
} else { } else {
return context.throw_type_error("'this' is not a Set"); return context.throw_type_error("'this' is not a Set");
} }
@ -332,12 +335,12 @@ impl Set {
} }
let callback_arg = &args[0]; let callback_arg = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(JsValue::undefined); let this_arg = args.get_or_undefined(1);
// TODO: if condition should also check that we are not in strict mode // TODO: if condition should also check that we are not in strict mode
let this_arg = if this_arg.is_undefined() { let this_arg = if this_arg.is_undefined() {
JsValue::Object(context.global_object()) JsValue::Object(context.global_object())
} else { } else {
this_arg this_arg.clone()
}; };
let mut index = 0; let mut index = 0;
@ -380,12 +383,12 @@ impl Set {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let value = args.get(0).cloned().unwrap_or_default(); let value = args.get_or_undefined(0);
if let JsValue::Object(ref object) = this { if let JsValue::Object(ref object) = this {
let object = object.borrow(); let object = object.borrow();
if let Some(set) = object.as_set_ref() { if let Some(set) = object.as_set_ref() {
return Ok(set.contains(&value).into()); return Ok(set.contains(value).into());
} }
} }

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

@ -29,6 +29,8 @@ use std::{
}; };
use unicode_normalization::UnicodeNormalization; use unicode_normalization::UnicodeNormalization;
use super::JsArgs;
pub(crate) fn code_point_at(string: JsString, position: i32) -> Option<(u32, u8, bool)> { pub(crate) fn code_point_at(string: JsString, position: i32) -> Option<(u32, u8, bool)> {
let size = string.encode_utf16().count() as i32; let size = string.encode_utf16().count() as i32;
if position < 0 || position >= size { if position < 0 || position >= size {
@ -562,9 +564,9 @@ impl String {
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = this.to_string(context)?; let primitive_val = this.to_string(context)?;
let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let arg = args.get_or_undefined(0);
if Self::is_regexp_object(&arg) { if Self::is_regexp_object(arg) {
context.throw_type_error( context.throw_type_error(
"First argument to String.prototype.startsWith must not be a regular expression", "First argument to String.prototype.startsWith must not be a regular expression",
)?; )?;
@ -576,12 +578,10 @@ impl String {
let search_length = search_string.chars().count() as i32; let search_length = search_string.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position = if args.len() < 2 { let position = if let Some(integer) = args.get(1) {
0 integer.to_integer(context)? as i32
} else { } else {
args.get(1) 0
.expect("failed to get arg")
.to_integer(context)? as i32
}; };
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -617,9 +617,9 @@ impl String {
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = this.to_string(context)?; let primitive_val = this.to_string(context)?;
let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let arg = args.get_or_undefined(0);
if Self::is_regexp_object(&arg) { if Self::is_regexp_object(arg) {
context.throw_type_error( context.throw_type_error(
"First argument to String.prototype.endsWith must not be a regular expression", "First argument to String.prototype.endsWith must not be a regular expression",
)?; )?;
@ -632,12 +632,10 @@ impl String {
// If less than 2 args specified, end_position is 'undefined', defaults to // If less than 2 args specified, end_position is 'undefined', defaults to
// length of this // length of this
let end_position = if args.len() < 2 { let end_position = if let Some(integer) = args.get(1) {
length integer.to_integer(context)? as i32
} else { } else {
args.get(1) length
.expect("Could not get argument")
.to_integer(context)? as i32
}; };
let end = min(max(end_position, 0), length); let end = min(max(end_position, 0), length);
@ -671,9 +669,9 @@ impl String {
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = this.to_string(context)?; let primitive_val = this.to_string(context)?;
let arg = args.get(0).cloned().unwrap_or_else(JsValue::undefined); let arg = args.get_or_undefined(0);
if Self::is_regexp_object(&arg) { if Self::is_regexp_object(arg) {
context.throw_type_error( context.throw_type_error(
"First argument to String.prototype.includes must not be a regular expression", "First argument to String.prototype.includes must not be a regular expression",
)?; )?;
@ -684,12 +682,11 @@ impl String {
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position = if args.len() < 2 {
0 let position = if let Some(integer) = args.get(1) {
integer.to_integer(context)? as i32
} else { } else {
args.get(1) 0
.expect("Could not get argument")
.to_integer(context)? as i32
}; };
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -730,9 +727,9 @@ impl String {
// 1. Let O be ? RequireObjectCoercible(this value). // 1. Let O be ? RequireObjectCoercible(this value).
this.require_object_coercible(context)?; this.require_object_coercible(context)?;
let search_value = args.get(0).cloned().unwrap_or_default(); let search_value = args.get_or_undefined(0);
let replace_value = args.get(1).cloned().unwrap_or_default(); let replace_value = args.get_or_undefined(1);
// 2. If searchValue is neither undefined nor null, then // 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() { if !search_value.is_null_or_undefined() {
@ -747,8 +744,8 @@ impl String {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return context.call( return context.call(
&replacer.into(), &replacer.into(),
&search_value, search_value,
&[this.clone(), replace_value], &[this.clone(), replace_value.clone()],
); );
} }
} }
@ -787,7 +784,7 @@ impl String {
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
context context
.call( .call(
&replace_value, replace_value,
&JsValue::undefined(), &JsValue::undefined(),
&[search_str.into(), position.into(), this_str.clone().into()], &[search_str.into(), position.into(), this_str.clone().into()],
)? )?
@ -846,8 +843,8 @@ impl String {
// 1. Let O be ? RequireObjectCoercible(this value). // 1. Let O be ? RequireObjectCoercible(this value).
let o = this.require_object_coercible(context)?; let o = this.require_object_coercible(context)?;
let search_value = args.get(0).cloned().unwrap_or_default(); let search_value = args.get_or_undefined(0);
let replace_value = args.get(1).cloned().unwrap_or_default(); let replace_value = args.get_or_undefined(1);
// 2. If searchValue is neither undefined nor null, then // 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() { if !search_value.is_null_or_undefined() {
@ -879,7 +876,7 @@ impl String {
// d. If replacer is not undefined, then // d. If replacer is not undefined, then
if let Some(replacer) = replacer { if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return replacer.call(&search_value, &[o.into(), replace_value], context); return replacer.call(search_value, &[o.into(), replace_value.clone()], context);
} }
} }
@ -945,7 +942,7 @@ impl String {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
context context
.call( .call(
&replace_value, replace_value,
&JsValue::undefined(), &JsValue::undefined(),
&[ &[
search_string.clone().into(), search_string.clone().into(),
@ -1109,14 +1106,14 @@ impl String {
let o = this.require_object_coercible(context)?; let o = this.require_object_coercible(context)?;
// 2. If regexp is neither undefined nor null, then // 2. If regexp is neither undefined nor null, then
let regexp = args.get(0).cloned().unwrap_or_default(); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let matcher be ? GetMethod(regexp, @@match). // a. Let matcher be ? GetMethod(regexp, @@match).
// b. If matcher is not undefined, then // b. If matcher is not undefined, then
if let Some(obj) = regexp.as_object() { if let Some(obj) = regexp.as_object() {
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? { if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
return matcher.call(&regexp, &[o.clone()], context); return matcher.call(regexp, &[o.clone()], context);
} }
} }
} }
@ -1125,7 +1122,7 @@ impl String {
let s = o.to_string(context)?; let s = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, undefined). // 4. Let rx be ? RegExpCreate(regexp, undefined).
let rx = RegExp::create(regexp, JsValue::undefined(), context)?; let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@match, « S »). // 5. Return ? Invoke(rx, @@match, « S »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); let obj = rx.as_object().expect("RegExpCreate must return Object");
@ -1370,21 +1367,17 @@ impl String {
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = this.to_string(context)?; let primitive_val = this.to_string(context)?;
// If no args are specified, start is 'undefined', defaults to 0 // If no args are specified, start is 'undefined', defaults to 0
let start = if args.is_empty() { let start = if let Some(integer) = args.get(0) {
0 integer.to_integer(context)? as i32
} else { } else {
args.get(0) 0
.expect("failed to get argument for String method")
.to_integer(context)? as i32
}; };
let length = primitive_val.encode_utf16().count() as i32; let length = primitive_val.encode_utf16().count() as i32;
// If less than 2 args specified, end is the length of the this object converted to a String // If less than 2 args specified, end is the length of the this object converted to a String
let end = if args.len() < 2 { let end = if let Some(integer) = args.get(1) {
length integer.to_integer(context)? as i32
} else { } else {
args.get(1) length
.expect("Could not get argument")
.to_integer(context)? as i32
}; };
// Both start and end args replaced by 0 if they were negative // Both start and end args replaced by 0 if they were negative
// or by the length of the String if they were greater // or by the length of the String if they were greater
@ -1425,24 +1418,20 @@ impl String {
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = this.to_string(context)?; let primitive_val = this.to_string(context)?;
// If no args are specified, start is 'undefined', defaults to 0 // If no args are specified, start is 'undefined', defaults to 0
let mut start = if args.is_empty() { let mut start = if let Some(integer) = args.get(0) {
0 integer.to_integer(context)? as i32
} else { } else {
args.get(0) 0
.expect("failed to get argument for String method")
.to_integer(context)? as i32
}; };
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
// If less than 2 args specified, end is +infinity, the maximum number value. // If less than 2 args specified, end is +infinity, the maximum number value.
// Using i32::max_value() should be safe because the final length used is at most // Using i32::max_value() should be safe because the final length used is at most
// the number of code units from start to the end of the string, // the number of code units from start to the end of the string,
// which should always be smaller or equals to both +infinity and i32::max_value // which should always be smaller or equals to both +infinity and i32::max_value
let end = if args.len() < 2 { let end = if let Some(integer) = args.get(1) {
i32::MAX integer.to_integer(context)? as i32
} else { } else {
args.get(1) i32::MAX
.expect("Could not get argument")
.to_integer(context)? as i32
}; };
// If start is negative it become the number of code units from the end of the string // If start is negative it become the number of code units from the end of the string
if start < 0 { if start < 0 {
@ -1485,8 +1474,8 @@ impl String {
// 1. Let O be ? RequireObjectCoercible(this value). // 1. Let O be ? RequireObjectCoercible(this value).
let this = this.require_object_coercible(context)?; let this = this.require_object_coercible(context)?;
let separator = args.get(0).cloned().unwrap_or_default(); let separator = args.get_or_undefined(0);
let limit = args.get(1).cloned().unwrap_or_default(); let limit = args.get_or_undefined(1);
// 2. If separator is neither undefined nor null, then // 2. If separator is neither undefined nor null, then
if !separator.is_null_or_undefined() { if !separator.is_null_or_undefined() {
@ -1498,7 +1487,7 @@ impl String {
.get_method(context, WellKnownSymbols::split())? .get_method(context, WellKnownSymbols::split())?
{ {
// i. Return ? Call(splitter, separator, « O, limit »). // i. Return ? Call(splitter, separator, « O, limit »).
return splitter.call(&separator, &[this.clone(), limit], context); return splitter.call(separator, &[this.clone(), limit.clone()], context);
} }
} }
@ -1661,7 +1650,7 @@ impl String {
let o = this.require_object_coercible(context)?; let o = this.require_object_coercible(context)?;
// 2. If regexp is neither undefined nor null, then // 2. If regexp is neither undefined nor null, then
let regexp = args.get(0).cloned().unwrap_or_default(); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let isRegExp be ? IsRegExp(regexp). // a. Let isRegExp be ? IsRegExp(regexp).
// b. If isRegExp is true, then // b. If isRegExp is true, then
@ -1685,7 +1674,7 @@ impl String {
if let Some(obj) = regexp.as_object() { if let Some(obj) = regexp.as_object() {
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? { if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
return matcher.call(&regexp, &[o.clone()], context); return matcher.call(regexp, &[o.clone()], context);
} }
} }
} }
@ -1694,7 +1683,7 @@ impl String {
let s = o.to_string(context)?; let s = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, "g"). // 4. Let rx be ? RegExpCreate(regexp, "g").
let rx = RegExp::create(regexp, JsValue::new("g"), context)?; let rx = RegExp::create(regexp.clone(), JsValue::new("g"), context)?;
// 5. Return ? Invoke(rx, @@matchAll, « S »). // 5. Return ? Invoke(rx, @@matchAll, « S »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); let obj = rx.as_object().expect("RegExpCreate must return Object");
@ -1722,7 +1711,7 @@ impl String {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let this = this.require_object_coercible(context)?; let this = this.require_object_coercible(context)?;
let s = this.to_string(context)?; let s = this.to_string(context)?;
let form = args.get(0).cloned().unwrap_or_default(); let form = args.get_or_undefined(0);
let f_str; let f_str;
@ -1762,14 +1751,14 @@ impl String {
let o = this.require_object_coercible(context)?; let o = this.require_object_coercible(context)?;
// 2. If regexp is neither undefined nor null, then // 2. If regexp is neither undefined nor null, then
let regexp = args.get(0).cloned().unwrap_or_default(); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let searcher be ? GetMethod(regexp, @@search). // a. Let searcher be ? GetMethod(regexp, @@search).
// b. If searcher is not undefined, then // b. If searcher is not undefined, then
if let Some(obj) = regexp.as_object() { if let Some(obj) = regexp.as_object() {
if let Some(searcher) = obj.get_method(context, WellKnownSymbols::search())? { if let Some(searcher) = obj.get_method(context, WellKnownSymbols::search())? {
// i. Return ? Call(searcher, regexp, « O »). // i. Return ? Call(searcher, regexp, « O »).
return searcher.call(&regexp, &[o.clone()], context); return searcher.call(regexp, &[o.clone()], context);
} }
} }
} }
@ -1778,7 +1767,7 @@ impl String {
let string = o.to_string(context)?; let string = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, undefined). // 4. Let rx be ? RegExpCreate(regexp, undefined).
let rx = RegExp::create(regexp, JsValue::undefined(), context)?; let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@search, « string »). // 5. Return ? Invoke(rx, @@search, « string »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); let obj = rx.as_object().expect("RegExpCreate must return Object");

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

@ -31,6 +31,8 @@ use std::cell::RefCell;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use super::JsArgs;
thread_local! { thread_local! {
static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new()); static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
} }
@ -276,7 +278,7 @@ impl Symbol {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let sym = args.get(0).cloned().unwrap_or_default(); let sym = args.get_or_undefined(0);
// 1. If Type(sym) is not Symbol, throw a TypeError exception. // 1. If Type(sym) is not Symbol, throw a TypeError exception.
if let Some(sym) = sym.as_symbol() { if let Some(sym) = sym.as_symbol() {
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do

3
boa/src/class.rs

@ -7,6 +7,7 @@
//!# class::{Class, ClassBuilder}, //!# class::{Class, ClassBuilder},
//!# gc::{Finalize, Trace}, //!# gc::{Finalize, Trace},
//!# Context, JsResult, JsValue, //!# Context, JsResult, JsValue,
//!# builtins::JsArgs,
//!# }; //!# };
//!# //!#
//! // This does not have to be an enum it can also be a struct. //! // This does not have to be an enum it can also be a struct.
@ -27,7 +28,7 @@
//! // This is what is called when we do `new Animal()` //! // This is what is called when we do `new Animal()`
//! fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Self> { //! fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Self> {
//! // This is equivalent to `String(arg)`. //! // This is equivalent to `String(arg)`.
//! let kind = args.get(0).cloned().unwrap_or_default().to_string(context)?; //! let kind = args.get_or_undefined(0).to_string(context)?;
//! //!
//! let animal = match kind.as_str() { //! let animal = match kind.as_str() {
//! "cat" => Self::Cat, //! "cat" => Self::Cat,

Loading…
Cancel
Save