Skip to content

Commit

Permalink
impl abstract-equality-comparison (#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
hello2dj authored May 13, 2020
1 parent d4d2729 commit 402041d
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 43 deletions.
12 changes: 6 additions & 6 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
builtins::{
object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property,
value::{ResultValue, Value, ValueData},
value::{same_value_zero, ResultValue, Value, ValueData},
},
exec::Interpreter,
};
Expand Down Expand Up @@ -418,7 +418,7 @@ pub fn shift(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue
let to = (k.wrapping_sub(1)).to_string();

let from_value = this.get_field_slice(&from);
if from_value == Value::undefined() {
if from_value.is_undefined() {
this.remove_property(&to);
} else {
this.set_field_slice(&to, from_value);
Expand Down Expand Up @@ -454,7 +454,7 @@ pub fn unshift(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultV
let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string();

let from_value = this.get_field_slice(&from);
if from_value == Value::undefined() {
if from_value.is_undefined() {
this.remove_property(&to);
} else {
this.set_field_slice(&to, from_value);
Expand Down Expand Up @@ -601,7 +601,7 @@ pub fn index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Result
while idx < len {
let check_element = this.get_field_slice(&idx.to_string()).clone();

if check_element == search_element {
if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx));
}

Expand Down Expand Up @@ -654,7 +654,7 @@ pub fn last_index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> R
while idx >= 0 {
let check_element = this.get_field_slice(&idx.to_string()).clone();

if check_element == search_element {
if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx));
}

Expand Down Expand Up @@ -797,7 +797,7 @@ pub fn includes_value(this: &mut Value, args: &[Value], _: &mut Interpreter) ->
for idx in 0..length {
let check_element = this.get_field_slice(&idx.to_string()).clone();

if check_element == search_element {
if same_value_zero(&check_element, &search_element) {
return Ok(Value::from(true));
}
}
Expand Down
44 changes: 44 additions & 0 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,47 @@ pub fn create(global: &Value) -> Value {
pub fn init(global: &Value) {
global.set_field_slice("Number", create(global));
}

/// The abstract operation Number::equal takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-equal
#[allow(clippy::float_cmp)]
pub fn equals(a: f64, b: f64) -> bool {
a == b
}

/// The abstract operation Number::sameValue takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue
#[allow(clippy::float_cmp)]
pub fn same_value(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
}

if a == 0.0 && b == 0.0 {
if (a.is_sign_negative() && b.is_sign_positive())
|| (a.is_sign_positive() && b.is_sign_negative())
{
return false;
};
true
} else {
a == b
}
}

/// The abstract operation Number::sameValueZero takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero
#[allow(clippy::float_cmp)]
pub fn same_value_zero(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
}

a == b
}
31 changes: 31 additions & 0 deletions boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,34 @@ fn value_of() {
assert_eq!(exp_val.to_number(), 12_000_f64);
assert_eq!(neg_val.to_number(), -12_000_f64);
}

#[test]
fn equal() {
assert_eq!(super::equals(0.0, 0.0), true);
assert_eq!(super::equals(-0.0, 0.0), true);
assert_eq!(super::equals(0.0, -0.0), true);
assert_eq!(super::equals(f64::NAN, -0.0), false);
assert_eq!(super::equals(0.0, f64::NAN), false);

assert_eq!(super::equals(1.0, 1.0), true);
}

#[test]
fn same_value() {
assert_eq!(super::same_value(0.0, 0.0), true);
assert_eq!(super::same_value(-0.0, 0.0), false);
assert_eq!(super::same_value(0.0, -0.0), false);
assert_eq!(super::same_value(f64::NAN, -0.0), false);
assert_eq!(super::same_value(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
}

#[test]
fn same_value_zero() {
assert_eq!(super::same_value_zero(0.0, 0.0), true);
assert_eq!(super::same_value_zero(-0.0, 0.0), true);
assert_eq!(super::same_value_zero(0.0, -0.0), true);
assert_eq!(super::same_value_zero(f64::NAN, -0.0), false);
assert_eq!(super::same_value_zero(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
}
2 changes: 1 addition & 1 deletion boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl ObjectInternalMethods for Object {
fn set_prototype_of(&mut self, val: Value) -> bool {
debug_assert!(val.is_object() || val.is_null());
let current = self.get_internal_slot(PROTOTYPE);
if current == val {
if same_value(&current, &val, false) {
return true;
}
let extensible = self.get_internal_slot("extensible");
Expand Down
4 changes: 2 additions & 2 deletions boa/src/builtins/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ pub fn value_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu
pub fn match_all(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let mut re: Value = match args.get(0) {
Some(arg) => {
if arg == &Value::null() {
if arg.is_null() {
make_regexp(
&mut Value::from(Object::default()),
&[
Expand All @@ -978,7 +978,7 @@ pub fn match_all(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Res
],
ctx,
)
} else if arg == &Value::undefined() {
} else if arg.is_undefined() {
make_regexp(
&mut Value::from(Object::default()),
&[Value::undefined(), Value::from(String::from("g"))],
Expand Down
21 changes: 15 additions & 6 deletions boa/src/builtins/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ impl ValueData {

/// Returns true if the value is a number
pub fn is_number(&self) -> bool {
self.is_double()
match self {
Self::Rational(_) | Self::Integer(_) => true,
_ => false,
}
}

/// Returns true if the value is a string
Expand Down Expand Up @@ -295,13 +298,19 @@ impl ValueData {
pub fn to_number(&self) -> f64 {
match *self {
Self::Object(_) | Self::Symbol(_) | Self::Undefined => NAN,
Self::String(ref str) => match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => NAN,
},
Self::Rational(num) => num,
Self::String(ref str) => {
if str.is_empty() {
return 0.0;
}

match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => NAN,
}
}
Self::Boolean(true) => 1.0,
Self::Boolean(false) | Self::Null => 0.0,
Self::Rational(num) => num,
Self::Integer(num) => f64::from(num),
}
}
Expand Down
97 changes: 78 additions & 19 deletions boa/src/builtins/value/operations.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,63 @@
use super::*;
use crate::builtins::number;
use crate::Interpreter;

use std::borrow::Borrow;

impl Value {
/// Strict equality comparison.
///
/// This method is executed when doing strict equality comparisons with the `===` operator.
/// For more information, check <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
pub fn strict_equals(&self, other: &Self) -> bool {
if self.get_type() != other.get_type() {
return false;
}

if self.is_number() {
return number::equals(f64::from(self), f64::from(other));
}

same_value_non_number(self, other)
}

/// Abstract equality comparison.
///
/// This method is executed when doing abstract equality comparisons with the `==` operator.
/// For more information, check <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
pub fn equals(&mut self, other: &mut Self, interpreter: &mut Interpreter) -> bool {
if self.get_type() == other.get_type() {
return self.strict_equals(other);
}

impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self.data(), other.data()) {
// TODO: fix this
// _ if self.ptr.to_inner() == &other.ptr.to_inner() => true,
_ if self.is_null_or_undefined() && other.is_null_or_undefined() => true,
(ValueData::String(_), _) | (_, ValueData::String(_)) => {
self.to_string() == other.to_string()

// https://github.com/rust-lang/rust/issues/54883
(ValueData::Integer(_), ValueData::String(_))
| (ValueData::Rational(_), ValueData::String(_))
| (ValueData::String(_), ValueData::Integer(_))
| (ValueData::String(_), ValueData::Rational(_))
| (ValueData::Rational(_), ValueData::Boolean(_))
| (ValueData::Integer(_), ValueData::Boolean(_)) => {
let a: &Value = self.borrow();
let b: &Value = other.borrow();
number::equals(f64::from(a), f64::from(b))
}
(ValueData::Boolean(_), _) => {
other.equals(&mut Value::from(self.to_integer()), interpreter)
}
(_, ValueData::Boolean(_)) => {
self.equals(&mut Value::from(other.to_integer()), interpreter)
}
(ValueData::Object(_), _) => {
let mut primitive = interpreter.to_primitive(self, None);
primitive.equals(other, interpreter)
}
(ValueData::Boolean(a), ValueData::Boolean(b)) if a == b => true,
(ValueData::Rational(a), ValueData::Rational(b))
if a == b && !a.is_nan() && !b.is_nan() =>
{
true
(_, ValueData::Object(_)) => {
let mut primitive = interpreter.to_primitive(other, None);
primitive.equals(self, interpreter)
}
(ValueData::Rational(a), _) if *a == other.to_number() => true,
(_, ValueData::Rational(a)) if *a == self.to_number() => true,
(ValueData::Integer(a), ValueData::Integer(b)) if a == b => true,
_ => false,
}
}
Expand Down Expand Up @@ -96,6 +136,25 @@ impl Not for Value {
}
}

/// The internal comparison abstract operation SameValueZero(x, y),
/// where x and y are ECMAScript language values, produces true or false.
/// SameValueZero differs from SameValue only in its treatment of +0 and -0.
///
/// Such a comparison is performed as follows:
///
/// <https://tc39.es/ecma262/#sec-samevaluezero>
pub fn same_value_zero(x: &Value, y: &Value) -> bool {
if x.get_type() != y.get_type() {
return false;
}

if x.is_number() {
return number::same_value_zero(f64::from(x), f64::from(y));
}

same_value_non_number(x, y)
}

/// The internal comparison abstract operation SameValue(x, y),
/// where x and y are ECMAScript language values, produces true or false.
/// Such a comparison is performed as follows:
Expand All @@ -114,10 +173,10 @@ pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool {
return false;
}

if x.get_type() == "number" {
let native_x = f64::from(x);
let native_y = f64::from(y);
return native_x.abs() - native_y.abs() == 0.0;
// TODO: check BigInt
// https://github.com/jasonwilliams/boa/pull/358
if x.is_number() {
return number::same_value(f64::from(x), f64::from(y));
}

same_value_non_number(x, y)
Expand All @@ -135,7 +194,7 @@ pub fn same_value_non_number(x: &Value, y: &Value) -> bool {
false
}
"boolean" => bool::from(x) == bool::from(y),
"object" => *x == *y,
"object" => std::ptr::eq(x, y),
_ => false,
}
}
48 changes: 48 additions & 0 deletions boa/src/builtins/value/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::{forward, Executor, Realm};

#[test]
fn check_is_object() {
Expand Down Expand Up @@ -46,3 +47,50 @@ fn check_number_is_true() {
assert_eq!(Value::from(-1.0).is_true(), true);
assert_eq!(Value::from(NAN).is_true(), false);
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
#[test]
fn abstract_equality_comparison() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "undefined == undefined"), "true");
assert_eq!(forward(&mut engine, "null == null"), "true");
assert_eq!(forward(&mut engine, "true == true"), "true");
assert_eq!(forward(&mut engine, "false == false"), "true");
assert_eq!(forward(&mut engine, "'foo' == 'foo'"), "true");
assert_eq!(forward(&mut engine, "0 == 0"), "true");
assert_eq!(forward(&mut engine, "+0 == -0"), "true");
assert_eq!(forward(&mut engine, "+0 == 0"), "true");
assert_eq!(forward(&mut engine, "-0 == 0"), "true");
assert_eq!(forward(&mut engine, "0 == false"), "true");
assert_eq!(forward(&mut engine, "'' == false"), "true");
assert_eq!(forward(&mut engine, "'' == 0"), "true");
assert_eq!(forward(&mut engine, "'17' == 17"), "true");
assert_eq!(forward(&mut engine, "[1,2] == '1,2'"), "true");
assert_eq!(forward(&mut engine, "new String('foo') == 'foo'"), "true");
assert_eq!(forward(&mut engine, "null == undefined"), "true");
assert_eq!(forward(&mut engine, "undefined == null"), "true");
assert_eq!(forward(&mut engine, "null == false"), "false");
assert_eq!(forward(&mut engine, "[] == ![]"), "true");
assert_eq!(
forward(&mut engine, "a = { foo: 'bar' }; b = { foo: 'bar'}; a == b"),
"false"
);
assert_eq!(
forward(&mut engine, "new String('foo') == new String('foo')"),
"false"
);
assert_eq!(forward(&mut engine, "0 == null"), "false");

// https://github.com/jasonwilliams/boa/issues/357
assert_eq!(forward(&mut engine, "0 == '-0'"), "true");
assert_eq!(forward(&mut engine, "0 == '+0'"), "true");
assert_eq!(forward(&mut engine, "'+0' == 0"), "true");
assert_eq!(forward(&mut engine, "'-0' == 0"), "true");

// https://github.com/jasonwilliams/boa/issues/393
// assert_eq!(forward(&mut engine, "0 == NaN"), "false");
// assert_eq!(forward(&mut engine, "'foo' == NaN"), "false");
// assert_eq!(forward(&mut engine, "NaN == NaN"), "false");
}
Loading

0 comments on commit 402041d

Please sign in to comment.