diff --git a/Cargo.lock b/Cargo.lock index fb34bccb96d..f080117fe54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,7 @@ dependencies = [ "static_assertions", "sys-locale", "tap", + "thiserror", "unicode-normalization", ] diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index d2555dbebd7..88694009e0e 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -118,7 +118,6 @@ impl Opt { #[derive(Debug, Clone, ValueEnum)] enum DumpFormat { /// The different types of format available for dumping. - /// // NOTE: This can easily support other formats just by // adding a field to this enum and adding the necessary // implementation. Example: Toml, Html, etc. @@ -204,7 +203,7 @@ pub fn main() -> Result<(), io::Error> { } else { match context.eval(&buffer) { Ok(v) => println!("{}", v.display()), - Err(v) => eprintln!("Uncaught {}", v.display()), + Err(v) => eprintln!("Uncaught {v}"), } } } @@ -251,11 +250,7 @@ pub fn main() -> Result<(), io::Error> { match context.eval(line.trim_end()) { Ok(v) => println!("{}", v.display()), Err(v) => { - eprintln!( - "{}: {}", - "Uncaught".red(), - v.display().to_string().red() - ); + eprintln!("{}: {}", "Uncaught".red(), v.to_string().red()); } } } diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 35a8d5d8c56..3ea19abbafb 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -53,6 +53,7 @@ once_cell = "1.15.0" tap = "1.0.1" sptr = "0.3.2" static_assertions = "1.1.0" +thiserror = "1.0.37" icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true } icu_locid = { version = "0.6.0", features = ["serde"], optional = true } icu_datetime = { version = "0.6.0", features = ["serde"], optional = true } diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index b06b921ea32..5ab9221ed1b 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -1,6 +1,6 @@ //! This module implements the JavaScript bigint primitive rust type. -use crate::{builtins::Number, Context, JsValue}; +use crate::{builtins::Number, error::JsNativeError, JsResult}; use num_integer::Integer; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; use std::{ @@ -148,11 +148,13 @@ impl JsBigInt { } #[inline] - pub fn pow(x: &Self, y: &Self, context: &mut Context) -> Result { + pub fn pow(x: &Self, y: &Self) -> JsResult { let y = if let Some(y) = y.inner.to_biguint() { y } else { - return context.throw_range_error("BigInt negative exponent"); + return Err(JsNativeError::range() + .with_message("BigInt negative exponent") + .into()); }; let num_bits = (x.inner.bits() as f64 @@ -161,14 +163,16 @@ impl JsBigInt { + 1f64; if num_bits > 1_000_000_000f64 { - return context.throw_range_error("Maximum BigInt size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum BigInt size exceeded") + .into()); } Ok(Self::new(x.inner.as_ref().clone().pow(y))) } #[inline] - pub fn shift_right(x: &Self, y: &Self, context: &mut Context) -> Result { + pub fn shift_right(x: &Self, y: &Self) -> JsResult { if let Some(n) = y.inner.to_i32() { let inner = if n > 0 { x.inner.as_ref().clone().shr(n as usize) @@ -178,12 +182,14 @@ impl JsBigInt { Ok(Self::new(inner)) } else { - context.throw_range_error("Maximum BigInt size exceeded") + Err(JsNativeError::range() + .with_message("Maximum BigInt size exceeded") + .into()) } } #[inline] - pub fn shift_left(x: &Self, y: &Self, context: &mut Context) -> Result { + pub fn shift_left(x: &Self, y: &Self) -> JsResult { if let Some(n) = y.inner.to_i32() { let inner = if n > 0 { x.inner.as_ref().clone().shl(n as usize) @@ -193,7 +199,9 @@ impl JsBigInt { Ok(Self::new(inner)) } else { - context.throw_range_error("Maximum BigInt size exceeded") + Err(JsNativeError::range() + .with_message("Maximum BigInt size exceeded") + .into()) } } diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 882a47c425a..67e985b94e2 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -1,5 +1,6 @@ use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, + error::JsNativeError, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, @@ -72,7 +73,7 @@ impl ArrayIterator { 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"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; let index = array_iterator.next_index; if array_iterator.done { return Ok(create_iter_result_object( @@ -84,9 +85,11 @@ impl ArrayIterator { 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", - ); + return Err(JsNativeError::typ() + .with_message( + "Cannot get value from typed array that has a detached array buffer", + ) + .into()); } f.array_length() diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 6aeea6020a4..d768cdf922d 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -23,6 +23,7 @@ use crate::{ builtins::BuiltIn, builtins::Number, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -177,7 +178,9 @@ impl Array { .expect("this ToUint32 call must not fail"); // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception. if !JsValue::same_value_zero(&int_len.into(), len) { - return context.throw_range_error("invalid array length"); + return Err(JsNativeError::range() + .with_message("invalid array length") + .into()); } int_len }; @@ -224,7 +227,9 @@ impl Array { ) -> JsResult { // 1. If length > 2^32 - 1, throw a RangeError exception. if length > 2u64.pow(32) - 1 { - return context.throw_range_error("array exceeded max size"); + return Err(JsNativeError::range() + .with_message("array exceeded max size") + .into()); } // 7. Return A. // 2. If proto is not present, set proto to %Array.prototype%. @@ -382,7 +387,9 @@ impl Array { // 8. Return ? Construct(C, « 𝔽(length) »). c.construct(&[JsValue::new(length)], Some(c), context) } else { - context.throw_type_error("Symbol.species must be a constructor") + Err(JsNativeError::typ() + .with_message("Symbol.species must be a constructor") + .into()) } } @@ -414,10 +421,12 @@ impl Array { JsValue::Undefined => None, JsValue::Object(o) if o.is_callable() => Some(o), _ => { - return context.throw_type_error(format!( - "{} is not a function", - mapfn.type_of().to_std_string_escaped() - )) + return Err(JsNativeError::typ() + .with_message(format!( + "{} is not a function", + mapfn.type_of().to_std_string_escaped() + )) + .into()) } }; @@ -488,7 +497,9 @@ impl Array { // which is why it's safe to have this as the fallback return // // 1. Let error be ThrowCompletion(a newly created TypeError object). - let error = context.throw_type_error("Invalid array length"); + let error = Err(JsNativeError::typ() + .with_message("Invalid array length") + .into()); // 2. Return ? IteratorClose(iteratorRecord, error). iterator_record.close(error, context) @@ -679,9 +690,11 @@ impl Array { let len = item.length_of_array_like(context)?; // iii. If n + len > 2^53 - 1, throw a TypeError exception. if n + len > Number::MAX_SAFE_INTEGER as u64 { - return context.throw_type_error( - "length + number of arguments exceeds the max safe integer limit", - ); + return Err(JsNativeError::typ() + .with_message( + "length + number of arguments exceeds the max safe integer limit", + ) + .into()); } // iv. Repeat, while k < len, for k in 0..len { @@ -705,7 +718,9 @@ impl Array { // i. NOTE: E is added as a single item rather than spread. // ii. If n ≥ 2^53 - 1, throw a TypeError exception. if n >= Number::MAX_SAFE_INTEGER as u64 { - return context.throw_type_error("length exceeds the max safe integer limit"); + return Err(JsNativeError::typ() + .with_message("length exceeds the max safe integer limit") + .into()); } // iii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), E). arr.create_data_property_or_throw(n, item, context)?; @@ -745,9 +760,11 @@ impl Array { let arg_count = args.len() as u64; // 4. If len + argCount > 2^53 - 1, throw a TypeError exception. if len + arg_count > 2u64.pow(53) - 1 { - return context.throw_type_error( - "the length + the number of arguments exceed the maximum safe integer limit", - ); + return Err(JsNativeError::typ() + .with_message( + "the length + the number of arguments exceed the maximum safe integer limit", + ) + .into()); } // 5. For each element E of items, do for element in args.iter().cloned() { @@ -822,7 +839,7 @@ impl Array { 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).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.forEach: invalid callback function") + JsNativeError::typ().with_message("Array.prototype.forEach: invalid callback function") })?; // 4. Let k be 0. // 5. Repeat, while k < len, @@ -1096,9 +1113,9 @@ impl Array { if arg_count > 0 { // a. If len + argCount > 2^53 - 1, throw a TypeError exception. if len + arg_count > 2u64.pow(53) - 1 { - return context.throw_type_error( - "length + number of arguments exceeds the max safe integer limit", - ); + return Err(JsNativeError::typ() + .with_message("length + number of arguments exceeds the max safe integer limit") + .into()); } // b. Let k be len. let mut k = len; @@ -1163,7 +1180,7 @@ impl Array { 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).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.every: callback is not callable") + JsNativeError::typ().with_message("Array.prototype.every: callback is not callable") })?; let this_arg = args.get_or_undefined(1); @@ -1215,7 +1232,7 @@ impl Array { 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).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.map: Callbackfn is not callable") + JsNativeError::typ().with_message("Array.prototype.map: Callbackfn is not callable") })?; // 4. Let A be ? ArraySpeciesCreate(O, len). @@ -1423,7 +1440,7 @@ impl Array { // 3. If IsCallable(predicate) is false, throw a TypeError exception. let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.find: predicate is not callable") + JsNativeError::typ().with_message("Array.prototype.find: predicate is not callable") })?; let this_arg = args.get_or_undefined(1); @@ -1480,7 +1497,8 @@ impl Array { // 3. If IsCallable(predicate) is false, throw a TypeError exception. let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.findIndex: predicate is not callable") + JsNativeError::typ() + .with_message("Array.prototype.findIndex: predicate is not callable") })?; let this_arg = args.get_or_undefined(1); @@ -1531,7 +1549,7 @@ impl Array { // 3. If IsCallable(predicate) is false, throw a TypeError exception. let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.findLast: predicate is not callable") + JsNativeError::typ().with_message("Array.prototype.findLast: predicate is not callable") })?; let this_arg = args.get_or_undefined(1); @@ -1583,7 +1601,8 @@ impl Array { // 3. If IsCallable(predicate) is false, throw a TypeError exception. let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.findLastIndex: predicate is not callable") + JsNativeError::typ() + .with_message("Array.prototype.findLastIndex: predicate is not callable") })?; let this_arg = args.get_or_undefined(1); @@ -1688,7 +1707,7 @@ impl Array { // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("flatMap mapper function is not callable") + JsNativeError::typ().with_message("flatMap mapper function is not callable") })?; // 4. Let A be ? ArraySpeciesCreate(O, 0). @@ -1804,8 +1823,9 @@ impl Array { } else { // 1. If targetIndex >= 2^53 - 1, throw a TypeError exception if target_index >= Number::MAX_SAFE_INTEGER as u64 { - return context - .throw_type_error("Target index exceeded max safe integer value"); + return Err(JsNativeError::typ() + .with_message("Target index exceeded max safe integer value") + .into()); } // 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element) @@ -2075,7 +2095,9 @@ impl Array { // 10. If len + insertCount - actualDeleteCount > 2^53 - 1, throw a TypeError exception. if len + insert_count - actual_delete_count > Number::MAX_SAFE_INTEGER as u64 { - return context.throw_type_error("Target splice exceeded max safe integer value"); + return Err(JsNativeError::typ() + .with_message("Target splice exceeded max safe integer value") + .into()); } // 11. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). @@ -2217,7 +2239,7 @@ impl Array { // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.filter: `callback` must be callable") + JsNativeError::typ().with_message("Array.prototype.filter: `callback` must be callable") })?; let this_arg = args.get_or_undefined(1); @@ -2281,7 +2303,7 @@ impl Array { 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).as_callable().ok_or_else(|| { - context.construct_type_error("Array.prototype.some: callback is not callable") + JsNativeError::typ().with_message("Array.prototype.some: callback is not callable") })?; // 4. Let k be 0. @@ -2332,9 +2354,9 @@ impl Array { 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", - ) + return Err(JsNativeError::typ() + .with_message("The comparison function must be either a function or undefined") + .into()) } }; @@ -2466,15 +2488,17 @@ impl Array { // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context - .construct_type_error("Array.prototype.reduce: callback function is not callable") + JsNativeError::typ() + .with_message("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() { - return context.throw_type_error( - "Array.prototype.reduce: called on an empty array and with no initial value", - ); + return Err(JsNativeError::typ() + .with_message( + "Array.prototype.reduce: called on an empty array and with no initial value", + ) + .into()); } // 5. Let k be 0. @@ -2506,9 +2530,9 @@ impl Array { } // c. If kPresent is false, throw a TypeError exception. if !k_present { - return context.throw_type_error( + return Err(JsNativeError::typ().with_message( "Array.prototype.reduce: called on an empty array and with no initial value", - ); + ).into()); } } @@ -2561,16 +2585,15 @@ impl Array { // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { - context.construct_type_error( - "Array.prototype.reduceRight: callback function is not callable", - ) + JsNativeError::typ() + .with_message("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() { - return context.throw_type_error( + return Err(JsNativeError::typ().with_message( "Array.prototype.reduceRight: called on an empty array and with no initial value", - ); + ).into()); } // 5. Let k be len - 1. @@ -2601,9 +2624,9 @@ impl Array { } // c. If kPresent is false, throw a TypeError exception. if !k_present { - return context.throw_type_error( + return Err(JsNativeError::typ().with_message( "Array.prototype.reduceRight: called on an empty array and with no initial value", - ); + ).into()); } } diff --git a/boa_engine/src/builtins/array/tests.rs b/boa_engine/src/builtins/array/tests.rs index 54499815370..67bda0387d6 100644 --- a/boa_engine/src/builtins/array/tests.rs +++ b/boa_engine/src/builtins/array/tests.rs @@ -1421,50 +1421,55 @@ fn array_spread_non_iterable() { fn get_relative_start() { let mut context = Context::default(); - assert_eq!(Array::get_relative_start(&mut context, None, 10), Ok(0)); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::undefined()), 10), - Ok(0) + Array::get_relative_start(&mut context, None, 10).unwrap(), + 0 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10), - Ok(0) + Array::get_relative_start(&mut context, Some(&JsValue::undefined()), 10).unwrap(), + 0 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::INFINITY)), 10), - Ok(10) + Array::get_relative_start(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10) + .unwrap(), + 0 + ); + assert_eq!( + Array::get_relative_start(&mut context, Some(&JsValue::new(f64::INFINITY)), 10).unwrap(), + 10 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(-1)), 10), - Ok(9) + Array::get_relative_start(&mut context, Some(&JsValue::new(-1)), 10).unwrap(), + 9 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(1)), 10), - Ok(1) + Array::get_relative_start(&mut context, Some(&JsValue::new(1)), 10).unwrap(), + 1 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(-11)), 10), - Ok(0) + Array::get_relative_start(&mut context, Some(&JsValue::new(-11)), 10).unwrap(), + 0 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(11)), 10), - Ok(10) + Array::get_relative_start(&mut context, Some(&JsValue::new(11)), 10).unwrap(), + 10 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MIN)), 10), - Ok(0) + Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MIN)), 10).unwrap(), + 0 ); assert_eq!( Array::get_relative_start( &mut context, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10 - ), - Ok(0) + ) + .unwrap(), + 0 ); assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MAX)), 10), - Ok(10) + Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MAX)), 10).unwrap(), + 10 ); // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) @@ -1473,8 +1478,9 @@ fn get_relative_start() { &mut context, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10 - ), - Ok(10) + ) + .unwrap(), + 10 ); } @@ -1482,50 +1488,51 @@ fn get_relative_start() { fn get_relative_end() { let mut context = Context::default(); - assert_eq!(Array::get_relative_end(&mut context, None, 10), Ok(10)); + assert_eq!(Array::get_relative_end(&mut context, None, 10).unwrap(), 10); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::undefined()), 10), - Ok(10) + Array::get_relative_end(&mut context, Some(&JsValue::undefined()), 10).unwrap(), + 10 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10), - Ok(0) + Array::get_relative_end(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10).unwrap(), + 0 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::INFINITY)), 10), - Ok(10) + Array::get_relative_end(&mut context, Some(&JsValue::new(f64::INFINITY)), 10).unwrap(), + 10 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(-1)), 10), - Ok(9) + Array::get_relative_end(&mut context, Some(&JsValue::new(-1)), 10).unwrap(), + 9 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(1)), 10), - Ok(1) + Array::get_relative_end(&mut context, Some(&JsValue::new(1)), 10).unwrap(), + 1 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(-11)), 10), - Ok(0) + Array::get_relative_end(&mut context, Some(&JsValue::new(-11)), 10).unwrap(), + 0 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(11)), 10), - Ok(10) + Array::get_relative_end(&mut context, Some(&JsValue::new(11)), 10).unwrap(), + 10 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MIN)), 10), - Ok(0) + Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MIN)), 10).unwrap(), + 0 ); assert_eq!( Array::get_relative_end( &mut context, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10 - ), - Ok(0) + ) + .unwrap(), + 0 ); assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MAX)), 10), - Ok(10) + Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MAX)), 10).unwrap(), + 10 ); // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) @@ -1534,8 +1541,9 @@ fn get_relative_end() { &mut context, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10 - ), - Ok(10) + ) + .unwrap(), + 10 ); } diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 2b0ff2690c0..e11b6d0410f 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -4,6 +4,7 @@ mod tests; use crate::{ builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -91,8 +92,9 @@ impl ArrayBuffer { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context - .throw_type_error("ArrayBuffer.constructor called with undefined new target"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.constructor called with undefined new target") + .into()); } // 2. Let byteLength be ? ToIndex(length). @@ -142,20 +144,24 @@ impl ArrayBuffer { pub(crate) fn get_byte_length( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). let obj = if let Some(obj) = this.as_object() { obj } else { - return context.throw_type_error("ArrayBuffer.byteLength called with non-object value"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.byteLength called with non-object value") + .into()); }; let obj = obj.borrow(); let o = if let Some(o) = obj.as_array_buffer() { o } else { - return context.throw_type_error("ArrayBuffer.byteLength called with invalid object"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.byteLength called with invalid object") + .into()); }; // TODO: Shared Array Buffer @@ -183,13 +189,17 @@ impl ArrayBuffer { let obj = if let Some(obj) = this.as_object() { obj } else { - return context.throw_type_error("ArrayBuffer.slice called with non-object value"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.slice called with non-object value") + .into()); }; let obj_borrow = obj.borrow(); let o = if let Some(o) = obj_borrow.as_array_buffer() { o } else { - return context.throw_type_error("ArrayBuffer.slice called with invalid object"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.slice called with invalid object") + .into()); }; // TODO: Shared Array Buffer @@ -197,7 +207,9 @@ impl ArrayBuffer { // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. if Self::is_detached_buffer(o) { - return context.throw_type_error("ArrayBuffer.slice called with detached buffer"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.slice called with detached buffer") + .into()); } // 5. Let len be O.[[ArrayBufferByteLength]]. @@ -247,7 +259,7 @@ impl ArrayBuffer { let new_obj = new.borrow(); // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { - context.construct_type_error("ArrayBuffer constructor returned invalid object") + JsNativeError::typ().with_message("ArrayBuffer constructor returned invalid object") })?; // TODO: Shared Array Buffer @@ -255,8 +267,9 @@ impl ArrayBuffer { // 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"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer constructor returned detached ArrayBuffer") + .into()); } } // 20. If SameValue(new, O) is true, throw a TypeError exception. @@ -265,7 +278,9 @@ impl ArrayBuffer { .map(|obj| JsObject::equals(obj, &new)) .unwrap_or_default() { - return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); + return Err(JsNativeError::typ() + .with_message("New ArrayBuffer is the same as this ArrayBuffer") + .into()); } { @@ -276,14 +291,17 @@ impl 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"); + return Err(JsNativeError::typ() + .with_message("New ArrayBuffer length too small") + .into()); } // 22. NOTE: Side-effects of the above steps may have detached O. // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. if Self::is_detached_buffer(o) { - return context - .throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer detached while ArrayBuffer.slice was running") + .into()); } // 24. Let fromBuf be O.[[ArrayBufferData]]. @@ -327,7 +345,7 @@ impl ArrayBuffer { obj.set_prototype(prototype.into()); // 2. Let block be ? CreateByteDataBlock(byteLength). - let block = create_byte_data_block(byte_length, context)?; + let block = create_byte_data_block(byte_length)?; // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. @@ -374,7 +392,9 @@ impl ArrayBuffer { let src_block = if let Some(b) = &self.array_buffer_data { b } else { - return context.throw_syntax_error("Cannot clone detached array buffer"); + return Err(JsNativeError::syntax() + .with_message("Cannot clone detached array buffer") + .into()); }; { @@ -745,16 +765,16 @@ impl ArrayBuffer { /// integer). For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock -pub fn create_byte_data_block(size: u64, context: &mut Context) -> JsResult> { +pub fn create_byte_data_block(size: u64) -> JsResult> { // 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to // create such a Data Block, throw a RangeError exception. let size = size.try_into().map_err(|e| { - context.construct_range_error(format!("couldn't allocate the data block: {e}")) + JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; let mut data_block = Vec::new(); data_block.try_reserve(size).map_err(|e| { - context.construct_range_error(format!("couldn't allocate the data block: {e}")) + JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; // 2. Set all of the bytes of db to 0. diff --git a/boa_engine/src/builtins/array_buffer/tests.rs b/boa_engine/src/builtins/array_buffer/tests.rs index 2670308c918..ae4bcd7da71 100644 --- a/boa_engine/src/builtins/array_buffer/tests.rs +++ b/boa_engine/src/builtins/array_buffer/tests.rs @@ -2,14 +2,10 @@ use super::*; #[test] fn ut_sunny_day_create_byte_data_block() { - let mut context = Context::default(); - - assert!(create_byte_data_block(100, &mut context).is_ok()); + assert!(create_byte_data_block(100).is_ok()); } #[test] fn ut_rainy_day_create_byte_data_block() { - let mut context = Context::default(); - - assert!(create_byte_data_block(u64::MAX, &mut context).is_err()); + assert!(create_byte_data_block(u64::MAX).is_err()); } diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 021141007aa..e6ea7180641 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -10,12 +10,13 @@ use crate::{ generator::GeneratorContext, iterable::create_iter_result_object, promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, }, + error::JsNativeError, object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor}, symbol::WellKnownSymbols, value::JsValue, vm::GeneratorResumeKind, - Context, JsResult, + Context, JsError, JsResult, }; use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_profiler::Profiler; @@ -172,13 +173,17 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object = generator.as_object().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator_object, promise_capability, context); let mut generator_obj_mut = generator_object.borrow_mut(); - let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator, promise_capability, context); @@ -261,13 +266,17 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object = generator.as_object().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator_object, promise_capability, context); let mut generator_obj_mut = generator_object.borrow_mut(); - let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator, promise_capability, context); @@ -345,13 +354,17 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object = generator.as_object().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator_object, promise_capability, context); let mut generator_obj_mut = generator_object.borrow_mut(); - let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator resumed on non generator object") + .into() }); if_abrupt_reject_promise!(generator, promise_capability, context); @@ -386,7 +399,10 @@ impl AsyncGenerator { } // 8. Let completion be ThrowCompletion(exception). - let completion = (Err(args.get_or_undefined(0).clone()), false); + let completion = ( + Err(JsError::from_opaque(args.get_or_undefined(0).clone())), + false, + ); // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -456,11 +472,11 @@ impl AsyncGenerator { // 6. Let value be completion.[[Value]]. match completion { // 7. If completion.[[Type]] is throw, then - Err(value) => { + Err(e) => { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). promise_capability .reject() - .call(&JsValue::undefined(), &[value], context) + .call(&JsValue::undefined(), &[e.to_opaque(context)], context) .expect("cannot fail per spec"); } // 8. Else, @@ -539,6 +555,7 @@ impl AsyncGenerator { } } (Err(value), _) => { + let value = value.to_opaque(context); context.vm.push(value); context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; } @@ -655,7 +672,7 @@ impl AsyncGenerator { gen.state = AsyncGeneratorState::Completed; // b. Let result be ThrowCompletion(reason). - let result = Err(args.get_or_undefined(0).clone()); + let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); // c. Perform AsyncGeneratorCompleteStep(generator, result, true). let next = gen.queue.pop_front().expect("must have one entry"); diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index dc1ddd3485a..ded2c5135a4 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -14,6 +14,7 @@ use crate::{ builtins::{BuiltIn, JsArgs}, + error::JsNativeError, object::ConstructorBuilder, property::Attribute, symbol::WellKnownSymbols, @@ -84,7 +85,9 @@ impl BigInt { ) -> JsResult { // 1. If NewTarget is not undefined, throw a TypeError exception. if !new_target.is_undefined() { - return context.throw_type_error("BigInt is not a constructor"); + return Err(JsNativeError::typ() + .with_message("BigInt is not a constructor") + .into()); } let value = args.get_or_undefined(0); @@ -94,7 +97,7 @@ impl BigInt { // 3. If Type(prim) is Number, return ? NumberToBigInt(prim). if let Some(number) = prim.as_number() { - return Self::number_to_bigint(number, context); + return Self::number_to_bigint(number); } // 4. Otherwise, return ? ToBigInt(value). @@ -108,10 +111,12 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-numbertobigint #[inline] - fn number_to_bigint(number: f64, context: &mut Context) -> JsResult { + fn number_to_bigint(number: f64) -> JsResult { // 1. If IsIntegralNumber(number) is false, throw a RangeError exception. if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { - return context.throw_range_error(format!("Cannot convert {number} to BigInt")); + return Err(JsNativeError::range() + .with_message(format!("Cannot convert {number} to BigInt")) + .into()); } // 2. Return the BigInt value that represents ℝ(number). @@ -129,7 +134,7 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue #[inline] - fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult { + fn this_bigint_value(value: &JsValue) -> JsResult { value // 1. If Type(value) is BigInt, return value. .as_bigint() @@ -143,7 +148,11 @@ impl BigInt { .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")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a BigInt") + .into() + }) } /// `BigInt.prototype.toString( [radix] )` @@ -163,7 +172,7 @@ impl BigInt { context: &mut Context, ) -> JsResult { // 1. Let x be ? thisBigIntValue(this value). - let x = Self::this_bigint_value(this, context)?; + let x = Self::this_bigint_value(this)?; let radix = args.get_or_undefined(0); @@ -181,9 +190,9 @@ impl BigInt { let radix_mv = match radix_mv { IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i, _ => { - return context.throw_range_error( - "radix must be an integer at least 2 and no greater than 36", - ) + return Err(JsNativeError::range() + .with_message("radix must be an integer at least 2 and no greater than 36") + .into()) } }; @@ -209,12 +218,8 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf - pub(crate) fn value_of( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - Ok(JsValue::new(Self::this_bigint_value(this, context)?)) + pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Ok(JsValue::new(Self::this_bigint_value(this)?)) } /// `BigInt.asIntN()` @@ -232,16 +237,11 @@ impl BigInt { let (modulo, bits) = Self::calculate_as_uint_n(args, context)?; if bits > 0 - && modulo - >= JsBigInt::pow( - &JsBigInt::new(2), - &JsBigInt::new(i64::from(bits) - 1), - context, - )? + && modulo >= JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits) - 1))? { Ok(JsValue::new(JsBigInt::sub( &modulo, - &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?, + &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?, ))) } else { Ok(JsValue::new(modulo)) @@ -282,7 +282,7 @@ impl BigInt { Ok(( JsBigInt::mod_floor( &bigint, - &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?, + &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?, ), bits, )) diff --git a/boa_engine/src/builtins/boolean/mod.rs b/boa_engine/src/builtins/boolean/mod.rs index 4377cbd8dab..9a70f288794 100644 --- a/boa_engine/src/builtins/boolean/mod.rs +++ b/boa_engine/src/builtins/boolean/mod.rs @@ -15,6 +15,7 @@ mod tests; use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, @@ -79,11 +80,15 @@ impl Boolean { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue - fn this_boolean_value(value: &JsValue, context: &mut Context) -> JsResult { + fn this_boolean_value(value: &JsValue) -> JsResult { 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")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a boolean") + .into() + }) } /// The `toString()` method returns a string representing the specified `Boolean` object. @@ -95,12 +100,8 @@ impl Boolean { /// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - let boolean = Self::this_boolean_value(this, context)?; + pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + let boolean = Self::this_boolean_value(this)?; Ok(JsValue::new(boolean.to_string())) } @@ -113,11 +114,7 @@ impl Boolean { /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf #[inline] - pub(crate) fn value_of( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - Ok(JsValue::new(Self::this_boolean_value(this, context)?)) + pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Ok(JsValue::new(Self::this_boolean_value(this)?)) } } diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index c2176e4bfbb..be04663e832 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -1,6 +1,7 @@ use crate::{ builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -103,31 +104,36 @@ impl DataView { let buffer_obj = args .get_or_undefined(0) .as_object() - .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; + .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; // 1. If NewTarget is undefined, throw a TypeError exception. let (offset, view_byte_length) = { if new_target.is_undefined() { - return context.throw_type_error("new target is undefined"); + return Err(JsNativeError::typ() + .with_message("new target is undefined") + .into()); } // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). let buffer_borrow = buffer_obj.borrow(); - let buffer = buffer_borrow - .as_array_buffer() - .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; + let buffer = buffer_borrow.as_array_buffer().ok_or_else(|| { + JsNativeError::typ().with_message("buffer must be an ArrayBuffer") + })?; // 3. Let offset be ? ToIndex(byteOffset). let offset = args.get_or_undefined(1).to_index(context)?; // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); } // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. let buffer_byte_length = buffer.array_buffer_byte_length(); // 6. If offset > bufferByteLength, throw a RangeError exception. if offset > buffer_byte_length { - return context - .throw_range_error("Start offset is outside the bounds of the buffer"); + return Err(JsNativeError::range() + .with_message("Start offset is outside the bounds of the buffer") + .into()); } // 7. If byteLength is undefined, then let view_byte_length = if byte_length.is_undefined() { @@ -138,7 +144,9 @@ impl DataView { let view_byte_length = byte_length.to_index(context)?; // 8.b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. if offset + view_byte_length > buffer_byte_length { - return context.throw_range_error("Invalid data view length"); + return Err(JsNativeError::range() + .with_message("Invalid data view length") + .into()); } view_byte_length @@ -154,10 +162,12 @@ impl DataView { if buffer_obj .borrow() .as_array_buffer() - .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))? + .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))? .is_detached_buffer() { - return context.throw_type_error("ArrayBuffer can't be detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer can't be detached") + .into()); } let obj = JsObject::from_proto_and_data( @@ -190,7 +200,7 @@ impl DataView { pub(crate) fn get_buffer( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). @@ -198,7 +208,7 @@ impl DataView { let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) - .ok_or_else(|| context.construct_type_error("`this` is not a DataView"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer = dataview.viewed_array_buffer.clone(); @@ -219,7 +229,7 @@ impl DataView { pub(crate) fn get_byte_length( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). @@ -227,7 +237,7 @@ impl DataView { let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) - .ok_or_else(|| context.construct_type_error("`this` is not a DataView"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer_borrow = dataview.viewed_array_buffer.borrow(); @@ -236,7 +246,9 @@ impl DataView { .expect("DataView must be constructed with an ArrayBuffer"); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if borrow.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); } // 6. Let size be O.[[ByteLength]]. let size = dataview.byte_length; @@ -258,7 +270,7 @@ impl DataView { pub(crate) fn get_byte_offset( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). @@ -266,7 +278,7 @@ impl DataView { let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) - .ok_or_else(|| context.construct_type_error("`this` is not a DataView"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer_borrow = dataview.viewed_array_buffer.borrow(); @@ -275,7 +287,9 @@ impl DataView { .expect("DataView must be constructed with an ArrayBuffer"); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if borrow.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); } // 6. Let offset be O.[[ByteOffset]]. let offset = dataview.byte_offset; @@ -306,7 +320,7 @@ impl DataView { let view = view .as_ref() .and_then(|obj| obj.as_data_view()) - .ok_or_else(|| context.construct_type_error("`this` is not a DataView"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; @@ -322,7 +336,9 @@ impl DataView { // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); } // 7. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; @@ -335,7 +351,9 @@ impl DataView { // 10. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + element_size > view_size { - return context.throw_range_error("Offset is outside the bounds of the DataView"); + return Err(JsNativeError::range() + .with_message("Offset is outside the bounds of the DataView") + .into()); } // 11. Let bufferIndex be getIndex + viewOffset. @@ -665,7 +683,7 @@ impl DataView { let view = view .as_ref() .and_then(|obj| obj.as_data_view()) - .ok_or_else(|| context.construct_type_error("`this` is not a DataView"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; @@ -688,7 +706,9 @@ impl DataView { // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); } // 9. Let viewOffset be view.[[ByteOffset]]. @@ -702,7 +722,9 @@ impl DataView { // 12. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + element_size > view_size { - return context.throw_range_error("Offset is outside the bounds of DataView"); + return Err(JsNativeError::range() + .with_message("Offset is outside the bounds of DataView") + .into()); } // 13. Let bufferIndex be getIndex + viewOffset. diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 7166fd19e59..3bd5bc6f214 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -5,6 +5,7 @@ use super::JsArgs; use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -49,8 +50,8 @@ fn ignore_ambiguity(result: LocalResult) -> Option { macro_rules! getter_method { ($name:ident) => {{ - fn get_value(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - Ok(JsValue::new(this_time_value(this, context)?.$name())) + fn get_value(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Ok(JsValue::new(this_time_value(this)?.$name())) } get_value }}; @@ -392,7 +393,7 @@ impl Date { context: &mut Context, ) -> JsResult { let value = &args[0]; - let tv = match this_time_value(value, context) { + let tv = match this_time_value(value) { Ok(dt) => dt.0, _ => match value.to_primitive(context, PreferredType::Default)? { JsValue::String(ref str) => str @@ -510,7 +511,9 @@ impl Date { let o = if let Some(o) = this.as_object() { o } else { - return context.throw_type_error("Date.prototype[@@toPrimitive] called on non object"); + return Err(JsNativeError::typ() + .with_message("Date.prototype[@@toPrimitive] called on non object") + .into()); }; let hint = args.get_or_undefined(0); @@ -526,8 +529,9 @@ impl Date { Some(number) if number == utf16!("number") => PreferredType::Number, // 5. Else, throw a TypeError exception. _ => { - return context - .throw_type_error("Date.prototype[@@toPrimitive] called with invalid hint") + return Err(JsNativeError::typ() + .with_message("Date.prototype[@@toPrimitive] called with invalid hint") + .into()) } }; @@ -705,10 +709,10 @@ impl Date { pub fn get_timezone_offset( this: &JsValue, _: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let t = this_time_value(this, context)?; + let t = this_time_value(this)?; // 2. If t is NaN, return NaN. if t.0.is_none() { @@ -854,7 +858,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate pub fn set_date(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let dt be ? ToNumber(date). let dt = args @@ -893,7 +897,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). if t.0.is_none() { @@ -951,7 +955,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours pub fn set_hours(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let h be ? ToNumber(hour). let h = args @@ -1010,7 +1014,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Set ms to ? ToNumber(ms). let ms = args @@ -1048,7 +1052,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let m be ? ToNumber(min). let m = args @@ -1096,7 +1100,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth pub fn set_month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let m be ? ToNumber(month). let m = args @@ -1141,7 +1145,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let s be ? ToNumber(sec). let s = args @@ -1182,7 +1186,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setYear pub fn set_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). if t.0.is_none() { @@ -1239,7 +1243,7 @@ impl Date { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime pub fn set_time(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Perform ? thisTimeValue(this value). - this_time_value(this, context)?; + this_time_value(this)?; // 2. Let t be ? ToNumber(time). let t = if let Some(t) = args.get(0) { @@ -1280,7 +1284,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let dt be ? ToNumber(date). let dt = args @@ -1319,7 +1323,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. If t is NaN, set t to +0𝔽. if t.0.is_none() { @@ -1381,7 +1385,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let h be ? ToNumber(hour). let h = args @@ -1440,7 +1444,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let milli be ? ToNumber(ms). let ms = args @@ -1478,7 +1482,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let m be ? ToNumber(min). let m = args @@ -1534,7 +1538,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let m be ? ToNumber(month). let m = args @@ -1581,7 +1585,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - let mut t = this_time_value(this, context)?; + let mut t = this_time_value(this)?; // 2. Let s be ? ToNumber(sec). let s = args @@ -1623,14 +1627,10 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.todatestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toDateString #[allow(clippy::wrong_self_convention)] - pub fn to_date_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub fn to_date_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be this Date object. // 2. Let tv be ? thisTimeValue(O). - let tv = this_time_value(this, context)?; + let tv = this_time_value(this)?; // 3. If tv is NaN, return "Invalid Date". // 4. Let t be LocalTime(tv). @@ -1674,12 +1674,8 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.toisostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString #[allow(clippy::wrong_self_convention)] - pub fn to_iso_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - if let Some(t) = this_time_value(this, context)?.0 { + pub fn to_iso_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + if let Some(t) = this_time_value(this)?.0 { Ok(Utc::now() .timezone() .from_utc_datetime(&t) @@ -1687,7 +1683,9 @@ impl Date { .to_string() .into()) } else { - context.throw_range_error("Invalid time value") + Err(JsNativeError::range() + .with_message("Invalid time value") + .into()) } } @@ -1732,9 +1730,9 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString #[allow(clippy::wrong_self_convention)] - pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + pub fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let tv be ? thisTimeValue(this value). - let tv = this_time_value(this, context)?; + let tv = this_time_value(this)?; // 2. Return ToDateString(tv). if let Some(t) = tv.0 { @@ -1761,14 +1759,10 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date.prototype.totimestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toTimeString #[allow(clippy::wrong_self_convention)] - pub fn to_time_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub fn to_time_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be this Date object. // 2. Let tv be ? thisTimeValue(O). - let tv = this_time_value(this, context)?; + let tv = this_time_value(this)?; // 3. If tv is NaN, return "Invalid Date". // 4. Let t be LocalTime(tv). @@ -1934,9 +1928,13 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-thistimevalue #[inline] -pub fn this_time_value(value: &JsValue, context: &mut Context) -> JsResult { +pub fn this_time_value(value: &JsValue) -> JsResult { value .as_object() .and_then(|obj| obj.borrow().as_date().copied()) - .ok_or_else(|| context.construct_type_error("'this' is not a Date")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a Date") + .into() + }) } diff --git a/boa_engine/src/builtins/date/tests.rs b/boa_engine/src/builtins/date/tests.rs index 057fd576fd1..ec59ae5fca2 100644 --- a/boa_engine/src/builtins/date/tests.rs +++ b/boa_engine/src/builtins/date/tests.rs @@ -7,21 +7,10 @@ use chrono::prelude::*; // this. fn forward_dt_utc(context: &mut Context, src: &str) -> Option { - let date_time = if let Ok(v) = forward_val(context, src) { - v - } else { - panic!("expected success") - }; - - if let JsValue::Object(ref date_time) = date_time { - if let Some(date_time) = date_time.borrow().as_date() { - date_time.0 - } else { - panic!("expected date") - } - } else { - panic!("expected object") - } + let date_time = forward_val(context, src).unwrap(); + let date_time = date_time.as_object().unwrap(); + let date_time = date_time.borrow(); + date_time.as_date().unwrap().0 } fn forward_dt_local(context: &mut Context, src: &str) -> Option { @@ -59,14 +48,9 @@ fn date_this_time_value() { &mut context, "({toString: Date.prototype.toString}).toString()", ) - .expect_err("Expected error"); - let message_property = &error - .get_property("message") - .expect("Expected 'message' property") - .expect_value() - .clone(); - - assert_eq!(JsValue::new("\'this\' is not a Date"), *message_property); + .unwrap_err(); + let error = error.as_native().unwrap(); + assert_eq!("\'this\' is not a Date", error.message()); } #[test] @@ -201,7 +185,7 @@ fn date_ctor_parse_call() { let date_time = forward_val(&mut context, "Date.parse('2020-06-08T09:16:15.779-07:30')"); - assert_eq!(Ok(JsValue::new(1591634775779f64)), date_time); + assert_eq!(JsValue::new(1591634775779f64), date_time.unwrap()); } #[test] @@ -210,14 +194,14 @@ fn date_ctor_utc_call() { let date_time = forward_val(&mut context, "Date.UTC(2020, 06, 08, 09, 16, 15, 779)"); - assert_eq!(Ok(JsValue::new(1594199775779f64)), date_time); + assert_eq!(JsValue::new(1594199775779f64), date_time.unwrap()); } #[test] fn date_ctor_utc_call_nan() { fn check(src: &str) { let mut context = Context::default(); - let date_time = forward_val(&mut context, src).expect("Expected Success"); + let date_time = forward_val(&mut context, src).unwrap(); assert_eq!(JsValue::nan(), date_time); } @@ -238,10 +222,10 @@ fn date_proto_get_date_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getDate()", ); - assert_eq!(Ok(JsValue::new(08f64)), actual); + assert_eq!(JsValue::new(08f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getDate()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -252,10 +236,10 @@ fn date_proto_get_day_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getDay()", ); - assert_eq!(Ok(JsValue::new(3f64)), actual); + assert_eq!(JsValue::new(3f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getDay()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -266,10 +250,10 @@ fn date_proto_get_full_year_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getFullYear()", ); - assert_eq!(Ok(JsValue::new(2020f64)), actual); + assert_eq!(JsValue::new(2020f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getFullYear()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -280,10 +264,10 @@ fn date_proto_get_hours_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getHours()", ); - assert_eq!(Ok(JsValue::new(09f64)), actual); + assert_eq!(JsValue::new(09f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getHours()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -294,10 +278,10 @@ fn date_proto_get_milliseconds_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getMilliseconds()", ); - assert_eq!(Ok(JsValue::new(779f64)), actual); + assert_eq!(JsValue::new(779f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getMilliseconds()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -308,10 +292,10 @@ fn date_proto_get_minutes_call() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getMinutes()", ); - assert_eq!(Ok(JsValue::new(16f64)), actual); + assert_eq!(JsValue::new(16f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getMinutes()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -322,10 +306,10 @@ fn date_proto_get_month() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getMonth()", ); - assert_eq!(Ok(JsValue::new(06f64)), actual); + assert_eq!(JsValue::new(06f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getMonth()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -336,10 +320,10 @@ fn date_proto_get_seconds() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getSeconds()", ); - assert_eq!(Ok(JsValue::new(15f64)), actual); + assert_eq!(JsValue::new(15f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getSeconds()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -355,10 +339,10 @@ fn date_proto_get_time() { .ymd(2020, 07, 08) .and_hms_milli(09, 16, 15, 779) .timestamp_millis() as f64; - assert_eq!(Ok(JsValue::new(ts)), actual); + assert_eq!(JsValue::new(ts), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getTime()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -369,10 +353,10 @@ fn date_proto_get_year() { &mut context, "new Date(2020, 06, 08, 09, 16, 15, 779).getYear()", ); - assert_eq!(Ok(JsValue::new(120f64)), actual); + assert_eq!(JsValue::new(120f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getYear()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -385,7 +369,7 @@ fn date_proto_get_timezone_offset() { ); // NB: Host Settings, not TZ specified in the DateTime. - assert_eq!(Ok(JsValue::new(true)), actual); + assert_eq!(JsValue::new(true), actual.unwrap()); let actual = forward_val( &mut context, @@ -395,13 +379,13 @@ fn date_proto_get_timezone_offset() { // The value of now().offset() depends on the host machine, so we have to replicate the method code here. let offset_seconds = f64::from(chrono::Local::now().offset().local_minus_utc()); let offset_minutes = -offset_seconds / 60f64; - assert_eq!(Ok(JsValue::new(offset_minutes)), actual); + assert_eq!(JsValue::new(offset_minutes), actual.unwrap()); let actual = forward_val( &mut context, "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset()", ); - assert_eq!(Ok(JsValue::new(offset_minutes)), actual); + assert_eq!(JsValue::new(offset_minutes), actual.unwrap()); } #[test] @@ -412,10 +396,10 @@ fn date_proto_get_utc_date_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDate()", ); - assert_eq!(Ok(JsValue::new(08f64)), actual); + assert_eq!(JsValue::new(08f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCDate()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -426,10 +410,10 @@ fn date_proto_get_utc_day_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCDay()", ); - assert_eq!(Ok(JsValue::new(3f64)), actual); + assert_eq!(JsValue::new(3f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCDay()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -440,10 +424,10 @@ fn date_proto_get_utc_full_year_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCFullYear()", ); - assert_eq!(Ok(JsValue::new(2020f64)), actual); + assert_eq!(JsValue::new(2020f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCFullYear()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -454,10 +438,10 @@ fn date_proto_get_utc_hours_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCHours()", ); - assert_eq!(Ok(JsValue::new(09f64)), actual); + assert_eq!(JsValue::new(09f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCHours()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -468,10 +452,10 @@ fn date_proto_get_utc_milliseconds_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMilliseconds()", ); - assert_eq!(Ok(JsValue::new(779f64)), actual); + assert_eq!(JsValue::new(779f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCMilliseconds()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -482,10 +466,10 @@ fn date_proto_get_utc_minutes_call() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMinutes()", ); - assert_eq!(Ok(JsValue::new(16f64)), actual); + assert_eq!(JsValue::new(16f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCMinutes()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -496,10 +480,10 @@ fn date_proto_get_utc_month() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCMonth()", ); - assert_eq!(Ok(JsValue::new(06f64)), actual); + assert_eq!(JsValue::new(06f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCMonth()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -510,10 +494,10 @@ fn date_proto_get_utc_seconds() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).getUTCSeconds()", ); - assert_eq!(Ok(JsValue::new(15f64)), actual); + assert_eq!(JsValue::new(15f64), actual.unwrap()); let actual = forward_val(&mut context, "new Date(1/0).getUTCSeconds()"); - assert_eq!(Ok(JsValue::nan()), actual); + assert_eq!(JsValue::nan(), actual.unwrap()); } #[test] @@ -1145,7 +1129,7 @@ fn date_proto_to_date_string() { &mut context, "let dt = new Date(2020, 06, 08, 09, 16, 15, 779); dt.toDateString()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new("Wed Jul 08 2020"), actual); } @@ -1157,7 +1141,7 @@ fn date_proto_to_gmt_string() { &mut context, "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toGMTString()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new("Wed, 08 Jul 2020 09:16:15 GMT"), actual); } @@ -1169,7 +1153,7 @@ fn date_proto_to_iso_string() { &mut context, "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toISOString()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new("2020-07-08T09:16:15.779Z"), actual); } @@ -1181,7 +1165,7 @@ fn date_proto_to_json() { &mut context, "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toJSON()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new("2020-07-08T09:16:15.779Z"), actual); } @@ -1245,7 +1229,7 @@ fn date_proto_to_utc_string() { &mut context, "let dt = new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)); dt.toUTCString()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new("Wed, 08 Jul 2020 09:16:15 GMT"), actual); } @@ -1257,7 +1241,7 @@ fn date_proto_value_of() { &mut context, "new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)).valueOf()", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new(1594199775779f64), actual); } @@ -1269,7 +1253,7 @@ fn date_neg() { &mut context, "-new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779))", ) - .expect("Successful eval"); + .unwrap(); assert_eq!(JsValue::new(-1594199775779f64), actual); } @@ -1281,7 +1265,7 @@ fn date_json() { &mut context, "JSON.stringify({ date: new Date(Date.UTC(2020, 06, 08, 09, 16, 15, 779)) })", ) - .expect("Successful eval"); + .unwrap(); assert_eq!( JsValue::new(r#"{"date":"2020-07-08T09:16:15.779Z"}"#), actual diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 5c384177c1d..433a8e1fe02 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -19,7 +19,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; #[derive(Debug, Clone, Copy)] pub(crate) struct AggregateError; @@ -73,7 +73,7 @@ impl AggregateError { StandardConstructors::aggregate_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Aggregate)); // 3. If message is not undefined, then let message = args.get_or_undefined(1); diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index 563749207c4..0451756fe1a 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -23,7 +23,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; /// JavaScript `EvalError` implementation. #[derive(Debug, Clone, Copy)] @@ -70,7 +70,7 @@ impl EvalError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Eval)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index ac6accf31ca..863417d7795 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -13,6 +13,7 @@ use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -45,6 +46,30 @@ pub(crate) use self::uri::UriError; use super::JsArgs; +/// The kind of a `NativeError` object, per the [ECMAScript spec][spec]. +/// +/// This is used internally to convert between [`JsObject`] and +/// [`JsNativeError`] correctly, but it can also be used to manually create `Error` +/// objects. However, the recommended way to create them is to construct a +/// `JsNativeError` first, then call [`JsNativeError::to_opaque`], +/// which will assign its prototype, properties and kind automatically. +/// +/// For a description of every error kind and its usage, see +/// [`JsNativeErrorKind`][crate::error::JsNativeErrorKind]. +/// +/// [spec]: https://tc39.es/ecma262/#sec-error-objects +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ErrorKind { + Aggregate, + Error, + Eval, + Type, + Range, + Reference, + Syntax, + Uri, +} + /// Built-in `Error` object. #[derive(Debug, Clone, Copy)] pub(crate) struct Error; @@ -109,7 +134,7 @@ impl Error { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Error)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); @@ -149,7 +174,9 @@ impl Error { o // 2. If Type(O) is not Object, throw a TypeError exception. } else { - return context.throw_type_error("'this' is not an Object"); + return Err(JsNativeError::typ() + .with_message("'this' is not an Object") + .into()); }; // 3. Let name be ? Get(O, "name"). diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index 1fa1258b0a8..3c5de072bf1 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -21,7 +21,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; /// JavaScript `RangeError` implementation. #[derive(Debug, Clone, Copy)] @@ -68,7 +68,7 @@ impl RangeError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Range)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index e130678b955..4eb341cf5c1 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -21,7 +21,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; #[derive(Debug, Clone, Copy)] pub(crate) struct ReferenceError; @@ -74,7 +74,7 @@ impl ReferenceError { StandardConstructors::reference_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Reference)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index c7dfb012d37..abc7f07429a 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -23,7 +23,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; /// JavaScript `SyntaxError` implementation. #[derive(Debug, Clone, Copy)] @@ -73,7 +73,7 @@ impl SyntaxError { StandardConstructors::syntax_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Syntax)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 1f39077784e..55c262ea5b4 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -18,6 +18,7 @@ use crate::{ builtins::{function::Function, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, @@ -27,7 +28,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; /// JavaScript `TypeError` implementation. #[derive(Debug, Clone, Copy)] @@ -74,7 +75,7 @@ impl TypeError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Type)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); @@ -95,8 +96,8 @@ impl TypeError { } pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject { - fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - context.throw_type_error("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") + fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Err(JsNativeError::typ().with_message("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them").into()) } let function = JsObject::from_proto_and_data( diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 21469f338df..cfdcad54daa 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -22,7 +22,7 @@ use crate::{ use boa_profiler::Profiler; use tap::{Conv, Pipe}; -use super::Error; +use super::{Error, ErrorKind}; /// JavaScript `URIError` implementation. #[derive(Debug, Clone, Copy)] @@ -69,7 +69,7 @@ impl UriError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Uri)); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 2fa416b611e..fc1088c4eed 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -11,9 +11,10 @@ use crate::{ builtins::{BuiltIn, JsArgs}, + error::JsNativeError, object::FunctionBuilder, property::Attribute, - Context, JsValue, + Context, JsResult, JsValue, }; use boa_profiler::Profiler; use rustc_hash::FxHashSet; @@ -48,7 +49,7 @@ impl Eval { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-eval-x - fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { + fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Return ? PerformEval(x, false, false). Self::perform_eval(args.get_or_undefined(0), false, false, context) } @@ -64,7 +65,7 @@ impl Eval { direct: bool, strict: bool, context: &mut Context, - ) -> Result { + ) -> JsResult { // 1. Assert: If direct is false, then strictCaller is also false. debug_assert!(direct || !strict); @@ -81,7 +82,7 @@ impl Eval { // Parse the script body and handle early errors (6 - 11) let body = match context.parse_eval(x.to_std_string_escaped().as_bytes(), direct, strict) { Ok(body) => body, - Err(e) => return context.throw_syntax_error(e.to_string()), + Err(e) => return Err(JsNativeError::syntax().with_message(e.to_string()).into()), }; // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`. @@ -107,8 +108,8 @@ impl Eval { .has_lex_binding_until_function_environment(&vars) { let name = context.interner().resolve_expect(name.sym()); - let msg = format!("variable declaration `{name}` in eval function already exists as lexically declaration"); - return context.throw_syntax_error(msg); + let msg = format!("variable declaration {name} in eval function already exists as a lexical variable"); + return Err(JsNativeError::syntax().with_message(msg).into()); } // Compile and execute the eval statement list. diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 8bc25cf3901..f2577cb4eaa 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -16,6 +16,7 @@ use crate::{ bytecompiler::FunctionCompiler, context::intrinsics::StandardConstructors, environments::DeclarativeEnvironmentStack, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, @@ -507,16 +508,16 @@ impl BuiltInFunctionObject { { Ok(parameters) => parameters, Err(e) => { - return context.throw_syntax_error(format!( - "failed to parse function parameters: {e}" - )) + return Err(JsNativeError::syntax() + .with_message(format!("failed to parse function parameters: {e}")) + .into()) } }; if generator && parameters.contains_yield_expression() { - return context.throw_syntax_error( + return Err(JsNativeError::syntax().with_message( "yield expression is not allowed in formal parameter list of generator function", - ); + ).into()); } parameters @@ -524,16 +525,16 @@ impl BuiltInFunctionObject { // It is a Syntax Error if FormalParameters Contains YieldExpression is true. if generator && r#async && parameters.contains_yield_expression() { - return context.throw_syntax_error( - "yield expression not allowed in async generator parameters", - ); + return Err(JsNativeError::syntax() + .with_message("yield expression not allowed in async generator parameters") + .into()); } // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. if generator && r#async && parameters.contains_await_expression() { - return context.throw_syntax_error( - "await expression not allowed in async generator parameters", - ); + return Err(JsNativeError::syntax() + .with_message("await expression not allowed in async generator parameters") + .into()); } let body_arg = body_arg.to_string(context)?; @@ -544,8 +545,9 @@ impl BuiltInFunctionObject { { Ok(statement_list) => statement_list, Err(e) => { - return context - .throw_syntax_error(format!("failed to parse function body: {e}")) + return Err(JsNativeError::syntax() + .with_message(format!("failed to parse function body: {e}")) + .into()) } }; @@ -555,9 +557,9 @@ impl BuiltInFunctionObject { for parameter in parameters.parameters.iter() { for name in parameter.names() { if name == Sym::ARGUMENTS || name == Sym::EVAL { - return context.throw_syntax_error( - " Unexpected 'eval' or 'arguments' in strict mode", - ); + return Err(JsNativeError::syntax() + .with_message(" Unexpected 'eval' or 'arguments' in strict mode") + .into()); } } } @@ -566,16 +568,19 @@ impl BuiltInFunctionObject { // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. if (body.strict()) && parameters.has_duplicates() { - return context - .throw_syntax_error("Duplicate parameter name not allowed in this context"); + return Err(JsNativeError::syntax() + .with_message("Duplicate parameter name not allowed in this context") + .into()); } // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true // and IsSimpleParameterList of FormalParameters is false. if body.strict() && !parameters.is_simple() { - return context.throw_syntax_error( - "Illegal 'use strict' directive in function with non-simple parameter list", - ); + return Err(JsNativeError::syntax() + .with_message( + "Illegal 'use strict' directive in function with non-simple parameter list", + ) + .into()); } // It is a Syntax Error if any element of the BoundNames of FormalParameters @@ -589,10 +594,12 @@ impl BuiltInFunctionObject { .iter() .any(|(name, _)| *name == param_name) { - return context.throw_syntax_error(format!( - "Redeclaration of formal parameter `{}`", - context.interner().resolve_expect(param_name.sym()) - )); + return Err(JsNativeError::syntax() + .with_message(format!( + "Redeclaration of formal parameter `{}`", + context.interner().resolve_expect(param_name.sym()) + )) + .into()); } } } @@ -662,7 +669,7 @@ impl BuiltInFunctionObject { // 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())) + JsNativeError::typ().with_message(format!("{} is not a function", this.display())) })?; let this_arg = args.get_or_undefined(0); @@ -702,7 +709,8 @@ impl BuiltInFunctionObject { // 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") + JsNativeError::typ() + .with_message("cannot bind `this` without a `[[Call]]` internal method") })?; let this_arg = args.get_or_undefined(0).clone(); @@ -781,7 +789,7 @@ impl BuiltInFunctionObject { // 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())) + JsNativeError::typ().with_message(format!("{} is not a function", this.display())) })?; let this_arg = args.get_or_undefined(0); @@ -798,7 +806,7 @@ impl BuiltInFunctionObject { let function = object .as_deref() .and_then(Object::as_function) - .ok_or_else(|| context.construct_type_error("Not a function"))?; + .ok_or_else(|| JsNativeError::typ().with_message("Not a function"))?; let name = { // Is there a case here where if there is no name field on a value diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index ca975413ec4..8b290bd0129 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -1,9 +1,10 @@ use crate::{ + error::JsNativeError, forward, forward_val, js_string, object::FunctionBuilder, property::{Attribute, PropertyDescriptor}, string::utf16, - Context, + Context, JsNativeErrorKind, }; #[allow(clippy::float_cmp)] @@ -140,10 +141,15 @@ fn function_prototype_call_throw() { let call = Function.prototype.call; call(call) "#; - let value = forward_val(&mut context, throw).unwrap_err(); - assert!(value.is_object()); - let string = value.to_string(&mut context).unwrap(); - assert!(string.starts_with(utf16!("TypeError"))); + let err = forward_val(&mut context, throw).unwrap_err(); + let err = err.as_native().unwrap(); + assert!(matches!( + err, + JsNativeError { + kind: JsNativeErrorKind::Type, + .. + } + )); } #[test] @@ -248,7 +254,7 @@ fn closure_capture_clone() { .__get_own_property__(&"key".into(), context)? .and_then(|prop| prop.value().cloned()) .and_then(|val| val.as_string().cloned()) - .ok_or_else(|| context.construct_type_error("invalid `key` property"))? + .ok_or_else(|| JsNativeError::typ().with_message("invalid `key` property"))? ); Ok(hw.into()) }, diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index c4460785006..74eea06fc13 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -12,12 +12,13 @@ use crate::{ builtins::{iterable::create_iter_result_object, BuiltIn, JsArgs}, environments::DeclarativeEnvironmentStack, + error::JsNativeError, object::{ConstructorBuilder, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor}, symbol::WellKnownSymbols, value::JsValue, vm::{CallFrame, GeneratorResumeKind, ReturnType}, - Context, JsResult, + Context, JsError, JsResult, }; use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_profiler::Profiler; @@ -151,7 +152,9 @@ impl Generator { Some(obj) if obj.is_generator() => { Self::generator_resume(obj, args.get_or_undefined(0), context) } - _ => context.throw_type_error("Generator.prototype.next called on non generator"), + _ => Err(JsNativeError::typ() + .with_message("Generator.prototype.next called on non generator") + .into()), } } @@ -195,7 +198,11 @@ impl Generator { // 1. Let g be the this value. // 2. Let C be ThrowCompletion(exception). // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) + Self::generator_resume_abrupt( + this, + Err(JsError::from_opaque(args.get_or_undefined(0).clone())), + context, + ) } /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` @@ -212,12 +219,14 @@ impl Generator { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). let mut generator_obj_mut = generator_obj.borrow_mut(); let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + JsNativeError::typ().with_message("generator resumed on non generator object") })?; let state = generator.state; if state == GeneratorState::Executing { - return Err(context.construct_type_error("Generator should not be executing")); + return Err(JsNativeError::typ() + .with_message("Generator should not be executing") + .into()); } // 2. If state is completed, return CreateIterResultObject(undefined, true). @@ -314,16 +323,18 @@ impl Generator { ) -> JsResult { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). let generator_obj = this.as_object().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + JsNativeError::typ().with_message("generator resumed on non generator object") })?; let mut generator_obj_mut = generator_obj.borrow_mut(); let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { - context.construct_type_error("generator resumed on non generator object") + JsNativeError::typ().with_message("generator resumed on non generator object") })?; let mut state = generator.state; if state == GeneratorState::Executing { - return Err(context.construct_type_error("Generator should not be executing")); + return Err(JsNativeError::typ() + .with_message("Generator should not be executing") + .into()); } // 2. If state is suspendedStart, then @@ -379,6 +390,7 @@ impl Generator { context.run() } Err(value) => { + let value = value.to_opaque(context); context.vm.push(value); context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; context.run() diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index fd2ecddabfa..103220794f1 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -9,6 +9,7 @@ use crate::{ context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsFunction, JsObject, @@ -205,13 +206,17 @@ pub(crate) fn to_date_time_options( // 9. If required is "date" and timeStyle is not undefined, then if required == &DateTimeReqs::Date && !time_style.is_undefined() { // a. Throw a TypeError exception. - return context.throw_type_error("'date' is required, but timeStyle was defined"); + return Err(JsNativeError::typ() + .with_message("'date' is required, but timeStyle was defined") + .into()); } // 10. If required is "time" and dateStyle is not undefined, then if required == &DateTimeReqs::Time && !date_style.is_undefined() { // a. Throw a TypeError exception. - return context.throw_type_error("'time' is required, but dateStyle was defined"); + return Err(JsNativeError::typ() + .with_message("'time' is required, but dateStyle was defined") + .into()); } // 11. If needDefaults is true and defaults is either "date" or "all", then diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 74af600460e..42ac1b1ad1e 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -10,6 +10,7 @@ use crate::{ builtins::intl::date_time_format::DateTimeFormat, builtins::{Array, BuiltIn, JsArgs}, + error::JsNativeError, object::{JsObject, ObjectInitializer}, property::Attribute, symbol::WellKnownSymbols, @@ -458,7 +459,9 @@ fn canonicalize_locale_list(args: &[JsValue], context: &mut Context) -> JsResult let k_value = o.get(k, context)?; // ii. If Type(kValue) is not String or Object, throw a TypeError exception. if !(k_value.is_object() || k_value.is_string()) { - return context.throw_type_error("locale should be a String or Object"); + return Err(JsNativeError::typ() + .with_message("locale should be a String or Object") + .into()); } // iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then // TODO: handle checks for InitializedLocale internal slot (there should be an if statement here) @@ -472,7 +475,8 @@ fn canonicalize_locale_list(args: &[JsValue], context: &mut Context) -> JsResult .ok() .and_then(|tag| tag.parse().ok()) .ok_or_else(|| { - context.construct_range_error("locale is not a structurally valid language tag") + JsNativeError::range() + .with_message("locale is not a structurally valid language tag") })?; // vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag). @@ -754,7 +758,9 @@ pub(crate) fn get_option( GetOptionType::String => { let string_value = value.to_string(context)?.to_std_string_escaped(); if !values.is_empty() && !values.contains(&string_value.as_str()) { - return context.throw_range_error("GetOption: values array does not contain value"); + return Err(JsNativeError::range() + .with_message("GetOption: values array does not contain value") + .into()); } JsValue::String(string_value.into()) } @@ -818,7 +824,9 @@ pub(crate) fn default_number_option( // 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception. if value.is_nan() || value < minimum || value > maximum { - return context.throw_range_error("DefaultNumberOption: value is out of range."); + return Err(JsNativeError::range() + .with_message("DefaultNumberOption: value is out of range.") + .into()); } // 4. Return floor(value). diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 1a68701ee20..b3bc6a47be7 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -380,8 +380,9 @@ fn get_opt() { let maximum = 10.0; let fallback_val = 5.0; let fallback = Some(fallback_val); - let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context); - assert_eq!(get_option_result, Ok(fallback)); + let get_option_result = + default_number_option(&value, minimum, maximum, fallback, &mut context).unwrap(); + assert_eq!(get_option_result, fallback); let value = JsValue::nan(); let minimum = 1.0; @@ -409,8 +410,9 @@ fn get_opt() { let minimum = 1.0; let maximum = 10.0; let fallback = Some(5.0); - let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context); - assert_eq!(get_option_result, Ok(Some(value_f64))); + let get_option_result = + default_number_option(&value, minimum, maximum, fallback, &mut context).unwrap(); + assert_eq!(get_option_result, Some(value_f64)); let options = JsObject::empty(); let property = "fractionalSecondDigits"; @@ -419,8 +421,8 @@ fn get_opt() { let fallback_val = 5.0; let fallback = Some(fallback_val); let get_option_result = - get_number_option(&options, property, minimum, maximum, fallback, &mut context); - assert_eq!(get_option_result, Ok(fallback)); + get_number_option(&options, property, minimum, maximum, fallback, &mut context).unwrap(); + assert_eq!(get_option_result, fallback); let options = JsObject::empty(); let value_f64 = 8.0; @@ -433,8 +435,8 @@ fn get_opt() { let maximum = 10.0; let fallback = Some(5.0); let get_option_result = - get_number_option(&options, property, minimum, maximum, fallback, &mut context); - assert_eq!(get_option_result, Ok(Some(value_f64))); + get_number_option(&options, property, minimum, maximum, fallback, &mut context).unwrap(); + assert_eq!(get_option_result, Some(value_f64)); } #[test] @@ -475,16 +477,16 @@ fn to_date_time_opts() { let numeric_jsstring = JsValue::String("numeric".into()); assert_eq!( - date_time_opts.get("year", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("year", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("month", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("month", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("day", &mut context), - Ok(numeric_jsstring) + date_time_opts.get("day", &mut context).unwrap(), + numeric_jsstring ); let date_time_opts = to_date_time_options( @@ -497,16 +499,16 @@ fn to_date_time_opts() { let numeric_jsstring = JsValue::String("numeric".into()); assert_eq!( - date_time_opts.get("hour", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("hour", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("minute", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("minute", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("second", &mut context), - Ok(numeric_jsstring) + date_time_opts.get("second", &mut context).unwrap(), + numeric_jsstring ); let date_time_opts = to_date_time_options( @@ -519,27 +521,27 @@ fn to_date_time_opts() { let numeric_jsstring = JsValue::String("numeric".into()); assert_eq!( - date_time_opts.get("year", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("year", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("month", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("month", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("day", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("day", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("hour", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("hour", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("minute", &mut context), - Ok(numeric_jsstring.clone()) + date_time_opts.get("minute", &mut context).unwrap(), + numeric_jsstring.clone() ); assert_eq!( - date_time_opts.get("second", &mut context), - Ok(numeric_jsstring) + date_time_opts.get("second", &mut context).unwrap(), + numeric_jsstring ); } diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index 9f1c3b1702a..86d9e983837 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -6,7 +6,7 @@ use crate::{ }, object::{FunctionBuilder, JsObject, ObjectData}, property::PropertyDescriptor, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -234,24 +234,25 @@ impl AsyncFromSyncIterator { if_abrupt_reject_promise!(result, promise_capability, context); // 11. If Type(result) is not Object, then - let result = - if let Some(result) = result.as_object() { - result - } else { - // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). - promise_capability - .reject() - .call( - &JsValue::Undefined, - &[context - .construct_type_error("iterator return function returned non-object")], - context, - ) - .expect("cannot fail according to spec"); + let result = if let Some(result) = result.as_object() { + result + } else { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). + promise_capability + .reject() + .call( + &JsValue::Undefined, + &[JsNativeError::typ() + .with_message("iterator return function returned non-object") + .to_opaque(context) + .into()], + context, + ) + .expect("cannot fail according to spec"); - // b. Return promiseCapability.[[Promise]]. - return Ok(promise_capability.promise().clone().into()); - }; + // b. Return promiseCapability.[[Promise]]. + return Ok(promise_capability.promise().clone().into()); + }; // 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). Self::continuation( @@ -341,7 +342,10 @@ impl AsyncFromSyncIterator { .reject() .call( &JsValue::Undefined, - &[context.construct_type_error("iterator throw function returned non-object")], + &[JsNativeError::typ() + .with_message("iterator throw function returned non-object") + .to_opaque(context) + .into()], context, ) .expect("cannot fail according to spec"); diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 27d4d28018b..f10ea672227 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -6,6 +6,7 @@ use crate::{ string::string_iterator::StringIterator, ArrayIterator, ForInIterator, MapIterator, SetIterator, }, + error::JsNativeError, object::{JsObject, ObjectInitializer}, symbol::WellKnownSymbols, Context, JsResult, JsValue, @@ -190,7 +191,7 @@ impl JsValue { // 4. If Type(iterator) is not Object, throw a TypeError exception. let iterator_obj = iterator .as_object() - .ok_or_else(|| context.construct_type_error("the iterator is not an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("the iterator is not an object"))?; // 5. Let nextMethod be ? GetV(iterator, "next"). let next_method = iterator.get_v("next", context)?; @@ -355,7 +356,9 @@ impl IteratorRecord { let next_method = if let Some(next_method) = self.next_method.as_callable() { next_method } else { - return context.throw_type_error("iterable next method not a function"); + return Err(JsNativeError::typ() + .with_message("iterable next method not a function") + .into()); }; let result = if let Some(value) = value { @@ -373,7 +376,9 @@ impl IteratorRecord { if let Some(o) = result.as_object() { Ok(IteratorResult { object: o.clone() }) } else { - context.throw_type_error("next value should be an object") + Err(JsNativeError::typ() + .with_message("next value should be an object") + .into()) } } @@ -468,7 +473,9 @@ impl IteratorRecord { Ok(completion) } else { // 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception. - context.throw_type_error("inner result was not an object") + Err(JsNativeError::typ() + .with_message("inner result was not an object") + .into()) } } } diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 952bb9ad289..b3b6b44a49d 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -21,6 +21,7 @@ use std::{ use crate::{ builtins::BuiltIn, + error::JsNativeError, js_string, object::{JsObject, ObjectInitializer, RecursionLimiter}, property::{Attribute, PropertyNameKind}, @@ -174,12 +175,12 @@ impl Json { .unwrap_or_default() .to_string(context)? .to_std_string() - .map_err(|e| context.construct_syntax_error(e.to_string()))?; + .map_err(|e| JsNativeError::syntax().with_message(e.to_string()))?; // 2. Parse ! StringToCodePoints(jsonString) as a JSON text as specified in ECMA-404. // Throw a SyntaxError exception if it is not a valid JSON text as defined in that specification. if let Err(e) = serde_json::from_str::(&json_string) { - return context.throw_syntax_error(e.to_string()); + return Err(JsNativeError::syntax().with_message(e.to_string()).into()); } // 3. Let scriptString be the string-concatenation of "(", jsonString, and ");". @@ -550,7 +551,9 @@ impl Json { // 10. If Type(value) is BigInt, throw a TypeError exception. if value.is_bigint() { - return context.throw_type_error("cannot serialize bigint to JSON"); + return Err(JsNativeError::typ() + .with_message("cannot serialize bigint to JSON") + .into()); } // 11. If Type(value) is Object and IsCallable(value) is false, then @@ -638,7 +641,9 @@ impl Json { // 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical. let limiter = RecursionLimiter::new(value); if limiter.live { - return context.throw_type_error("cyclic object value"); + return Err(JsNativeError::typ() + .with_message("cyclic object value") + .into()); } // 2. Append value to state.[[Stack]]. @@ -764,7 +769,9 @@ impl Json { // 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical. let limiter = RecursionLimiter::new(value); if limiter.live { - return context.throw_type_error("cyclic object value"); + return Err(JsNativeError::typ() + .with_message("cyclic object value") + .into()); } // 2. Append value to state.[[Stack]]. diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index 80cc1697ced..12d48fdc1f4 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -1,6 +1,7 @@ use super::ordered_map::MapLock; use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, + error::JsNativeError, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, @@ -60,7 +61,9 @@ impl MapIterator { return Ok(map_iterator.into()); } } - context.throw_type_error("`this` is not a Map") + Err(JsNativeError::typ() + .with_message("`this` is not a Map") + .into()) } /// %MapIteratorPrototype%.next( ) @@ -76,7 +79,7 @@ impl MapIterator { 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"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a MapIterator"))?; let item_kind = map_iterator.map_iteration_kind; diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 8e8920744be..9a128d1746f 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -15,6 +15,7 @@ use super::JsArgs; use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -121,8 +122,9 @@ impl Map { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context - .throw_type_error("calling a builtin Map constructor without new is forbidden"); + return Err(JsNativeError::typ() + .with_message("calling a builtin Map constructor without new is forbidden") + .into()); } // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »). @@ -206,11 +208,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.set /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set - pub(crate) fn set( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn set(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let key = args.get_or_undefined(0); let value = args.get_or_undefined(1); @@ -241,7 +239,9 @@ impl Map { return Ok(this.clone()); } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `get Map.prototype.size` @@ -255,11 +255,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-get-map.prototype.size /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size - pub(crate) fn get_size( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn get_size(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let M be the this value. if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). @@ -272,7 +268,9 @@ impl Map { return Ok(map.len().into()); } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `Map.prototype.delete( key )` @@ -286,11 +284,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.delete /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete - pub(crate) fn delete( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let key = args.get_or_undefined(0); // 1. Let M be the this value. @@ -306,7 +300,9 @@ impl Map { return Ok(map.remove(key).is_some().into()); } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `Map.prototype.get( key )` @@ -319,11 +315,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.get /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get - pub(crate) fn get( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn get(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { const JS_ZERO: &JsValue = &JsValue::Rational(0f64); let key = args.get_or_undefined(0); @@ -350,7 +342,9 @@ impl Map { } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `Map.prototype.clear( )` @@ -363,7 +357,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear - pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[MapData]]). if let Some(object) = this.as_object() { @@ -378,7 +372,9 @@ impl Map { return Ok(JsValue::undefined()); } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `Map.prototype.has( key )` @@ -391,11 +387,7 @@ impl Map { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has - pub(crate) fn has( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn has(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { const JS_ZERO: &JsValue = &JsValue::Rational(0f64); let key = args.get_or_undefined(0); @@ -422,7 +414,9 @@ impl Map { } } - context.throw_type_error("'this' is not a Map") + Err(JsNativeError::typ() + .with_message("'this' is not a Map") + .into()) } /// `Map.prototype.forEach( callbackFn [ , thisArg ] )` @@ -445,12 +439,12 @@ impl Map { let map = this .as_object() .filter(|obj| obj.is_map()) - .ok_or_else(|| context.construct_type_error("`this` is not a Map"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a Map"))?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. 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())) + JsNativeError::typ().with_message(format!("{} is not a function", callback.display())) })?; let this_arg = args.get_or_undefined(1); @@ -533,7 +527,7 @@ pub(crate) fn add_entries_from_iterable( ) -> JsResult { // 1. If IsCallable(adder) is false, throw a TypeError exception. let adder = adder.as_callable().ok_or_else(|| { - context.construct_type_error("property `set` of `NewTarget` is not callable") + JsNativeError::typ().with_message("property `set` of `NewTarget` is not callable") })?; // 2. Let iteratorRecord be ? GetIterator(iterable). @@ -557,8 +551,9 @@ pub(crate) fn add_entries_from_iterable( // d. If Type(nextItem) is not Object, then } else { // i. Let error be ThrowCompletion(a newly created TypeError object). - let err = context - .throw_type_error("cannot get key and value from primitive item of `iterable`"); + let err = Err(JsNativeError::typ() + .with_message("cannot get key and value from primitive item of `iterable`") + .into()); // ii. Return ? IteratorClose(iteratorRecord, error). return iterator_record.close(err, context); diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index f142402e7ea..375a36e3e73 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -16,6 +16,7 @@ use crate::{ builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -196,11 +197,15 @@ impl Number { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue - fn this_number_value(value: &JsValue, context: &mut Context) -> JsResult { + fn this_number_value(value: &JsValue) -> JsResult { 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")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a number") + .into() + }) } /// `Number.prototype.toExponential( [fractionDigits] )` @@ -220,7 +225,7 @@ impl Number { context: &mut Context, ) -> JsResult { // 1. Let x be ? thisNumberValue(this value). - let this_num = Self::this_number_value(this, context)?; + let this_num = Self::this_number_value(this)?; let precision = match args.get(0) { None | Some(JsValue::Undefined) => None, // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). @@ -240,8 +245,9 @@ impl Number { f64_to_exponential_with_precision(this_num, precision as usize) } _ => { - return context - .throw_range_error("toExponential() argument must be between 0 and 100") + return Err(JsNativeError::range() + .with_message("toExponential() argument must be between 0 and 100") + .into()) } }; Ok(JsValue::new(this_str_num)) @@ -264,7 +270,7 @@ impl Number { context: &mut Context, ) -> JsResult { // 1. Let this_num be ? thisNumberValue(this value). - let this_num = Self::this_number_value(this, context)?; + let this_num = Self::this_number_value(this)?; // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). // 3. Assert: If fractionDigits is undefined, then f is 0. @@ -275,7 +281,8 @@ impl Number { .as_integer() .filter(|i| (0..=100).contains(i)) .ok_or_else(|| { - context.construct_range_error("toFixed() digits argument must be between 0 and 100") + JsNativeError::range() + .with_message("toFixed() digits argument must be between 0 and 100") })? as usize; // 6. If x is not finite, return ! Number::toString(x). @@ -309,9 +316,9 @@ impl Number { pub(crate) fn to_locale_string( this: &JsValue, _: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { - let this_num = Self::this_number_value(this, context)?; + let this_num = Self::this_number_value(this)?; let this_str_num = this_num.to_string(); Ok(JsValue::new(this_str_num)) } @@ -320,7 +327,6 @@ impl Number { /// /// This function traverses a string representing a number, /// returning the floored log10 of this number. - /// fn flt_str_to_exp(flt: &str) -> i32 { let mut non_zero_encountered = false; let mut dot_encountered = false; @@ -354,7 +360,6 @@ impl Number { /// the exponent. The string is kept at an exact length of `precision`. /// /// When this procedure returns, `digits` is exactly `precision` long. - /// fn round_to_precision(digits: &mut String, precision: usize) -> bool { if digits.len() > precision { let to_round = digits.split_off(precision); @@ -424,7 +429,7 @@ impl Number { let precision = args.get_or_undefined(0); // 1 & 6 - let mut this_num = Self::this_number_value(this, context)?; + let mut this_num = Self::this_number_value(this)?; // 2 if precision.is_undefined() { return Self::to_string(this, &[], context); @@ -442,9 +447,9 @@ impl Number { IntegerOrInfinity::Integer(x) if (1..=100).contains(&x) => x as usize, _ => { // 5 - return context.throw_range_error( - "precision must be an integer at least 1 and no greater than 100", - ); + return Err(JsNativeError::range() + .with_message("precision must be an integer at least 1 and no greater than 100") + .into()); } }; let precision_i32 = precision as i32; @@ -674,7 +679,7 @@ impl Number { context: &mut Context, ) -> JsResult { // 1. Let x be ? thisNumberValue(this value). - let x = Self::this_number_value(this, context)?; + let x = Self::this_number_value(this)?; let radix = args.get_or_undefined(0); let radix_number = if radix.is_undefined() { @@ -688,9 +693,8 @@ impl Number { // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. .filter(|i| (2..=36).contains(i)) .ok_or_else(|| { - context.construct_range_error( - "radix must be an integer at least 2 and no greater than 36", - ) + JsNativeError::range() + .with_message("radix must be an integer at least 2 and no greater than 36") })? } as u8; @@ -731,12 +735,8 @@ impl Number { /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf - pub(crate) fn value_of( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - Ok(JsValue::new(Self::this_number_value(this, context)?)) + pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Ok(JsValue::new(Self::this_number_value(this)?)) } /// Builtin javascript 'parseInt(str, radix)' function. diff --git a/boa_engine/src/builtins/number/tests.rs b/boa_engine/src/builtins/number/tests.rs index f7ea2226980..a513364a105 100644 --- a/boa_engine/src/builtins/number/tests.rs +++ b/boa_engine/src/builtins/number/tests.rs @@ -170,7 +170,8 @@ fn to_precision() { String::from("\"0.333333333333333314829616256247390992939472198486328125000000\"") ); - let expected = "Uncaught \"RangeError\": \"precision must be an integer at least 1 and no greater than 100\""; + let expected = + "Uncaught RangeError: precision must be an integer at least 1 and no greater than 100"; let range_error_1 = r#"(1).toPrecision(101);"#; let range_error_2 = r#"(1).toPrecision(0);"#; diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index da89afec4b2..18ce1984e59 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -1,5 +1,6 @@ use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, + error::JsNativeError, object::{JsObject, ObjectData}, property::PropertyDescriptor, property::PropertyKey, @@ -71,7 +72,7 @@ impl ForInIterator { 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"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a ForInIterator"))?; let mut object = iterator.object.to_object(context)?; loop { if !iterator.object_was_visited { diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 8e277b72d59..e4482455073 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -13,10 +13,13 @@ //! [spec]: https://tc39.es/ecma262/#sec-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object +use std::ops::Deref; + use super::Array; use crate::{ builtins::{map, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -26,7 +29,7 @@ use crate::{ string::utf16, symbol::WellKnownSymbols, value::JsValue, - Context, JsResult, + Context, JsResult, JsString, }; use boa_profiler::Profiler; use tap::{Conv, Pipe}; @@ -177,7 +180,7 @@ impl Object { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. If Type(proto) is neither Object nor Null, return undefined. let proto = match args.get_or_undefined(0) { @@ -197,7 +200,9 @@ impl Object { // 5. If status is false, throw a TypeError exception. if !status { - return context.throw_type_error("__proto__ called on null or undefined"); + return Err(JsNativeError::typ() + .with_message("__proto__ called on null or undefined") + .into()); } // 6. Return undefined. @@ -226,8 +231,9 @@ impl Object { // 2. If IsCallable(getter) is false, throw a TypeError exception. if !getter.is_callable() { - return context - .throw_type_error("Object.prototype.__defineGetter__: Expecting function"); + return Err(JsNativeError::typ() + .with_message("Object.prototype.__defineGetter__: Expecting function") + .into()); } // 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }. @@ -268,8 +274,9 @@ impl Object { // 2. If IsCallable(setter) is false, throw a TypeError exception. if !setter.is_callable() { - return context - .throw_type_error("Object.prototype.__defineSetter__: Expecting function"); + return Err(JsNativeError::typ() + .with_message("Object.prototype.__defineSetter__: Expecting function") + .into()); } // 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }. @@ -397,10 +404,12 @@ impl Object { ObjectData::ordinary(), ), _ => { - return context.throw_type_error(format!( - "Object prototype may only be an Object or null: {}", - prototype.display() - )) + return Err(JsNativeError::typ() + .with_message(format!( + "Object prototype may only be an Object or null: {}", + prototype.display() + )) + .into()) } }; @@ -563,19 +572,25 @@ impl Object { /// [More information][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof - pub fn get_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { + pub fn get_prototype_of( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { if args.is_empty() { - return ctx.throw_type_error( - "Object.getPrototypeOf: At least 1 argument required, but only 0 passed", - ); + return Err(JsNativeError::typ() + .with_message( + "Object.getPrototypeOf: At least 1 argument required, but only 0 passed", + ) + .into()); } // 1. Let obj be ? ToObject(O). - let obj = args[0].clone().to_object(ctx)?; + let obj = args[0].clone().to_object(context)?; // 2. Return ? obj.[[GetPrototypeOf]](). Ok(obj - .__get_prototype_of__(ctx)? + .__get_prototype_of__(context)? .map_or(JsValue::Null, JsValue::new)) } @@ -584,12 +599,18 @@ impl Object { /// [More information][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof - pub fn set_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { + pub fn set_prototype_of( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { if args.len() < 2 { - return ctx.throw_type_error(format!( - "Object.setPrototypeOf: At least 2 arguments required, but only {} passed", - args.len() - )); + return Err(JsNativeError::typ() + .with_message(format!( + "Object.setPrototypeOf: At least 2 arguments required, but only {} passed", + args.len() + )) + .into()); } // 1. Set O to ? RequireObjectCoercible(O). @@ -597,7 +618,7 @@ impl Object { .get(0) .cloned() .unwrap_or_default() - .require_object_coercible(ctx)? + .require_object_coercible()? .clone(); let proto = match args.get_or_undefined(1) { @@ -605,10 +626,12 @@ impl Object { JsValue::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. val => { - return ctx.throw_type_error(format!( - "expected an object or null, got {}", - val.type_of().to_std_string_escaped() - )) + return Err(JsNativeError::typ() + .with_message(format!( + "expected an object or null, got {}", + val.type_of().to_std_string_escaped() + )) + .into()) } }; @@ -620,11 +643,13 @@ impl Object { }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = obj.__set_prototype_of__(proto, ctx)?; + let status = obj.__set_prototype_of__(proto, context)?; // 5. If status is false, throw a TypeError exception. if !status { - return ctx.throw_type_error("can't set prototype of this object"); + return Err(JsNativeError::typ() + .with_message("can't set prototype of this object") + .into()); } // 6. Return O. @@ -684,7 +709,9 @@ impl Object { Ok(object.clone().into()) } else { - context.throw_type_error("Object.defineProperty called on non-object") + Err(JsNativeError::typ() + .with_message("Object.defineProperty called on non-object") + .into()) } } @@ -709,7 +736,9 @@ impl Object { object_define_properties(obj, props, context)?; Ok(arg.clone()) } else { - context.throw_type_error("Expected an object") + Err(JsNativeError::typ() + .with_message("Expected an object") + .into()) } } @@ -752,7 +781,7 @@ impl Object { // 4. Let isArray be ? IsArray(O). // 5. If isArray is true, let builtinTag be "Array". let builtin_tag = if o.is_array_abstract(context)? { - js_string!("Array") + utf16!("Array") } else { // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". @@ -765,15 +794,15 @@ impl Object { // 14. Else, let builtinTag be "Object". let o = o.borrow(); match o.kind() { - ObjectKind::Arguments(_) => js_string!("Arguments"), - ObjectKind::Function(_) => js_string!("Function"), - ObjectKind::Error => js_string!("Error"), - ObjectKind::Boolean(_) => js_string!("Boolean"), - ObjectKind::Number(_) => js_string!("Number"), - ObjectKind::String(_) => js_string!("String"), - ObjectKind::Date(_) => js_string!("Date"), - ObjectKind::RegExp(_) => js_string!("RegExp"), - _ => js_string!("Object"), + ObjectKind::Arguments(_) => utf16!("Arguments"), + ObjectKind::Function(_) => utf16!("Function"), + ObjectKind::Error(_) => utf16!("Error"), + ObjectKind::Boolean(_) => utf16!("Boolean"), + ObjectKind::Number(_) => utf16!("Number"), + ObjectKind::String(_) => utf16!("String"), + ObjectKind::Date(_) => utf16!("Date"), + ObjectKind::RegExp(_) => utf16!("RegExp"), + _ => utf16!("Object"), } }; @@ -781,7 +810,7 @@ impl Object { let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; // 16. If Type(tag) is not String, set tag to builtinTag. - let tag_str = tag.as_string().unwrap_or(&builtin_tag); + let tag_str = tag.as_string().map_or(builtin_tag, JsString::deref); // 17. Return the string-concatenation of "[object ", tag, and "]". Ok(js_string!(utf16!("[object "), tag_str, utf16!("]")).into()) @@ -1015,7 +1044,9 @@ impl Object { let status = o.set_integrity_level(IntegrityLevel::Sealed, context)?; // 3. If status is false, throw a TypeError exception. if !status { - return context.throw_type_error("cannot seal object"); + return Err(JsNativeError::typ() + .with_message("cannot seal object") + .into()); } } // 1. If Type(O) is not Object, return O. @@ -1060,7 +1091,9 @@ impl Object { let status = o.set_integrity_level(IntegrityLevel::Frozen, context)?; // 3. If status is false, throw a TypeError exception. if !status { - return context.throw_type_error("cannot freeze object"); + return Err(JsNativeError::typ() + .with_message("cannot freeze object") + .into()); } } // 1. If Type(O) is not Object, return O. @@ -1109,7 +1142,9 @@ impl Object { let status = o.__prevent_extensions__(context)?; // 3. If status is false, throw a TypeError exception. if !status { - return context.throw_type_error("cannot prevent extensions"); + return Err(JsNativeError::typ() + .with_message("cannot prevent extensions") + .into()); } } // 1. If Type(O) is not Object, return O. @@ -1205,7 +1240,7 @@ impl Object { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Perform ? RequireObjectCoercible(iterable). - let iterable = args.get_or_undefined(0).require_object_coercible(context)?; + let iterable = args.get_or_undefined(0).require_object_coercible()?; // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. diff --git a/boa_engine/src/builtins/object/tests.rs b/boa_engine/src/builtins/object/tests.rs index 8c41b307013..cc7bd37fe38 100644 --- a/boa_engine/src/builtins/object/tests.rs +++ b/boa_engine/src/builtins/object/tests.rs @@ -323,7 +323,7 @@ fn object_is_prototype_of() { #[test] fn object_get_own_property_names_invalid_args() { - let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#; + let error_message = "Uncaught TypeError: cannot convert 'null' or 'undefined' to object"; check_output(&[ TestAction::TestEq("Object.getOwnPropertyNames()", error_message), @@ -358,7 +358,7 @@ fn object_get_own_property_names() { #[test] fn object_get_own_property_symbols_invalid_args() { - let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#; + let error_message = "Uncaught TypeError: cannot convert 'null' or 'undefined' to object"; check_output(&[ TestAction::TestEq("Object.getOwnPropertySymbols()", error_message), @@ -391,7 +391,7 @@ fn object_get_own_property_symbols() { #[test] fn object_from_entries_invalid_args() { - let error_message = r#"Uncaught "TypeError": "cannot convert null or undefined to Object""#; + let error_message = "Uncaught TypeError: cannot convert null or undefined to Object"; check_output(&[ TestAction::TestEq("Object.fromEntries()", error_message), diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 42189781a6b..ff0ee49acd2 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -8,8 +8,9 @@ mod promise_job; use self::promise_job::PromiseJob; use super::{iterable::IteratorRecord, JsArgs}; use crate::{ - builtins::{Array, BuiltIn}, + builtins::{error::ErrorKind, Array, BuiltIn}, context::intrinsics::StandardConstructors, + error::JsNativeError, job::JobCallback, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -18,7 +19,7 @@ use crate::{ property::{Attribute, PropertyDescriptorBuilder}, symbol::WellKnownSymbols, value::JsValue, - Context, JsResult, + Context, JsError, JsResult, }; use boa_gc::{Cell as GcCell, Finalize, Gc, Trace}; use boa_profiler::Profiler; @@ -37,12 +38,13 @@ macro_rules! if_abrupt_reject_promise { ($value:ident, $capability:expr, $context: expr) => { let $value = match $value { // 1. If value is an abrupt completion, then - Err(value) => { + Err(err) => { + let err = err.to_opaque($context); // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). $context.call( &$capability.reject().clone().into(), &JsValue::undefined(), - &[value], + &[err], )?; // b. Return capability.[[Promise]]. @@ -110,7 +112,9 @@ impl PromiseCapability { match c.as_constructor() { // 1. If IsConstructor(C) is false, throw a TypeError exception. - None => context.throw_type_error("PromiseCapability: expected constructor"), + None => Err(JsNativeError::typ() + .with_message("PromiseCapability: expected constructor") + .into()), Some(c) => { let c = c.clone(); @@ -125,19 +129,20 @@ impl PromiseCapability { // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). let executor = FunctionBuilder::closure_with_captures( context, - |_this, args: &[JsValue], captures, context| { + |_this, args: &[JsValue], captures, _| { let mut promise_capability = captures.borrow_mut(); // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. if !promise_capability.resolve.is_undefined() { - return context.throw_type_error( - "promiseCapability.[[Resolve]] is not undefined", - ); + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Resolve]] is not undefined") + .into()); } // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. if !promise_capability.reject.is_undefined() { - return context - .throw_type_error("promiseCapability.[[Reject]] is not undefined"); + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Reject]] is not undefined") + .into()); } let resolve = args.get_or_undefined(0); @@ -173,8 +178,8 @@ impl PromiseCapability { .cloned() .and_then(JsFunction::from_object) .ok_or_else(|| { - context - .construct_type_error("promiseCapability.[[Resolve]] is not callable") + JsNativeError::typ() + .with_message("promiseCapability.[[Resolve]] is not callable") })?; // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. @@ -183,7 +188,8 @@ impl PromiseCapability { .cloned() .and_then(JsFunction::from_object) .ok_or_else(|| { - context.construct_type_error("promiseCapability.[[Reject]] is not callable") + JsNativeError::typ() + .with_message("promiseCapability.[[Reject]] is not callable") })?; // 9. Set promiseCapability.[[Promise]] to promise. @@ -284,14 +290,18 @@ impl Promise { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context.throw_type_error("Promise NewTarget cannot be undefined"); + return Err(JsNativeError::typ() + .with_message("Promise NewTarget cannot be undefined") + .into()); } let executor = args.get_or_undefined(0); // 2. If IsCallable(executor) is false, throw a TypeError exception. if !executor.is_callable() { - return context.throw_type_error("Promise executor is not callable"); + return Err(JsNativeError::typ() + .with_message("Promise executor is not callable") + .into()); } // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). @@ -327,9 +337,10 @@ impl Promise { ); // 10. If completion is an abrupt completion, then - if let Err(value) = completion { + if let Err(e) = completion { + let e = e.to_opaque(context); // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). - context.call(&resolving_functions.reject, &JsValue::Undefined, &[value])?; + context.call(&resolving_functions.reject, &JsValue::Undefined, &[e])?; } // 11. Return promise. @@ -1033,7 +1044,7 @@ impl Promise { .constructors() .aggregate_error() .prototype(), - ObjectData::error(), + ObjectData::error(ErrorKind::Aggregate), ); // 2. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). @@ -1053,7 +1064,7 @@ impl Promise { .expect("cannot fail per spec"); // 3. Return ThrowCompletion(error). - return Err(error.into()); + return Err(JsError::from_opaque(error.into())); } // iv. Return resultCapability.[[Promise]]. @@ -1128,7 +1139,7 @@ impl Promise { .constructors() .aggregate_error() .prototype(), - ObjectData::error(), + ObjectData::error(ErrorKind::Aggregate), ); // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). @@ -1146,7 +1157,6 @@ impl Promise { context, ) .expect("cannot fail per spec"); - // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). return captures.capability_reject.call( &JsValue::undefined(), @@ -1243,15 +1253,16 @@ impl Promise { // 7. If SameValue(resolution, promise) is true, then if JsValue::same_value(resolution, &promise.clone().into()) { // a. Let selfResolutionError be a newly created TypeError object. - let self_resolution_error = - context.construct_type_error("SameValue(resolution, promise) is true"); + let self_resolution_error = JsNativeError::typ() + .with_message("SameValue(resolution, promise) is true") + .to_opaque(context); // b. Perform RejectPromise(promise, selfResolutionError). promise .borrow_mut() .as_promise_mut() .expect("Expected promise to be a Promise") - .reject_promise(&self_resolution_error, context); + .reject_promise(&self_resolution_error.into(), context); // c. Return undefined. return Ok(JsValue::Undefined); @@ -1275,13 +1286,13 @@ impl Promise { let then_action = match then { // 10. If then is an abrupt completion, then - Err(value) => { + Err(e) => { // a. Perform RejectPromise(promise, then.[[Value]]). promise .borrow_mut() .as_promise_mut() .expect("Expected promise to be a Promise") - .reject_promise(&value, context); + .reject_promise(&e.to_opaque(context), context); // b. Return undefined. return Ok(JsValue::Undefined); @@ -1674,7 +1685,9 @@ impl Promise { Self::promise_resolve(c.clone(), x.clone(), context) } else { // 2. If Type(C) is not Object, throw a TypeError exception. - context.throw_type_error("Promise.resolve() called on a non-object") + Err(JsNativeError::typ() + .with_message("Promise.resolve() called on a non-object") + .into()) } } @@ -1731,7 +1744,9 @@ impl Promise { let promise_obj = if let Some(p) = promise.as_object() { p } else { - return context.throw_type_error("finally called with a non-object promise"); + return Err(JsNativeError::typ() + .with_message("finally called with a non-object promise") + .into()); }; // 3. Let C be ? SpeciesConstructor(promise, %Promise%). @@ -1819,7 +1834,7 @@ impl Promise { context, |_this, _args, captures, _context| { // 1. Return ThrowCompletion(reason). - Err(captures.reason.clone()) + Err(JsError::from_opaque(captures.reason.clone())) }, ThrowReasonCaptures { reason: reason.clone(), @@ -1868,7 +1883,11 @@ impl Promise { // 2. If IsPromise(promise) is false, throw a TypeError exception. let promise_obj = match promise.as_promise() { Some(obj) => obj, - None => return context.throw_type_error("IsPromise(promise) is false"), + None => { + return Err(JsNativeError::typ() + .with_message("IsPromise(promise) is false") + .into()) + } }; // 3. Let C be ? SpeciesConstructor(promise, %Promise%). @@ -2068,7 +2087,9 @@ impl Promise { // 3. Return promiseResolve. Ok(promise_resolve.clone()) } else { - context.throw_type_error("retrieving a non-callable promise resolver") + Err(JsNativeError::typ() + .with_message("retrieving a non-callable promise resolver") + .into()) } } } diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index 30b486e93e4..af97c6887b6 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -54,9 +54,9 @@ impl PromiseJob { } }, // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). - Some(handler) => { - handler.call_job_callback(&JsValue::Undefined, &[argument.clone()], context) - } + Some(handler) => handler + .call_job_callback(&JsValue::Undefined, &[argument.clone()], context) + .map_err(|e| e.to_opaque(context)), }; match promise_capability { @@ -146,6 +146,7 @@ impl PromiseJob { // c. If thenCallResult is an abrupt completion, then if let Err(value) = then_call_result { + let value = value.to_opaque(context); // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). return context.call( &resolving_functions.reject, diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 91f58fa2721..a9d498ec6ee 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -12,6 +12,7 @@ use crate::{ builtins::{BuiltIn, JsArgs}, + error::JsNativeError, object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData}, Context, JsResult, JsValue, }; @@ -58,9 +59,11 @@ impl Proxy { /// This is an internal method only built for usage in the proxy internal methods. /// /// It returns the (target, handler) of the proxy. - pub(crate) fn try_data(&self, context: &mut Context) -> JsResult<(JsObject, JsObject)> { + pub(crate) fn try_data(&self) -> JsResult<(JsObject, JsObject)> { self.data.clone().ok_or_else(|| { - context.construct_type_error("Proxy object has empty handler and target") + JsNativeError::typ() + .with_message("Proxy object has empty handler and target") + .into() }) } @@ -77,7 +80,9 @@ impl Proxy { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context.throw_type_error("Proxy constructor called on undefined new target"); + return Err(JsNativeError::typ() + .with_message("Proxy constructor called on undefined new target") + .into()); } // 2. Return ? ProxyCreate(target, handler). @@ -97,12 +102,12 @@ impl Proxy { ) -> JsResult { // 1. If Type(target) is not Object, throw a TypeError exception. let target = target.as_object().ok_or_else(|| { - context.construct_type_error("Proxy constructor called with non-object target") + JsNativeError::typ().with_message("Proxy constructor called with non-object target") })?; // 2. If Type(handler) is not Object, throw a TypeError exception. let handler = handler.as_object().ok_or_else(|| { - context.construct_type_error("Proxy constructor called with non-object handler") + JsNativeError::typ().with_message("Proxy constructor called with non-object handler") })?; // 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »). diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 52da25618f9..afa7ab2c8f9 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -13,6 +13,7 @@ use super::{Array, JsArgs}; use crate::{ builtins::{self, BuiltIn}, + error::JsNativeError, object::ObjectInitializer, property::Attribute, symbol::WellKnownSymbols, @@ -78,12 +79,14 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be a function"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be a function"))?; let this_arg = args.get_or_undefined(1); let args_list = args.get_or_undefined(2); if !target.is_callable() { - return context.throw_type_error("target must be a function"); + return Err(JsNativeError::typ() + .with_message("target must be a function") + .into()); } let args = args_list.create_list_from_array_like(&[], context)?; target.call(this_arg, &args, context) @@ -106,14 +109,16 @@ impl Reflect { let target = args .get_or_undefined(0) .as_constructor() - .ok_or_else(|| context.construct_type_error("target must be a constructor"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be a constructor"))?; let new_target = if let Some(new_target) = args.get(2) { // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception. if let Some(new_target) = new_target.as_constructor() { new_target } else { - return context.throw_type_error("newTarget must be a constructor"); + return Err(JsNativeError::typ() + .with_message("newTarget must be a constructor") + .into()); } } else { // 2. If newTarget is not present, set newTarget to target. @@ -147,12 +152,14 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let key = args.get_or_undefined(1).to_property_key(context)?; let prop_desc: JsValue = args .get(2) .and_then(|v| v.as_object().cloned()) - .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? + .ok_or_else(|| { + JsNativeError::typ().with_message("property descriptor must be an object") + })? .into(); target @@ -176,7 +183,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let key = args.get_or_undefined(1).to_property_key(context)?; Ok(target.__delete__(&key, context)?.into()) @@ -195,7 +202,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; // 2. Let key be ? ToPropertyKey(propertyKey). let key = args.get_or_undefined(1).to_property_key(context)?; // 3. If receiver is not present, then @@ -231,7 +238,9 @@ impl Reflect { context, ) } else { - context.throw_type_error("target must be an object") + Err(JsNativeError::typ() + .with_message("target must be an object") + .into()) } } @@ -251,7 +260,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; Ok(target .__get_prototype_of__(context)? .map_or(JsValue::Null, JsValue::new)) @@ -269,7 +278,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let key = args .get(1) .unwrap_or(&JsValue::undefined()) @@ -293,7 +302,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; Ok(target.__is_extensible__(context)?.into()) } @@ -313,7 +322,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let keys: Vec = target .__own_property_keys__(context)? @@ -340,7 +349,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; Ok(target.__prevent_extensions__(context)?.into()) } @@ -357,7 +366,7 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let key = args.get_or_undefined(1).to_property_key(context)?; let value = args.get_or_undefined(2); let receiver = if let Some(receiver) = args.get(3).cloned() { @@ -386,11 +395,15 @@ impl Reflect { let target = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| context.construct_type_error("target must be an object"))?; + .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let proto = match args.get_or_undefined(1) { JsValue::Object(obj) => Some(obj.clone()), JsValue::Null => None, - _ => return context.throw_type_error("proto must be an object or null"), + _ => { + return Err(JsNativeError::typ() + .with_message("proto must be an object or null") + .into()) + } }; Ok(target.__set_prototype_of__(proto, context)?.into()) } diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 0a1caf7470c..632178bd4c1 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -16,6 +16,7 @@ use super::JsArgs; use crate::{ builtins::{array::Array, string, BuiltIn}, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -285,7 +286,7 @@ impl RegExp { // or if it contains the same code unit more than once, throw a SyntaxError exception. // TODO: Should directly parse the JsString instead of converting to String let flags = match RegExpFlags::from_str(&f.to_std_string_escaped()) { - Err(msg) => return context.throw_syntax_error(msg), + Err(msg) => return Err(JsNativeError::syntax().with_message(msg).into()), Ok(result) => result, }; @@ -303,8 +304,9 @@ impl RegExp { let fs = f.to_std_string_escaped(); let matcher = match Regex::with_flags(&ps, fs.as_ref()) { Err(error) => { - return context - .throw_syntax_error(format!("failed to create matcher: {}", error.text)); + return Err(JsNativeError::syntax() + .with_message(format!("failed to create matcher: {}", error.text)) + .into()); } Ok(val) => val, }; @@ -391,9 +393,11 @@ impl RegExp { _ => unreachable!(), }; - context.throw_type_error(format!( - "RegExp.prototype.{name} getter called on non-RegExp object", - )) + Err(JsNativeError::typ() + .with_message(format!( + "RegExp.prototype.{name} getter called on non-RegExp object", + )) + .into()) } /// `get RegExp.prototype.hasIndices` @@ -588,7 +592,9 @@ impl RegExp { return Ok(result.into()); } - context.throw_type_error("RegExp.prototype.flags getter called on non-object") + Err(JsNativeError::typ() + .with_message("RegExp.prototype.flags getter called on non-object") + .into()) } /// `get RegExp.prototype.source` @@ -623,9 +629,11 @@ impl RegExp { ) { Ok(JsValue::new("(?:)")) } else { - context.throw_type_error( - "RegExp.prototype.source method called on incompatible value", - ) + Err(JsNativeError::typ() + .with_message( + "RegExp.prototype.source method called on incompatible value", + ) + .into()) } } // 4. Assert: R has an [[OriginalFlags]] internal slot. @@ -640,7 +648,9 @@ impl RegExp { } } } else { - context.throw_type_error("RegExp.prototype.source method called on incompatible value") + Err(JsNativeError::typ() + .with_message("RegExp.prototype.source method called on incompatible value") + .into()) } } @@ -690,8 +700,8 @@ impl RegExp { // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. let this = this.as_object().ok_or_else(|| { - context - .construct_type_error("RegExp.prototype.test method called on incompatible value") + JsNativeError::typ() + .with_message("RegExp.prototype.test method called on incompatible value") })?; // 3. Let string be ? ToString(S). @@ -735,7 +745,7 @@ impl RegExp { .as_object() .filter(|obj| obj.is_regexp()) .ok_or_else(|| { - context.construct_type_error("RegExp.prototype.exec called with invalid value") + JsNativeError::typ().with_message("RegExp.prototype.exec called with invalid value") })?; // 3. Let S be ? ToString(string). @@ -773,7 +783,9 @@ impl RegExp { // b. If Type(result) is neither Object nor Null, throw a TypeError exception. if !result.is_object() && !result.is_null() { - return context.throw_type_error("regexp exec returned neither object nor null"); + return Err(JsNativeError::typ() + .with_message("regexp exec returned neither object nor null") + .into()); } // c. Return result. @@ -782,7 +794,9 @@ impl RegExp { // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). if !this.is_regexp() { - return context.throw_type_error("RegExpExec called with invalid value"); + return Err(JsNativeError::typ() + .with_message("RegExpExec called with invalid value") + .into()); } // 6. Return ? RegExpBuiltinExec(R, S). @@ -806,7 +820,9 @@ impl RegExp { if let Some(rx) = obj.as_regexp() { rx.clone() } else { - return context.throw_type_error("RegExpBuiltinExec called with invalid value"); + return Err(JsNativeError::typ() + .with_message("RegExpBuiltinExec called with invalid value") + .into()); } }; @@ -859,7 +875,9 @@ impl RegExp { let last_byte_index = match String::from_utf16(&input[..last_index as usize]) { Ok(s) => s.len(), Err(_) => { - return Ok(None); + return Err(JsNativeError::typ() + .with_message("Failed to get byte index from utf16 encoded string") + .into()) } }; let r = matcher @@ -1030,8 +1048,9 @@ impl RegExp { let rx = if let Some(rx) = this.as_object() { rx } else { - return context - .throw_type_error("RegExp.prototype.match method called on incompatible value"); + return Err(JsNativeError::typ() + .with_message("RegExp.prototype.match method called on incompatible value") + .into()); }; // 3. Let S be ? ToString(string). @@ -1118,25 +1137,23 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let (body, flags) = if let Some(object) = this.as_object() { let object = object.borrow(); let regex = object.as_regexp().ok_or_else(|| { - context.construct_type_error(format!( + JsNativeError::typ().with_message(format!( "Method RegExp.prototype.toString called on incompatible receiver {}", this.display() )) })?; (regex.original_source.clone(), regex.original_flags.clone()) } else { - return context.throw_type_error(format!( - "Method RegExp.prototype.toString called on incompatible receiver {}", - this.display() - )); + return Err(JsNativeError::typ() + .with_message(format!( + "Method RegExp.prototype.toString called on incompatible receiver {}", + this.display() + )) + .into()); }; Ok(js_string!(utf16!("/"), &body, utf16!("/"), &flags).into()) } @@ -1159,9 +1176,8 @@ impl RegExp { // 1. Let R be the this value. // 2. If Type(R) is not Object, throw a TypeError exception. let regexp = this.as_object().ok_or_else(|| { - context.construct_type_error( - "RegExp.prototype.match_all method called on incompatible value", - ) + JsNativeError::typ() + .with_message("RegExp.prototype.match_all method called on incompatible value") })?; // 3. Let S be ? ToString(string). @@ -1222,9 +1238,11 @@ impl RegExp { let rx = if let Some(rx) = this.as_object() { rx } else { - return context.throw_type_error( - "RegExp.prototype[Symbol.replace] method called on incompatible value", - ); + return Err(JsNativeError::typ() + .with_message( + "RegExp.prototype[Symbol.replace] method called on incompatible value", + ) + .into()); }; // 3. Let S be ? ToString(string). @@ -1442,9 +1460,9 @@ impl RegExp { let rx = if let Some(rx) = this.as_object() { rx } else { - return context.throw_type_error( - "RegExp.prototype[Symbol.search] method called on incompatible value", - ); + return Err(JsNativeError::typ() + .with_message("RegExp.prototype[Symbol.search] method called on incompatible value") + .into()); }; // 3. Let S be ? ToString(string). @@ -1500,8 +1518,9 @@ impl RegExp { let rx = if let Some(rx) = this.as_object() { rx } else { - return context - .throw_type_error("RegExp.prototype.split method called on incompatible value"); + return Err(JsNativeError::typ() + .with_message("RegExp.prototype.split method called on incompatible value") + .into()); }; // 3. Let S be ? ToString(string). diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 620d26640b8..7c4724b5c35 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -12,6 +12,7 @@ use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp}, + error::JsNativeError, object::{JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -85,7 +86,9 @@ impl RegExpStringIterator { 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"))?; + .ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a RegExpStringIterator") + })?; if iterator.completed { return Ok(create_iter_result_object( JsValue::undefined(), diff --git a/boa_engine/src/builtins/regexp/tests.rs b/boa_engine/src/builtins/regexp/tests.rs index 66c156bfc1b..3dd09c6b307 100644 --- a/boa_engine/src/builtins/regexp/tests.rs +++ b/boa_engine/src/builtins/regexp/tests.rs @@ -214,7 +214,8 @@ fn search() { assert_eq!(forward(&mut context, "/c/[Symbol.search]('abc')"), "2"); // this-val-non-obj - let error = "Uncaught \"TypeError\": \"RegExp.prototype[Symbol.search] method called on incompatible value\""; + let error = + "Uncaught TypeError: RegExp.prototype[Symbol.search] method called on incompatible value"; let init = "var search = RegExp.prototype[Symbol.search]"; eprintln!("{}", forward(&mut context, init)); assert_eq!(forward(&mut context, "search.call()"), error); diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 282f2f7d3d0..7362701d743 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -15,6 +15,7 @@ use super::JsArgs; use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -117,8 +118,9 @@ impl Set { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context - .throw_type_error("calling a builtin Set constructor without new is forbidden"); + return Err(JsNativeError::typ() + .with_message("calling a builtin Set constructor without new is forbidden") + .into()); } // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »). @@ -138,7 +140,7 @@ impl Set { // 6. If IsCallable(adder) is false, throw a TypeError exception. let adder = adder.as_callable().ok_or_else(|| { - context.construct_type_error("'add' of 'newTarget' is not a function") + JsNativeError::typ().with_message("'add' of 'newTarget' is not a function") })?; // 7. Let iteratorRecord be ? GetIterator(iterable). @@ -214,11 +216,7 @@ impl Set { /// /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add - pub(crate) fn add( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn add(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let value = args.get_or_undefined(0); if let Some(object) = this.as_object() { @@ -229,10 +227,14 @@ impl Set { value.clone() }); } else { - return context.throw_type_error("'this' is not a Set"); + return Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()); } } else { - return context.throw_type_error("'this' is not a Set"); + return Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()); }; Ok(this.clone()) @@ -248,16 +250,20 @@ impl Set { /// /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear - pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { if let Some(object) = this.as_object() { if object.borrow().is_set() { this.set_data(ObjectData::set(OrderedSet::new())); Ok(JsValue::undefined()) } else { - context.throw_type_error("'this' is not a Set") + Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()) } } else { - context.throw_type_error("'this' is not a Set") + Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()) } } @@ -272,21 +278,21 @@ impl Set { /// /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete - pub(crate) fn delete( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let value = args.get_or_undefined(0); let res = if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { set.delete(value) } else { - return context.throw_type_error("'this' is not a Set"); + return Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()); } } else { - return context.throw_type_error("'this' is not a Set"); + return Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()); }; Ok(res.into()) @@ -310,13 +316,14 @@ impl Set { if let Some(object) = this.as_object() { let object = object.borrow(); if !object.is_set() { - return context.throw_type_error( - "Method Set.prototype.entries called on incompatible receiver", - ); + return Err(JsNativeError::typ() + .with_message("Method Set.prototype.entries called on incompatible receiver") + .into()); } } else { - return context - .throw_type_error("Method Set.prototype.entries called on incompatible receiver"); + return Err(JsNativeError::typ() + .with_message("Method Set.prototype.entries called on incompatible receiver") + .into()); } Ok(SetIterator::create_set_iterator( @@ -342,7 +349,9 @@ impl Set { context: &mut Context, ) -> JsResult { if args.is_empty() { - return Err(JsValue::new("Missing argument for Set.prototype.forEach")); + return Err(JsNativeError::typ() + .with_message("Missing argument for Set.prototype.forEach") + .into()); } let callback_arg = &args[0]; @@ -357,7 +366,7 @@ impl Set { let mut index = 0; - while index < Self::get_size(this, context)? { + while index < Self::get_size(this)? { let arguments = this .as_object() .and_then(|obj| { @@ -366,7 +375,7 @@ impl Set { .map(|value| [value.clone(), value.clone(), this.clone()]) }) }) - .ok_or_else(|| context.construct_type_error("'this' is not a Set"))?; + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; if let Some(arguments) = arguments { context.call(callback_arg, &this_arg, &arguments)?; @@ -388,11 +397,7 @@ impl Set { /// /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has - pub(crate) fn has( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn has(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let value = args.get_or_undefined(0); this.as_object() @@ -401,7 +406,11 @@ impl Set { .as_set_ref() .map(|set| set.contains(value).into()) }) - .ok_or_else(|| context.construct_type_error("'this' is not a Set")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a Set") + .into() + }) } /// `Set.prototype.values( )` @@ -422,13 +431,14 @@ impl Set { if let Some(object) = this.as_object() { let object = object.borrow(); if !object.is_set() { - return context.throw_type_error( - "Method Set.prototype.values called on incompatible receiver", - ); + return Err(JsNativeError::typ() + .with_message("Method Set.prototype.values called on incompatible receiver") + .into()); } } else { - return context - .throw_type_error("Method Set.prototype.values called on incompatible receiver"); + return Err(JsNativeError::typ() + .with_message("Method Set.prototype.values called on incompatible receiver") + .into()); } Ok(SetIterator::create_set_iterator( @@ -438,14 +448,18 @@ impl Set { )) } - fn size_getter(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - Self::get_size(this, context).map(JsValue::from) + fn size_getter(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + Self::get_size(this).map(JsValue::from) } /// Helper function to get the size of the `Set` object. - pub(crate) fn get_size(set: &JsValue, context: &mut Context) -> JsResult { + pub(crate) fn get_size(set: &JsValue) -> JsResult { set.as_object() .and_then(|obj| obj.borrow().as_set_ref().map(OrderedSet::size)) - .ok_or_else(|| context.construct_type_error("'this' is not a Set")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a Set") + .into() + }) } } diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 4d3e86fc1b7..653458b81c1 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -1,5 +1,6 @@ use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, + error::JsNativeError, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, @@ -72,7 +73,7 @@ impl SetIterator { 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"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an SetIterator"))?; { let m = &set_iterator.iterated_set; let mut index = set_iterator.next_index; @@ -90,7 +91,7 @@ impl SetIterator { let entries = entries .as_ref() .and_then(|obj| obj.as_set_ref()) - .ok_or_else(|| context.construct_type_error("'this' is not a Set"))?; + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; let num_entries = entries.size(); while index < num_entries { diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index c3a97da9bb4..a83a23f1023 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -17,6 +17,7 @@ use super::JsArgs; use crate::{ builtins::{string::string_iterator::StringIterator, Array, BuiltIn, Number, RegExp}, context::intrinsics::StandardConstructors, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -207,7 +208,7 @@ impl String { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#thisstringvalue - fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult { + fn this_string_value(this: &JsValue) -> JsResult { // 1. If Type(value) is String, return value. this.as_string() .cloned() @@ -217,7 +218,11 @@ impl String { // c. Return s. .or_else(|| this.as_object().and_then(|obj| obj.borrow().as_string())) // 3. Throw a TypeError exception. - .ok_or_else(|| context.construct_type_error("'this' is not a string")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a string") + .into() + }) } /// `String.fromCodePoint(num1[, ...[, numN]])` @@ -247,12 +252,16 @@ impl String { // b. If ! IsIntegralNumber(nextCP) is false, throw a RangeError exception. if !Number::is_float_integer(nextcp) { - return Err(context.construct_range_error(format!("invalid code point: {nextcp}"))); + return Err(JsNativeError::range() + .with_message(format!("invalid code point: {nextcp}")) + .into()); } // c. If ℝ(nextCP) < 0 or ℝ(nextCP) > 0x10FFFF, throw a RangeError exception. if nextcp < 0.0 || nextcp > f64::from(0x10FFFF) { - return Err(context.construct_range_error(format!("invalid code point: {nextcp}"))); + return Err(JsNativeError::range() + .with_message(format!("invalid code point: {nextcp}")) + .into()); } let nextcp = @@ -375,13 +384,9 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.tostring #[allow(clippy::wrong_self_convention)] #[inline] - pub(crate) fn to_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return ? thisStringValue(this value). - Ok(Self::this_string_value(this, context)?.into()) + Ok(Self::this_string_value(this)?.into()) } /// `String.prototype.charAt( index )` @@ -406,7 +411,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -439,7 +444,7 @@ impl String { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let s = this.to_string(context)?; @@ -484,7 +489,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -527,7 +532,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -566,7 +571,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let mut string = this.to_string(context)?; @@ -600,7 +605,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -628,10 +633,12 @@ impl String { // 5. If n is 0, return the empty String. IntegerOrInfinity::Integer(n) if n == 0 => Ok(js_string!().into()), // 4. If n < 0 or n is +∞, throw a RangeError exception. - _ => context.throw_range_error( - "repeat count must be a positive finite number \ + _ => Err(JsNativeError::range() + .with_message( + "repeat count must be a positive finite number \ that doesn't overflow the maximum string length (2^32 - 1)", - ), + ) + .into()), } } @@ -651,7 +658,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -716,7 +723,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -726,9 +733,9 @@ impl String { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. if is_reg_exp(search_string, context)? { - context.throw_type_error( + return Err(JsNativeError::typ().with_message( "First argument to String.prototype.startsWith must not be a regular expression", - )?; + ).into()); } // 5. Let searchStr be ? ToString(searchString). @@ -785,7 +792,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -794,9 +801,9 @@ impl String { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. search_string if is_reg_exp(search_string, context)? => { - return context.throw_type_error( + return Err(JsNativeError::typ().with_message( "First argument to String.prototype.endsWith must not be a regular expression", - ); + ).into()); } // 5. Let searchStr be ? ToString(searchString). search_string => search_string.to_string(context)?, @@ -851,7 +858,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -859,10 +866,10 @@ impl String { let search_str = match args.get_or_undefined(0) { // 3. Let isRegExp be ? IsRegExp(searchString). search_string if is_reg_exp(search_string, context)? => { - return context.throw_type_error( + return Err(JsNativeError::typ().with_message( // 4. If isRegExp is true, throw a TypeError exception. "First argument to String.prototype.includes must not be a regular expression", - ); + ).into()); } // 5. Let searchStr be ? ToString(searchString). search_string => search_string.to_string(context)?, @@ -903,7 +910,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - this.require_object_coercible(context)?; + this.require_object_coercible()?; let search_value = args.get_or_undefined(0); @@ -1013,7 +1020,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let o = this.require_object_coercible(context)?; + let o = this.require_object_coercible()?; let search_value = args.get_or_undefined(0); let replace_value = args.get_or_undefined(1); @@ -1028,13 +1035,13 @@ impl String { let flags = obj.get("flags", context)?; // ii. Perform ? RequireObjectCoercible(flags). - flags.require_object_coercible(context)?; + flags.require_object_coercible()?; // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. - if !flags.to_string(context)?.contains(&('g' as u16)) { - return context.throw_type_error( + if !flags.to_string(context)?.contains(&u16::from(b'g')) { + return Err(JsNativeError::typ().with_message( "String.prototype.replaceAll called with a non-global RegExp argument", - ); + ).into()); } } } @@ -1170,7 +1177,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1214,7 +1221,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1282,7 +1289,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let o = this.require_object_coercible(context)?; + let o = this.require_object_coercible()?; // 2. If regexp is neither undefined nor null, then let regexp = args.get_or_undefined(0); @@ -1394,7 +1401,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; let max_length = args.get_or_undefined(0); let fill_string = args.get_or_undefined(1); @@ -1421,7 +1428,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; let max_length = args.get_or_undefined(0); let fill_string = args.get_or_undefined(1); @@ -1445,7 +1452,7 @@ impl String { pub(crate) fn trim(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let S be the this value. // 2. Return ? TrimString(S, start+end). - let object = this.require_object_coercible(context)?; + let object = this.require_object_coercible()?; let string = object.to_string(context)?; Ok(js_string!(string.trim()).into()) } @@ -1469,7 +1476,7 @@ impl String { ) -> JsResult { // 1. Let S be the this value. // 2. Return ? TrimString(S, start). - let object = this.require_object_coercible(context)?; + let object = this.require_object_coercible()?; let string = object.to_string(context)?; Ok(js_string!(string.trim_start()).into()) } @@ -1493,7 +1500,7 @@ impl String { ) -> JsResult { // 1. Let S be the this value. // 2. Return ? TrimString(S, end). - let object = this.require_object_coercible(context)?; + let object = this.require_object_coercible()?; let string = object.to_string(context)?; Ok(js_string!(string.trim_end()).into()) } @@ -1515,7 +1522,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1578,7 +1585,7 @@ impl String { // Comments below are an adaptation of the `String.prototype.toLowerCase` documentation. // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1633,7 +1640,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1683,7 +1690,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let string = this.to_string(context)?; @@ -1746,7 +1753,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; let separator = args.get_or_undefined(0); let limit = args.get_or_undefined(1); @@ -1862,10 +1869,10 @@ impl String { pub(crate) fn value_of( this: &JsValue, _args: &[JsValue], - context: &mut Context, + _context: &mut Context, ) -> JsResult { // 1. Return ? thisStringValue(this value). - Self::this_string_value(this, context).map(JsValue::from) + Self::this_string_value(this).map(JsValue::from) } /// `String.prototype.matchAll( regexp )` @@ -1886,7 +1893,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let o = this.require_object_coercible(context)?; + let o = this.require_object_coercible()?; // 2. If regexp is neither undefined nor null, then let regexp = args.get_or_undefined(0); @@ -1899,13 +1906,15 @@ impl String { let flags = regexp_obj.get("flags", context)?; // ii. Perform ? RequireObjectCoercible(flags). - flags.require_object_coercible(context)?; + flags.require_object_coercible()?; // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. - if !flags.to_string(context)?.contains(&('g' as u16)) { - return context.throw_type_error( + if !flags.to_string(context)?.contains(&u16::from(b'g')) { + return Err(JsNativeError::typ() + .with_message( "String.prototype.matchAll called with a non-global RegExp argument", - ); + ) + .into()); } } } @@ -1952,7 +1961,7 @@ impl String { Nfkd, } // 1. Let O be ? RequireObjectCoercible(this value). - let this = this.require_object_coercible(context)?; + let this = this.require_object_coercible()?; // 2. Let S be ? ToString(O). let s = this.to_string(context)?; @@ -1974,9 +1983,9 @@ impl String { ntype if &ntype == utf16!("NFKD") => Normalization::Nfkd, // 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception. _ => { - return context.throw_range_error( - "The normalization form should be one of NFC, NFD, NFKC, NFKD.", - ) + return Err(JsNativeError::range() + .with_message("The normalization form should be one of NFC, NFD, NFKC, NFKD.") + .into()); } }; @@ -2045,7 +2054,7 @@ impl String { context: &mut Context, ) -> JsResult { // 1. Let O be ? RequireObjectCoercible(this value). - let o = this.require_object_coercible(context)?; + let o = this.require_object_coercible()?; // 2. If regexp is neither undefined nor null, then let regexp = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 7b3357dc075..fb3c5493a0d 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -1,5 +1,6 @@ use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, + error::JsNativeError, object::{JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -39,7 +40,7 @@ impl StringIterator { 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"))?; + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; if string_iterator.string.is_undefined() { return Ok(create_iter_result_object( diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 3dd58078e45..5ed87e055da 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -21,6 +21,7 @@ mod tests; use super::JsArgs; use crate::{ builtins::BuiltIn, + error::JsNativeError, object::{ConstructorBuilder, FunctionBuilder}, property::Attribute, symbol::{JsSymbol, WellKnownSymbols}, @@ -175,7 +176,9 @@ impl Symbol { ) -> JsResult { // 1. If NewTarget is not undefined, throw a TypeError exception. if !new_target.is_undefined() { - return context.throw_type_error("Symbol is not a constructor"); + return Err(JsNativeError::typ() + .with_message("Symbol is not a constructor") + .into()); } // 2. If description is undefined, let descString be undefined. @@ -189,11 +192,15 @@ impl Symbol { Ok(JsSymbol::new(description).into()) } - fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { + fn this_symbol_value(value: &JsValue) -> JsResult { 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")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("'this' is not a Symbol") + .into() + }) } /// `Symbol.prototype.toString()` @@ -207,13 +214,9 @@ impl Symbol { /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let sym be ? thisSymbolValue(this value). - let symbol = Self::this_symbol_value(this, context)?; + let symbol = Self::this_symbol_value(this)?; // 2. Return SymbolDescriptiveString(sym). Ok(symbol.descriptive_string().into()) @@ -229,13 +232,9 @@ impl Symbol { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/valueOf /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.valueof - pub(crate) fn value_of( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Return ? thisSymbolValue(this value). - let symbol = Self::this_symbol_value(this, context)?; + let symbol = Self::this_symbol_value(this)?; Ok(JsValue::Symbol(symbol)) } @@ -252,9 +251,9 @@ impl Symbol { pub(crate) fn get_description( this: &JsValue, _: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { - let symbol = Self::this_symbol_value(this, context)?; + let symbol = Self::this_symbol_value(this)?; if let Some(ref description) = symbol.description() { Ok(description.clone().into()) } else { @@ -300,11 +299,7 @@ impl Symbol { /// /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.keyfor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor - pub(crate) fn key_for( - _: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn key_for(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { let sym = args.get_or_undefined(0); // 1. If Type(sym) is not Symbol, throw a TypeError exception. if let Some(sym) = sym.as_symbol() { @@ -319,7 +314,9 @@ impl Symbol { Ok(symbol.map(JsValue::from).unwrap_or_default()) } else { - context.throw_type_error("Symbol.keyFor: sym is not a symbol") + Err(JsNativeError::typ() + .with_message("Symbol.keyFor: sym is not a symbol") + .into()) } } @@ -337,9 +334,9 @@ impl Symbol { pub(crate) fn to_primitive( this: &JsValue, _: &[JsValue], - context: &mut Context, + _: &mut Context, ) -> JsResult { - let sym = Self::this_symbol_value(this, context)?; + let sym = Self::this_symbol_value(this)?; // 1. Return ? thisSymbolValue(this value). Ok(sym.into()) } diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 6e7da8b1bed..b40bfca0c0d 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -20,6 +20,7 @@ use crate::{ Array, ArrayIterator, BuiltIn, JsArgs, }, context::intrinsics::{StandardConstructor, StandardConstructors}, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -121,10 +122,12 @@ macro_rules! typed_array { ) -> JsResult { // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { - return context.throw_type_error(concat!( - "new target was undefined when constructing an ", - $name - )); + return Err(JsNativeError::typ() + .with_message(concat!( + "new target was undefined when constructing an ", + $name + )) + .into()); } // 2. Let constructorName be the String value of the Constructor Name value specified in Table 72 for this TypedArray constructor. @@ -380,13 +383,11 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%typedarray% - fn constructor( - _new_target: &JsValue, - _args: &[JsValue], - context: &mut Context, - ) -> JsResult { + fn constructor(_new_target: &JsValue, _args: &[JsValue], _: &mut Context) -> JsResult { // 1. Throw a TypeError exception. - context.throw_type_error("the TypedArray constructor should never be called directly") + Err(JsNativeError::typ() + .with_message("the TypedArray constructor should never be called directly") + .into()) } /// `23.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` @@ -401,8 +402,9 @@ impl TypedArray { let constructor = match this.as_object() { Some(obj) if obj.is_constructor() => obj, _ => { - return context - .throw_type_error("TypedArray.from called on non-constructable value") + return Err(JsNativeError::typ() + .with_message("TypedArray.from called on non-constructable value") + .into()) } }; @@ -415,8 +417,9 @@ impl TypedArray { Some(obj) if obj.is_callable() => Some(obj), // a. If IsCallable(mapfn) is false, throw a TypeError exception. _ => { - return context - .throw_type_error("TypedArray.from called with non-callable mapfn") + return Err(JsNativeError::typ() + .with_message("TypedArray.from called with non-callable mapfn") + .into()) } }, }; @@ -511,7 +514,9 @@ impl TypedArray { let constructor = match this.as_object() { Some(obj) if obj.is_constructor() => obj, _ => { - return context.throw_type_error("TypedArray.of called on non-constructable value") + return Err(JsNativeError::typ() + .with_message("TypedArray.of called on non-constructable value") + .into()) } }; @@ -552,15 +557,17 @@ impl TypedArray { pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -597,17 +604,17 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer - fn buffer(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + fn buffer(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let typed_array = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. Return buffer. @@ -622,21 +629,17 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength - pub(crate) fn byte_length( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn byte_length(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let typed_array = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. @@ -655,21 +658,17 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset - pub(crate) fn byte_offset( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn byte_offset(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let typed_array = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. @@ -690,19 +689,21 @@ impl TypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin fn copy_within(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let len = { let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 2. Perform ? ValidateTypedArray(O). if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -757,9 +758,9 @@ impl TypedArray { let count = std::cmp::min(r#final - from, len - to); let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 17. If count > 0, then if count > 0 { @@ -767,7 +768,9 @@ impl TypedArray { // b. Let buffer be O.[[ViewedArrayBuffer]]. // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. @@ -860,15 +863,17 @@ impl TypedArray { fn entries(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let o = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.borrow() .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Return CreateArrayIterator(O, key+value). @@ -892,15 +897,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -910,9 +917,11 @@ impl TypedArray { let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, _ => { - return context.throw_type_error( - "TypedArray.prototype.every called with non-callable callback function", - ) + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.every called with non-callable callback function", + ) + .into()) } }; @@ -955,15 +964,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1008,7 +1019,9 @@ impl TypedArray { // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 15. Repeat, while k < final, @@ -1039,29 +1052,32 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return context.throw_type_error( - "TypedArray.prototype.filter called with non-callable callback function", - ) - } - }; + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.filter called with non-callable callback function", + ) + .into()), + }; // 5. Let kept be a new empty List. let mut kept = Vec::new(); @@ -1124,15 +1140,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1142,9 +1160,11 @@ impl TypedArray { let predicate = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, _ => { - return context.throw_type_error( - "TypedArray.prototype.find called with non-callable predicate function", - ) + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.find called with non-callable predicate function", + ) + .into()) } }; @@ -1186,28 +1206,31 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); // 4. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = - match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return context.throw_type_error( + let predicate = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( "TypedArray.prototype.findindex called with non-callable predicate function", - ), - }; + ) + .into()), + }; // 5. Let k be 0. // 6. Repeat, while k < len, @@ -1247,29 +1270,32 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return context.throw_type_error( - "TypedArray.prototype.foreach called with non-callable callback function", - ) - } - }; + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.foreach called with non-callable callback function", + ) + .into()), + }; // 5. Let k be 0. // 6. Repeat, while k < len, @@ -1303,15 +1329,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1380,15 +1408,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1466,15 +1496,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1523,15 +1555,17 @@ impl TypedArray { pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let o = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.borrow() .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Return CreateArrayIterator(O, key). @@ -1555,15 +1589,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1626,21 +1662,17 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length - pub(crate) fn length( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { + pub(crate) fn length(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let typed_array = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. @@ -1666,15 +1698,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1684,9 +1718,11 @@ impl TypedArray { let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, _ => { - return context.throw_type_error( - "TypedArray.prototype.map called with non-callable callback function", - ) + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.map called with non-callable callback function", + ) + .into()) } }; @@ -1728,34 +1764,38 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return context.throw_type_error( - "TypedArray.prototype.reduce called with non-callable callback function", - ) - } - }; + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.reduce called with non-callable callback function", + ) + .into()), + }; // 5. If len = 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { - return context - .throw_type_error("Typed array length is 0 and initial value is not present"); + return Err(JsNativeError::typ() + .with_message("Typed array length is 0 and initial value is not present") + .into()); } // 6. Let k be 0. @@ -1809,33 +1849,37 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = - match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return context.throw_type_error( + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( "TypedArray.prototype.reduceright called with non-callable callback function", - ), - }; + ) + .into()), + }; // 5. If len = 0 and initialValue is not present, throw a TypeError exception. if len == 0 && args.get(1).is_none() { - return context - .throw_type_error("Typed array length is 0 and initial value is not present"); + return Err(JsNativeError::typ() + .with_message("Typed array length is 0 and initial value is not present") + .into()); } // 6. Let k be len - 1. @@ -1893,15 +1937,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -1954,10 +2000,12 @@ impl TypedArray { // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. let target = this.as_object().ok_or_else(|| { - context.construct_type_error("TypedArray.set must be called on typed array object") + JsNativeError::typ().with_message("TypedArray.set must be called on typed array object") })?; if !target.is_typed_array() { - return context.throw_type_error("TypedArray.set must be called on typed array object"); + return Err(JsNativeError::typ() + .with_message("TypedArray.set must be called on typed array object") + .into()); } // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). @@ -1966,10 +2014,14 @@ impl TypedArray { // 5. If targetOffset < 0, throw a RangeError exception. match target_offset { IntegerOrInfinity::Integer(i) if i < 0 => { - return context.throw_range_error("TypedArray.set called with negative offset") + return Err(JsNativeError::range() + .with_message("TypedArray.set called with negative offset") + .into()) } IntegerOrInfinity::NegativeInfinity => { - return context.throw_range_error("TypedArray.set called with negative offset") + return Err(JsNativeError::range() + .with_message("TypedArray.set called with negative offset") + .into()) } _ => {} } @@ -2017,7 +2069,9 @@ impl TypedArray { // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. if target_array.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } let target_buffer_obj = target_array .viewed_array_buffer() @@ -2029,7 +2083,9 @@ impl TypedArray { // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. if source_array.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } let mut src_buffer_obj = source_array .viewed_array_buffer() @@ -2063,23 +2119,27 @@ impl TypedArray { let target_offset = match target_offset { IntegerOrInfinity::Integer(i) if i >= 0 => i as u64, IntegerOrInfinity::PositiveInfinity => { - return context.throw_range_error("Target offset cannot be Infinity"); + return Err(JsNativeError::range() + .with_message("Target offset cannot be Infinity") + .into()); } _ => unreachable!(), }; // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. if src_length + target_offset > target_length { - return context.throw_range_error( - "Source typed array and target offset longer than target typed array", - ); + return Err(JsNativeError::range() + .with_message("Source typed array and target offset longer than target typed array") + .into()); } // 17. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. if target_name.content_type() != src_name.content_type() { - return context.throw_type_error( - "Source typed array and target typed array have different content types", - ); + return Err(JsNativeError::typ() + .with_message( + "Source typed array and target typed array have different content types", + ) + .into()); } // TODO: Shared Array Buffer @@ -2227,7 +2287,9 @@ impl TypedArray { // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. if target_array.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let targetLength be target.[[ArrayLength]]. @@ -2252,7 +2314,9 @@ impl TypedArray { let target_offset = match target_offset { // 10. If targetOffset is +∞, throw a RangeError exception. IntegerOrInfinity::PositiveInfinity => { - return context.throw_range_error("Target offset cannot be Infinity") + return Err(JsNativeError::range() + .with_message("Target offset cannot be Infinity") + .into()) } IntegerOrInfinity::Integer(i) if i >= 0 => i as u64, _ => unreachable!(), @@ -2260,9 +2324,9 @@ impl TypedArray { // 11. If srcLength + targetOffset > targetLength, throw a RangeError exception. if src_length + target_offset > target_length { - return context.throw_range_error( - "Source object and target offset longer than target typed array", - ); + return Err(JsNativeError::range() + .with_message("Source object and target offset longer than target typed array") + .into()); } // 12. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. @@ -2298,7 +2362,9 @@ impl TypedArray { // e. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. if target_buffer.is_detached_buffer() { - return context.throw_type_error("Cannot set value on detached array buffer"); + return Err(JsNativeError::typ() + .with_message("Cannot set value on detached array buffer") + .into()); } // f. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). @@ -2334,15 +2400,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -2391,7 +2459,9 @@ impl TypedArray { if count > 0 { // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // b. Let srcName be the String value of O.[[TypedArrayName]]. @@ -2503,15 +2573,17 @@ impl TypedArray { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Let len be O.[[ArrayLength]]. @@ -2521,9 +2593,11 @@ impl TypedArray { let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, _ => { - return context.throw_type_error( - "TypedArray.prototype.some called with non-callable callback function", - ) + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.some called with non-callable callback function", + ) + .into()) } }; @@ -2568,37 +2642,41 @@ impl TypedArray { None | Some(JsValue::Undefined) => None, Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), _ => { - return context - .throw_type_error("TypedArray.sort called with non-callable comparefn") + return Err(JsNativeError::typ() + .with_message("TypedArray.sort called with non-callable comparefn") + .into()) } }; // 2. Let obj be the this value. let obj = this.as_object().ok_or_else(|| { - context.construct_type_error("TypedArray.sort must be called on typed array object") + JsNativeError::typ() + .with_message("TypedArray.sort must be called on typed array object") })?; // 4. Let buffer be obj.[[ViewedArrayBuffer]]. // 5. Let len be obj.[[ArrayLength]]. - let (buffer, len) = { - // 3. Perform ? ValidateTypedArray(obj). - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - context.construct_type_error("TypedArray.sort must be called on typed array object") - })?; - if o.is_detached() { - return context.throw_type_error( + let (buffer, len) = + { + // 3. Perform ? ValidateTypedArray(obj). + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ() + .with_message("TypedArray.sort must be called on typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ().with_message( "TypedArray.sort called on typed array object with detached array buffer", - ); - } + ).into()); + } - ( - o.viewed_array_buffer() - .expect("Already checked for detached buffer") - .clone(), - o.array_length(), - ) - }; + ( + o.viewed_array_buffer() + .expect("Already checked for detached buffer") + .clone(), + o.array_length(), + ) + }; // 4. Let items be a new empty List. let mut items = Vec::with_capacity(len as usize); @@ -2641,8 +2719,9 @@ impl TypedArray { .expect("Must be array buffer") .is_detached_buffer() { - return context - .throw_type_error("Cannot sort typed array with detached buffer"); + return Err(JsNativeError::typ() + .with_message("Cannot sort typed array with detached buffer") + .into()); } // c. If v is NaN, return +0𝔽. @@ -2778,13 +2857,13 @@ impl TypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; let obj_borrow = obj.borrow(); - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer = o @@ -2862,15 +2941,17 @@ impl TypedArray { fn values(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). - let o = this - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.borrow() .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))? + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. Return CreateArrayIterator(O, value). @@ -2948,8 +3029,9 @@ impl TypedArray { .content_type() != typed_array_name.content_type() { - return context - .throw_type_error("New typed array has different context type than exemplar"); + return Err(JsNativeError::typ() + .with_message("New typed array has different context type than exemplar") + .into()); } // 6. Return result. @@ -2972,11 +3054,13 @@ impl TypedArray { let obj_borrow = new_typed_array.borrow(); // 2. Perform ? ValidateTypedArray(newTypedArray). - let o = obj_borrow - .as_typed_array() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { - return context.throw_type_error("Buffer of the typed array is detached"); + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); } // 3. If argumentList is a List of a single Number, then @@ -2984,8 +3068,9 @@ impl TypedArray { if let Some(number) = args[0].as_number() { // a. If newTypedArray.[[ArrayLength]] < ℝ(argumentList[0]), throw a TypeError exception. if (o.array_length() as f64) < number { - return context - .throw_type_error("New typed array length is smaller than expected"); + return Err(JsNativeError::typ() + .with_message("New typed array length is smaller than expected") + .into()); } } } @@ -3133,7 +3218,9 @@ impl TypedArray { // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. if src_array.is_detached() { - return context.throw_type_error("Cannot initialize typed array from detached buffer"); + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from detached buffer") + .into()); } let src_data_obj = src_array .viewed_array_buffer() @@ -3191,14 +3278,16 @@ impl TypedArray { // b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. if src_data.is_detached_buffer() { - return context - .throw_type_error("Cannot initialize typed array from detached buffer"); + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from detached buffer") + .into()); } // c. If srcArray.[[ContentType]] ≠ O.[[ContentType]], throw a TypeError exception. if src_name.content_type() != constructor_name.content_type() { - return context - .throw_type_error("Cannot initialize typed array from different content type"); + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from different content type") + .into()); } // d. Let srcByteIndex be srcByteOffset. @@ -3283,7 +3372,9 @@ impl TypedArray { // 4. If offset modulo elementSize ≠ 0, throw a RangeError exception. if offset % constructor_name.element_size() != 0 { - return context.throw_range_error("Invalid length for typed array"); + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); } let buffer_byte_length = { @@ -3294,8 +3385,9 @@ impl TypedArray { // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer_array.is_detached_buffer() { - return context - .throw_type_error("Cannot construct typed array from detached buffer"); + return Err(JsNativeError::typ() + .with_message("Cannot construct typed array from detached buffer") + .into()); } // 7. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. @@ -3306,7 +3398,9 @@ impl TypedArray { let new_byte_length = if length.is_undefined() { // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. if buffer_byte_length % constructor_name.element_size() != 0 { - return context.throw_range_error("Invalid length for typed array"); + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); } // b. Let newByteLength be bufferByteLength - offset. @@ -3314,7 +3408,9 @@ impl TypedArray { // c. If newByteLength < 0, throw a RangeError exception. if new_byte_length < 0 { - return context.throw_range_error("Invalid length for typed array"); + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); } new_byte_length as u64 @@ -3328,7 +3424,9 @@ impl TypedArray { // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. if offset + new_byte_length > buffer_byte_length { - return context.throw_range_error("Invalid length for typed array"); + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); } new_byte_length diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index e5c1fa73963..b7b11ca8b19 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -20,7 +20,7 @@ use self::consts::{ use super::BuiltIn; use crate::{ builtins::JsArgs, js_string, object::FunctionBuilder, property::Attribute, string::CodePoint, - Context, JsResult, JsString, JsValue, + Context, JsNativeError, JsResult, JsString, JsValue, }; /// URI Handling Functions @@ -111,11 +111,7 @@ impl Uri { let reserved_uri_set = is_uri_reserved_or_number_sign; // 3. Return ? Decode(uriString, reservedURISet). - Ok(JsValue::from(decode( - context, - &uri_string, - reserved_uri_set, - )?)) + Ok(JsValue::from(decode(&uri_string, reserved_uri_set)?)) } /// Builtin JavaScript `decodeURIComponent ( encodedURIComponent )` function. @@ -145,7 +141,6 @@ impl Uri { // 3. Return ? Decode(componentString, reservedURIComponentSet). Ok(JsValue::from(decode( - context, &component_string, reserved_uri_component_set, )?)) @@ -177,11 +172,7 @@ impl Uri { let unescaped_uri_set = is_uri_reserved_or_uri_unescaped_or_number_sign; // 3. Return ? Encode(uriString, unescapedURISet). - Ok(JsValue::from(encode( - context, - &uri_string, - unescaped_uri_set, - )?)) + Ok(JsValue::from(encode(&uri_string, unescaped_uri_set)?)) } /// Builtin JavaScript `encodeURIComponent ( uriComponent )` function. @@ -211,7 +202,6 @@ impl Uri { // 3. Return ? Encode(componentString, unescapedURIComponentSet). Ok(JsValue::from(encode( - context, &component_string, unescaped_uri_component_set, )?)) @@ -228,7 +218,7 @@ impl Uri { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-encode -fn encode(context: &mut Context, string: &JsString, unescaped_set: F) -> JsResult +fn encode(string: &JsString, unescaped_set: F) -> JsResult where F: Fn(u16) -> bool, { @@ -266,7 +256,9 @@ where let ch = if let CodePoint::Unicode(ch) = cp { ch } else { - return Err(context.construct_uri_error("trying to encode an invalid string")); + return Err(JsNativeError::uri() + .with_message("trying to encode an invalid string") + .into()); }; // iii. Set k to k + cp.[[CodeUnitCount]]. @@ -302,7 +294,7 @@ where /// /// [spec]: https://tc39.es/ecma262/#sec-decode #[allow(clippy::many_single_char_names)] -fn decode(context: &mut Context, string: &JsString, reserved_set: F) -> JsResult +fn decode(string: &JsString, reserved_set: F) -> JsResult where F: Fn(u16) -> bool, { @@ -337,14 +329,17 @@ where // ii. If k + 2 ≥ strLen, throw a URIError exception. if k + 2 >= str_len { - context.throw_uri_error("invalid escape character found")?; + return Err(JsNativeError::uri() + .with_message("invalid escape character found") + .into()); } // iii. If the code units at index (k + 1) and (k + 2) within string do not represent // hexadecimal digits, throw a URIError exception. // iv. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). - let b = decode_hex_byte(string[k + 1], string[k + 2]) - .ok_or_else(|| context.construct_uri_error("invalid hexadecimal digit found"))?; + let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| { + JsNativeError::uri().with_message("invalid hexadecimal digit found") + })?; // v. Set k to k + 2. k += 2; @@ -370,12 +365,16 @@ where // viii. Else, // 1. If n = 1 or n > 4, throw a URIError exception. if n == 1 || n > 4 { - context.throw_uri_error("invalid escaped character found")?; + return Err(JsNativeError::uri() + .with_message("invalid escaped character found") + .into()); } // 2. If k + (3 × (n - 1)) ≥ strLen, throw a URIError exception. if k + (3 * (n - 1)) > str_len { - context.throw_uri_error("non-terminated escape character found")?; + return Err(JsNativeError::uri() + .with_message("non-terminated escape character found") + .into()); } // 3. Let Octets be « B ». @@ -389,14 +388,15 @@ where // b. If the code unit at index k within string is not the code unit 0x0025 (PERCENT SIGN), throw a URIError exception. if string[k] != 0x0025 { - context - .throw_uri_error("escape characters must be preceded with a % sign")?; + return Err(JsNativeError::uri() + .with_message("escape characters must be preceded with a % sign") + .into()); } // c. If the code units at index (k + 1) and (k + 2) within string do not represent hexadecimal digits, throw a URIError exception. // d. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| { - context.construct_uri_error("invalid hexadecimal digit found") + JsNativeError::uri().with_message("invalid hexadecimal digit found") })?; // e. Set k to k + 2. @@ -414,7 +414,9 @@ where // 7. If Octets does not contain a valid UTF-8 encoding of a Unicode code point, throw a URIError exception. match std::str::from_utf8(&octets) { Err(_) => { - return Err(context.construct_uri_error("invalid UTF-8 encoding found")) + return Err(JsNativeError::uri() + .with_message("invalid UTF-8 encoding found") + .into()) } Ok(v) => { // 8. Let V be the code point obtained by applying the UTF-8 transformation to Octets, that is, from a List of octets into a 21-bit value. diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index d31a244cd17..3709719fc56 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -24,7 +24,7 @@ use crate::{ Declaration, Expression, Statement, StatementList, StatementListItem, }, vm::{BindingOpcode, CodeBlock, Opcode}, - Context, JsBigInt, JsResult, JsString, JsValue, + Context, JsBigInt, JsNativeError, JsResult, JsString, JsValue, }; use boa_gc::Gc; use boa_interner::{Interner, Sym}; @@ -808,8 +808,7 @@ impl<'b> ByteCompiler<'b> { self.emit(Opcode::Inc, &[]); let access = Self::compile_access(unary.target()).ok_or_else(|| { - self.context - .construct_syntax_error("Invalid increment operand") + JsNativeError::syntax().with_message("Invalid increment operand") })?; self.access_set(access, None, true)?; None @@ -818,9 +817,9 @@ impl<'b> ByteCompiler<'b> { self.compile_expr(unary.target(), true)?; self.emit(Opcode::Dec, &[]); + // TODO: promote to an early error. let access = Self::compile_access(unary.target()).ok_or_else(|| { - self.context - .construct_syntax_error("Invalid decrement operand") + JsNativeError::syntax().with_message("Invalid decrement operand") })?; self.access_set(access, None, true)?; None @@ -829,9 +828,9 @@ impl<'b> ByteCompiler<'b> { self.compile_expr(unary.target(), true)?; self.emit(Opcode::IncPost, &[]); + // TODO: promote to an early error. let access = Self::compile_access(unary.target()).ok_or_else(|| { - self.context - .construct_syntax_error("Invalid increment operand") + JsNativeError::syntax().with_message("Invalid increment operand") })?; self.access_set(access, None, false)?; @@ -841,9 +840,9 @@ impl<'b> ByteCompiler<'b> { self.compile_expr(unary.target(), true)?; self.emit(Opcode::DecPost, &[]); + // TODO: promote to an early error. let access = Self::compile_access(unary.target()).ok_or_else(|| { - self.context - .construct_syntax_error("Invalid decrement operand") + JsNativeError::syntax().with_message("Invalid decrement operand") })?; self.access_set(access, None, false)?; @@ -1190,10 +1189,11 @@ impl<'b> ByteCompiler<'b> { self.emit(Opcode::CopyDataProperties, &[0, 0]); self.emit_opcode(Opcode::Pop); } + // TODO: Promote to early errors PropertyDefinition::CoverInitializedName(_, _) => { - return self.context.throw_syntax_error( - "invalid assignment pattern in object literal", - ); + return Err(JsNativeError::syntax() + .with_message("invalid assignment pattern in object literal") + .into()) } } } @@ -1933,9 +1933,10 @@ impl<'b> ByteCompiler<'b> { emit_for_of_in_exit += 1; } } + // TODO: promote to an early error. let address = address_info .ok_or_else(|| { - self.context.construct_syntax_error(format!( + JsNativeError::syntax().with_message(format!( "Cannot use the undeclared label '{}'", self.context.interner().resolve_expect(label_name) )) @@ -1953,9 +1954,9 @@ impl<'b> ByteCompiler<'b> { } else { items .next() + // TODO: promote to an early error. .ok_or_else(|| { - self.context - .construct_syntax_error("continue must be inside loop") + JsNativeError::syntax().with_message("continue must be inside loop") })? .start_address }; @@ -1998,19 +1999,22 @@ impl<'b> ByteCompiler<'b> { break; } } + // TODO: promote to an early error. if !found { - return self.context.throw_syntax_error(format!( - "Cannot use the undeclared label '{}'", - self.interner().resolve_expect(label_name) - )); + return Err(JsNativeError::syntax() + .with_message(format!( + "Cannot use the undeclared label '{}'", + self.interner().resolve_expect(label_name) + )) + .into()); } } else { self.jump_info .last_mut() + // TODO: promote to an early error. .ok_or_else(|| { - self.context.construct_syntax_error( - "unlabeled break must be inside loop or switch", - ) + JsNativeError::syntax() + .with_message("unlabeled break must be inside loop or switch") })? .breaks .push(label); diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index bd2dacdf107..d06c5b85306 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -2,14 +2,14 @@ //! //! Native classes are implemented through the [`Class`][class-trait] trait. //! ``` -//!# use boa_engine::{ -//!# property::Attribute, -//!# class::{Class, ClassBuilder}, -//!# Context, JsResult, JsValue, -//!# builtins::JsArgs, -//!# }; -//!# use boa_gc::{Finalize, Trace}; -//!# +//! # use boa_engine::{ +//! # property::Attribute, +//! # class::{Class, ClassBuilder}, +//! # Context, JsResult, JsValue, +//! # builtins::JsArgs, +//! # }; +//! # use boa_gc::{Finalize, Trace}; +//! # //! // This does not have to be an enum it can also be a struct. //! #[derive(Debug, Trace, Finalize)] //! enum Animal { @@ -63,6 +63,7 @@ use crate::{ builtins::function::NativeFunctionSignature, + error::JsNativeError, object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, @@ -104,29 +105,35 @@ impl ClassConstructor for T { Self: Sized, { if this.is_undefined() { - return context.throw_type_error(format!( - "cannot call constructor of native class `{}` without new", - T::NAME - )); + return Err(JsNativeError::typ() + .with_message(format!( + "cannot call constructor of native class `{}` without new", + T::NAME + )) + .into()); } let class_constructor = context.global_object().clone().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 - )); + return Err(JsNativeError::typ() + .with_message(format!( + "invalid constructor for native class `{}` ", + T::NAME + )) + .into()); }; let class_prototype = 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 `{}`", - T::NAME - )); + return Err(JsNativeError::typ() + .with_message(format!( + "invalid default prototype for native class `{}`", + T::NAME + )) + .into()); }; let prototype = this diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 81226e5a8a8..4c64ed42099 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -15,6 +15,7 @@ use crate::{ builtins::{self, function::NativeFunctionSignature}, bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, + error::JsNativeError, job::JobCallback, object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor, PropertyKey}, @@ -47,9 +48,9 @@ pub use icu::BoaProvider; /// /// ```rust /// use boa_engine::{ -/// Context, /// object::ObjectInitializer, -/// property::{Attribute, PropertyDescriptor} +/// property::{Attribute, PropertyDescriptor}, +/// Context, /// }; /// /// let script = r#" @@ -70,11 +71,7 @@ pub use icu::BoaProvider; /// let arg = ObjectInitializer::new(&mut context) /// .property("x", 12, Attribute::READONLY) /// .build(); -/// context.register_global_property( -/// "arg", -/// arg, -/// Attribute::all() -/// ); +/// context.register_global_property("arg", arg, Attribute::all()); /// /// let value = context.eval("test(arg)").unwrap(); /// @@ -210,7 +207,11 @@ impl Context { // 2. If IsCallable(F) is false, throw a TypeError exception. // 3. Return ? F.[[Call]](V, argumentsList). f.as_callable() - .ok_or_else(|| self.construct_type_error("Value is not callable")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("Value is not callable") + .into() + }) .and_then(|f| f.call(v, arguments_list, self)) } @@ -245,179 +246,6 @@ impl Context { .expect("Into used as message") } - /// Throws a `Error` with the specified message. - #[inline] - pub fn throw_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_error(message)) - } - - /// Constructs a `RangeError` with the specified message. - #[inline] - pub fn construct_range_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::RangeError::constructor( - &self - .intrinsics() - .constructors() - .range_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Throws a `RangeError` with the specified message. - #[inline] - pub fn throw_range_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_range_error(message)) - } - - /// Constructs a `TypeError` with the specified message. - #[inline] - pub fn construct_type_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::TypeError::constructor( - &self - .intrinsics() - .constructors() - .type_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Throws a `TypeError` with the specified message. - #[inline] - pub fn throw_type_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_type_error(message)) - } - - /// Constructs a `ReferenceError` with the specified message. - #[inline] - pub fn construct_reference_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::ReferenceError::constructor( - &self - .intrinsics() - .constructors() - .reference_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Throws a `ReferenceError` with the specified message. - #[inline] - pub fn throw_reference_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_reference_error(message)) - } - - /// Constructs a `SyntaxError` with the specified message. - #[inline] - pub fn construct_syntax_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::SyntaxError::constructor( - &self - .intrinsics() - .constructors() - .syntax_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Throws a `SyntaxError` with the specified message. - #[inline] - pub fn throw_syntax_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_syntax_error(message)) - } - - /// Constructs a `EvalError` with the specified message. - pub fn construct_eval_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::EvalError::constructor( - &self - .intrinsics() - .constructors() - .eval_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Constructs a `URIError` with the specified message. - pub fn construct_uri_error(&mut self, message: M) -> JsValue - where - M: Into, - { - crate::builtins::error::UriError::constructor( - &self - .intrinsics() - .constructors() - .uri_error() - .constructor() - .into(), - &[message.into().into()], - self, - ) - .expect("Into used as message") - } - - /// Throws a `EvalError` with the specified message. - pub fn throw_eval_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_eval_error(message)) - } - - /// Throws a `URIError` with the specified message. - pub fn throw_uri_error(&mut self, message: M) -> JsResult - where - M: Into, - { - Err(self.construct_uri_error(message)) - } - /// Register a global native function. /// /// This is more efficient that creating a closure function, since this does not allocate, @@ -590,36 +418,20 @@ impl Context { /// # Example /// ``` /// use boa_engine::{ - /// Context, + /// object::ObjectInitializer, /// property::{Attribute, PropertyDescriptor}, - /// object::ObjectInitializer + /// Context, /// }; /// /// let mut context = Context::default(); /// - /// context.register_global_property( - /// "myPrimitiveProperty", - /// 10, - /// Attribute::all() - /// ); + /// context.register_global_property("myPrimitiveProperty", 10, Attribute::all()); /// /// let object = ObjectInitializer::new(&mut context) - /// .property( - /// "x", - /// 0, - /// Attribute::all() - /// ) - /// .property( - /// "y", - /// 1, - /// Attribute::all() - /// ) - /// .build(); - /// context.register_global_property( - /// "myObjectProperty", - /// object, - /// Attribute::all() - /// ); + /// .property("x", 0, Attribute::all()) + /// .property("y", 1, Attribute::all()) + /// .build(); + /// context.register_global_property("myObjectProperty", object, Attribute::all()); /// ``` #[inline] pub fn register_global_property(&mut self, key: K, value: V, attribute: Attribute) @@ -642,7 +454,7 @@ impl Context { /// /// # Examples /// ``` - ///# use boa_engine::Context; + /// # use boa_engine::Context; /// let mut context = Context::default(); /// /// let value = context.eval("1 + 3").unwrap(); @@ -657,14 +469,7 @@ impl Context { { let main_timer = Profiler::global().start_event("Evaluation", "Main"); - let parsing_result = Parser::new(src.as_ref()) - .parse_all(self) - .map_err(|e| e.to_string()); - - let statement_list = match parsing_result { - Ok(statement_list) => statement_list, - Err(e) => return self.throw_syntax_error(e), - }; + let statement_list = Parser::new(src.as_ref()).parse_all(self)?; let code_block = self.compile(&statement_list)?; let result = self.execute(code_block); @@ -785,7 +590,6 @@ impl Context { /// Additionally, if the `intl` feature is enabled, [`ContextBuilder`] becomes /// the only way to create a new [`Context`], since now it requires a /// valid data provider for the `Intl` functionality. -/// #[cfg_attr( feature = "intl", doc = "The required data in a valid provider is specified in [`BoaProvider`]" diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 06acff567bd..e7d2be0e77a 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -1,6 +1,6 @@ use crate::{ - environments::CompileTimeEnvironment, object::JsObject, syntax::ast::expression::Identifier, - Context, JsResult, JsValue, + environments::CompileTimeEnvironment, error::JsNativeError, object::JsObject, + syntax::ast::expression::Identifier, Context, JsValue, }; use boa_gc::{Cell, Finalize, Gc, Trace}; @@ -156,17 +156,17 @@ impl FunctionSlots { /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding - pub(crate) fn get_this_binding(&self) -> Option<&JsValue> { + pub(crate) fn get_this_binding(&self) -> Result<&JsValue, JsNativeError> { // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical); // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. if self.this_binding_status == ThisBindingStatus::Uninitialized { - return None; + Err(JsNativeError::reference().with_message("Must call super constructor in derived class before accessing 'this' or returning from derived constructor")) + } else { + // 3. Return envRec.[[ThisValue]]. + Ok(&self.this) } - - // 3. Return envRec.[[ThisValue]]. - Some(&self.this) } } @@ -796,12 +796,15 @@ impl BindingLocator { /// Helper method to throws an error if the binding access is illegal. #[inline] - pub(crate) fn throw_mutate_immutable(&self, context: &mut Context) -> JsResult<()> { + pub(crate) fn throw_mutate_immutable( + &self, + context: &mut Context, + ) -> Result<(), JsNativeError> { if self.mutate_immutable { - context.throw_type_error(format!( + Err(JsNativeError::typ().with_message(format!( "cannot mutate an immutable binding '{}'", context.interner().resolve_expect(self.name.sym()) - )) + ))) } else { Ok(()) } diff --git a/boa_engine/src/error/mod.rs b/boa_engine/src/error/mod.rs new file mode 100644 index 00000000000..ab134bd6feb --- /dev/null +++ b/boa_engine/src/error/mod.rs @@ -0,0 +1,767 @@ +//! Error-related types and conversions. + +use crate::{ + builtins::{error::ErrorKind, Array}, + object::JsObject, + object::ObjectData, + property::PropertyDescriptor, + syntax::parser, + Context, JsString, JsValue, +}; +use boa_gc::{Finalize, Trace}; +use thiserror::Error; + +/// The error type returned by all operations related +/// to the execution of Javascript code. +/// +/// This is essentially an enum that can store either [`JsNativeError`]s (for ideal +/// native errors) or opaque [`JsValue`]s, since Javascript allows throwing any valid +/// `JsValue`. +/// +/// The implementation doesn't provide a [`From`] conversion +/// for `JsValue`. This is with the intent of encouraging the usage of proper +/// `JsNativeError`s instead of plain `JsValue`s. However, if you +/// do need a proper opaque error, you can construct one using the +/// [`JsError::from_opaque`] method. +/// +/// # Examples +/// +/// ```rust +/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue}; +/// let cause = JsError::from_opaque("error!".into()); +/// +/// assert!(cause.as_opaque().is_some()); +/// assert_eq!(cause.as_opaque().unwrap(), &JsValue::from("error!")); +/// +/// let native_error: JsError = JsNativeError::typ() +/// .with_message("invalid type!") +/// .with_cause(cause) +/// .into(); +/// +/// assert!(native_error.as_native().is_some()); +/// +/// let kind = &native_error.as_native().unwrap().kind; +/// assert!(matches!(kind, JsNativeErrorKind::Type)); +/// ``` +#[derive(Debug, Clone, Trace, Finalize)] +pub struct JsError { + inner: Repr, +} + +/// Internal representation of a [`JsError`]. +/// +/// `JsError` is represented by an opaque enum because it restricts +/// matching against `JsError` without calling `try_native` first. +/// This allows us to provide a safe API for `Error` objects that extracts +/// their info as a native `Rust` type ([`JsNativeError`]). +/// +/// This should never be used outside of this module. If that's not the case, +/// you should add methods to either `JsError` or `JsNativeError` to +/// represent that special use case. +#[derive(Debug, Clone, Trace, Finalize)] +enum Repr { + Native(JsNativeError), + Opaque(JsValue), +} + +/// The error type returned by the [`JsError::try_native`] method. +#[derive(Debug, Clone, Error)] +pub enum TryNativeError { + #[error("invalid type of property `{0}`")] + InvalidPropertyType(&'static str), + #[error("property `message` cannot contain unpaired surrogates")] + InvalidMessageEncoding, + #[error("could not access property `{property}`")] + InaccessibleProperty { + property: &'static str, + source: JsError, + }, + #[error("could not get element `{index}` of property `errors`")] + InvalidErrorsIndex { index: u64, source: JsError }, + #[error("opaque error of type `{:?}` is not an Error object", .0.get_type())] + NotAnErrorObject(JsValue), +} + +impl std::error::Error for JsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.inner { + Repr::Native(err) => err.source(), + Repr::Opaque(_) => None, + } + } +} + +impl JsError { + /// Creates a new `JsError` from a native error `err`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsError, JsNativeError}; + /// let error = JsError::from_native(JsNativeError::syntax()); + /// + /// assert!(error.as_native().is_some()); + /// ``` + pub fn from_native(err: JsNativeError) -> Self { + Self { + inner: Repr::Native(err), + } + } + + /// Creates a new `JsError` from an opaque error `value`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::JsError; + /// let error = JsError::from_opaque(5.0f64.into()); + /// + /// assert!(error.as_opaque().is_some()); + /// ``` + pub fn from_opaque(value: JsValue) -> Self { + Self { + inner: Repr::Opaque(value), + } + } + + /// Converts the error to an opaque `JsValue` error + /// + /// Unwraps the inner `JsValue` if the error is already an opaque error. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{Context, JsError, JsNativeError}; + /// let context = &mut Context::default(); + /// let error: JsError = JsNativeError::eval().with_message("invalid script").into(); + /// let error_val = error.to_opaque(context); + /// + /// assert!(error_val.as_object().unwrap().borrow().is_error()); + /// ``` + pub fn to_opaque(&self, context: &mut Context) -> JsValue { + match &self.inner { + Repr::Native(e) => e.to_opaque(context).into(), + Repr::Opaque(v) => v.clone(), + } + } + + /// Unwraps the inner error if this contains a native error. + /// Otherwise, inspects the opaque error and tries to extract the + /// necessary information to construct a native error similar to the provided + /// opaque error. If the conversion fails, returns a [`TryNativeError`] + /// with the cause of the failure. + /// + /// # Note 1 + /// + /// This method won't try to make any conversions between JS types. + /// In other words, for this conversion to succeed: + /// - `message` **MUST** be a `JsString` value. + /// - `errors` (in the case of `AggregateError`s) **MUST** be an `Array` object. + /// + /// # Note 2 + /// + /// This operation should be considered a lossy conversion, since it + /// won't store any additional properties of the opaque + /// error, other than `message`, `cause` and `errors` (in the case of + /// `AggregateError`s). If you cannot affort a lossy conversion, clone + /// the object before calling [`from_opaque`][JsError::from_opaque] + /// to preserve its original properties. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{Context, JsError, JsNativeError, JsNativeErrorKind}; + /// let context = &mut Context::default(); + /// + /// // create a new, opaque Error object + /// let error: JsError = JsNativeError::typ().with_message("type error!").into(); + /// let error_val = error.to_opaque(context); + /// + /// // then, try to recover the original + /// let error = JsError::from_opaque(error_val).try_native(context).unwrap(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Type)); + /// assert_eq!(error.message(), "type error!"); + /// ``` + pub fn try_native(&self, context: &mut Context) -> Result { + match &self.inner { + Repr::Native(e) => Ok(e.clone()), + Repr::Opaque(val) => { + let obj = val + .as_object() + .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; + let error = obj + .borrow() + .as_error() + .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; + + let try_get_property = |key, context: &mut Context| { + obj.has_property(key, context) + .map_err(|e| TryNativeError::InaccessibleProperty { + property: key, + source: e, + })? + .then(|| obj.get(key, context)) + .transpose() + .map_err(|e| TryNativeError::InaccessibleProperty { + property: key, + source: e, + }) + }; + + let message = if let Some(msg) = try_get_property("message", context)? { + msg.as_string() + .map(JsString::to_std_string) + .transpose() + .map_err(|_| TryNativeError::InvalidMessageEncoding)? + .ok_or(TryNativeError::InvalidPropertyType("message"))? + .into() + } else { + Box::default() + }; + + let cause = try_get_property("cause", context)?; + + let kind = match error { + ErrorKind::Error => JsNativeErrorKind::Error, + ErrorKind::Eval => JsNativeErrorKind::Eval, + ErrorKind::Type => JsNativeErrorKind::Type, + ErrorKind::Range => JsNativeErrorKind::Range, + ErrorKind::Reference => JsNativeErrorKind::Reference, + ErrorKind::Syntax => JsNativeErrorKind::Syntax, + ErrorKind::Uri => JsNativeErrorKind::Uri, + ErrorKind::Aggregate => { + let errors = obj.get("errors", context).map_err(|e| { + TryNativeError::InaccessibleProperty { + property: "errors", + source: e, + } + })?; + let mut error_list = Vec::new(); + match errors.as_object() { + Some(errors) if errors.is_array() => { + let length = errors.length_of_array_like(context).map_err(|e| { + TryNativeError::InaccessibleProperty { + property: "errors.length", + source: e, + } + })?; + for i in 0..length { + error_list.push(JsError::from_opaque( + errors.get(i, context).map_err(|e| { + TryNativeError::InvalidErrorsIndex { + index: i, + source: e, + } + })?, + )); + } + } + _ => return Err(TryNativeError::InvalidPropertyType("errors")), + } + + JsNativeErrorKind::Aggregate(error_list) + } + }; + + Ok(JsNativeError { + kind, + message, + cause: cause.map(|v| Box::new(JsError::from_opaque(v))), + }) + } + } + } + + /// Gets the inner [`JsValue`] if the error is an opaque error, + /// or `None` otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsError, JsNativeError}; + /// let error: JsError = JsNativeError::reference() + /// .with_message("variable not found!") + /// .into(); + /// + /// assert!(error.as_opaque().is_none()); + /// + /// let error = JsError::from_opaque(256u32.into()); + /// + /// assert!(error.as_opaque().is_some()); + /// ``` + pub fn as_opaque(&self) -> Option<&JsValue> { + match self.inner { + Repr::Native(_) => None, + Repr::Opaque(ref v) => Some(v), + } + } + + /// Gets the inner [`JsNativeError`] if the error is a native + /// error, or `None` otherwise. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsError, JsNativeError, JsValue}; + /// let error: JsError = JsNativeError::error().with_message("Unknown error").into(); + /// + /// assert!(error.as_native().is_some()); + /// + /// let error = JsError::from_opaque(JsValue::undefined().into()); + /// + /// assert!(error.as_native().is_none()); + /// ``` + pub fn as_native(&self) -> Option<&JsNativeError> { + match self.inner { + Repr::Native(ref e) => Some(e), + Repr::Opaque(_) => None, + } + } +} + +impl From for JsError { + fn from(err: parser::ParseError) -> Self { + Self::from(JsNativeError::from(err)) + } +} + +impl From for JsError { + fn from(error: JsNativeError) -> Self { + Self { + inner: Repr::Native(error), + } + } +} + +impl std::fmt::Display for JsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner { + Repr::Native(e) => e.fmt(f), + Repr::Opaque(v) => v.display().fmt(f), + } + } +} + +/// Native representation of an ideal `Error` object from Javascript. +/// +/// This representation is more space efficient than its [`JsObject`] equivalent, +/// since it doesn't need to create a whole new `JsObject` to be instantiated. +/// Prefer using this over [`JsError`] when you don't need to throw +/// plain [`JsValue`]s as errors, or when you need to inspect the error type +/// of a `JsError`. +/// +/// # Examples +/// +/// ```rust +/// # use boa_engine::{JsNativeError, JsNativeErrorKind}; +/// let native_error = JsNativeError::uri().with_message("cannot decode uri"); +/// +/// match native_error.kind { +/// JsNativeErrorKind::Uri => { /* handle URI error*/ } +/// _ => unreachable!(), +/// } +/// +/// assert_eq!(native_error.message(), "cannot decode uri"); +/// ``` +#[derive(Debug, Clone, Trace, Finalize, Error)] +#[error("{kind}: {message}")] +pub struct JsNativeError { + /// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.) + pub kind: JsNativeErrorKind, + message: Box, + #[source] + cause: Option>, +} + +impl JsNativeError { + /// Creates a new `JsNativeError` from its `kind`, `message` and (optionally) its `cause`. + fn new(kind: JsNativeErrorKind, message: Box, cause: Option>) -> Self { + Self { + kind, + message, + cause, + } + } + + /// Creates a new `JsNativeError` of kind `AggregateError` from a list of [`JsError`]s, with + /// empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let inner_errors = vec![ + /// JsNativeError::typ().into(), + /// JsNativeError::syntax().into() + /// ]; + /// let error = JsNativeError::aggregate(inner_errors); + /// + /// assert!(matches!( + /// error.kind, + /// JsNativeErrorKind::Aggregate(ref errors) if errors.len() == 2 + /// )); + /// ``` + pub fn aggregate(errors: Vec) -> Self { + Self::new(JsNativeErrorKind::Aggregate(errors), Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `Error`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::error(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Error)); + /// ``` + pub fn error() -> Self { + Self::new(JsNativeErrorKind::Error, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `EvalError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::eval(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Eval)); + /// ``` + pub fn eval() -> Self { + Self::new(JsNativeErrorKind::Eval, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `RangeError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::range(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Range)); + /// ``` + pub fn range() -> Self { + Self::new(JsNativeErrorKind::Range, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `ReferenceError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::reference(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Reference)); + /// ``` + pub fn reference() -> Self { + Self::new(JsNativeErrorKind::Reference, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `SyntaxError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::syntax(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Syntax)); + /// ``` + pub fn syntax() -> Self { + Self::new(JsNativeErrorKind::Syntax, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `TypeError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::typ(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Type)); + /// ``` + pub fn typ() -> Self { + Self::new(JsNativeErrorKind::Type, Box::default(), None) + } + + /// Creates a new `JsNativeError` of kind `UriError`, with empty `message` and undefined `cause`. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{JsNativeError, JsNativeErrorKind}; + /// let error = JsNativeError::uri(); + /// + /// assert!(matches!(error.kind, JsNativeErrorKind::Uri)); + /// ``` + pub fn uri() -> Self { + Self::new(JsNativeErrorKind::Uri, Box::default(), None) + } + + /// Appends a message to this error. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::JsNativeError; + /// let error = JsNativeError::range().with_message("number too large"); + /// + /// assert_eq!(error.message(), "number too large"); + /// ``` + #[must_use] + pub fn with_message(mut self, message: S) -> Self + where + S: Into>, + { + self.message = message.into(); + self + } + + /// Appends a cause to this error. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::JsNativeError; + /// let cause = JsNativeError::syntax(); + /// let error = JsNativeError::error().with_cause(cause); + /// + /// assert!(error.cause().unwrap().as_native().is_some()); + /// ``` + #[must_use] + pub fn with_cause(mut self, cause: V) -> Self + where + V: Into, + { + self.cause = Some(Box::new(cause.into())); + self + } + + /// Gets the `message` of this error. + /// + /// This is equivalent to the [`NativeError.prototype.message`][spec] + /// property. + /// + /// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::JsNativeError; + /// let error = JsNativeError::range().with_message("number too large"); + /// + /// assert_eq!(error.message(), "number too large"); + /// ``` + pub fn message(&self) -> &str { + &self.message + } + + /// Gets the `cause` of this error. + /// + /// This is equivalent to the [`NativeError.prototype.cause`][spec] + /// property. + /// + /// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::JsNativeError; + /// let cause = JsNativeError::syntax(); + /// let error = JsNativeError::error().with_cause(cause); + /// + /// assert!(error.cause().unwrap().as_native().is_some()); + /// ``` + pub fn cause(&self) -> Option<&JsError> { + self.cause.as_deref() + } + + /// Converts this native error to its opaque representation as a [`JsObject`]. + /// + /// # Examples + /// + /// ```rust + /// # use boa_engine::{Context, JsError, JsNativeError}; + /// let context = &mut Context::default(); + /// + /// let error = JsNativeError::error().with_message("error!"); + /// let error_obj = error.to_opaque(context); + /// + /// assert!(error_obj.borrow().is_error()); + /// assert_eq!(error_obj.get("message", context).unwrap(), "error!".into()) + /// ``` + pub fn to_opaque(&self, context: &mut Context) -> JsObject { + let Self { + kind, + message, + cause, + } = self; + let constructors = context.intrinsics().constructors(); + let (prototype, tag) = match kind { + JsNativeErrorKind::Aggregate(_) => ( + constructors.aggregate_error().prototype(), + ErrorKind::Aggregate, + ), + JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorKind::Error), + JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorKind::Eval), + JsNativeErrorKind::Range => (constructors.range_error().prototype(), ErrorKind::Range), + JsNativeErrorKind::Reference => ( + constructors.reference_error().prototype(), + ErrorKind::Reference, + ), + JsNativeErrorKind::Syntax => { + (constructors.syntax_error().prototype(), ErrorKind::Syntax) + } + JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type), + JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri), + }; + + let o = JsObject::from_proto_and_data(prototype, ObjectData::error(tag)); + + o.create_non_enumerable_data_property_or_throw("message", &**message, context); + + if let Some(cause) = cause { + o.create_non_enumerable_data_property_or_throw( + "cause", + cause.to_opaque(context), + context, + ); + } + + if let JsNativeErrorKind::Aggregate(errors) = kind { + let errors = errors + .iter() + .map(|e| e.to_opaque(context)) + .collect::>(); + let errors = Array::create_array_from_list(errors, context); + o.define_property_or_throw( + "errors", + PropertyDescriptor::builder() + .configurable(true) + .enumerable(false) + .writable(true) + .value(errors), + context, + ) + .expect("The spec guarantees this succeeds for a newly created object "); + } + o + } +} + +impl From for JsNativeError { + fn from(err: parser::ParseError) -> Self { + Self::syntax().with_message(err.to_string()) + } +} + +/// The list of possible error types a [`JsNativeError`] can be. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-error-objects +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error +#[derive(Debug, Clone, Trace, Finalize)] +#[non_exhaustive] +pub enum JsNativeErrorKind { + /// A collection of errors wrapped in a single error. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError + Aggregate(Vec), + /// A generic error. Commonly used as the base for custom exceptions. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-error-constructor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error + Error, + /// An error related to the global function [`eval()`][eval]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError + /// [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval + Eval, + /// An error thrown when a value is outside its valid range. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError + Range, + /// An error representing an invalid de-reference of a variable. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError + Reference, + /// An error representing an invalid syntax in the Javascript language. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError + Syntax, + /// An error thrown when a variable or argument is not of a valid type. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError + Type, + /// An error thrown when the [`encodeURI()`][e_uri] and [`decodeURI()`][d_uri] functions receive + /// invalid parameters. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError + /// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI + /// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI + Uri, +} + +impl std::fmt::Display for JsNativeErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + JsNativeErrorKind::Aggregate(_) => "AggregateError", + JsNativeErrorKind::Error => "Error", + JsNativeErrorKind::Eval => "EvalError", + JsNativeErrorKind::Range => "RangeError", + JsNativeErrorKind::Reference => "ReferenceError", + JsNativeErrorKind::Syntax => "SyntaxError", + JsNativeErrorKind::Type => "TypeError", + JsNativeErrorKind::Uri => "UriError", + } + .fmt(f) + } +} diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 7a0f0e030cd..684047661e5 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -83,6 +83,7 @@ pub mod bytecompiler; pub mod class; pub mod context; pub mod environments; +pub mod error; pub mod job; pub mod object; pub mod property; @@ -98,7 +99,11 @@ mod tests; /// A convenience module that re-exports the most commonly-used Boa APIs pub mod prelude { - pub use crate::{object::JsObject, Context, JsBigInt, JsResult, JsString, JsValue}; + pub use crate::{ + error::{JsError, JsNativeError, JsNativeErrorKind}, + object::JsObject, + Context, JsBigInt, JsResult, JsString, JsValue, + }; } use std::result::Result as StdResult; @@ -106,11 +111,16 @@ use std::result::Result as StdResult; // Export things to root level #[doc(inline)] pub use crate::{ - bigint::JsBigInt, context::Context, string::JsString, symbol::JsSymbol, value::JsValue, + bigint::JsBigInt, + context::Context, + error::{JsError, JsNativeError, JsNativeErrorKind}, + string::JsString, + symbol::JsSymbol, + value::JsValue, }; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) -pub type JsResult = StdResult; +pub type JsResult = StdResult; /// Execute the code using an existing `Context`. /// @@ -120,10 +130,9 @@ pub(crate) fn forward(context: &mut Context, src: S) -> String where S: AsRef<[u8]>, { - context.eval(src.as_ref()).map_or_else( - |e| format!("Uncaught {}", e.display()), - |v| v.display().to_string(), - ) + context + .eval(src.as_ref()) + .map_or_else(|e| format!("Uncaught {}", e), |v| v.display().to_string()) } /// Execute the code using an existing Context. @@ -154,7 +163,7 @@ pub(crate) fn exec>(src: T) -> String { match Context::default().eval(src_bytes) { Ok(value) => value.display().to_string(), - Err(error) => error.display().to_string(), + Err(error) => error.to_string(), } } diff --git a/boa_engine/src/object/builtins/jsarray.rs b/boa_engine/src/object/builtins/jsarray.rs index ddb7fcedd55..90770738818 100644 --- a/boa_engine/src/object/builtins/jsarray.rs +++ b/boa_engine/src/object/builtins/jsarray.rs @@ -1,5 +1,6 @@ use crate::{ builtins::Array, + error::JsNativeError, object::{JsFunction, JsObject, JsObjectType}, value::IntoOrUndefined, Context, JsResult, JsString, JsValue, @@ -38,11 +39,13 @@ impl JsArray { /// /// This does not clone the fields of the array, it only does a shallow clone of the object. #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_array() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not an Array") + Err(JsNativeError::typ() + .with_message("object is not an Array") + .into()) } } @@ -112,7 +115,7 @@ impl JsArray { .cloned() .expect("Array.prototype.filter should always return object"); - Self::from_object(object, context) + Self::from_object(object) } #[inline] @@ -234,7 +237,7 @@ impl JsArray { .cloned() .expect("Array.prototype.filter should always return object"); - Self::from_object(object, context) + Self::from_object(object) } #[inline] @@ -253,7 +256,7 @@ impl JsArray { .cloned() .expect("Array.prototype.map should always return object"); - Self::from_object(object, context) + Self::from_object(object) } #[inline] @@ -319,7 +322,7 @@ impl JsArray { .cloned() .expect("Array.prototype.slice should always return object"); - Self::from_object(object, context) + Self::from_object(object) } #[inline] diff --git a/boa_engine/src/object/builtins/jsarraybuffer.rs b/boa_engine/src/object/builtins/jsarraybuffer.rs index 95d6514af06..f09c2c71ff3 100644 --- a/boa_engine/src/object/builtins/jsarraybuffer.rs +++ b/boa_engine/src/object/builtins/jsarraybuffer.rs @@ -1,6 +1,7 @@ use crate::{ builtins::array_buffer::ArrayBuffer, context::intrinsics::StandardConstructors, + error::JsNativeError, object::{ internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, ObjectData, }, @@ -79,11 +80,13 @@ impl JsArrayBuffer { /// /// This does not clone the fields of the array buffer, it only does a shallow clone of the object. #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_array_buffer() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not an ArrayBuffer") + Err(JsNativeError::typ() + .with_message("object is not an ArrayBuffer") + .into()) } } diff --git a/boa_engine/src/object/builtins/jsdataview.rs b/boa_engine/src/object/builtins/jsdataview.rs index 2f15fa3db82..3990c812265 100644 --- a/boa_engine/src/object/builtins/jsdataview.rs +++ b/boa_engine/src/object/builtins/jsdataview.rs @@ -6,7 +6,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType, ObjectData, }, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -43,22 +43,25 @@ impl JsDataView { ) -> JsResult { let (byte_offset, byte_length) = { let borrowed_buffer = array_buffer.borrow(); - let buffer = borrowed_buffer - .as_array_buffer() - .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; + let buffer = borrowed_buffer.as_array_buffer().ok_or_else(|| { + JsNativeError::typ().with_message("buffer must be an ArrayBuffer") + })?; let provided_offset = offset.unwrap_or(0_u64); // Check if buffer is detached. if buffer.is_detached_buffer() { - return context.throw_type_error("ArrayBuffer is detached"); + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); }; let array_buffer_length = buffer.array_buffer_byte_length(); if provided_offset > array_buffer_length { - return context - .throw_range_error("Provided offset is outside the bounds of the buffer"); + return Err(JsNativeError::range() + .with_message("Provided offset is outside the bounds of the buffer") + .into()); } let view_byte_length = if let Some(..) = byte_length { @@ -67,7 +70,9 @@ impl JsDataView { // Check that the provided length and offset does not exceed the bounds of the ArrayBuffer if provided_offset + provided_length > array_buffer_length { - return context.throw_range_error("Invalid data view length"); + return Err(JsNativeError::range() + .with_message("Invalid data view length") + .into()); } provided_length @@ -101,11 +106,13 @@ impl JsDataView { } #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_data_view() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not a DataView") + Err(JsNativeError::typ() + .with_message("object is not a DataView") + .into()) } } diff --git a/boa_engine/src/object/builtins/jsmap.rs b/boa_engine/src/object/builtins/jsmap.rs index a689c87659e..4a3b6867be7 100644 --- a/boa_engine/src/object/builtins/jsmap.rs +++ b/boa_engine/src/object/builtins/jsmap.rs @@ -2,6 +2,7 @@ use crate::{ builtins::map::{add_entries_from_iterable, ordered_map::OrderedMap}, builtins::Map, + error::JsNativeError, object::{JsFunction, JsMapIterator, JsObject, JsObjectType, ObjectData}, Context, JsResult, JsValue, }; @@ -31,7 +32,6 @@ use std::ops::Deref; /// map.set("Key-2", 10, context).unwrap(); /// /// assert_eq!(map.get_size(context).unwrap(), 2.into()); -/// /// ``` /// /// Create a `JsMap` from a `JsArray` @@ -51,15 +51,18 @@ use std::ops::Deref; /// let vec_one: Vec = vec![JsValue::new("first-key"), JsValue::new("first-value")]; /// /// // We create an push our `[key, value]` pair onto our array as a `JsArray` -/// js_array.push(JsArray::from_iter(vec_one, context), context).unwrap(); +/// js_array +/// .push(JsArray::from_iter(vec_one, context), context) +/// .unwrap(); /// /// // Create a `JsMap` from the `JsArray` using it's iterable property. /// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context).unwrap(); /// -/// assert_eq!(js_iterable_map.get("first-key", context).unwrap(), "first-value".into()); -/// +/// assert_eq!( +/// js_iterable_map.get("first-key", context).unwrap(), +/// "first-value".into() +/// ); /// ``` -/// #[derive(Debug, Clone, Trace, Finalize)] pub struct JsMap { inner: JsObject, @@ -81,7 +84,6 @@ impl JsMap { /// /// // Create a new empty `JsMap`. /// let map = JsMap::new(context); - /// /// ``` #[inline] pub fn new(context: &mut Context) -> Self { @@ -106,13 +108,13 @@ impl JsMap { /// /// // Create a `[key, value]` pair of JsValues and add it to the `JsArray` as a `JsArray` /// let vec_one: Vec = vec![JsValue::new("first-key"), JsValue::new("first-value")]; - /// js_array.push(JsArray::from_iter(vec_one, context), context).unwrap(); + /// js_array + /// .push(JsArray::from_iter(vec_one, context), context) + /// .unwrap(); /// /// // Create a `JsMap` from the `JsArray` using it's iterable property. /// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context).unwrap(); - /// /// ``` - /// #[inline] pub fn from_js_iterable(iterable: &JsValue, context: &mut Context) -> JsResult { // Create a new map object. @@ -145,12 +147,11 @@ impl JsMap { /// // `some_object` can be any JavaScript `Map` object. /// let some_object = JsObject::from_proto_and_data( /// context.intrinsics().constructors().map().prototype(), - /// ObjectData::map(OrderedMap::new()) + /// ObjectData::map(OrderedMap::new()), /// ); /// /// // Create `JsMap` object with incoming object. - /// let js_map = JsMap::from_object(some_object, context).unwrap(); - /// + /// let js_map = JsMap::from_object(some_object).unwrap(); /// ``` /// /// Invalid Example - returns a `TypeError` with the message "object is not a Map" @@ -164,16 +165,17 @@ impl JsMap { /// /// let some_object = JsArray::new(context); /// - /// // Some object is an Array object, not a map object - /// assert!(JsMap::from_object(some_object.into(), context).is_err()); - /// + /// // `some_object` is an Array object, not a map object + /// assert!(JsMap::from_object(some_object.into()).is_err()); /// ``` #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_map() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not a Map") + Err(JsNativeError::typ() + .with_message("object is not a Map") + .into()) } } @@ -192,7 +194,7 @@ impl JsMap { let iterator_record = Map::entries(&self.inner.clone().into(), &[], context)? .get_iterator(context, None, None)?; let map_iterator_object = iterator_record.iterator(); - JsMapIterator::from_object(map_iterator_object.clone(), context) + JsMapIterator::from_object(map_iterator_object.clone()) } /// Returns a new [`JsMapIterator`] object that yields the `key` for each element within the [`JsMap`] in insertion order. @@ -201,7 +203,7 @@ impl JsMap { let iterator_record = Map::keys(&self.inner.clone().into(), &[], context)? .get_iterator(context, None, None)?; let map_iterator_object = iterator_record.iterator(); - JsMapIterator::from_object(map_iterator_object.clone(), context) + JsMapIterator::from_object(map_iterator_object.clone()) } /// Inserts a new entry into the [`JsMap`] object @@ -223,7 +225,6 @@ impl JsMap { /// /// assert_eq!(js_map.get("foo", context).unwrap(), "bar".into()); /// assert_eq!(js_map.get(2, context).unwrap(), 4.into()) - /// /// ``` #[inline] pub fn set(&self, key: K, value: V, context: &mut Context) -> JsResult @@ -257,7 +258,6 @@ impl JsMap { /// let map_size = js_map.get_size(context).unwrap(); /// /// assert_eq!(map_size, 1.into()); - /// /// ``` #[inline] pub fn get_size(&self, context: &mut Context) -> JsResult { @@ -284,7 +284,6 @@ impl JsMap { /// /// assert_eq!(js_map.get_size(context).unwrap(), 1.into()); /// assert_eq!(js_map.get("foo", context).unwrap(), JsValue::undefined()); - /// /// ``` #[inline] pub fn delete(&self, key: T, context: &mut Context) -> JsResult @@ -311,7 +310,6 @@ impl JsMap { /// let retrieved_value = js_map.get("foo", context).unwrap(); /// /// assert_eq!(retrieved_value, "bar".into()); - /// /// ``` #[inline] pub fn get(&self, key: T, context: &mut Context) -> JsResult @@ -340,7 +338,6 @@ impl JsMap { /// js_map.clear(context).unwrap(); /// /// assert_eq!(js_map.get_size(context).unwrap(), 0.into()); - /// /// ``` #[inline] pub fn clear(&self, context: &mut Context) -> JsResult { @@ -365,7 +362,6 @@ impl JsMap { /// let has_key = js_map.has("foo", context).unwrap(); /// /// assert_eq!(has_key, true.into()); - /// /// ``` #[inline] pub fn has(&self, key: T, context: &mut Context) -> JsResult @@ -396,7 +392,7 @@ impl JsMap { let iterator_record = Map::values(&self.inner.clone().into(), &[], context)? .get_iterator(context, None, None)?; let map_iterator_object = iterator_record.iterator(); - JsMapIterator::from_object(map_iterator_object.clone(), context) + JsMapIterator::from_object(map_iterator_object.clone()) } } diff --git a/boa_engine/src/object/builtins/jsmap_iterator.rs b/boa_engine/src/object/builtins/jsmap_iterator.rs index ae19562cc2d..dffa23c1d3b 100644 --- a/boa_engine/src/object/builtins/jsmap_iterator.rs +++ b/boa_engine/src/object/builtins/jsmap_iterator.rs @@ -1,6 +1,7 @@ //! This module implements a wrapper for the `MapIterator` object use crate::{ builtins::map::map_iterator::MapIterator, + error::JsNativeError, object::{JsObject, JsObjectType}, Context, JsResult, JsValue, }; @@ -17,11 +18,13 @@ pub struct JsMapIterator { impl JsMapIterator { /// Create a [`JsMapIterator`] from a [`JsObject`]. If object is not a `MapIterator`, throw `TypeError` #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_map_iterator() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not a MapIterator") + Err(JsNativeError::typ() + .with_message("object is not a MapIterator") + .into()) } } diff --git a/boa_engine/src/object/builtins/jsregexp.rs b/boa_engine/src/object/builtins/jsregexp.rs index a76ddb75620..ff71f12880d 100644 --- a/boa_engine/src/object/builtins/jsregexp.rs +++ b/boa_engine/src/object/builtins/jsregexp.rs @@ -2,7 +2,7 @@ use crate::{ builtins::RegExp, object::{JsArray, JsObject, JsObjectType}, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -75,11 +75,13 @@ impl JsRegExp { /// Create a `JsRegExp` from a regular expression `JsObject` #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_regexp() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not a RegExp") + Err(JsNativeError::typ() + .with_message("object is not a RegExp") + .into()) } } @@ -210,11 +212,8 @@ impl JsRegExp { None } else { Some( - JsArray::from_object( - v.to_object(context).expect("v must be an array"), - context, - ) - .expect("from_object must not fail if v is an array object"), + JsArray::from_object(v.to_object(context).expect("v must be an array")) + .expect("from_object must not fail if v is an array object"), ) } }) diff --git a/boa_engine/src/object/builtins/jsset.rs b/boa_engine/src/object/builtins/jsset.rs index ce2e35b17cf..b2a78ed1ff1 100644 --- a/boa_engine/src/object/builtins/jsset.rs +++ b/boa_engine/src/object/builtins/jsset.rs @@ -4,6 +4,7 @@ use boa_gc::{Finalize, Trace}; use crate::{ builtins::Set, + error::JsNativeError, object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, Context, JsResult, JsValue, }; @@ -30,8 +31,8 @@ impl JsSet { /// /// Same as JavaScript's `set.size`. #[inline] - pub fn size(&self, context: &mut Context) -> JsResult { - Set::get_size(&self.inner.clone().into(), context) + pub fn size(&self) -> JsResult { + Set::get_size(&self.inner.clone().into()) } /// Appends value to the Set object. @@ -74,9 +75,10 @@ impl JsSet { where T: Into, { + // TODO: Make `delete` return a native `bool` match Set::delete(&self.inner.clone().into(), &[value.into()], context)? { JsValue::Boolean(bool) => Ok(bool), - _ => Err(JsValue::Undefined), + _ => unreachable!("`delete` must always return a bool"), } } @@ -89,9 +91,10 @@ impl JsSet { where T: Into, { + // TODO: Make `has` return a native `bool` match Set::has(&self.inner.clone().into(), &[value.into()], context)? { JsValue::Boolean(bool) => Ok(bool), - _ => Err(JsValue::Undefined), + _ => unreachable!("`has` must always return a bool"), } } @@ -104,7 +107,7 @@ impl JsSet { let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? .get_iterator(context, None, None)?; - JsSetIterator::from_object(iterator_object.iterator().clone(), context) + JsSetIterator::from_object(iterator_object.iterator().clone()) } /// Alias for `Set.prototype.values()` @@ -117,7 +120,7 @@ impl JsSet { let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? .get_iterator(context, None, None)?; - JsSetIterator::from_object(iterator_object.iterator().clone(), context) + JsSetIterator::from_object(iterator_object.iterator().clone()) } /// Calls callbackFn once for each value present in the Set object, @@ -141,11 +144,13 @@ impl JsSet { /// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`. #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_set() { Ok(Self { inner: object }) } else { - context.throw_error("Object is not a Set") + Err(JsNativeError::typ() + .with_message("Object is not a Set") + .into()) } } diff --git a/boa_engine/src/object/builtins/jsset_iterator.rs b/boa_engine/src/object/builtins/jsset_iterator.rs index 64f06ad0f84..8d893f54991 100644 --- a/boa_engine/src/object/builtins/jsset_iterator.rs +++ b/boa_engine/src/object/builtins/jsset_iterator.rs @@ -4,6 +4,7 @@ use boa_gc::{Finalize, Trace}; use crate::{ builtins::SetIterator, + error::JsNativeError, object::{JsObject, JsObjectType}, Context, JsResult, JsValue, }; @@ -17,11 +18,13 @@ pub struct JsSetIterator { impl JsSetIterator { /// Create a `JsSetIterator` from a `JsObject`. /// If object is not a `SetIterator`, throw `TypeError`. - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_set_iterator() { Ok(Self { inner: object }) } else { - context.throw_type_error("object is not a SetIterator") + Err(JsNativeError::typ() + .with_message("object is not a SetIterator") + .into()) } } /// Advances the `JsSetIterator` and gets the next result in the `JsSet`. diff --git a/boa_engine/src/object/builtins/jstypedarray.rs b/boa_engine/src/object/builtins/jstypedarray.rs index 6fb5bc60bfd..fede5a8b1f7 100644 --- a/boa_engine/src/object/builtins/jstypedarray.rs +++ b/boa_engine/src/object/builtins/jstypedarray.rs @@ -1,5 +1,6 @@ use crate::{ builtins::typed_array::TypedArray, + error::JsNativeError, object::{JsArrayBuffer, JsFunction, JsObject, JsObjectType}, value::IntoOrUndefined, Context, JsResult, JsString, JsValue, @@ -18,13 +19,15 @@ impl JsTypedArray { /// /// This does not clone the fields of the typed array, it only does a shallow clone of the object. #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { + pub fn from_object(object: JsObject) -> JsResult { if object.borrow().is_typed_array() { Ok(Self { inner: object.into(), }) } else { - context.throw_type_error("object is not a TypedArray") + Err(JsNativeError::typ() + .with_message("object is not a TypedArray") + .into()) } } diff --git a/boa_engine/src/object/internal_methods/array.rs b/boa_engine/src/object/internal_methods/array.rs index 976bf0cdcec..fcbd807ab83 100644 --- a/boa_engine/src/object/internal_methods/array.rs +++ b/boa_engine/src/object/internal_methods/array.rs @@ -1,4 +1,5 @@ use crate::{ + error::JsNativeError, object::JsObject, property::{PropertyDescriptor, PropertyKey}, string::utf16, @@ -127,7 +128,9 @@ fn array_set_length( // 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception. #[allow(clippy::float_cmp)] if f64::from(new_len) != number_len { - return context.throw_range_error("bad length for array"); + return Err(JsNativeError::range() + .with_message("bad length for array") + .into()); } // 2. Let newLenDesc be a copy of Desc. diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index c70175923dc..2fbacd8e9c0 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -1,5 +1,6 @@ use crate::{ builtins::{array, object::Object}, + error::JsNativeError, object::{InternalObjectMethods, JsObject, JsPrototype}, property::{PropertyDescriptor, PropertyKey}, value::Type, @@ -61,7 +62,7 @@ pub(crate) fn proxy_exotic_get_prototype_of( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). let trap = if let Some(trap) = handler.get_method("getPrototypeOf", context)? { @@ -79,7 +80,11 @@ pub(crate) fn proxy_exotic_get_prototype_of( let handler_proto = match &handler_proto { JsValue::Object(obj) => Some(obj.clone()), JsValue::Null => None, - _ => return context.throw_type_error("Proxy trap result is neither object nor null"), + _ => { + return Err(JsNativeError::typ() + .with_message("Proxy trap result is neither object nor null") + .into()) + } }; // 9. Let extensibleTarget be ? IsExtensible(target). @@ -93,7 +98,9 @@ pub(crate) fn proxy_exotic_get_prototype_of( // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception. if handler_proto != target_proto { - return context.throw_type_error("Proxy trap returned unexpected prototype"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected prototype") + .into()); } // 13. Return handlerProto. @@ -120,7 +127,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "setPrototypeOf"). let trap = if let Some(trap) = handler.get_method("setPrototypeOf", context)? { @@ -158,7 +165,9 @@ pub(crate) fn proxy_exotic_set_prototype_of( // 12. If SameValue(V, targetProto) is false, throw a TypeError exception. if val != target_proto { - return context.throw_type_error("Proxy trap failed to set prototype"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set prototype") + .into()); } // 13. Return true. @@ -181,7 +190,7 @@ pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "isExtensible"). let trap = if let Some(trap) = handler.get_method("isExtensible", context)? { @@ -202,7 +211,9 @@ pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception. if boolean_trap_result != target_result { - return context.throw_type_error("Proxy trap returned unexpected extensible value"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected extensible value") + .into()); } // 10. Return booleanTrapResult. @@ -228,7 +239,7 @@ pub(crate) fn proxy_exotic_prevent_extensions( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "preventExtensions"). let trap = if let Some(trap) = handler.get_method("preventExtensions", context)? { @@ -248,7 +259,9 @@ pub(crate) fn proxy_exotic_prevent_extensions( if boolean_trap_result && target.is_extensible(context)? { // a. Let extensibleTarget be ? IsExtensible(target). // b. If extensibleTarget is true, throw a TypeError exception. - return context.throw_type_error("Proxy trap failed to set extensible"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set extensible") + .into()); } // 9. Return booleanTrapResult. @@ -275,7 +288,7 @@ pub(crate) fn proxy_exotic_get_own_property( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). let trap = if let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? { @@ -295,7 +308,9 @@ pub(crate) fn proxy_exotic_get_own_property( // 8. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() { - return context.throw_type_error("Proxy trap result is neither object nor undefined"); + return Err(JsNativeError::typ() + .with_message("Proxy trap result is neither object nor undefined") + .into()); } // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). @@ -306,17 +321,19 @@ pub(crate) fn proxy_exotic_get_own_property( if let Some(desc) = target_desc { // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if !desc.expect_configurable() { - return context.throw_type_error( - "Proxy trap result is undefined adn target result is not configurable", - ); + return Err(JsNativeError::typ() + .with_message( + "Proxy trap result is undefined adn target result is not configurable", + ) + .into()); } // c. Let extensibleTarget be ? IsExtensible(target). // d. If extensibleTarget is false, throw a TypeError exception. if !target.is_extensible(context)? { - return context.throw_type_error( - "Proxy trap result is undefined and target is not extensible", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap result is undefined and target is not extensible") + .into()); } // e. Return undefined. return Ok(None); @@ -342,7 +359,9 @@ pub(crate) fn proxy_exotic_get_own_property( result_desc.clone(), target_desc.clone(), ) { - return context.throw_type_error("Proxy trap returned unexpected property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); } // 16. If resultDesc.[[Configurable]] is false, then @@ -355,16 +374,18 @@ pub(crate) fn proxy_exotic_get_own_property( // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. if desc.expect_writable() { return - context.throw_type_error("Proxy trap result is writable and not configurable while target result is not configurable") + Err(JsNativeError::typ().with_message("Proxy trap result is writable and not configurable while target result is not configurable").into()) ; } } } // i. Throw a TypeError exception. _ => { - return context.throw_type_error( - "Proxy trap result is not configurable and target result is undefined", - ) + return Err(JsNativeError::typ() + .with_message( + "Proxy trap result is not configurable and target result is undefined", + ) + .into()) } } } @@ -394,7 +415,7 @@ pub(crate) fn proxy_exotic_define_own_property( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "defineProperty"). let trap = if let Some(trap) = handler.get_method("defineProperty", context)? { @@ -435,12 +456,16 @@ pub(crate) fn proxy_exotic_define_own_property( None => { // a. If extensibleTarget is false, throw a TypeError exception. if !extensible_target { - return context.throw_type_error("Proxy trap failed to set property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set property") + .into()); } // b. If settingConfigFalse is true, throw a TypeError exception. if setting_config_false { - return context.throw_type_error("Proxy trap failed to set property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set property") + .into()); } } // 15. Else, @@ -451,14 +476,16 @@ pub(crate) fn proxy_exotic_define_own_property( desc.clone(), Some(target_desc.clone()), ) { - return context.throw_type_error("Proxy trap set property to unexpected value"); + return Err(JsNativeError::typ() + .with_message("Proxy trap set property to unexpected value") + .into()); } // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. if setting_config_false && target_desc.expect_configurable() { - return context.throw_type_error( - "Proxy trap set property with unexpected configurable field", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap set property with unexpected configurable field") + .into()); } // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then @@ -469,9 +496,9 @@ pub(crate) fn proxy_exotic_define_own_property( // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. if let Some(writable) = desc.writable() { if !writable { - return context.throw_type_error( - "Proxy trap set property with unexpected writable field", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap set property with unexpected writable field") + .into()); } } } @@ -502,7 +529,7 @@ pub(crate) fn proxy_exotic_has_property( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "has"). let trap = if let Some(trap) = handler.get_method("has", context)? { @@ -531,13 +558,17 @@ pub(crate) fn proxy_exotic_has_property( if let Some(target_desc) = target_desc { // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if !target_desc.expect_configurable() { - return context.throw_type_error("Proxy trap returned unexpected property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); } // ii. Let extensibleTarget be ? IsExtensible(target). // iii. If extensibleTarget is false, throw a TypeError exception. if !target.is_extensible(context)? { - return context.throw_type_error("Proxy trap returned unexpected property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); } } } @@ -567,7 +598,7 @@ pub(crate) fn proxy_exotic_get( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "get"). let trap = if let Some(trap) = handler.get_method("get", context)? { @@ -595,8 +626,9 @@ pub(crate) fn proxy_exotic_get( if target_desc.is_data_descriptor() && !target_desc.expect_writable() { // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception. if !JsValue::same_value(&trap_result, target_desc.expect_value()) { - return context - .throw_type_error("Proxy trap returned unexpected data descriptor"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected data descriptor") + .into()); } } @@ -604,8 +636,9 @@ pub(crate) fn proxy_exotic_get( if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() { // i. If trapResult is not undefined, throw a TypeError exception. if !trap_result.is_undefined() { - return context - .throw_type_error("Proxy trap returned unexpected accessor descriptor"); + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected accessor descriptor") + .into()); } } } @@ -637,7 +670,7 @@ pub(crate) fn proxy_exotic_set( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "set"). let trap = if let Some(trap) = handler.get_method("set", context)? { @@ -671,7 +704,9 @@ pub(crate) fn proxy_exotic_set( if target_desc.is_data_descriptor() && !target_desc.expect_writable() { // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception. if !JsValue::same_value(&value, target_desc.expect_value()) { - return context.throw_type_error("Proxy trap set unexpected data descriptor"); + return Err(JsNativeError::typ() + .with_message("Proxy trap set unexpected data descriptor") + .into()); } } @@ -680,8 +715,9 @@ pub(crate) fn proxy_exotic_set( // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. match target_desc.set() { None | Some(&JsValue::Undefined) => { - return context - .throw_type_error("Proxy trap set unexpected accessor descriptor"); + return Err(JsNativeError::typ() + .with_message("Proxy trap set unexpected accessor descriptor") + .into()); } _ => {} } @@ -713,7 +749,7 @@ pub(crate) fn proxy_exotic_delete( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "deleteProperty"). let trap = if let Some(trap) = handler.get_method("deleteProperty", context)? { @@ -744,7 +780,9 @@ pub(crate) fn proxy_exotic_delete( // 11. If targetDesc.[[Configurable]] is false, throw a TypeError exception. Some(target_desc) => { if !target_desc.expect_configurable() { - return context.throw_type_error("Proxy trap failed to delete property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to delete property") + .into()); } } } @@ -752,7 +790,9 @@ pub(crate) fn proxy_exotic_delete( // 12. Let extensibleTarget be ? IsExtensible(target). // 13. If extensibleTarget is false, throw a TypeError exception. if !target.is_extensible(context)? { - return context.throw_type_error("Proxy trap failed to delete property"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to delete property") + .into()); } // 14. Return true. @@ -778,7 +818,7 @@ pub(crate) fn proxy_exotic_own_property_keys( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "ownKeys"). let trap = if let Some(trap) = handler.get_method("ownKeys", context)? { @@ -803,17 +843,17 @@ pub(crate) fn proxy_exotic_own_property_keys( match value { JsValue::String(s) => { if !unchecked_result_keys.insert(s.clone().into()) { - return context.throw_type_error( - "Proxy trap result contains duplicate string property keys", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap result contains duplicate string property keys") + .into()); } trap_result.push(s.clone().into()); } JsValue::Symbol(s) => { if !unchecked_result_keys.insert(s.clone().into()) { - return context.throw_type_error( - "Proxy trap result contains duplicate symbol property keys", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap result contains duplicate symbol property keys") + .into()); } trap_result.push(s.clone().into()); } @@ -863,9 +903,9 @@ pub(crate) fn proxy_exotic_own_property_keys( // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. // b. Remove key from uncheckedResultKeys. if !unchecked_result_keys.remove(&key) { - return context.throw_type_error( - "Proxy trap failed to return all non-configurable property keys", - ); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all non-configurable property keys") + .into()); } } @@ -879,14 +919,17 @@ pub(crate) fn proxy_exotic_own_property_keys( // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. // b. Remove key from uncheckedResultKeys. if !unchecked_result_keys.remove(&key) { - return context - .throw_type_error("Proxy trap failed to return all configurable property keys"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all configurable property keys") + .into()); } } // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. if !unchecked_result_keys.is_empty() { - return context.throw_type_error("Proxy trap failed to return all property keys"); + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all property keys") + .into()); } // 23. Return trapResult. @@ -913,7 +956,7 @@ fn proxy_exotic_call( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Let trap be ? GetMethod(handler, "apply"). let trap = if let Some(trap) = handler.get_method("apply", context)? { @@ -955,7 +998,7 @@ fn proxy_exotic_construct( .borrow() .as_proxy() .expect("Proxy object internal internal method called on non-proxy object") - .try_data(context)?; + .try_data()?; // 5. Assert: IsConstructor(target) is true. assert!(target.is_constructor()); @@ -985,7 +1028,7 @@ fn proxy_exotic_construct( // 10. If Type(newObj) is not Object, throw a TypeError exception. let new_obj = new_obj.as_object().cloned().ok_or_else(|| { - context.construct_type_error("Proxy trap constructor returned non-object value") + JsNativeError::typ().with_message("Proxy trap constructor returned non-object value") })?; // 11. Return newObj. diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index e92137bdb99..cf1dc0c7ebc 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -4,6 +4,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap}; use crate::{ + error::JsNativeError, object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, value::PreferredType, @@ -186,7 +187,9 @@ impl JsObject { } // 6. Throw a TypeError exception. - context.throw_type_error("cannot convert object to primitive value") + Err(JsNativeError::typ() + .with_message("cannot convert object to primitive value") + .into()) } /// Return `true` if it is a native object and the native type is `T`. @@ -546,7 +549,9 @@ impl JsObject { // b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception. // todo: extract IsCallable to be callable from Value if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) { - return context.throw_type_error("Property descriptor getter must be callable"); + return Err(JsNativeError::typ() + .with_message("Property descriptor getter must be callable") + .into()); } // c. Set desc.[[Get]] to getter. Some(getter) @@ -562,7 +567,9 @@ impl JsObject { // 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception. // todo: extract IsCallable to be callable from Value if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) { - return context.throw_type_error("Property descriptor setter must be callable"); + return Err(JsNativeError::typ() + .with_message("Property descriptor setter must be callable") + .into()); } // 14.c. Set desc.[[Set]] to setter. Some(setter) @@ -573,10 +580,12 @@ impl JsObject { // 15. If desc.[[Get]] is present or desc.[[Set]] is present, then ... // a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception. if get.as_ref().or(set.as_ref()).is_some() && desc.inner().is_data_descriptor() { - return context.throw_type_error( - "Invalid property descriptor.\ + return Err(JsNativeError::typ() + .with_message( + "Invalid property descriptor.\ Cannot both specify accessors and a value or writable attribute", - ); + ) + .into()); } desc = desc.maybe_get(get).maybe_set(set); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 2f382782621..dbcd4225884 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -30,6 +30,7 @@ use crate::{ array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, async_generator::AsyncGenerator, + error::ErrorKind, function::arguments::Arguments, function::{ arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, @@ -49,6 +50,7 @@ use crate::{ DataView, Date, Promise, RegExp, }, context::intrinsics::StandardConstructor, + error::JsNativeError, js_string, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, @@ -182,7 +184,7 @@ pub enum ObjectKind { StringIterator(StringIterator), Number(f64), Symbol(JsSymbol), - Error, + Error(ErrorKind), Ordinary, Proxy(Proxy), Date(Date), @@ -226,7 +228,7 @@ unsafe impl Trace for ObjectKind { | Self::String(_) | Self::Date(_) | Self::Array - | Self::Error + | Self::Error(_) | Self::Ordinary | Self::Global | Self::Number(_) @@ -453,9 +455,9 @@ impl ObjectData { } /// Create the `Error` object data - pub fn error() -> Self { + pub(crate) fn error(error: ErrorKind) -> Self { Self { - kind: ObjectKind::Error, + kind: ObjectKind::Error(error), internal_methods: &ORDINARY_INTERNAL_METHODS, } } @@ -559,7 +561,7 @@ impl Display for ObjectKind { Self::String(_) => "String", Self::StringIterator(_) => "StringIterator", Self::Symbol(_) => "Symbol", - Self::Error => "Error", + Self::Error(_) => "Error", Self::Ordinary => "Ordinary", Self::Proxy(_) => "Proxy", Self::Boolean(_) => "Boolean", @@ -1067,12 +1069,23 @@ impl Object { matches!( self.data, ObjectData { - kind: ObjectKind::Error, + kind: ObjectKind::Error(_), .. } ) } + #[inline] + pub fn as_error(&self) -> Option { + match self.data { + ObjectData { + kind: ObjectKind::Error(e), + .. + } => Some(e), + _ => None, + } + } + /// Checks if it a Boolean object. #[inline] pub fn is_boolean(&self) -> bool { @@ -1675,7 +1688,8 @@ impl<'context> FunctionBuilder<'context> { function: Box::new(move |this, args, captures: Captures, context| { let mut captures = captures.as_mut_any(); let captures = captures.downcast_mut::().ok_or_else(|| { - context.construct_type_error("cannot downcast `Captures` to given type") + JsNativeError::typ() + .with_message("cannot downcast `Captures` to given type") })?; function(this, args, captures, context) }), @@ -1789,16 +1803,8 @@ impl<'context> FunctionBuilder<'context> { /// # use boa_engine::{Context, JsValue, object::ObjectInitializer, property::Attribute}; /// let mut context = Context::default(); /// let object = ObjectInitializer::new(&mut context) -/// .property( -/// "hello", -/// "world", -/// Attribute::all() -/// ) -/// .property( -/// 1, -/// 1, -/// Attribute::all() -/// ) +/// .property("hello", "world", Attribute::all()) +/// .property(1, 1, Attribute::all()) /// .function(|_, _, _| Ok(JsValue::undefined()), "func", 0) /// .build(); /// ``` diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 85d852edecf..bbe4d286633 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -1,6 +1,7 @@ use crate::{ builtins::Array, context::intrinsics::{StandardConstructor, StandardConstructors}, + error::JsNativeError, object::JsObject, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, @@ -91,7 +92,9 @@ impl JsObject { let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; // 5. If success is false and Throw is true, throw a TypeError exception. if !success && throw { - return context.throw_type_error(format!("cannot set non-writable property: {key}")); + return Err(JsNativeError::typ() + .with_message(format!("cannot set non-writable property: {key}")) + .into()); } // 6. Return success. Ok(success) @@ -150,7 +153,9 @@ impl JsObject { let success = self.create_data_property(key.clone(), value, context)?; // 4. If success is false, throw a TypeError exception. if !success { - return context.throw_type_error(format!("cannot redefine property: {key}")); + return Err(JsNativeError::typ() + .with_message(format!("cannot redefine property: {key}")) + .into()); } // 5. Return success. Ok(success) @@ -217,7 +222,9 @@ impl JsObject { let success = self.__define_own_property__(key.clone(), desc.into(), context)?; // 4. If success is false, throw a TypeError exception. if !success { - return context.throw_type_error(format!("cannot redefine property: {key}")); + return Err(JsNativeError::typ() + .with_message(format!("cannot redefine property: {key}")) + .into()); } // 5. Return success. Ok(success) @@ -241,7 +248,9 @@ impl JsObject { let success = self.__delete__(&key, context)?; // 4. If success is false, throw a TypeError exception. if !success { - return context.throw_type_error(format!("cannot delete property: {key}")); + return Err(JsNativeError::typ() + .with_message(format!("cannot delete property: {key}")) + .into()); } // 5. Return success. Ok(success) @@ -303,7 +312,7 @@ impl JsObject { // 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"); + return Err(JsNativeError::typ().with_message("not a function").into()); } // 3. Return ? F.[[Call]](V, argumentsList). self.__call__(this, args, context) @@ -496,7 +505,9 @@ impl JsObject { let c = if let Some(c) = c.as_object() { c } else { - return context.throw_type_error("property 'constructor' is not an object"); + return Err(JsNativeError::typ() + .with_message("property 'constructor' is not an object") + .into()); }; // 5. Let S be ? Get(C, @@species). @@ -511,7 +522,9 @@ impl JsObject { // 8. Throw a TypeError exception. match s.as_object() { Some(obj) if obj.is_constructor() => Ok(obj.clone()), - _ => context.throw_type_error("property 'constructor' is not a constructor"), + _ => Err(JsNativeError::typ() + .with_message("property 'constructor' is not a constructor") + .into()), } } @@ -600,9 +613,9 @@ impl JsObject { // 5. Return func. JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. - _ => { - context.throw_type_error("value returned for property of object is not a function") - } + _ => Err(JsNativeError::typ() + .with_message("value returned for property of object is not a function") + .into()), } } @@ -628,7 +641,7 @@ impl JsObject { if let Some(proxy) = object.as_proxy() { // a. If argument.[[ProxyHandler]] is null, throw a TypeError exception. // b. Let target be argument.[[ProxyTarget]]. - let (target, _) = proxy.try_data(context)?; + let (target, _) = proxy.try_data()?; // c. Return ? IsArray(target). return target.is_array_abstract(context); @@ -727,9 +740,9 @@ impl JsValue { }; // 2. If Type(obj) is not Object, throw a TypeError exception. - let obj = self - .as_object() - .ok_or_else(|| context.construct_type_error("cannot create list from a primitive"))?; + let obj = self.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("cannot create list from a primitive") + })?; // 3. Let len be ? LengthOfArrayLike(obj). let len = obj.length_of_array_like(context)?; @@ -745,7 +758,7 @@ impl JsValue { let next = obj.get(index, context)?; // c. If Type(next) is not an element of elementTypes, throw a TypeError exception. if !types.contains(&next.get_type()) { - return context.throw_type_error("bad type"); + return Err(JsNativeError::typ().with_message("bad type").into()); } // d. Append next as the last element of list. list.push(next.clone()); @@ -819,8 +832,9 @@ impl JsValue { obj } else { // 5. If Type(P) is not Object, throw a TypeError exception. - return context - .throw_type_error("function has non-object prototype in instanceof check"); + return Err(JsNativeError::typ() + .with_message("function has non-object prototype in instanceof check") + .into()); }; // 6. Repeat, diff --git a/boa_engine/src/string/mod.rs b/boa_engine/src/string/mod.rs index 7b0d9888c5b..b8843b7cd2b 100644 --- a/boa_engine/src/string/mod.rs +++ b/boa_engine/src/string/mod.rs @@ -55,7 +55,7 @@ use self::common::{COMMON_STRINGS, COMMON_STRINGS_CACHE, MAX_COMMON_STRING_LENGT /// /// /// You can create a `JsString` from a string literal, which completely skips the runtime -/// conversion from [`&str`] to [`&\[u16\]`]: +/// conversion from [`&str`] to [&\[u16\]][slice]: /// /// ``` /// # use boa_engine::js_string; @@ -255,7 +255,7 @@ impl TaggedJsString { } /// Enum representing either a reference to a heap allocated [`RawJsString`] or a static reference to -/// a [`\[u16\]`][std::slice] inside [`COMMON_STRINGS`]. +/// a [`\[u16\]`][slice] inside [`COMMON_STRINGS`]. enum JsStringPtrKind<'a> { // A string allocated on the heap. Heap(&'a mut RawJsString), @@ -265,8 +265,8 @@ enum JsStringPtrKind<'a> { /// A UTF-16–encoded, reference counted, immutable string. /// -/// This is pretty similar to a [Rc][std::rc::Rc]\<[\[u16\]][std::slice]\>, but without -/// the length metadata associated with the [`Rc`][std::rc::Rc] fat pointer. Instead, the length of +/// This is pretty similar to a [Rc][std::rc::Rc]\<[\[u16\]][slice]\>, but without +/// the length metadata associated with the `Rc` fat pointer. Instead, the length of /// every string is stored on the heap, along with its reference counter and its data. /// /// We define some commonly used string constants in an interner. For these strings, we don't allocate @@ -274,8 +274,8 @@ enum JsStringPtrKind<'a> { /// /// # Deref /// -/// [`JsString`] implements [Deref], inheriting all of -/// [`\[u16\]`][std::slice]'s methods. +/// [`JsString`] implements [Deref], inheriting all of +/// \[u16\]'s methods. #[derive(Finalize)] pub struct JsString { ptr: TaggedJsString, @@ -287,7 +287,7 @@ unsafe impl Trace for JsString { } impl JsString { - /// Obtains the underlying [`&[u16]`][std::slice] slice of a [`JsString`] + /// Obtains the underlying [`&[u16]`][slice] slice of a [`JsString`] pub fn as_slice(&self) -> &[u16] { self } @@ -805,6 +805,30 @@ impl PartialEq<[u16; N]> for JsString { } } +impl PartialEq for JsString { + fn eq(&self, other: &str) -> bool { + let utf16 = self.code_points(); + let mut utf8 = other.chars(); + + for lhs in utf16 { + if let Some(rhs) = utf8.next() { + match lhs { + CodePoint::Unicode(lhs) if lhs == rhs => continue, + _ => return false, + } + } + return false; + } + utf8.next().is_none() + } +} + +impl PartialEq for str { + fn eq(&self, other: &JsString) -> bool { + other == self + } +} + impl PartialOrd for JsString { fn partial_cmp(&self, other: &Self) -> Option { self[..].partial_cmp(other) @@ -850,8 +874,7 @@ impl Utf16Trim for [u16] { } } -/// Utility trait that adds a `UTF-16` escaped representation to every -/// [`[u16]`][std::slice]. +/// Utility trait that adds a `UTF-16` escaped representation to every [`[u16]`][slice]. pub(crate) trait ToStringEscaped { /// Decodes `self` as an `UTF-16` encoded string, escaping any unpaired surrogates by its /// codepoint value. diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index f500b86077a..f7f44488294 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -27,7 +27,7 @@ use std::{ /// /// # Examples /// ``` -///# use boa_engine::symbol::WellKnownSymbols; +/// # use boa_engine::symbol::WellKnownSymbols; /// /// let iterator = WellKnownSymbols::iterator(); /// assert_eq!(iterator.description().unwrap().to_std_string_escaped(), "Symbol.iterator"); diff --git a/boa_engine/src/syntax/ast/function/parameters.rs b/boa_engine/src/syntax/ast/function/parameters.rs index 32ff6b88ed6..da450b7513b 100644 --- a/boa_engine/src/syntax/ast/function/parameters.rs +++ b/boa_engine/src/syntax/ast/function/parameters.rs @@ -237,10 +237,10 @@ impl Default for FormalParameterListFlags { /// /// In the declaration of a function, the parameters must be identifiers, /// not any value like numbers, strings, or objects. -///```text -///function foo(formalParameter1, formalParameter2) { -///} -///``` +/// ```text +/// function foo(formalParameter1, formalParameter2) { +/// } +/// ``` /// /// More information: /// - [ECMAScript reference][spec] diff --git a/boa_engine/src/syntax/ast/statement/switch/mod.rs b/boa_engine/src/syntax/ast/statement/switch/mod.rs index 2a2b286ca24..9a5fd59b39a 100644 --- a/boa_engine/src/syntax/ast/statement/switch/mod.rs +++ b/boa_engine/src/syntax/ast/statement/switch/mod.rs @@ -1,10 +1,8 @@ //! Switch node. //! -use crate::syntax::ast::{expression::Expression, statement::Statement}; +use crate::syntax::ast::{expression::Expression, statement::Statement, StatementList}; use boa_interner::{Interner, ToInternedString}; -use crate::syntax::ast::StatementList; - use super::ContainsSymbol; #[cfg(test)] diff --git a/boa_engine/src/syntax/parser/expression/identifiers.rs b/boa_engine/src/syntax/parser/expression/identifiers.rs index 4de308f7127..2e284dbd3b2 100644 --- a/boa_engine/src/syntax/parser/expression/identifiers.rs +++ b/boa_engine/src/syntax/parser/expression/identifiers.rs @@ -4,7 +4,6 @@ //! - [ECMAScript specification][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-identifiers -//! use crate::syntax::{ ast::{expression::Identifier, Keyword}, diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index d02657f2edf..a0bc3295bf5 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -467,7 +467,7 @@ fn test_invalid_break() { let string = forward(&mut context, src); assert_eq!( string, - "Uncaught \"SyntaxError\": \"unlabeled break must be inside loop or switch\"" + "Uncaught SyntaxError: unlabeled break must be inside loop or switch" ); } @@ -482,7 +482,7 @@ fn test_invalid_continue_target() { let string = forward(&mut context, src); assert_eq!( string, - "Uncaught \"SyntaxError\": \"Cannot use the undeclared label 'nonexistent'\"" + "Uncaught SyntaxError: Cannot use the undeclared label 'nonexistent'" ); } @@ -490,10 +490,7 @@ fn test_invalid_continue_target() { fn test_invalid_continue() { let mut context = Context::default(); let string = forward(&mut context, r"continue;"); - assert_eq!( - string, - "Uncaught \"SyntaxError\": \"continue must be inside loop\"" - ); + assert_eq!(string, "Uncaught SyntaxError: continue must be inside loop"); } #[test] @@ -542,10 +539,10 @@ fn unary_pre() { #[test] fn invalid_unary_access() { check_output(&[ - TestAction::TestStartsWith("++[];", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("[]++;", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("--[];", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("[]--;", "Uncaught \"SyntaxError\": "), + TestAction::TestStartsWith("++[];", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("[]++;", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("--[];", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("[]--;", "Uncaught SyntaxError: "), ]); } @@ -851,7 +848,7 @@ mod in_operator { check_output(&[TestAction::TestEq( scenario, - "Uncaught \"TypeError\": \"not a constructor\"", + "Uncaught TypeError: not a constructor", )]); } @@ -1237,20 +1234,6 @@ fn calling_function_with_unspecified_arguments() { assert_eq!(forward(&mut context, scenario), "undefined"); } -#[test] -fn to_object() { - let mut context = Context::default(); - - assert!(JsValue::undefined() - .to_object(&mut context) - .unwrap_err() - .is_object()); - assert!(JsValue::null() - .to_object(&mut context) - .unwrap_err() - .is_object()); -} - #[test] fn check_this_binding_in_object_literal() { let mut context = Context::default(); @@ -1404,7 +1387,7 @@ fn assignment_to_non_assignable() { for case in &test_cases { let string = forward(&mut context, case); - assert!(string.starts_with("Uncaught \"SyntaxError\": ")); + assert!(string.starts_with("Uncaught SyntaxError: ")); assert!(string.contains("1:3")); } } @@ -1412,15 +1395,15 @@ fn assignment_to_non_assignable() { #[test] fn assignment_to_non_assignable_ctd() { check_output(&[ - TestAction::TestStartsWith("(()=>{})() -= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() *= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() /= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() %= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() &= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() ^= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() |= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() += 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() = 5", "Uncaught \"SyntaxError\": "), + TestAction::TestStartsWith("(()=>{})() -= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() *= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() /= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() %= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() &= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() ^= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() |= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() += 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() = 5", "Uncaught SyntaxError: "), ]); } @@ -1435,7 +1418,7 @@ fn multicharacter_assignment_to_non_assignable() { for case in &test_cases { let string = forward(&mut context, case); - assert!(string.starts_with("Uncaught \"SyntaxError\": ")); + assert!(string.starts_with("Uncaught SyntaxError: ")); assert!(string.contains("1:3")); } } @@ -1443,9 +1426,9 @@ fn multicharacter_assignment_to_non_assignable() { #[test] fn multicharacter_assignment_to_non_assignable_ctd() { check_output(&[ - TestAction::TestStartsWith("(()=>{})() **= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() <<= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() >>= 5", "Uncaught \"SyntaxError\": "), + TestAction::TestStartsWith("(()=>{})() **= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() <<= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() >>= 5", "Uncaught SyntaxError: "), ]); } @@ -1459,7 +1442,7 @@ fn multicharacter_bitwise_assignment_to_non_assignable() { for case in &test_cases { let string = forward(&mut context, case); - assert!(string.starts_with("Uncaught \"SyntaxError\": ")); + assert!(string.starts_with("Uncaught SyntaxError: ")); assert!(string.contains("1:3")); } } @@ -1467,27 +1450,27 @@ fn multicharacter_bitwise_assignment_to_non_assignable() { #[test] fn multicharacter_bitwise_assignment_to_non_assignable_ctd() { check_output(&[ - TestAction::TestStartsWith("(()=>{})() >>>= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() &&= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() ||= 5", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("(()=>{})() ??= 5", "Uncaught \"SyntaxError\": "), + TestAction::TestStartsWith("(()=>{})() >>>= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() &&= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() ||= 5", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("(()=>{})() ??= 5", "Uncaught SyntaxError: "), ]); } #[test] fn assign_to_array_decl() { check_output(&[ - TestAction::TestStartsWith("[1] = [2]", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("[3, 5] = [7, 8]", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("[6, 8] = [2]", "Uncaught \"SyntaxError\": "), - TestAction::TestStartsWith("[6] = [2, 9]", "Uncaught \"SyntaxError\": "), + TestAction::TestStartsWith("[1] = [2]", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("[3, 5] = [7, 8]", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("[6, 8] = [2]", "Uncaught SyntaxError: "), + TestAction::TestStartsWith("[6] = [2, 9]", "Uncaught SyntaxError: "), ]); } #[test] fn assign_to_object_decl() { const ERR_MSG: &str = - "Uncaught \"SyntaxError\": \"unexpected token '=', primary expression at line 1, col 8\""; + "Uncaught SyntaxError: unexpected token '=', primary expression at line 1, col 8"; let mut context = Context::default(); @@ -1531,7 +1514,7 @@ fn test_conditional_op() { #[test] fn test_identifier_op() { let scenario = "break = 1"; - assert_eq!(&exec(scenario), "\"SyntaxError\": \"expected token \'identifier\', got \'=\' in binding identifier at line 1, col 7\""); + assert_eq!(&exec(scenario), "SyntaxError: expected token \'identifier\', got \'=\' in binding identifier at line 1, col 7"); } #[test] @@ -1546,7 +1529,7 @@ fn test_strict_mode_octal() { check_output(&[TestAction::TestStartsWith( scenario, - "Uncaught \"SyntaxError\": ", + "Uncaught SyntaxError: ", )]); } @@ -1566,7 +1549,7 @@ fn test_strict_mode_with() { check_output(&[TestAction::TestStartsWith( scenario, - "Uncaught \"SyntaxError\": ", + "Uncaught SyntaxError: ", )]); } @@ -1583,7 +1566,7 @@ fn test_strict_mode_delete() { check_output(&[TestAction::TestStartsWith( scenario, - "Uncaught \"SyntaxError\": ", + "Uncaught SyntaxError: ", )]); } @@ -1612,7 +1595,7 @@ fn test_strict_mode_reserved_name() { let string = forward(&mut context, &scenario); - assert!(string.starts_with("Uncaught \"SyntaxError\": ")); + assert!(string.starts_with("Uncaught SyntaxError: ")); } } @@ -1628,7 +1611,7 @@ fn test_strict_mode_dup_func_parameters() { check_output(&[TestAction::TestStartsWith( scenario, - "Uncaught \"SyntaxError\": ", + "Uncaught SyntaxError: ", )]); } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 41e72ba7a79..3b9ac4fcd3b 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -10,6 +10,7 @@ use crate::{ number::{f64_to_int32, f64_to_uint32}, Number, }, + error::JsNativeError, js_string, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, @@ -375,7 +376,9 @@ impl JsValue { // v. If Type(result) is not Object, return result. // vi. Throw a TypeError exception. return if result.is_object() { - context.throw_type_error("Symbol.toPrimitive cannot return an object") + Err(JsNativeError::typ() + .with_message("Symbol.toPrimitive cannot return an object") + .into()) } else { Ok(result) }; @@ -403,29 +406,37 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { match self { - Self::Null => context.throw_type_error("cannot convert null to a BigInt"), - Self::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), + Self::Null => Err(JsNativeError::typ() + .with_message("cannot convert null to a BigInt") + .into()), + Self::Undefined => Err(JsNativeError::typ() + .with_message("cannot convert undefined to a BigInt") + .into()), Self::String(ref string) => { if let Some(value) = string.to_big_int() { Ok(value) } else { - context.throw_syntax_error(format!( - "cannot convert string '{}' to bigint primitive", - string.to_std_string_escaped() - )) + Err(JsNativeError::syntax() + .with_message(format!( + "cannot convert string '{}' to bigint primitive", + string.to_std_string_escaped() + )) + .into()) } } Self::Boolean(true) => Ok(JsBigInt::one()), Self::Boolean(false) => Ok(JsBigInt::zero()), - Self::Integer(_) | Self::Rational(_) => { - context.throw_type_error("cannot convert Number to a BigInt") - } + Self::Integer(_) | Self::Rational(_) => Err(JsNativeError::typ() + .with_message("cannot convert Number to a BigInt") + .into()), Self::BigInt(b) => Ok(b.clone()), Self::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - Self::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), + Self::Symbol(_) => Err(JsNativeError::typ() + .with_message("cannot convert Symbol to a BigInt") + .into()), } } @@ -462,7 +473,9 @@ impl JsValue { Self::Rational(rational) => Ok(Number::to_native_string(*rational).into()), Self::Integer(integer) => Ok(integer.to_string().into()), Self::String(string) => Ok(string.clone()), - Self::Symbol(_) => context.throw_type_error("can't convert symbol to string"), + Self::Symbol(_) => Err(JsNativeError::typ() + .with_message("can't convert symbol to string") + .into()), Self::BigInt(ref bigint) => Ok(bigint.to_string().into()), Self::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; @@ -478,9 +491,9 @@ impl JsValue { /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { match self { - Self::Undefined | Self::Null => { - context.throw_type_error("cannot convert 'null' or 'undefined' to object") - } + Self::Undefined | Self::Null => Err(JsNativeError::typ() + .with_message("cannot convert 'null' or 'undefined' to object") + .into()), Self::Boolean(boolean) => { let prototype = context.intrinsics().constructors().boolean().prototype(); Ok(JsObject::from_proto_and_data( @@ -817,7 +830,9 @@ impl JsValue { // c. If ! SameValue(𝔽(integer), clamped) is false, throw a RangeError exception. if integer != clamped { - return context.throw_range_error("Index must be between 0 and 2^53 - 1"); + return Err(JsNativeError::range() + .with_message("Index must be between 0 and 2^53 - 1") + .into()); } // d. Assert: 0 ≤ integer ≤ 2^53 - 1. @@ -884,8 +899,12 @@ impl JsValue { Self::String(ref string) => Ok(string.to_number()), Self::Rational(number) => Ok(number), Self::Integer(integer) => Ok(f64::from(integer)), - Self::Symbol(_) => context.throw_type_error("argument must not be a symbol"), - Self::BigInt(_) => context.throw_type_error("argument must not be a bigint"), + Self::Symbol(_) => Err(JsNativeError::typ() + .with_message("argument must not be a symbol") + .into()), + Self::BigInt(_) => Err(JsNativeError::typ() + .with_message("argument must not be a bigint") + .into()), Self::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) @@ -918,9 +937,11 @@ impl JsValue { /// [table]: https://tc39.es/ecma262/#table-14 /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible #[inline] - pub fn require_object_coercible(&self, context: &mut Context) -> JsResult<&Self> { + pub fn require_object_coercible(&self) -> JsResult<&Self> { if self.is_null_or_undefined() { - context.throw_type_error("cannot convert null or undefined to Object") + Err(JsNativeError::typ() + .with_message("cannot convert null or undefined to Object") + .into()) } else { Ok(self) } @@ -931,9 +952,9 @@ impl JsValue { // 1. If Type(Obj) is not Object, throw a TypeError exception. self.as_object() .ok_or_else(|| { - context.construct_type_error( - "Cannot construct a property descriptor from a non-object", - ) + JsNativeError::typ() + .with_message("Cannot construct a property descriptor from a non-object") + .into() }) .and_then(|obj| obj.to_property_descriptor(context)) } diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index 0a836f4a155..9359cfbf8d2 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -3,11 +3,12 @@ use crate::{ number::{f64_to_int32, f64_to_uint32}, Number, }, + error::JsNativeError, js_string, + value::{Numeric, PreferredType, WellKnownSymbols}, + Context, JsBigInt, JsResult, JsValue, }; -use super::{Context, JsBigInt, JsResult, JsValue, Numeric, PreferredType, WellKnownSymbols}; - impl JsValue { #[inline] pub fn add(&self, other: &Self, context: &mut Context) -> JsResult { @@ -40,9 +41,11 @@ impl JsValue { Self::new(JsBigInt::add(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ) + return Err(JsNativeError::typ() + .with_message( + "cannot mix BigInt and other types, use explicit conversions", + ) + .into()) } }, }, @@ -67,9 +70,9 @@ impl JsValue { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a - b), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -93,9 +96,9 @@ impl JsValue { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a * b), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -115,7 +118,9 @@ impl JsValue { (Self::BigInt(ref x), Self::BigInt(ref y)) => { if y.is_zero() { - return context.throw_range_error("BigInt division by zero"); + return Err(JsNativeError::range() + .with_message("BigInt division by zero") + .into()); } Self::new(JsBigInt::div(x, y)) } @@ -125,14 +130,16 @@ impl JsValue { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a / b), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { if y.is_zero() { - return context.throw_range_error("BigInt division by zero"); + return Err(JsNativeError::range() + .with_message("BigInt division by zero") + .into()); } Self::new(JsBigInt::div(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -162,7 +169,9 @@ impl JsValue { (Self::BigInt(ref x), Self::BigInt(ref y)) => { if y.is_zero() { - return context.throw_range_error("BigInt division by zero"); + return Err(JsNativeError::range() + .with_message("BigInt division by zero") + .into()); } Self::new(JsBigInt::rem(x, y)) } @@ -172,14 +181,16 @@ impl JsValue { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { if y.is_zero() { - return context.throw_range_error("BigInt division by zero"); + return Err(JsNativeError::range() + .with_message("BigInt division by zero") + .into()); } Self::new(JsBigInt::rem(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -197,18 +208,16 @@ impl JsValue { (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)), (Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)), - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b, context)?), + (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a.powf(b)), - (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => { - Self::new(JsBigInt::pow(a, b, context)?) - } + (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?), (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -236,9 +245,9 @@ impl JsValue { Self::new(JsBigInt::bitand(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -266,9 +275,9 @@ impl JsValue { Self::new(JsBigInt::bitor(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -296,9 +305,9 @@ impl JsValue { Self::new(JsBigInt::bitxor(x, y)) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -317,9 +326,7 @@ impl JsValue { Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { - Self::new(JsBigInt::shift_left(a, b, context)?) - } + (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_left(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -327,12 +334,12 @@ impl JsValue { Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) } (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { - Self::new(JsBigInt::shift_left(x, y, context)?) + Self::new(JsBigInt::shift_left(x, y)?) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -351,9 +358,7 @@ impl JsValue { Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { - Self::new(JsBigInt::shift_right(a, b, context)?) - } + (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_right(a, b)?), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -361,12 +366,12 @@ impl JsValue { Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) } (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { - Self::new(JsBigInt::shift_right(x, y, context)?) + Self::new(JsBigInt::shift_right(x, y)?) } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -393,13 +398,14 @@ impl JsValue { Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) } (Numeric::BigInt(_), Numeric::BigInt(_)) => { - return context - .throw_type_error("BigInts have no unsigned right shift, use >> instead"); + return Err(JsNativeError::typ() + .with_message("BigInts have no unsigned right shift, use >> instead") + .into()); } (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ); + return Err(JsNativeError::typ() + .with_message("cannot mix BigInt and other types, use explicit conversions") + .into()); } }, }) @@ -415,10 +421,12 @@ impl JsValue { pub fn instance_of(&self, target: &Self, context: &mut Context) -> JsResult { // 1. If Type(target) is not Object, throw a TypeError exception. if !target.is_object() { - return context.throw_type_error(format!( - "right-hand side of 'instanceof' should be an object, got {}", - target.type_of().to_std_string_escaped() - )); + return Err(JsNativeError::typ() + .with_message(format!( + "right-hand side of 'instanceof' should be an object, got {}", + target.type_of().to_std_string_escaped() + )) + .into()); } // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). @@ -436,7 +444,9 @@ impl JsValue { } None => { // 4. If IsCallable(target) is false, throw a TypeError exception. - context.throw_type_error("right-hand side of 'instanceof' is not callable") + Err(JsNativeError::typ() + .with_message("right-hand side of 'instanceof' is not callable") + .into()) } } } diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 4b0cca514b7..ec5a6d3c622 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -3,6 +3,7 @@ use super::JsValue; use crate::{ builtins::Array, + error::JsNativeError, property::{PropertyDescriptor, PropertyKey}, Context, JsResult, }; @@ -49,9 +50,9 @@ impl JsValue { .map(|i| Self::Integer(i as i32)) .or_else(|| num.as_f64().map(Self::Rational)) .ok_or_else(|| { - context.construct_type_error(format!( - "could not convert JSON number {num} to JsValue" - )) + JsNativeError::typ() + .with_message(format!("could not convert JSON number {num} to JsValue")) + .into() }), Value::String(string) => Ok(Self::from(string.as_str())), Value::Array(vec) => { @@ -111,7 +112,9 @@ impl JsValue { Self::String(string) => Ok(string.to_std_string_escaped().into()), &Self::Rational(rat) => Ok(rat.into()), &Self::Integer(int) => Ok(int.into()), - Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), + Self::BigInt(_bigint) => Err(JsNativeError::typ() + .with_message("cannot convert bigint to JSON") + .into()), Self::Object(obj) => { if obj.is_array() { let len = obj.length_of_array_like(context)?; @@ -134,7 +137,9 @@ impl JsValue { PropertyKey::String(string) => string.to_std_string_escaped(), PropertyKey::Index(i) => i.to_string(), PropertyKey::Symbol(_sym) => { - return context.throw_type_error("cannot convert Symbol to JSON") + return Err(JsNativeError::typ() + .with_message("cannot convert Symbol to JSON") + .into()) } }; @@ -149,7 +154,9 @@ impl JsValue { Ok(Value::Object(map)) } } - Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), + Self::Symbol(_sym) => Err(JsNativeError::typ() + .with_message("cannot convert Symbol to JSON") + .into()), } } } @@ -198,7 +205,7 @@ mod tests { let phones = obj.get("phones", &mut context).unwrap(); let phones = phones.as_object().unwrap(); - let arr = JsArray::from_object(phones.clone(), &mut context).unwrap(); + let arr = JsArray::from_object(phones.clone()).unwrap(); assert_eq!(arr.at(0, &mut context).unwrap(), "+44 1234567".into()); assert_eq!(arr.at(1, &mut context).unwrap(), JsValue::from(-45_i32)); assert!(arr.at(2, &mut context).unwrap().is_object()); diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index 77c030a832e..11a379a112d 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -539,46 +539,64 @@ fn to_integer_or_infinity() { let mut context = Context::default(); assert_eq!( - JsValue::undefined().to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(0)) + JsValue::undefined() + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(0) ); assert_eq!( - JsValue::nan().to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(0)) + JsValue::nan().to_integer_or_infinity(&mut context).unwrap(), + IntegerOrInfinity::Integer(0) ); assert_eq!( - JsValue::new(0.0).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(0)) + JsValue::new(0.0) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(0) ); assert_eq!( - JsValue::new(-0.0).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(0)) + JsValue::new(-0.0) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(0) ); assert_eq!( - JsValue::new(f64::INFINITY).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::PositiveInfinity) + JsValue::new(f64::INFINITY) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::PositiveInfinity ); assert_eq!( - JsValue::new(f64::NEG_INFINITY).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::NegativeInfinity) + JsValue::new(f64::NEG_INFINITY) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::NegativeInfinity ); assert_eq!( - JsValue::new(10).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(10)) + JsValue::new(10) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(10) ); assert_eq!( - JsValue::new(11.0).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(11)) + JsValue::new(11.0) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(11) ); assert_eq!( - JsValue::new("12").to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(12)) + JsValue::new("12") + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(12) ); assert_eq!( - JsValue::new(true).to_integer_or_infinity(&mut context), - Ok(IntegerOrInfinity::Integer(1)) + JsValue::new(true) + .to_integer_or_infinity(&mut context) + .unwrap(), + IntegerOrInfinity::Integer(1) ); } @@ -679,7 +697,7 @@ mod cyclic_conversions { assert_eq!( forward(&mut context, src), - r#"Uncaught "TypeError": "cyclic object value""#, + "Uncaught TypeError: cyclic object value", ); } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 2fd5dc0566e..3e63f15226d 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -13,6 +13,7 @@ use crate::{ }, context::intrinsics::StandardConstructors, environments::{BindingLocator, CompileTimeEnvironment}, + error::JsNativeError, js_string, object::{ internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PrivateElement, @@ -658,7 +659,9 @@ impl JsObject { let this_function_object = self.clone(); if !self.is_callable() { - return context.throw_type_error("not a callable function"); + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()); } let object = self.borrow(); @@ -696,8 +699,9 @@ impl JsObject { drop(object); if code.is_class_constructor { - return context - .throw_type_error("Class constructor cannot be invoked without 'new'"); + return Err(JsNativeError::typ() + .with_message("Class constructor cannot be invoked without 'new'") + .into()); } std::mem::swap(&mut environments, &mut context.realm.environments); @@ -1237,7 +1241,9 @@ impl JsObject { }; if !self.is_constructor() { - return context.throw_type_error("not a constructor function"); + return Err(JsNativeError::typ() + .with_message("not a constructor function") + .into()); } let object = self.borrow(); @@ -1259,9 +1265,11 @@ impl JsObject { if constructor.expect("hmm").is_base() || val.is_undefined() { create_this(context) } else { - context.throw_type_error( - "Derived constructor can only return an Object or undefined", - ) + Err(JsNativeError::typ() + .with_message( + "Derived constructor can only return an Object or undefined", + ) + .into()) } } } @@ -1283,9 +1291,11 @@ impl JsObject { if constructor.expect("hmma").is_base() || val.is_undefined() { create_this(context) } else { - context.throw_type_error( - "Derived constructor can only return an Object or undefined", - ) + Err(JsNativeError::typ() + .with_message( + "Derived constructor can only return an Object or undefined", + ) + .into()) } } } @@ -1435,22 +1445,21 @@ impl JsObject { } else if let Some(this) = this { Ok(this) } else if !result.is_undefined() { - context.throw_type_error("Function constructor must not return non-object") + Err(JsNativeError::typ() + .with_message("Function constructor must not return non-object") + .into()) } else { let function_env = environment .slots() .expect("must be function environment") .as_function_slots() .expect("must be function environment"); - if let Some(this_binding) = function_env.borrow().get_this_binding() { - Ok(this_binding - .as_object() - .expect("this binding must be object") - .clone()) - } else { - //context.throw_type_error("Function constructor must not return non-object") - context.throw_reference_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor") - } + Ok(function_env + .borrow() + .get_this_binding()? + .as_object() + .expect("this binding must be object") + .clone()) } } Function::Generator { .. } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 17d27b6a50a..1fe3d507824 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -10,6 +10,7 @@ use crate::{ Array, ForInIterator, JsArgs, Number, Promise, }, environments::EnvironmentSlots, + error::JsNativeError, object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement}, property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey}, value::Numeric, @@ -17,7 +18,7 @@ use crate::{ call_frame::CatchAddresses, code_block::{initialize_instance_elements, Readable}, }, - Context, JsBigInt, JsResult, JsString, JsValue, + Context, JsBigInt, JsError, JsResult, JsString, JsValue, }; use boa_interner::ToInternedString; use boa_profiler::Profiler; @@ -205,8 +206,9 @@ impl Context { if let Some(superclass) = superclass.as_constructor() { let proto = superclass.get("prototype", self)?; if !proto.is_object() && !proto.is_null() { - return self - .throw_type_error("superclass prototype must be an object or null"); + return Err(JsNativeError::typ() + .with_message("superclass prototype must be an object or null") + .into()); } let class = self.vm.pop(); @@ -231,7 +233,9 @@ impl Context { } else if superclass.is_null() { self.vm.push(JsValue::Null); } else { - return self.throw_type_error("superclass must be a constructor"); + return Err(JsNativeError::typ() + .with_message("superclass must be a constructor") + .into()); } } Opcode::SetClassPrototype => { @@ -386,10 +390,12 @@ impl Context { let lhs = self.vm.pop(); if !rhs.is_object() { - return self.throw_type_error(format!( - "right-hand side of 'in' should be an object, got {}", - rhs.type_of().to_std_string_escaped() - )); + return Err(JsNativeError::typ() + .with_message(format!( + "right-hand side of 'in' should be an object, got {}", + rhs.type_of().to_std_string_escaped() + )) + .into()); } let key = lhs.to_property_key(self)?; let value = self.has_property(&rhs, &key)?; @@ -569,17 +575,21 @@ impl Context { self.call(&get, &self.global_object().clone().into(), &[])? } _ => { - return self.throw_reference_error(format!( - "{} is not defined", - key.to_std_string_escaped() - )) + return Err(JsNativeError::reference() + .with_message(format!( + "{} is not defined", + key.to_std_string_escaped() + )) + .into()) } }, _ => { - return self.throw_reference_error(format!( - "{} is not defined", - key.to_std_string_escaped() - )) + return Err(JsNativeError::reference() + .with_message(format!( + "{} is not defined", + key.to_std_string_escaped() + )) + .into()) } } } @@ -594,7 +604,9 @@ impl Context { .interner() .resolve_expect(binding_locator.name().sym()) .to_string(); - return self.throw_reference_error(format!("{name} is not initialized",)); + return Err(JsNativeError::reference() + .with_message(format!("{name} is not initialized")) + .into()); }; self.vm.push(value); @@ -662,10 +674,12 @@ impl Context { let exists = self.global_bindings_mut().contains_key(&key); if !exists && self.vm.frame().code.strict { - return self.throw_reference_error(format!( - "assignment to undeclared variable {}", - key.to_std_string_escaped() - )); + return Err(JsNativeError::reference() + .with_message(format!( + "assignment to undeclared variable {}", + key.to_std_string_escaped() + )) + .into()); } let success = @@ -676,10 +690,12 @@ impl Context { )?; if !success && self.vm.frame().code.strict { - return self.throw_type_error(format!( - "cannot set non-writable property: {}", - key.to_std_string_escaped() - )); + return Err(JsNativeError::typ() + .with_message(format!( + "cannot set non-writable property: {}", + key.to_std_string_escaped() + )) + .into()); } } } else if !self.realm.environments.put_value_if_initialized( @@ -688,10 +704,12 @@ impl Context { binding_locator.name(), value, ) { - self.throw_reference_error(format!( - "cannot access '{}' before initialization", - self.interner().resolve_expect(binding_locator.name().sym()) - ))?; + return Err(JsNativeError::reference() + .with_message(format!( + "cannot access '{}' before initialization", + self.interner().resolve_expect(binding_locator.name().sym()) + )) + .into()); } } Opcode::Jump => { @@ -1162,7 +1180,9 @@ impl Context { .set_private_element(name.sym(), PrivateElement::Field(value)); } Some(PrivateElement::Method(_)) => { - return self.throw_type_error("private method is not writable"); + return Err(JsNativeError::typ() + .with_message("private method is not writable") + .into()); } Some(PrivateElement::Accessor { setter: Some(setter), @@ -1173,14 +1193,20 @@ impl Context { setter.call(&object.clone().into(), &[value], self)?; } None => { - return self.throw_type_error("private field not defined"); + return Err(JsNativeError::typ() + .with_message("private field not defined") + .into()); } _ => { - return self.throw_type_error("private field defined without a setter"); + return Err(JsNativeError::typ() + .with_message("private field defined without a setter") + .into()); } } } else { - return self.throw_type_error("cannot set private property on non-object"); + return Err(JsNativeError::typ() + .with_message("cannot set private property on non-object") + .into()); } } Opcode::SetPrivateField => { @@ -1203,7 +1229,9 @@ impl Context { .set_private_element(name.sym(), PrivateElement::Field(value)); } } else { - return self.throw_type_error("cannot set private property on non-object"); + return Err(JsNativeError::typ() + .with_message("cannot set private property on non-object") + .into()); } } Opcode::SetPrivateMethod => { @@ -1217,7 +1245,9 @@ impl Context { object_borrow_mut .set_private_element(name.sym(), PrivateElement::Method(value.clone())); } else { - return self.throw_type_error("cannot set private setter on non-object"); + return Err(JsNativeError::typ() + .with_message("cannot set private setter on non-object") + .into()); } } Opcode::SetPrivateSetter => { @@ -1230,7 +1260,9 @@ impl Context { let mut object_borrow_mut = object.borrow_mut(); object_borrow_mut.set_private_element_setter(name.sym(), value.clone()); } else { - return self.throw_type_error("cannot set private setter on non-object"); + return Err(JsNativeError::typ() + .with_message("cannot set private setter on non-object") + .into()); } } Opcode::SetPrivateGetter => { @@ -1243,7 +1275,9 @@ impl Context { let mut object_borrow_mut = object.borrow_mut(); object_borrow_mut.set_private_element_getter(name.sym(), value.clone()); } else { - return self.throw_type_error("cannot set private getter on non-object"); + return Err(JsNativeError::typ() + .with_message("cannot set private getter on non-object") + .into()); } } Opcode::GetPrivateField => { @@ -1264,16 +1298,20 @@ impl Context { self.vm.push(value); } PrivateElement::Accessor { .. } => { - return self.throw_type_error( - "private property was defined without a getter", - ); + return Err(JsNativeError::typ() + .with_message("private property was defined without a getter") + .into()); } } } else { - return self.throw_type_error("private property does not exist"); + return Err(JsNativeError::typ() + .with_message("private property does not exist") + .into()); } } else { - return self.throw_type_error("cannot read private property from non-object"); + return Err(JsNativeError::typ() + .with_message("cannot read private property from non-object") + .into()); } } Opcode::PushClassField => { @@ -1393,7 +1431,9 @@ impl Context { let object = self.vm.pop(); let result = object.to_object(self)?.__delete__(&key, self)?; if !result && self.vm.frame().code.strict { - return Err(self.construct_type_error("Cannot delete property")); + return Err(JsNativeError::typ() + .with_message("Cannot delete property") + .into()); } self.vm.push(result); } @@ -1404,7 +1444,9 @@ impl Context { .to_object(self)? .__delete__(&key.to_property_key(self)?, self)?; if !result && self.vm.frame().code.strict { - return Err(self.construct_type_error("Cannot delete property")); + return Err(JsNativeError::typ() + .with_message("Cannot delete property") + .into()); } self.vm.push(result); } @@ -1435,7 +1477,7 @@ impl Context { } Opcode::Throw => { let value = self.vm.pop(); - return Err(value); + return Err(JsError::from_opaque(value)); } Opcode::TryStart => { let next = self.vm.read::(); @@ -1518,7 +1560,7 @@ impl Context { return Ok(ShouldExit::True); } FinallyReturn::Err => { - return Err(self.vm.pop()); + return Err(JsError::from_opaque(self.vm.pop())); } } } @@ -1535,13 +1577,7 @@ impl Context { let env = self.realm.environments.get_this_environment(); match env { EnvironmentSlots::Function(env) => { - let env_b = env.borrow(); - if let Some(this) = env_b.get_this_binding() { - self.vm.push(this); - } else { - drop(env_b); - return self.throw_reference_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor"); - } + self.vm.push(env.borrow().get_this_binding()?); } EnvironmentSlots::Global => { let this = self.realm.global_object(); @@ -1550,32 +1586,21 @@ impl Context { } } Opcode::Super => { - let env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super access must be in a function environment"); - - let home = if env.borrow().get_this_binding().is_some() { + let home = { + let env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super access must be in a function environment"); let env = env.borrow(); + let this = env.get_this_binding()?; let function_object = env.function_object().borrow(); let function = function_object .as_function() .expect("must be function object"); - let mut home_object = function.get_home_object().cloned(); - - if home_object.is_none() { - home_object = env - .get_this_binding() - .expect("can not get `this` object") - .as_object() - .cloned(); - } - home_object - } else { - return self.throw_range_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor"); + function.get_home_object().or(this.as_object()).cloned() }; if let Some(home) = home { @@ -1617,7 +1642,9 @@ impl Context { .expect("function object must have prototype"); if !super_constructor.is_constructor() { - return self.throw_type_error("super constructor object must be constructor"); + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); } let result = super_constructor.__construct__(&arguments, &new_target, self)?; @@ -1632,7 +1659,9 @@ impl Context { .expect("super call must be in function environment"); if !this_env.borrow_mut().bind_this_value(&result) { - return self.throw_reference_error("this already initialized"); + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); } self.vm.push(result); } @@ -1670,7 +1699,9 @@ impl Context { .expect("function object must have prototype"); if !super_constructor.is_constructor() { - return self.throw_type_error("super constructor object must be constructor"); + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); } let result = super_constructor.__construct__(&arguments, &new_target, self)?; @@ -1685,7 +1716,9 @@ impl Context { .expect("super call must be in function environment"); if !this_env.borrow_mut().bind_this_value(&result) { - return self.throw_reference_error("this already initialized"); + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); } self.vm.push(result); } @@ -1718,7 +1751,9 @@ impl Context { .expect("function object must have prototype"); if !super_constructor.is_constructor() { - return self.throw_type_error("super constructor object must be constructor"); + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); } let result = super_constructor.__construct__(&arguments, &new_target, self)?; @@ -1732,7 +1767,9 @@ impl Context { .as_function_slots() .expect("super call must be in function environment"); if !this_env.borrow_mut().bind_this_value(&result) { - return self.throw_reference_error("this already initialized"); + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); } self.vm.push(result); @@ -1779,7 +1816,9 @@ impl Context { } Opcode::CallEval => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } let argument_count = self.vm.read::(); let mut arguments = Vec::with_capacity(argument_count as usize); @@ -1793,7 +1832,11 @@ impl Context { let object = match func { JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => return self.throw_type_error("not a callable function"), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } }; // A native function with the name "eval" implies, that is this the built-in eval function. @@ -1816,7 +1859,9 @@ impl Context { } Opcode::CallEvalSpread => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } // Get the arguments that are stored as an array object on the stack. @@ -1836,7 +1881,11 @@ impl Context { let object = match func { JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => return self.throw_type_error("not a callable function"), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } }; // A native function with the name "eval" implies, that is this the built-in eval function. @@ -1859,7 +1908,9 @@ impl Context { } Opcode::Call => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } let argument_count = self.vm.read::(); let mut arguments = Vec::with_capacity(argument_count as usize); @@ -1873,7 +1924,11 @@ impl Context { let object = match func { JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => return self.throw_type_error("not a callable function"), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } }; let result = object.__call__(&this, &arguments, self)?; @@ -1882,7 +1937,9 @@ impl Context { } Opcode::CallSpread => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } // Get the arguments that are stored as an array object on the stack. @@ -1902,7 +1959,11 @@ impl Context { let object = match func { JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => return self.throw_type_error("not a callable function"), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } }; let result = object.__call__(&this, &arguments, self)?; @@ -1911,7 +1972,9 @@ impl Context { } Opcode::New => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } let argument_count = self.vm.read::(); let mut arguments = Vec::with_capacity(argument_count as usize); @@ -1923,14 +1986,20 @@ impl Context { let result = func .as_constructor() - .ok_or_else(|| self.construct_type_error("not a constructor")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("not a constructor") + .into() + }) .and_then(|cons| cons.__construct__(&arguments, cons, self))?; self.vm.push(result); } Opcode::NewSpread => { if self.vm.stack_size_limit <= self.vm.stack.len() { - return self.throw_range_error("Maximum call stack size exceeded"); + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); } // Get the arguments that are stored as an array object on the stack. let arguments_array = self.vm.pop(); @@ -1948,9 +2017,12 @@ impl Context { let result = func .as_constructor() - .ok_or_else(|| self.construct_type_error("not a constructor")) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("not a constructor") + .into() + }) .and_then(|cons| cons.__construct__(&arguments, cons, self))?; - self.vm.push(result); } Opcode::Return => { @@ -2057,7 +2129,9 @@ impl Context { .as_ref() .map(PropertyDescriptor::expect_value) .cloned() - .ok_or_else(|| self.construct_type_error("Could not find property `next`"))?; + .ok_or_else(|| { + JsNativeError::typ().with_message("Could not find property `next`") + })?; self.vm.push(iterator); self.vm.push(next_method); @@ -2181,7 +2255,9 @@ impl Context { let next_method_object = if let Some(object) = next_method.as_callable() { object } else { - return self.throw_type_error("iterable next method not a function"); + return Err(JsNativeError::typ() + .with_message("iterable next method not a function") + .into()); }; let iterator = self.vm.pop(); let next_result = next_method_object.call(&iterator, &[], self)?; @@ -2196,7 +2272,9 @@ impl Context { let next_result = if let Some(next_result) = next_result.as_object() { IteratorResult::new(next_result.clone()) } else { - return self.throw_type_error("next value should be an object"); + return Err(JsNativeError::typ() + .with_message("next value should be an object") + .into()); }; if next_result.complete(self)? { @@ -2228,16 +2306,20 @@ impl Context { } Opcode::RequireObjectCoercible => { let value = self.vm.pop(); - let value = value.require_object_coercible(self)?; + let value = value.require_object_coercible()?; self.vm.push(value); } Opcode::ValueNotNullOrUndefined => { let value = self.vm.pop(); if value.is_null() { - return self.throw_type_error("Cannot destructure 'null' value"); + return Err(JsNativeError::typ() + .with_message("Cannot destructure 'null' value") + .into()); } if value.is_undefined() { - return self.throw_type_error("Cannot destructure 'undefined' value"); + return Err(JsNativeError::typ() + .with_message("Cannot destructure 'undefined' value") + .into()); } self.vm.push(value); } @@ -2281,7 +2363,7 @@ impl Context { GeneratorResumeKind::Normal => return Ok(ShouldExit::False), GeneratorResumeKind::Throw => { let received = self.vm.pop(); - return Err(received); + return Err(JsError::from_opaque(received)); } GeneratorResumeKind::Return => { let mut finally_left = false; @@ -2308,7 +2390,7 @@ impl Context { let value = self.vm.pop(); if self.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { - return Err(value); + return Err(JsError::from_opaque(value)); } let completion = Ok(value); @@ -2336,9 +2418,11 @@ impl Context { if let Some(next) = gen.queue.front() { let (completion, r#return) = &next.completion; if *r#return { - match completion { - Ok(value) | Err(value) => self.vm.push(value), - } + let value = match completion { + Ok(value) => value.clone(), + Err(e) => e.clone().to_opaque(self), + }; + self.vm.push(value); self.vm.push(true); } else { self.vm.push(completion.clone()?); @@ -2369,7 +2453,8 @@ impl Context { let result = self.call(&next_method, &iterator.clone().into(), &[received])?; let result_object = result.as_object().ok_or_else(|| { - self.construct_type_error("generator next method returned non-object") + JsNativeError::typ() + .with_message("generator next method returned non-object") })?; let done = result_object.get("done", self)?.to_boolean(); if done { @@ -2390,9 +2475,8 @@ impl Context { if let Some(throw) = throw { let result = throw.call(&iterator.clone().into(), &[received], self)?; let result_object = result.as_object().ok_or_else(|| { - self.construct_type_error( - "generator throw method returned non-object", - ) + JsNativeError::typ() + .with_message("generator throw method returned non-object") })?; let done = result_object.get("done", self)?.to_boolean(); if done { @@ -2412,9 +2496,9 @@ impl Context { let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); iterator_record.close(Ok(JsValue::Undefined), self)?; - let error = - self.construct_type_error("iterator does not have a throw method"); - return Err(error); + return Err(JsNativeError::typ() + .with_message("iterator does not have a throw method") + .into()); } GeneratorResumeKind::Return => { let r#return = iterator.get_method("return", self)?; @@ -2422,9 +2506,8 @@ impl Context { let result = r#return.call(&iterator.clone().into(), &[received], self)?; let result_object = result.as_object().ok_or_else(|| { - self.construct_type_error( - "generator return method returned non-object", - ) + JsNativeError::typ() + .with_message("generator return method returned non-object") })?; let done = result_object.get("done", self)?.to_boolean(); if done { @@ -2742,12 +2825,14 @@ impl Context { self.vm.frame_mut().catch.pop(); self.vm.frame_mut().finally_return = FinallyReturn::Err; self.vm.frame_mut().thrown = true; + let e = e.to_opaque(self); self.vm.push(e); } else { self.vm.stack.truncate(start_stack_size); // Step 3.f in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart). if let Some(promise_capability) = promise_capability { + let e = e.to_opaque(self); promise_capability .reject() .call(&JsValue::undefined(), &[e.clone()], self) diff --git a/boa_engine/src/vm/tests.rs b/boa_engine/src/vm/tests.rs index 636e616a09a..cb5738afcfb 100644 --- a/boa_engine/src/vm/tests.rs +++ b/boa_engine/src/vm/tests.rs @@ -43,7 +43,14 @@ fn try_catch_finally_from_init() { } "#; - assert_eq!(Context::default().eval(source.as_bytes()), Err("h".into())); + assert_eq!( + Context::default() + .eval(source.as_bytes()) + .unwrap_err() + .as_opaque() + .unwrap(), + &"h".into() + ); } #[test] @@ -61,8 +68,8 @@ fn multiple_catches() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::Undefined) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::Undefined ); } @@ -80,8 +87,8 @@ fn use_last_expr_try_block() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::from("Hello!")) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::from("Hello!") ); } #[test] @@ -98,8 +105,8 @@ fn use_last_expr_catch_block() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::from("Hello!")) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::from("Hello!") ); } @@ -114,8 +121,8 @@ fn no_use_last_expr_finally_block() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::undefined()) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::undefined() ); } @@ -133,8 +140,8 @@ fn finally_block_binding_env() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::from("Hey hey people")) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::from("Hey hey people") ); } @@ -152,8 +159,8 @@ fn run_super_method_in_object() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::from("super")) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::from("super") ); } @@ -178,7 +185,7 @@ fn get_reference_by_super() { "#; assert_eq!( - Context::default().eval(source.as_bytes()), - Ok(JsValue::from("ab")) + Context::default().eval(source.as_bytes()).unwrap(), + JsValue::from("ab") ); } diff --git a/boa_examples/src/bin/classes.rs b/boa_examples/src/bin/classes.rs index a45eb3f5906..15b125a71dd 100644 --- a/boa_examples/src/bin/classes.rs +++ b/boa_examples/src/bin/classes.rs @@ -2,6 +2,7 @@ use boa_engine::{ builtins::JsArgs, class::{Class, ClassBuilder}, + error::JsNativeError, property::Attribute, Context, JsResult, JsString, JsValue, }; @@ -29,7 +30,7 @@ struct Person { // or any function that matches the required signature. impl Person { /// Says hello if `this` is a `Person` - fn say_hello(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + fn say_hello(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // We check if this is an object. if let Some(object) = this.as_object() { // If it is we downcast the type to type `Person`. @@ -45,7 +46,9 @@ impl Person { } // If `this` was not an object or the type of `this` was not a native object `Person`, // we throw a `TypeError`. - context.throw_type_error("'this' is not a Person object") + Err(JsNativeError::typ() + .with_message("'this' is not a Person object") + .into()) } } diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index 412336cd0d5..c6f059f03a9 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -6,11 +6,11 @@ use boa_engine::{ object::{FunctionBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, string::utf16, - Context, JsString, JsValue, + Context, JsError, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; -fn main() -> Result<(), JsValue> { +fn main() -> Result<(), JsError> { // We create a new `Context` to create a new Javascript executor. let mut context = Context::default(); diff --git a/boa_examples/src/bin/jsset.rs b/boa_examples/src/bin/jsset.rs index b9cc0f424bb..fbae5849d13 100644 --- a/boa_examples/src/bin/jsset.rs +++ b/boa_examples/src/bin/jsset.rs @@ -1,21 +1,21 @@ // This example shows how to manipulate a Javascript Set using Rust code. #![allow(clippy::bool_assert_comparison)] -use boa_engine::{object::builtins::JsSet, Context, JsValue}; +use boa_engine::{object::builtins::JsSet, Context, JsError, JsValue}; -fn main() -> Result<(), JsValue> { +fn main() -> Result<(), JsError> { // New `Context` for a new Javascript executor. let context = &mut Context::default(); // Create an empty set. let set = JsSet::new(context); - assert_eq!(set.size(context)?, 0); + assert_eq!(set.size()?, 0); set.add(5, context)?; - assert_eq!(set.size(context)?, 1); + assert_eq!(set.size()?, 1); set.add(10, context)?; - assert_eq!(set.size(context)?, 2); + assert_eq!(set.size()?, 2); set.clear(context)?; - assert_eq!(set.size(context)?, 0); + assert_eq!(set.size()?, 0); set.add("one", context)?; set.add("two", context)?; @@ -32,7 +32,7 @@ fn main() -> Result<(), JsValue> { assert_eq!(set.has("one", context)?, false); assert_eq!(set.has("three", context)?, false); - assert_eq!(set.size(context)?, 0); + assert_eq!(set.size()?, 0); // Add a slice into a set; set.add_items( @@ -40,12 +40,12 @@ fn main() -> Result<(), JsValue> { context, )?; // Will return 1, as one slice was added. - assert_eq!(set.size(context)?, 1); + assert_eq!(set.size()?, 1); // Make a new set from a slice let slice_set = JsSet::from_iter([JsValue::new(1), JsValue::new(2), JsValue::new(3)], context); // Will return 3, as each element of slice was added into the set. - assert_eq!(slice_set.size(context)?, 3); + assert_eq!(slice_set.size()?, 3); set.clear(context)?; diff --git a/boa_examples/src/bin/loadfile.rs b/boa_examples/src/bin/loadfile.rs index f6c92a1dce3..595e863f7c2 100644 --- a/boa_examples/src/bin/loadfile.rs +++ b/boa_examples/src/bin/loadfile.rs @@ -22,7 +22,7 @@ fn main() { } Err(e) => { // Pretty print the error - eprintln!("Uncaught {}", e.display()); + eprintln!("Uncaught {e}"); } }; } diff --git a/boa_examples/src/bin/loadstring.rs b/boa_examples/src/bin/loadstring.rs index ed9e9843d9d..5a47cfb2a98 100644 --- a/boa_examples/src/bin/loadstring.rs +++ b/boa_examples/src/bin/loadstring.rs @@ -18,7 +18,7 @@ fn main() { } Err(e) => { // Pretty print the error - eprintln!("Uncaught {}", e.display()); + eprintln!("Uncaught {e}"); } }; } diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index 57eb9bf08d1..8cdb1118cbb 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -2,7 +2,7 @@ use boa_engine::{ builtins::JsArgs, object::{JsObject, ObjectInitializer}, property::Attribute, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, }; /// Initializes the object in the context. @@ -40,24 +40,18 @@ fn create_realm(_this: &JsValue, _: &[JsValue], _context: &mut Context) -> JsRes /// The `$262.detachArrayBuffer()` function. /// /// Implements the `DetachArrayBuffer` abstract operation. -fn detach_array_buffer( - _this: &JsValue, - args: &[JsValue], - context: &mut Context, -) -> JsResult { +fn detach_array_buffer(_this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { #[inline] - fn type_err(context: &mut Context) -> JsValue { - context.construct_type_error("The provided object was not an ArrayBuffer") + fn type_err() -> JsNativeError { + JsNativeError::typ().with_message("The provided object was not an ArrayBuffer") } let array_buffer = args .get(0) .and_then(JsValue::as_object) - .ok_or_else(|| type_err(context))?; + .ok_or_else(type_err)?; let mut array_buffer = array_buffer.borrow_mut(); - let array_buffer = array_buffer - .as_array_buffer_mut() - .ok_or_else(|| type_err(context))?; + let array_buffer = array_buffer.as_array_buffer_mut().ok_or_else(type_err)?; // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. TODO // 2. If key is not present, set key to undefined. @@ -65,7 +59,9 @@ fn detach_array_buffer( // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) { - return context.throw_type_error("Cannot detach array buffer with different key"); + return Err(JsNativeError::typ() + .with_message("Cannot detach array buffer with different key") + .into()); } // 4. Set arrayBuffer.[[ArrayBufferData]] to null. @@ -85,7 +81,9 @@ fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsRe if let Some(source_text) = args.get(0).and_then(JsValue::as_string) { match context.parse(source_text.to_std_string_escaped()) { // TODO: check strict - Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {e}")), + Err(e) => Err(JsNativeError::typ() + .with_message(format!("Uncaught Syntax Error: {e}")) + .into()), // Calling eval here parses the code a second time. // TODO: We can fix this after we have have defined the public api for the vm executer. Ok(_) => context.eval(source_text.to_std_string_escaped()), diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 8a38ebda42e..40a1dc223cc 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -2,13 +2,15 @@ mod js262; +use crate::read::ErrorType; + use super::{ Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult, TestSuite, IGNORED, }; use boa_engine::{ builtins::JsArgs, object::FunctionBuilder, property::Attribute, syntax::Parser, Context, - JsResult, JsValue, + JsNativeErrorKind, JsResult, JsValue, }; use boa_gc::{Cell, Finalize, Gc, Trace}; use colored::Colorize; @@ -181,7 +183,7 @@ impl Test { && matches!(*callback_obj.result.borrow(), Some(true) | None); let text = match res { Ok(val) => val.display().to_string(), - Err(e) => format!("Uncaught {}", e.display()), + Err(e) => format!("Uncaught {e}",), }; (passed, text) @@ -191,11 +193,11 @@ impl Test { } Outcome::Negative { phase: Phase::Parse | Phase::Early, - ref error_type, + error_type, } => { assert_eq!( - error_type.as_ref(), - "SyntaxError", + error_type, + ErrorType::SyntaxError, "non-SyntaxError parsing/early error found in {}", self.name ); @@ -215,7 +217,7 @@ impl Test { } => todo!("check module resolution errors"), Outcome::Negative { phase: Phase::Runtime, - ref error_type, + error_type, } => { let mut context = Context::default(); if let Err(e) = Parser::new(test_content.as_bytes()).parse_all(&mut context) { @@ -226,13 +228,45 @@ impl Test { Ok(_) => match context.eval(&test_content) { Ok(res) => (false, res.display().to_string()), Err(e) => { - let passed = e - .display() - .internals(true) - .to_string() - .contains(error_type.as_ref()); - - (passed, format!("Uncaught {}", e.display())) + let passed = if let Ok(e) = e.try_native(&mut context) { + match &e.kind { + JsNativeErrorKind::Syntax + if error_type == ErrorType::SyntaxError => + { + true + } + JsNativeErrorKind::Reference + if error_type == ErrorType::ReferenceError => + { + true + } + JsNativeErrorKind::Range + if error_type == ErrorType::RangeError => + { + true + } + JsNativeErrorKind::Type + if error_type == ErrorType::TypeError => + { + true + } + _ => false, + } + } else { + e.as_opaque() + .expect("try_native cannot fail if e is not opaque") + .as_object() + .and_then(|o| o.get("constructor", &mut context).ok()) + .as_ref() + .and_then(JsValue::as_object) + .and_then(|o| o.get("name", &mut context).ok()) + .as_ref() + .and_then(JsValue::as_string) + .map(|s| s == error_type.as_str()) + .unwrap_or_default() + }; + + (passed, format!("Uncaught {e}")) } }, Err(e) => (false, e), @@ -331,15 +365,15 @@ impl Test { context .eval(harness.assert.as_ref()) - .map_err(|e| format!("could not run assert.js:\n{}", e.display()))?; + .map_err(|e| format!("could not run assert.js:\n{e}"))?; context .eval(harness.sta.as_ref()) - .map_err(|e| format!("could not run sta.js:\n{}", e.display()))?; + .map_err(|e| format!("could not run sta.js:\n{e}"))?; if self.flags.contains(TestFlags::ASYNC) { context .eval(harness.doneprint_handle.as_ref()) - .map_err(|e| format!("could not run doneprintHandle.js:\n{}", e.display()))?; + .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; } for include in self.includes.iter() { @@ -351,12 +385,7 @@ impl Test { .ok_or_else(|| format!("could not find the {include} include file."))? .as_ref(), ) - .map_err(|e| { - format!( - "could not run the {include} include file:\nUncaught {}", - e.display() - ) - })?; + .map_err(|e| format!("could not run the {include} include file:\nUncaught {e}"))?; } Ok(()) diff --git a/boa_tester/src/main.rs b/boa_tester/src/main.rs index 080da9ef40d..2d2214af5e4 100644 --- a/boa_tester/src/main.rs +++ b/boa_tester/src/main.rs @@ -77,6 +77,7 @@ use clap::{ArgAction, Parser, ValueHint}; use colored::Colorize; use fxhash::{FxHashMap, FxHashSet}; use once_cell::sync::Lazy; +use read::ErrorType; use serde::{Deserialize, Serialize}; use std::{ fs, @@ -456,7 +457,7 @@ impl Test { #[derive(Debug, Clone)] enum Outcome { Positive, - Negative { phase: Phase, error_type: Box }, + Negative { phase: Phase, error_type: ErrorType }, } impl Default for Outcome { diff --git a/boa_tester/src/read.rs b/boa_tester/src/read.rs index 86cadb7221a..e198b64220a 100644 --- a/boa_tester/src/read.rs +++ b/boa_tester/src/read.rs @@ -33,7 +33,30 @@ pub(super) struct MetaData { pub(super) struct Negative { pub(super) phase: Phase, #[serde(rename = "type")] - pub(super) error_type: Box, + pub(super) error_type: ErrorType, +} + +/// All possible error types +#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +#[allow(clippy::enum_variant_names)] // Better than appending `rename` to all variants +pub(super) enum ErrorType { + Test262Error, + SyntaxError, + ReferenceError, + RangeError, + TypeError, +} + +impl ErrorType { + pub(super) fn as_str(self) -> &'static str { + match self { + ErrorType::Test262Error => "Test262Error", + ErrorType::SyntaxError => "SyntaxError", + ErrorType::ReferenceError => "ReferenceError", + ErrorType::RangeError => "RangeError", + ErrorType::TypeError => "TypeError", + } + } } /// Individual test flag. diff --git a/boa_wasm/src/lib.rs b/boa_wasm/src/lib.rs index fe37e482953..bfb70791e4a 100644 --- a/boa_wasm/src/lib.rs +++ b/boa_wasm/src/lib.rs @@ -67,6 +67,6 @@ pub fn evaluate(src: &str) -> Result { // Setup executor Context::default() .eval(src) - .map_err(|e| JsValue::from(format!("Uncaught {}", e.display()))) + .map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map(|v| v.display().to_string()) }