Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing Number methods. #562

Merged
merged 5 commits into from
Jul 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,188 @@ impl Number {
}
}

/// Builtin javascript 'isFinite(number)' function.
///
/// Converts the argument to a number, throwing a type error if the conversion is invalid.
///
/// If the number is NaN, +∞, or -∞ false is returned.
///
/// Otherwise true is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isfinite-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite
pub(crate) fn global_is_finite(
_this: &Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
Ok(number.is_finite().into())
} else {
Ok(false.into())
}
}

/// Builtin javascript 'isNaN(number)' function.
///
/// Converts the argument to a number, throwing a type error if the conversion is invalid.
///
/// If the number is NaN true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN
pub(crate) fn global_is_nan(
_this: &Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
Ok(number.is_nan().into())
} else {
Ok(true.into())
}
}

/// `Number.isFinite( number )`
///
/// Checks if the argument is a number, returning false if it isn't.
///
/// If the number is NaN, +∞, or -∞ false is returned.
///
/// Otherwise true is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.isfinite
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
pub(crate) fn number_is_finite(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(if let Some(val) = args.get(0) {
match val {
Value::Integer(_) => true,
Value::Rational(number) => number.is_finite(),
_ => false,
}
} else {
false
}))
}

/// `Number.isInteger( number )`
///
/// Checks if the argument is an integer.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.isinteger
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
pub(crate) fn number_is_integer(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(args.get(0).map_or(false, Self::is_integer).into())
joshwd36 marked this conversation as resolved.
Show resolved Hide resolved
}

/// `Number.isNaN( number )`
///
/// Checks if the argument is a number, returning false if it isn't.
///
/// If the number is NaN true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
pub(crate) fn number_is_nan(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(if let Some(val) = args.get(0) {
match val {
Value::Integer(_) => false,
Value::Rational(number) => number.is_nan(),
_ => false,
}
} else {
false
}))
}

/// `Number.isSafeInteger( number )`
///
/// Checks if the argument is an integer, returning false if it isn't.
///
/// If abs(number) ≤ MAX_SAFE_INTEGER true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
pub(crate) fn is_safe_integer(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(match args.get(0) {
Some(Value::Integer(_)) => true,
Some(Value::Rational(number)) if Self::is_float_integer(*number) => {
number.abs() <= Number::MAX_SAFE_INTEGER
}
_ => false,
}))
}

/// Checks if the argument is a finite integer Number value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isinteger
#[inline]
pub(crate) fn is_integer(val: &Value) -> bool {
match val {
Value::Integer(_) => true,
Value::Rational(number) => Number::is_float_integer(*number),
_ => false,
}
}

/// Checks if the float argument is an integer.
#[inline]
#[allow(clippy::float_cmp)]
pub(crate) fn is_float_integer(number: f64) -> bool {
number.is_finite() && number.abs().floor() == number.abs()
}

/// Initialise the `Number` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
Expand All @@ -577,6 +759,9 @@ impl Number {
PARSE_FLOAT_MAX_ARG_COUNT,
);

make_builtin_fn(Self::global_is_finite, "isFinite", global, 1);
make_builtin_fn(Self::global_is_nan, "isNaN", global, 1);

let number_object = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Expand All @@ -586,6 +771,11 @@ impl Number {
true,
);

make_builtin_fn(Self::number_is_finite, "isFinite", &number_object, 1);
make_builtin_fn(Self::number_is_nan, "isNaN", &number_object, 1);
make_builtin_fn(Self::is_safe_integer, "isSafeInteger", &number_object, 1);
make_builtin_fn(Self::number_is_integer, "isInteger", &number_object, 1);

// Constants from:
// https://tc39.es/ecma262/#sec-properties-of-the-number-constructor
{
Expand Down
175 changes: 175 additions & 0 deletions boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,178 @@ fn parse_float_too_many_args() {

assert_eq!(&forward(&mut engine, "parseFloat(\"100.5\", 10)"), "100.5");
}

#[test]
fn global_is_finite() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("false", &forward(&mut engine, "isFinite(Infinity)"));
assert_eq!("false", &forward(&mut engine, "isFinite(NaN)"));
assert_eq!("false", &forward(&mut engine, "isFinite(-Infinity)"));
assert_eq!("true", &forward(&mut engine, "isFinite(0)"));
assert_eq!("true", &forward(&mut engine, "isFinite(2e64)"));
assert_eq!("true", &forward(&mut engine, "isFinite(910)"));
assert_eq!("true", &forward(&mut engine, "isFinite(null)"));
assert_eq!("true", &forward(&mut engine, "isFinite('0')"));
assert_eq!("false", &forward(&mut engine, "isFinite()"));
}

#[test]
fn global_is_nan() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("true", &forward(&mut engine, "isNaN(NaN)"));
assert_eq!("true", &forward(&mut engine, "isNaN('NaN')"));
assert_eq!("true", &forward(&mut engine, "isNaN(undefined)"));
assert_eq!("true", &forward(&mut engine, "isNaN({})"));
assert_eq!("false", &forward(&mut engine, "isNaN(true)"));
assert_eq!("false", &forward(&mut engine, "isNaN(null)"));
assert_eq!("false", &forward(&mut engine, "isNaN(37)"));
assert_eq!("false", &forward(&mut engine, "isNaN('37')"));
assert_eq!("false", &forward(&mut engine, "isNaN('37.37')"));
assert_eq!("true", &forward(&mut engine, "isNaN('37,5')"));
assert_eq!("true", &forward(&mut engine, "isNaN('123ABC')"));
// Incorrect due to ToNumber implementation inconsistencies.
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
//assert_eq!("false", &forward(&mut engine, "isNaN('')"));
//assert_eq!("false", &forward(&mut engine, "isNaN(' ')"));
assert_eq!("true", &forward(&mut engine, "isNaN('blabla')"));
}

#[test]
fn number_is_finite() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("false", &forward(&mut engine, "Number.isFinite(Infinity)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(NaN)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(-Infinity)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(0)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(2e64)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(910)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(null)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite('0')"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite()"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite({})"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(Number(5))"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isFinite(new Number(5))")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isFinite(new Number(NaN))")
);
assert_eq!("false", &forward(&mut engine, "Number.isFinite(BigInt(5))"));
}

#[test]
fn number_is_integer() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("true", &forward(&mut engine, "Number.isInteger(0)"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(1)"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(-100000)"));
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(99999999999999999999999)")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger(0.1)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(Math.PI)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(NaN)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(Infinity)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(-Infinity)")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger('10')"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(true)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(false)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger([1])"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(5.0)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(5.000000000000001)")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(5.0000000000000001)")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(Number(5.000000000000001))")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(Number(5.0000000000000001))")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger()"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(new Number(5))")
);
}

#[test]
fn number_is_nan() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("true", &forward(&mut engine, "Number.isNaN(NaN)"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(Number.NaN)"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(0 / 0)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(undefined)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN({})"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(true)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(null)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(37)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37.37')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37,5')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('123ABC')"));
// Incorrect due to ToNumber implementation inconsistencies.
//assert_eq!("false", &forward(&mut engine, "Number.isNaN('')"));
//assert_eq!("false", &forward(&mut engine, "Number.isNaN(' ')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('blabla')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(Number(5))"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(Number(NaN))"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(BigInt(5))"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isNaN(new Number(5))")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isNaN(new Number(NaN))")
);
}

#[test]
fn number_is_safe_integer() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);

assert_eq!("true", &forward(&mut engine, "Number.isSafeInteger(3)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(Math.pow(2, 53))")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isSafeInteger(Math.pow(2, 53) - 1)")
);
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger(NaN)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(Infinity)")
);
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger('3')"));
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger(3.1)"));
assert_eq!("true", &forward(&mut engine, "Number.isSafeInteger(3.0)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(new Number(5))")
);
}