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

impl abstract-equality-comparison #395

Merged
merged 11 commits into from
May 13, 2020
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
54 changes: 54 additions & 0 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,57 @@ 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
hello2dj marked this conversation as resolved.
Show resolved Hide resolved
#[allow(clippy::float_cmp)]
pub fn equals(a: &Value, b: &Value) -> bool {
let a: f64 = a.into();
let b: f64 = b.into();

if a.is_nan() || b.is_nan() {
return false;
}

a == b
hello2dj marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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
hello2dj marked this conversation as resolved.
Show resolved Hide resolved
#[allow(clippy::float_cmp)]
pub fn same_value(a: &Value, b: &Value) -> bool {
let a: f64 = a.into();
let b: f64 = b.into();

if a.is_nan() && b.is_nan() {
return true;
}

let multi = a * b;
if multi.is_sign_negative() && a == 0.0 && b == 0.0 {
return false;
}
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved

a == b
}

/// The abstract operation Number::sameValueZero takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///

hello2dj marked this conversation as resolved.
Show resolved Hide resolved
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero
hello2dj marked this conversation as resolved.
Show resolved Hide resolved
#[allow(clippy::float_cmp)]
pub fn same_value_zero(a: &Value, b: &Value) -> bool {
let a: f64 = a.into();
let b: f64 = b.into();

if a.is_nan() && b.is_nan() {
return true;
}

a == b
}
73 changes: 73 additions & 0 deletions boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,76 @@ 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(&Value::integer(0), &Value::integer(0)), true);
assert_eq!(
super::equals(&Value::rational(-0.0), &Value::rational(0.0)),
true
);
assert_eq!(
super::equals(&Value::rational(0.0), &Value::rational(-0.0)),
true
);
assert_eq!(
super::equals(&Value::rational(f64::NAN), &Value::rational(-0.0)),
false
);
assert_eq!(
super::equals(&Value::integer(0), &Value::rational(f64::NAN)),
false
);

assert_eq!(super::equals(&Value::integer(1), &Value::rational(1)), true);
}

#[test]
fn same_value() {
assert_eq!(
super::same_value(&Value::integer(0), &Value::integer(0)),
true
);
assert_eq!(
super::same_value(&Value::rational(-0.0), &Value::rational(0.0)),
false
);
assert_eq!(
super::same_value(&Value::rational(0.0), &Value::rational(-0.0)),
false
);
assert_eq!(
super::same_value(&Value::rational(f64::NAN), &Value::rational(-0.0)),
false
);
assert_eq!(
super::same_value(&Value::integer(0), &Value::rational(f64::NAN)),
false
);
assert_eq!(super::equals(&Value::integer(1), &Value::rational(1)), true);
}

#[test]
fn same_value_zero() {
assert_eq!(
super::same_value_zero(&Value::integer(0), &Value::integer(0)),
true
);
assert_eq!(
super::same_value_zero(&Value::rational(-0.0), &Value::rational(0.0)),
true
);
assert_eq!(
super::same_value_zero(&Value::rational(0.0), &Value::rational(-0.0)),
true
);
assert_eq!(
super::same_value_zero(&Value::rational(f64::NAN), &Value::rational(-0.0)),
false
);
assert_eq!(
super::same_value_zero(&Value::integer(0), &Value::rational(f64::NAN)),
false
);
assert_eq!(super::equals(&Value::integer(1), &Value::rational(1)), 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
69 changes: 26 additions & 43 deletions boa/src/builtins/value/operations.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
use super::*;
use crate::builtins::number;
use crate::Interpreter;
use std::borrow::Borrow;

#[allow(clippy::float_cmp)]
/// https://tc39.es/ecma262/#sec-numeric-types-number-equal
fn strict_number_equals<T: Into<f64>>(a: T, b: T) -> bool {
let a: f64 = a.into();
let b: f64 = b.into();

if a.is_nan() || b.is_nan() {
return false;
}

a == b
}
use std::borrow::Borrow;

impl Value {
/// Strict equality comparison.
Expand All @@ -26,7 +15,7 @@ impl Value {
}

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

same_value_non_number(self, other)
Expand All @@ -53,7 +42,7 @@ impl Value {
| (ValueData::Integer(_), ValueData::Boolean(_)) => {
let a: &Value = self.borrow();
let b: &Value = other.borrow();
strict_number_equals(a, b)
number::equals(a, b)
}
(ValueData::Boolean(_), _) => {
other.equals(&mut Value::from(self.to_integer()), interpreter)
Expand All @@ -74,29 +63,6 @@ impl Value {
}
}

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

impl Add for Value {
type Output = Self;
fn add(self, other: Self) -> Self {
Expand Down Expand Up @@ -170,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
hello2dj marked this conversation as resolved.
Show resolved Hide resolved
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(x, y);
}

same_value_non_number(x, y)
}

hello2dj marked this conversation as resolved.
Show resolved Hide resolved
/// 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 @@ -188,10 +173,8 @@ 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;
if x.is_number() {
Razican marked this conversation as resolved.
Show resolved Hide resolved
return number::same_value(x, y);
}

same_value_non_number(x, y)
Expand All @@ -209,7 +192,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,
}
}
2 changes: 1 addition & 1 deletion boa/src/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ impl Executor for Interpreter {
for tup in vals.iter() {
let cond = &tup.0;
let block = &tup.1;
if val == self.run(cond)? {
if val.strict_equals(&self.run(cond)?) {
matched = true;
let last_expr = block.last().expect("Block has no expressions");
for expr in block.iter() {
Expand Down