diff --git a/runtime/vm/exceptions_test.cc b/runtime/vm/exceptions_test.cc index e3138a33a3c..1901c58663d 100644 --- a/runtime/vm/exceptions_test.cc +++ b/runtime/vm/exceptions_test.cc @@ -19,7 +19,7 @@ void FUNCTION_NAME(Unhandled_equals)(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); const Instance& expected = Instance::CheckedHandle(arguments->NativeArgAt(0)); const Instance& actual = Instance::CheckedHandle(arguments->NativeArgAt(1)); - if (!expected.Equals(actual)) { + if (!expected.CanonicalizeEquals(actual)) { OS::Print("expected: '%s' actual: '%s'\n", expected.ToCString(), actual.ToCString()); FATAL("Unhandled_equals fails.\n"); diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index c3f2ae5be61..681206d252f 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -12733,7 +12733,7 @@ RawObject* Instance::Evaluate(const String& expr, -bool Instance::Equals(const Instance& other) const { +bool Instance::CanonicalizeEquals(const Instance& other) const { if (this->raw() == other.raw()) { return true; // "===". } @@ -12844,7 +12844,7 @@ RawInstance* Instance::CheckAndCanonicalize(const char** error_str) const { if (result.IsNull()) { break; } - if (this->Equals(result)) { + if (this->CanonicalizeEquals(result)) { return result.raw(); } index++; @@ -12950,13 +12950,22 @@ bool Instance::IsInstanceOf(const AbstractType& other, } +bool Instance::OperatorEquals(const Instance& other) const { + // TODO(koda): Optimize for all builtin classes and all classes + // that do not override operator==. + const Object& result = + Object::Handle(DartLibraryCalls::Equals(*this, other)); + return result.raw() == Object::bool_true().raw(); +} + + bool Instance::IsIdenticalTo(const Instance& other) const { if (raw() == other.raw()) return true; if (IsInteger() && other.IsInteger()) { - return Equals(other); + return Integer::Cast(*this).Equals(other); } if (IsDouble() && other.IsDouble()) { - if (Equals(other)) return true; + if (Double::Cast(*this).CanonicalizeEquals(other)) return true; // Check for NaN. const Double& a_double = Double::Cast(*this); const Double& b_double = Double::Cast(other); @@ -15482,7 +15491,7 @@ void Double::set_value(double value) const { } -bool Double::EqualsToDouble(double value) const { +bool Double::BitwiseEqualsToDouble(double value) const { intptr_t value_offset = Double::value_offset(); void* this_addr = reinterpret_cast( reinterpret_cast(this->raw_ptr()) + value_offset); @@ -15491,14 +15500,25 @@ bool Double::EqualsToDouble(double value) const { } -bool Double::Equals(const Instance& other) const { +bool Double::OperatorEquals(const Instance& other) const { + if (this->IsNull() || other.IsNull()) { + return (this->IsNull() && other.IsNull()); + } + if (!other.IsDouble()) { + return false; + } + return this->value() == Double::Cast(other).value(); +} + + +bool Double::CanonicalizeEquals(const Instance& other) const { if (this->raw() == other.raw()) { return true; // "===". } if (other.IsNull() || !other.IsDouble()) { return false; } - return EqualsToDouble(Double::Cast(other).value()); + return BitwiseEqualsToDouble(Double::Cast(other).value()); } @@ -15540,7 +15560,7 @@ RawDouble* Double::NewCanonical(double value) { if (canonical_value.IsNull()) { break; } - if (canonical_value.EqualsToDouble(value)) { + if (canonical_value.BitwiseEqualsToDouble(value)) { return canonical_value.raw(); } index++; @@ -17441,7 +17461,7 @@ void Bool::PrintJSONImpl(JSONStream* stream, bool ref) const { } -bool Array::Equals(const Instance& other) const { +bool Array::CanonicalizeEquals(const Instance& other) const { if (this->raw() == other.raw()) { // Both handles point to the same raw instance. return true; @@ -17681,7 +17701,7 @@ RawObject* GrowableObjectArray::RemoveLast() const { } -bool GrowableObjectArray::Equals(const Instance& other) const { +bool GrowableObjectArray::CanonicalizeEquals(const Instance& other) const { // If both handles point to the same raw instance they are equal. if (this->raw() == other.raw()) { return true; @@ -18591,7 +18611,7 @@ const char* JSRegExp::Flags() const { } -bool JSRegExp::Equals(const Instance& other) const { +bool JSRegExp::CanonicalizeEquals(const Instance& other) const { if (this->raw() == other.raw()) { return true; // "===". } diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 0520b1477d7..5d8f302daae 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -4136,7 +4136,17 @@ class UnwindError : public Error { // in Dart source code. class Instance : public Object { public: - virtual bool Equals(const Instance& other) const; + // Equality and identity testing. + // 1. OperatorEquals: true iff 'this == other' is true in Dart code. + // 2. IsIdenticalTo: true iff 'identical(this, other)' is true in Dart code. + // 3. CanonicalizeEquals: used to canonicalize compile-time constants, e.g., + // using bitwise equality of fields and list elements. + // Subclasses where 1 and 3 coincide may also define a plain Equals, e.g., + // String and Integer. + virtual bool OperatorEquals(const Instance& other) const; + bool IsIdenticalTo(const Instance& other) const; + virtual bool CanonicalizeEquals(const Instance& other) const; + // Returns Instance::null() if instance cannot be canonicalized. // Any non-canonical number of string will be canonicalized here. // An instance cannot be canonicalized if it still contains non-canonical @@ -4166,10 +4176,6 @@ class Instance : public Object { const TypeArguments& type_instantiator, Error* bound_error) const; - // Check whether this instance is identical to the argument according to the - // specification of dare:core's identical(). - bool IsIdenticalTo(const Instance& other) const; - bool IsValidNativeIndex(int index) const { return ((index >= 0) && (index < clazz()->ptr()->num_native_fields_)); } @@ -4314,6 +4320,9 @@ class AbstractType : public Instance { virtual RawTypeArguments* arguments() const; virtual intptr_t token_pos() const; virtual bool IsInstantiated(GrowableObjectArray* trail = NULL) const; + virtual bool CanonicalizeEquals(const Instance& other) const { + return Equals(other); + } virtual bool Equals(const Instance& other) const { return IsEquivalent(other); } @@ -4880,6 +4889,17 @@ class Integer : public Number { Heap::Space space = Heap::kNew, const bool silent = false); + virtual bool OperatorEquals(const Instance& other) const { + return Equals(other); + } + virtual bool CanonicalizeEquals(const Instance& other) const { + return Equals(other); + } + virtual bool Equals(const Instance& other) const { + UNREACHABLE(); + return false; + } + // Integer is an abstract class. virtual bool IsZero() const { UNREACHABLE(); @@ -5144,8 +5164,9 @@ class Double : public Number { return raw_ptr()->value_; } - bool EqualsToDouble(double value) const; - virtual bool Equals(const Instance& other) const; + bool BitwiseEqualsToDouble(double value) const; + virtual bool OperatorEquals(const Instance& other) const; + virtual bool CanonicalizeEquals(const Instance& other) const; static RawDouble* New(double d, Heap::Space space = Heap::kNew); @@ -5265,6 +5286,12 @@ class String : public Instance { // Compares to an array of UTF-32 encoded characters. bool Equals(const int32_t* characters, intptr_t len) const; + virtual bool OperatorEquals(const Instance& other) const { + return Equals(other); + } + virtual bool CanonicalizeEquals(const Instance& other) const { + return Equals(other); + } virtual bool Equals(const Instance& other) const; intptr_t CompareTo(const String& other) const; @@ -5895,7 +5922,7 @@ class Array : public Instance { StorePointer(&raw_ptr()->type_arguments_, value.raw()); } - virtual bool Equals(const Instance& other) const; + virtual bool CanonicalizeEquals(const Instance& other) const; static const intptr_t kBytesPerElement = kWordSize; static const intptr_t kMaxElements = kSmiMax / kBytesPerElement; @@ -6055,7 +6082,7 @@ class GrowableObjectArray : public Instance { StorePointer(&raw_ptr()->type_arguments_, value.raw()); } - virtual bool Equals(const Instance& other) const; + virtual bool CanonicalizeEquals(const Instance& other) const; virtual RawInstance* CheckAndCanonicalize(const char** error_str) const { UNREACHABLE(); @@ -6766,7 +6793,7 @@ class JSRegExp : public Instance { static RawJSRegExp* FromDataStartAddress(void* data); const char* Flags() const; - virtual bool Equals(const Instance& other) const; + virtual bool CanonicalizeEquals(const Instance& other) const; static const intptr_t kBytesPerElement = 1; static const intptr_t kMaxElements = kSmiMax / kBytesPerElement; diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc index aaa75728da5..6058481992b 100644 --- a/runtime/vm/object_test.cc +++ b/runtime/vm/object_test.cc @@ -516,11 +516,18 @@ TEST_CASE(Double) { const double dbl_const = 2.0; const Double& dbl1 = Double::Handle(Double::New(dbl_const)); const Double& dbl2 = Double::Handle(Double::New(dbl_const)); - EXPECT(dbl1.Equals(dbl2)); + EXPECT(dbl1.OperatorEquals(dbl2)); + EXPECT(dbl1.IsIdenticalTo(dbl2)); + EXPECT(dbl1.CanonicalizeEquals(dbl2)); const Double& dbl3 = Double::Handle(Double::New(3.3)); - EXPECT(!dbl1.Equals(dbl3)); - EXPECT(!dbl1.Equals(Smi::Handle(Smi::New(3)))); - EXPECT(!dbl1.Equals(Double::Handle())); + EXPECT(!dbl1.OperatorEquals(dbl3)); + EXPECT(!dbl1.OperatorEquals(Smi::Handle(Smi::New(3)))); + EXPECT(!dbl1.OperatorEquals(Double::Handle())); + const Double& nan0 = Double::Handle(Double::New(NAN)); + EXPECT(nan0.IsIdenticalTo(nan0)); + EXPECT(nan0.CanonicalizeEquals(nan0)); + EXPECT(!nan0.OperatorEquals(nan0)); + // TODO(18738): Test bitwise different NaNs after agreement on spec. } { const String& dbl_str0 = String::Handle(String::New("bla")); @@ -1838,16 +1845,16 @@ TEST_CASE(Array) { other_array.SetAt(0, array); other_array.SetAt(2, array); - EXPECT(array.Equals(array)); - EXPECT(array.Equals(other_array)); + EXPECT(array.CanonicalizeEquals(array)); + EXPECT(array.CanonicalizeEquals(other_array)); other_array.SetAt(1, other_array); - EXPECT(!array.Equals(other_array)); + EXPECT(!array.CanonicalizeEquals(other_array)); other_array = Array::New(kArrayLen - 1); other_array.SetAt(0, array); other_array.SetAt(2, array); - EXPECT(!array.Equals(other_array)); + EXPECT(!array.CanonicalizeEquals(other_array)); EXPECT_EQ(0, Object::empty_array().Length()); @@ -4014,4 +4021,33 @@ TEST_CASE(PrintJSON) { heap->IterateObjects(&verifier); } + +TEST_CASE(InstanceEquality) { + // Test that Instance::OperatorEquals can call a user-defined operator==. + const char* kScript = + "class A {\n" + " bool operator==(A other) { return true; }\n" + "}\n" + "main() {\n" + " A a = new A();\n" + "}"; + + Dart_Handle h_lib = TestCase::LoadTestScript(kScript, NULL); + EXPECT_VALID(h_lib); + Library& lib = Library::Handle(); + lib ^= Api::UnwrapHandle(h_lib); + EXPECT(!lib.IsNull()); + Dart_Handle result = Dart_Invoke(h_lib, NewString("main"), 0, NULL); + EXPECT_VALID(result); + const Class& clazz = Class::Handle(GetClass(lib, "A")); + EXPECT(!clazz.IsNull()); + const Instance& a0 = Instance::Handle(Instance::New(clazz)); + const Instance& a1 = Instance::Handle(Instance::New(clazz)); + EXPECT(a0.raw() != a1.raw()); + EXPECT(a0.OperatorEquals(a0)); + EXPECT(a0.OperatorEquals(a1)); + EXPECT(a0.IsIdenticalTo(a0)); + EXPECT(!a0.IsIdenticalTo(a1)); +} + } // namespace dart diff --git a/runtime/vm/snapshot_test.cc b/runtime/vm/snapshot_test.cc index edc5c55e6a5..7761863dc81 100644 --- a/runtime/vm/snapshot_test.cc +++ b/runtime/vm/snapshot_test.cc @@ -572,7 +572,7 @@ TEST_CASE(SerializeArray) { Snapshot::kMessage, Isolate::Current()); Array& serialized_array = Array::Handle(); serialized_array ^= reader.ReadObject(); - EXPECT(array.Equals(serialized_array)); + EXPECT(array.CanonicalizeEquals(serialized_array)); // Read object back from the snapshot into a C structure. ApiNativeScope scope; @@ -657,7 +657,7 @@ TEST_CASE(SerializeEmptyArray) { Snapshot::kMessage, Isolate::Current()); Array& serialized_array = Array::Handle(); serialized_array ^= reader.ReadObject(); - EXPECT(array.Equals(serialized_array)); + EXPECT(array.CanonicalizeEquals(serialized_array)); // Read object back from the snapshot into a C structure. ApiNativeScope scope; diff --git a/runtime/vm/stack_frame_test.cc b/runtime/vm/stack_frame_test.cc index 851786b452d..2472160ae64 100644 --- a/runtime/vm/stack_frame_test.cc +++ b/runtime/vm/stack_frame_test.cc @@ -42,7 +42,7 @@ void FUNCTION_NAME(StackFrame_equals)(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); const Instance& expected = Instance::CheckedHandle(arguments->NativeArgAt(0)); const Instance& actual = Instance::CheckedHandle(arguments->NativeArgAt(1)); - if (!expected.Equals(actual)) { + if (!expected.OperatorEquals(actual)) { OS::Print("expected: '%s' actual: '%s'\n", expected.ToCString(), actual.ToCString()); FATAL("Expect_equals fails.\n"); diff --git a/runtime/vm/unit_test.h b/runtime/vm/unit_test.h index 8c5704d29b2..98b192627e4 100644 --- a/runtime/vm/unit_test.h +++ b/runtime/vm/unit_test.h @@ -90,7 +90,7 @@ EXPECT(!result.IsError()); \ Instance& actual = Instance::Handle(); \ actual ^= result.raw(); \ - EXPECT(actual.Equals(Instance::Handle(expected))); \ + EXPECT(actual.CanonicalizeEquals(Instance::Handle(expected))); \ } @@ -128,7 +128,7 @@ EXPECT(!result.IsError()); \ Instance& actual = Instance::Handle(); \ actual ^= result.raw(); \ - EXPECT(actual.Equals(Instance::Handle(expected))); \ + EXPECT(actual.CanonicalizeEquals(Instance::Handle(expected))); \ }