Browse Source

Added `TypeError` implementation (#442)

pull/455/head
HalidOdat 5 years ago committed by GitHub
parent
commit
bb2b6f638c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      boa/src/builtins/array/mod.rs
  2. 19
      boa/src/builtins/bigint/mod.rs
  3. 5
      boa/src/builtins/error/mod.rs
  4. 22
      boa/src/builtins/error/range.rs
  5. 83
      boa/src/builtins/error/type.rs
  6. 2
      boa/src/builtins/function/mod.rs
  7. 2
      boa/src/builtins/json/tests.rs
  8. 3
      boa/src/builtins/mod.rs
  9. 15
      boa/src/builtins/number/mod.rs
  10. 4
      boa/src/builtins/string/mod.rs
  11. 36
      boa/src/exec/exception.rs
  12. 11
      boa/src/exec/mod.rs
  13. 5
      boa/src/exec/operator/mod.rs
  14. 15
      boa/src/exec/tests.rs

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

@ -15,7 +15,6 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
error::RangeError,
object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property, property::Property,
value::{same_value_zero, ResultValue, Value, ValueData}, value::{same_value_zero, ResultValue, Value, ValueData},
@ -130,7 +129,7 @@ impl Array {
} }
} }
1 if args[0].is_double() => { 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() { for (n, value) in args.iter().enumerate() {

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

@ -16,7 +16,6 @@ use crate::{
builtins::{ builtins::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
value::{ResultValue, Value}, value::{ResultValue, Value},
RangeError,
}, },
exec::Interpreter, exec::Interpreter,
syntax::ast::bigint::BigInt as AstBigInt, syntax::ast::bigint::BigInt as AstBigInt,
@ -50,13 +49,11 @@ impl BigInt {
if let Some(bigint) = value.to_bigint() { if let Some(bigint) = value.to_bigint() {
Value::from(bigint) Value::from(bigint)
} else { } else {
return Err(RangeError::run_new( let message = format!(
format!( "{} can't be converted to BigInt because it isn't an integer",
"{} can't be converted to BigInt because it isn't an integer", ctx.to_string(value)?
ctx.to_string(value)? );
), return ctx.throw_range_error(message);
ctx,
)?);
} }
} }
None => Value::from(AstBigInt::from(0)), None => Value::from(AstBigInt::from(0)),
@ -98,10 +95,8 @@ impl BigInt {
10 10
}; };
if radix < 2 && radix > 36 { if radix < 2 && radix > 36 {
return Err(RangeError::run_new( return ctx
"radix must be an integer at least 2 and no greater than 36", .throw_range_error("radix must be an integer at least 2 and no greater than 36");
ctx,
)?);
} }
Ok(Value::from(Self::to_native_string_radix( Ok(Value::from(Self::to_native_string_radix(
&this.to_bigint().unwrap(), &this.to_bigint().unwrap(),

5
boa/src/builtins/error/mod.rs

@ -23,9 +23,10 @@ use crate::{
pub(crate) mod range; pub(crate) mod range;
// mod reference; // mod reference;
// mod syntax; // mod syntax;
// mod type_err; pub(crate) mod r#type;
// mod uri; // mod uri;
pub(crate) use self::r#type::TypeError;
pub(crate) use self::range::RangeError; pub(crate) use self::range::RangeError;
/// Built-in `Error` object. /// Built-in `Error` object.
@ -48,7 +49,7 @@ impl Error {
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error); this.set_kind(ObjectKind::Error);
Ok(Value::undefined()) Err(this.clone())
} }
/// `Error.prototype.toString()` /// `Error.prototype.toString()`

22
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 // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error); this.set_kind(ObjectKind::Error);
Ok(Value::undefined()) Err(this.clone())
} }
/// `Error.prototype.toString()` /// `Error.prototype.toString()`
@ -69,26 +69,6 @@ impl RangeError {
make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true) make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true)
} }
/// Runs a `new RangeError(message)`.
pub(crate) fn run_new<M>(message: M, interpreter: &mut Interpreter) -> ResultValue
where
M: Into<String>,
{
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. /// Initialise the global object with the `RangeError` object.
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) {
global.set_field("RangeError", Self::create(global)); global.set_field("RangeError", Self::create(global));

83
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));
}
}

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

@ -224,7 +224,7 @@ impl Function {
if self.constructable { if self.constructable {
match self.body { match self.body {
FunctionBody::BuiltIn(func) => { FunctionBody::BuiltIn(func) => {
func(this_obj, args_list, interpreter).unwrap(); func(this_obj, args_list, interpreter)?;
Ok(this_obj.clone()) Ok(this_obj.clone())
} }
FunctionBody::Ordinary(ref body) => { FunctionBody::Ordinary(ref body) => {

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

@ -170,7 +170,7 @@ fn json_stringify_function_replacer_propogate_error() {
thrown thrown
"#, "#,
); );
let expected = forward(&mut engine, r#"1"#); let expected = forward(&mut engine, "1");
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }

3
boa/src/builtins/mod.rs

@ -20,7 +20,7 @@ pub(crate) use self::{
array::Array, array::Array,
bigint::BigInt, bigint::BigInt,
boolean::Boolean, boolean::Boolean,
error::{Error, RangeError}, error::{Error, RangeError, TypeError},
number::Number, number::Number,
regexp::RegExp, regexp::RegExp,
string::String, string::String,
@ -44,4 +44,5 @@ pub fn init(global: &Value) {
console::init(global); console::init(global);
Error::init(global); Error::init(global);
RangeError::init(global); RangeError::init(global);
TypeError::init(global);
} }

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

@ -24,12 +24,11 @@ use crate::{
builtins::{ builtins::{
object::internal_methods_trait::ObjectInternalMethods, object::internal_methods_trait::ObjectInternalMethods,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
RangeError,
}, },
exec::Interpreter, exec::Interpreter,
}; };
use num_traits::float::FloatCore; use num_traits::float::FloatCore;
use std::{borrow::Borrow, f64, ops::Deref}; use std::{borrow::Borrow, ops::Deref};
const BUF_SIZE: usize = 2200; const BUF_SIZE: usize = 2200;
@ -354,10 +353,8 @@ impl Number {
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix < 2 || radix > 36 { if radix < 2 || radix > 36 {
return Err(RangeError::run_new( return ctx
"radix must be an integer at least 2 and no greater than 36", .throw_range_error("radix must be an integer at least 2 and no greater than 36");
ctx,
)?);
} }
if x == -0. { if x == -0. {
@ -423,11 +420,11 @@ impl Number {
// Constants from: // Constants from:
// https://tc39.es/ecma262/#sec-properties-of-the-number-constructor // 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("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("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("MAX_VALUE", Value::from(f64::MAX));
number.set_field("MIN_VALUE", Value::from(std::f64::MIN)); number.set_field("MIN_VALUE", Value::from(f64::MIN));
number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY));
number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY));
number.set_field("NaN", Value::from(f64::NAN)); number.set_field("NaN", Value::from(f64::NAN));

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

@ -68,12 +68,12 @@ impl String {
/// Get the string value to a primitive string /// Get the string value to a primitive string
#[allow(clippy::wrong_self_convention)] #[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 // Get String from String Object and send it back as a new value
match this.get_internal_slot("StringData").data() { match this.get_internal_slot("StringData").data() {
ValueData::String(ref string) => Ok(Value::from(string.clone())), ValueData::String(ref string) => Ok(Value::from(string.clone())),
// Throw expection here: // Throw expection here:
_ => panic!("TypeError: this is not a string"), _ => ctx.throw_type_error("'this' is not a string"),
} }
} }

36
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<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
// 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<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
// Runs a `new TypeError(message)`.
New::from(Call::new(
Identifier::from("TypeError"),
vec![Const::from(message.into()).into()],
))
.run(self)
}
}

11
boa/src/exec/mod.rs

@ -3,13 +3,15 @@
mod array; mod array;
mod block; mod block;
mod declaration; mod declaration;
mod exception;
mod expression; mod expression;
mod iteration; mod iteration;
mod operator; mod operator;
mod statement_list; mod statement_list;
mod try_node;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod try_node;
use crate::{ use crate::{
builtins::{ builtins::{
@ -140,7 +142,10 @@ impl Interpreter {
ValueData::Rational(rational) => Ok(Number::to_native_string(*rational)), ValueData::Rational(rational) => Ok(Number::to_native_string(*rational)),
ValueData::Integer(integer) => Ok(integer.to_string()), ValueData::Integer(integer) => Ok(integer.to_string()),
ValueData::String(string) => Ok(string.clone()), 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::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)),
ValueData::Object(_) => { ValueData::Object(_) => {
let primitive = self.to_primitive(&mut value.clone(), Some("string")); let primitive = self.to_primitive(&mut value.clone(), Some("string"));
@ -261,7 +266,7 @@ impl Interpreter {
/// https://tc39.es/ecma262/#sec-toobject /// https://tc39.es/ecma262/#sec-toobject
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue {
match *value.deref().borrow() { match value.data() {
ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => {
Err(Value::undefined()) Err(Value::undefined())
} }

5
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::LessThanOrEqual => v_a.to_number() <= v_b.to_number(),
CompOp::In => { CompOp::In => {
if !v_b.is_object() { 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)?; let key = interpreter.to_property_key(&mut v_a)?;
interpreter.has_property(&mut v_b, &key) interpreter.has_property(&mut v_b, &key)

15
boa/src/exec/tests.rs

@ -609,12 +609,21 @@ mod in_operator {
} }
#[test] #[test]
#[should_panic(expected = "TypeError: undefined is not an Object.")]
fn should_type_error_when_rhs_not_object() { fn should_type_error_when_rhs_not_object() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let scenario = r#" 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] #[test]

Loading…
Cancel
Save