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
#[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
}

/// 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: &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;
}

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: &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
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
95 changes: 76 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(self, 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(a, 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(x, 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,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() {
return number::same_value(x, y);
}

same_value_non_number(x, y)
Expand All @@ -135,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,
}
}
Loading