From bb2b6f638cba59269694d44424d16c9a5ede6a81 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 2 Jun 2020 16:52:28 +0200 Subject: [PATCH] Added `TypeError` implementation (#442) --- boa/src/builtins/array/mod.rs | 3 +- boa/src/builtins/bigint/mod.rs | 19 +++----- boa/src/builtins/error/mod.rs | 5 +- boa/src/builtins/error/range.rs | 22 +-------- boa/src/builtins/error/type.rs | 83 ++++++++++++++++++++++++++++++++ boa/src/builtins/function/mod.rs | 2 +- boa/src/builtins/json/tests.rs | 2 +- boa/src/builtins/mod.rs | 3 +- boa/src/builtins/number/mod.rs | 15 +++--- boa/src/builtins/string/mod.rs | 4 +- boa/src/exec/exception.rs | 36 ++++++++++++++ boa/src/exec/mod.rs | 11 +++-- boa/src/exec/operator/mod.rs | 5 +- boa/src/exec/tests.rs | 25 +++++++--- 14 files changed, 172 insertions(+), 63 deletions(-) create mode 100644 boa/src/builtins/error/type.rs create mode 100644 boa/src/exec/exception.rs diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 44c23e5345..7dd3461116 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -15,7 +15,6 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - error::RangeError, object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value_zero, ResultValue, Value, ValueData}, @@ -130,7 +129,7 @@ impl Array { } } 1 if args[0].is_double() => { - return Err(RangeError::run_new("invalid array length", ctx)?); + return ctx.throw_range_error("invalid array length"); } _ => { for (n, value) in args.iter().enumerate() { diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 5b38583396..fabb2be33f 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -16,7 +16,6 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, value::{ResultValue, Value}, - RangeError, }, exec::Interpreter, syntax::ast::bigint::BigInt as AstBigInt, @@ -50,13 +49,11 @@ impl BigInt { if let Some(bigint) = value.to_bigint() { Value::from(bigint) } else { - return Err(RangeError::run_new( - format!( - "{} can't be converted to BigInt because it isn't an integer", - ctx.to_string(value)? - ), - ctx, - )?); + let message = format!( + "{} can't be converted to BigInt because it isn't an integer", + ctx.to_string(value)? + ); + return ctx.throw_range_error(message); } } None => Value::from(AstBigInt::from(0)), @@ -98,10 +95,8 @@ impl BigInt { 10 }; if radix < 2 && radix > 36 { - return Err(RangeError::run_new( - "radix must be an integer at least 2 and no greater than 36", - ctx, - )?); + return ctx + .throw_range_error("radix must be an integer at least 2 and no greater than 36"); } Ok(Value::from(Self::to_native_string_radix( &this.to_bigint().unwrap(), diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 328edc50bf..d142994c7d 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -23,9 +23,10 @@ use crate::{ pub(crate) mod range; // mod reference; // mod syntax; -// mod type_err; +pub(crate) mod r#type; // mod uri; +pub(crate) use self::r#type::TypeError; pub(crate) use self::range::RangeError; /// Built-in `Error` object. @@ -48,7 +49,7 @@ impl Error { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) this.set_kind(ObjectKind::Error); - Ok(Value::undefined()) + Err(this.clone()) } /// `Error.prototype.toString()` diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 34e4c88ff7..afd85021ee 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -39,7 +39,7 @@ impl RangeError { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) this.set_kind(ObjectKind::Error); - Ok(Value::undefined()) + Err(this.clone()) } /// `Error.prototype.toString()` @@ -69,26 +69,6 @@ impl RangeError { make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true) } - /// Runs a `new RangeError(message)`. - pub(crate) fn run_new(message: M, interpreter: &mut Interpreter) -> ResultValue - where - M: Into, - { - use crate::{ - exec::Executable, - syntax::ast::{ - node::{Call, Identifier, New}, - Const, - }, - }; - - New::from(Call::new( - Identifier::from("RangeError"), - vec![Const::from(message.into()).into()], - )) - .run(interpreter) - } - /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) { global.set_field("RangeError", Self::create(global)); diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs new file mode 100644 index 0000000000..f15c39cd7f --- /dev/null +++ b/boa/src/builtins/error/type.rs @@ -0,0 +1,83 @@ +//! This module implements the global `TypeError` object. +//! +//! The `TypeError` object represents an error when an operation could not be performed, +//! typically (but not exclusively) when a value is not of the expected type. +//! +//! A `TypeError` may be thrown when: +//! - an operand or argument passed to a function is incompatible with the type expected by that operator or function. +//! - when attempting to modify a value that cannot be changed. +//! - when attempting to use a value in an inappropriate way. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [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 + +use crate::{ + builtins::{ + function::make_builtin_fn, + function::make_constructor_fn, + object::ObjectKind, + value::{ResultValue, Value}, + }, + exec::Interpreter, +}; + +/// JavaScript `TypeError` implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct TypeError; + +impl TypeError { + /// Create a new error object. + pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + if !args.is_empty() { + this.set_field( + "message", + Value::from( + args.get(0) + .expect("failed getting error message") + .to_string(), + ), + ); + } + + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_kind(ObjectKind::Error); + Err(this.clone()) + } + + /// `Error.prototype.toString()` + /// + /// The toString() method returns a string representing the specified Error object. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let name = this.get_field("name"); + let message = this.get_field("message"); + Ok(Value::from(format!("{}: {}", name, message))) + } + + /// Create a new `RangeError` object. + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_field("message", Value::from("")); + + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + + make_constructor_fn("TypeError", 1, Self::make_error, global, prototype, true) + } + + /// Initialise the global object with the `RangeError` object. + pub(crate) fn init(global: &Value) { + global.set_field("TypeError", Self::create(global)); + } +} diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 2886b97051..bea69bbbf8 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -224,7 +224,7 @@ impl Function { if self.constructable { match self.body { FunctionBody::BuiltIn(func) => { - func(this_obj, args_list, interpreter).unwrap(); + func(this_obj, args_list, interpreter)?; Ok(this_obj.clone()) } FunctionBody::Ordinary(ref body) => { diff --git a/boa/src/builtins/json/tests.rs b/boa/src/builtins/json/tests.rs index 90ceb1dd2f..2358f11b1b 100644 --- a/boa/src/builtins/json/tests.rs +++ b/boa/src/builtins/json/tests.rs @@ -170,7 +170,7 @@ fn json_stringify_function_replacer_propogate_error() { thrown "#, ); - let expected = forward(&mut engine, r#"1"#); + let expected = forward(&mut engine, "1"); assert_eq!(actual, expected); } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 60a93cc9eb..2d7ecdcc1a 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -20,7 +20,7 @@ pub(crate) use self::{ array::Array, bigint::BigInt, boolean::Boolean, - error::{Error, RangeError}, + error::{Error, RangeError, TypeError}, number::Number, regexp::RegExp, string::String, @@ -44,4 +44,5 @@ pub fn init(global: &Value) { console::init(global); Error::init(global); RangeError::init(global); + TypeError::init(global); } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 4683a192b3..d51ac0f996 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -24,12 +24,11 @@ use crate::{ builtins::{ object::internal_methods_trait::ObjectInternalMethods, value::{ResultValue, Value, ValueData}, - RangeError, }, exec::Interpreter, }; use num_traits::float::FloatCore; -use std::{borrow::Borrow, f64, ops::Deref}; +use std::{borrow::Borrow, ops::Deref}; const BUF_SIZE: usize = 2200; @@ -354,10 +353,8 @@ impl Number { // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. if radix < 2 || radix > 36 { - return Err(RangeError::run_new( - "radix must be an integer at least 2 and no greater than 36", - ctx, - )?); + return ctx + .throw_range_error("radix must be an integer at least 2 and no greater than 36"); } if x == -0. { @@ -423,11 +420,11 @@ impl Number { // Constants from: // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor - number.set_field("EPSILON", Value::from(std::f64::EPSILON)); + number.set_field("EPSILON", Value::from(f64::EPSILON)); number.set_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); number.set_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); - number.set_field("MAX_VALUE", Value::from(std::f64::MAX)); - number.set_field("MIN_VALUE", Value::from(std::f64::MIN)); + number.set_field("MAX_VALUE", Value::from(f64::MAX)); + number.set_field("MIN_VALUE", Value::from(f64::MIN)); number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); number.set_field("NaN", Value::from(f64::NAN)); diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index fb4efc76b2..73833263f6 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -68,12 +68,12 @@ impl String { /// Get the string value to a primitive string #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value match this.get_internal_slot("StringData").data() { ValueData::String(ref string) => Ok(Value::from(string.clone())), // Throw expection here: - _ => panic!("TypeError: this is not a string"), + _ => ctx.throw_type_error("'this' is not a string"), } } diff --git a/boa/src/exec/exception.rs b/boa/src/exec/exception.rs new file mode 100644 index 0000000000..ddef2e82ee --- /dev/null +++ b/boa/src/exec/exception.rs @@ -0,0 +1,36 @@ +use super::*; +use crate::{ + exec::Executable, + syntax::ast::{ + node::{Call, Identifier, New}, + Const, + }, +}; + +impl Interpreter { + /// Throws a `RangeError` with the specified message. + pub fn throw_range_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + // Runs a `new RangeError(message)`. + New::from(Call::new( + Identifier::from("RangeError"), + vec![Const::from(message.into()).into()], + )) + .run(self) + } + + /// Throws a `TypeError` with the specified message. + pub fn throw_type_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + // Runs a `new TypeError(message)`. + New::from(Call::new( + Identifier::from("TypeError"), + vec![Const::from(message.into()).into()], + )) + .run(self) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 12e43fefc4..e08ce8b701 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -3,13 +3,15 @@ mod array; mod block; mod declaration; +mod exception; mod expression; mod iteration; mod operator; mod statement_list; +mod try_node; + #[cfg(test)] mod tests; -mod try_node; use crate::{ builtins::{ @@ -140,7 +142,10 @@ impl Interpreter { ValueData::Rational(rational) => Ok(Number::to_native_string(*rational)), ValueData::Integer(integer) => Ok(integer.to_string()), ValueData::String(string) => Ok(string.clone()), - ValueData::Symbol(_) => panic!("TypeError exception."), + ValueData::Symbol(_) => { + self.throw_type_error("can't convert symbol to string")?; + unreachable!(); + } ValueData::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)), ValueData::Object(_) => { let primitive = self.to_primitive(&mut value.clone(), Some("string")); @@ -261,7 +266,7 @@ impl Interpreter { /// https://tc39.es/ecma262/#sec-toobject #[allow(clippy::wrong_self_convention)] pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { - match *value.deref().borrow() { + match value.data() { ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { Err(Value::undefined()) } diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index 325142dddd..83bad26f0a 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -87,7 +87,10 @@ impl Executable for BinOp { CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(), CompOp::In => { if !v_b.is_object() { - panic!("TypeError: {} is not an Object.", v_b); + return interpreter.throw_type_error(format!( + "right-hand side of 'in' should be an object, got {}", + v_b.get_type() + )); } let key = interpreter.to_property_key(&mut v_a)?; interpreter.has_property(&mut v_b, &key) diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 105e34b7f2..a09c98904c 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -609,12 +609,21 @@ mod in_operator { } #[test] - #[should_panic(expected = "TypeError: undefined is not an Object.")] fn should_type_error_when_rhs_not_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let scenario = r#" - 'fail' in undefined + var x = false; + try { + 'fail' in undefined + } catch(e) { + x = true; + } "#; - exec(scenario); + + forward(&mut engine, scenario); + assert_eq!(forward(&mut engine, "x"), "true"); } #[test] @@ -627,7 +636,7 @@ mod in_operator { this.a = "a"; this.b = "b"; } - + var bar = new Foo(); "#; forward(&mut engine, scenario); @@ -642,7 +651,7 @@ mod in_operator { let mut engine = Interpreter::new(realm); let scenario = r#" - function Foo() {} + function Foo() {} var bar = new Foo(); "#; forward(&mut engine, scenario); @@ -655,7 +664,7 @@ mod in_operator { fn var_decl_hoisting() { let scenario = r#" x = 5; - + var x; x; "#; @@ -729,7 +738,7 @@ fn function_decl_hoisting() { function a() {return 5} function b() {return a()} - + x; "#; assert_eq!(&exec(scenario), "5"); @@ -739,7 +748,7 @@ fn function_decl_hoisting() { function b() {return a()} function a() {return 5} - + x; "#; assert_eq!(&exec(scenario), "5");