Browse Source

Added `TypeError` implementation (#442)

pull/455/head
HalidOdat 4 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. 25
      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 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() {

19
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(),

5
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()`

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
// 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<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.
pub(crate) fn init(global: &Value) {
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 {
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) => {

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

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

15
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));

4
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"),
}
}

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 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())
}

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::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)

25
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");

Loading…
Cancel
Save