Implement base::Value::operator== for various base::Value subtypes.
It should be possible to directly compare int, double, et cetera against
base::Value without first wrapping an operand in base::Value. While this
is not problematic for types like int which are inexpensive to copy, it
can be considerably more expensive for base::Value::Dict or std::string.
After this lands:
- existing base::Value::Equals() usage will be complete migrated over to
use operator==.
- existing places that wrap the operands, e.g. write base::Value(x) == y
or EXPECT_EQ(base::Value(x), y), will be migrated to the version that
does not require wrapping.
Why not just implement operator== for base::ValueView?
- base::ValueView is only intended for limited usage, e.g. in generic
JSON handling code.
- implementing it for base::ValueView does not allow a compile-time
restriction against blobs (under the hope of eventual deprecation
and/or removal of blob handling in base::Value itself).
Bug: 646113
Change-Id: Ie52c788e69315f2b85bd45a2f7ee0438c4c57e58
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3665235
Commit-Queue: Daniel Cheng <[email protected]>
Reviewed-by: Lei Zhang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1007204}
diff --git a/base/values.cc b/base/values.cc
index 279b0ab..a22422a 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1467,6 +1467,30 @@
return !(lhs < rhs);
}
+bool operator==(const Value& lhs, bool rhs) {
+ return lhs.is_bool() && lhs.GetBool() == rhs;
+}
+
+bool operator==(const Value& lhs, int rhs) {
+ return lhs.is_int() && lhs.GetInt() == rhs;
+}
+
+bool operator==(const Value& lhs, double rhs) {
+ return lhs.is_double() && lhs.GetDouble() == rhs;
+}
+
+bool operator==(const Value& lhs, StringPiece rhs) {
+ return lhs.is_string() && lhs.GetString() == rhs;
+}
+
+bool operator==(const Value& lhs, const Value::Dict& rhs) {
+ return lhs.is_dict() && lhs.GetDict() == rhs;
+}
+
+bool operator==(const Value& lhs, const Value::List& rhs) {
+ return lhs.is_list() && lhs.GetList() == rhs;
+}
+
bool Value::Equals(const Value* other) const {
DCHECK(other);
return *this == *other;
diff --git a/base/values.h b/base/values.h
index ddb8aa10..50521aec 100644
--- a/base/values.h
+++ b/base/values.h
@@ -1050,6 +1050,51 @@
BASE_EXPORT friend bool operator<=(const Value& lhs, const Value& rhs);
BASE_EXPORT friend bool operator>=(const Value& lhs, const Value& rhs);
+ BASE_EXPORT friend bool operator==(const Value& lhs, bool rhs);
+ friend bool operator==(bool lhs, const Value& rhs) { return rhs == lhs; }
+ friend bool operator!=(const Value& lhs, bool rhs) { return !(lhs == rhs); }
+ friend bool operator!=(bool lhs, const Value& rhs) { return !(lhs == rhs); }
+ BASE_EXPORT friend bool operator==(const Value& lhs, int rhs);
+ friend bool operator==(int lhs, const Value& rhs) { return rhs == lhs; }
+ friend bool operator!=(const Value& lhs, int rhs) { return !(lhs == rhs); }
+ friend bool operator!=(int lhs, const Value& rhs) { return !(lhs == rhs); }
+ BASE_EXPORT friend bool operator==(const Value& lhs, double rhs);
+ friend bool operator==(double lhs, const Value& rhs) { return rhs == lhs; }
+ friend bool operator!=(const Value& lhs, double rhs) { return !(lhs == rhs); }
+ friend bool operator!=(double lhs, const Value& rhs) { return !(lhs == rhs); }
+ BASE_EXPORT friend bool operator==(const Value& lhs, StringPiece rhs);
+ friend bool operator==(StringPiece lhs, const Value& rhs) {
+ return rhs == lhs;
+ }
+ friend bool operator!=(const Value& lhs, StringPiece rhs) {
+ return !(lhs == rhs);
+ }
+ friend bool operator!=(StringPiece lhs, const Value& rhs) {
+ return !(lhs == rhs);
+ }
+ // Note: Blob support intentionally omitted as an experiment for potentially
+ // wholly removing Blob support from Value itself in the future.
+ BASE_EXPORT friend bool operator==(const Value& lhs, const Value::Dict& rhs);
+ friend bool operator==(const Value::Dict& lhs, const Value& rhs) {
+ return rhs == lhs;
+ }
+ friend bool operator!=(const Value& lhs, const Value::Dict& rhs) {
+ return !(lhs == rhs);
+ }
+ friend bool operator!=(const Value::Dict& lhs, const Value& rhs) {
+ return !(lhs == rhs);
+ }
+ BASE_EXPORT friend bool operator==(const Value& lhs, const Value::List& rhs);
+ friend bool operator==(const Value::List& lhs, const Value& rhs) {
+ return rhs == lhs;
+ }
+ friend bool operator!=(const Value& lhs, const Value::List& rhs) {
+ return !(lhs == rhs);
+ }
+ friend bool operator!=(const Value::List& lhs, const Value& rhs) {
+ return !(lhs == rhs);
+ }
+
// Compares if two Value objects have equal contents.
// DEPRECATED, use `operator==(const Value& lhs, const Value& rhs)` instead.
// TODO(crbug.com/646113): Delete this and migrate callsites.
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index 90f1485..6379486 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -1754,6 +1754,77 @@
EXPECT_TRUE(copy_nested_dictionary->FindKey("key"));
}
+TEST(ValuesTest, SpecializedEquals) {
+ std::vector<Value> values;
+ values.emplace_back(false);
+ values.emplace_back(true);
+ values.emplace_back(0);
+ values.emplace_back(1);
+ values.emplace_back(1.0);
+ values.emplace_back(2.0);
+ values.emplace_back("hello");
+ values.emplace_back("world");
+ base::Value::Dict dict;
+ dict.Set("hello", "world");
+ values.emplace_back(std::move(dict));
+ base::Value::Dict dict2;
+ dict2.Set("world", "hello");
+ values.emplace_back(std::move(dict2));
+ base::Value::List list;
+ list.Append("hello");
+ list.Append("world");
+ values.emplace_back(std::move(list));
+ base::Value::List list2;
+ list2.Append("world");
+ list2.Append("hello");
+ values.emplace_back(std::move(list2));
+
+ for (const Value& outer_value : values) {
+ for (const Value& inner_value : values) {
+ SCOPED_TRACE(::testing::Message()
+ << "Outer: " << outer_value << "Inner: " << inner_value);
+ const bool should_be_equal = &outer_value == &inner_value;
+ if (should_be_equal) {
+ EXPECT_EQ(outer_value, inner_value);
+ EXPECT_EQ(inner_value, outer_value);
+ EXPECT_FALSE(outer_value != inner_value);
+ EXPECT_FALSE(inner_value != outer_value);
+ } else {
+ EXPECT_NE(outer_value, inner_value);
+ EXPECT_NE(inner_value, outer_value);
+ EXPECT_FALSE(outer_value == inner_value);
+ EXPECT_FALSE(inner_value == outer_value);
+ }
+ // Also test the various overloads for operator== against concrete
+ // subtypes.
+ outer_value.Visit([&](const auto& outer_member) {
+ using T = std::decay_t<decltype(outer_member)>;
+ if constexpr (!std::is_same_v<T, absl::monostate> &&
+ !std::is_same_v<T, Value::BlobStorage>) {
+ if (should_be_equal) {
+ EXPECT_EQ(outer_member, inner_value);
+ EXPECT_EQ(inner_value, outer_member);
+ EXPECT_FALSE(outer_member != inner_value);
+ EXPECT_FALSE(inner_value != outer_member);
+ } else {
+ EXPECT_NE(outer_member, inner_value);
+ EXPECT_NE(inner_value, outer_member);
+ EXPECT_FALSE(outer_member == inner_value);
+ EXPECT_FALSE(inner_value == outer_member);
+ }
+ }
+ });
+ }
+
+ // A copy of a Value should also compare equal to itself.
+ Value copied_value = outer_value.Clone();
+ EXPECT_EQ(outer_value, copied_value);
+ EXPECT_EQ(copied_value, outer_value);
+ EXPECT_FALSE(outer_value != copied_value);
+ EXPECT_FALSE(copied_value != outer_value);
+ }
+}
+
TEST(ValuesTest, Equals) {
auto null1 = std::make_unique<Value>();
auto null2 = std::make_unique<Value>();