[value] Implement scope == operator

Bug: crbug.com/gn/11
Change-Id: Iea0cd4903b1f948c5c085fb9b15d47c416356a8a
Reviewed-on: https://gn-review.googlesource.com/c/2880
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc
index df57c9e..f4f028f 100644
--- a/tools/gn/parser.cc
+++ b/tools/gn/parser.cc
@@ -214,6 +214,11 @@
 
     myvalues.foo += 2
     empty_scope.new_thing = [ 1, 2, 3 ]
+
+  Scope equality is defined as single-level scopes identical within the current
+  scope. That is, all values in the first scope must be present and identical
+  within the second, and vice versa. Note that this means inherited scopes are
+  always unequal by definition.
 )*";
 
 enum Precedence {
diff --git a/tools/gn/scope.cc b/tools/gn/scope.cc
index 2368356..dd1a576 100644
--- a/tools/gn/scope.cc
+++ b/tools/gn/scope.cc
@@ -274,6 +274,23 @@
     (*output)[pair.first] = pair.second.value;
 }
 
+bool Scope::CheckCurrentScopeValuesEqual(const Scope* other) const {
+  // If there are containing scopes, equality shouldn't work.
+  if (containing()) {
+    return false;
+  }
+  if (values_.size() != other->values_.size()) {
+    return false;
+  }
+  for (const auto& pair : values_) {
+    const Value* v = other->GetValue(pair.first);
+    if (!v || *v != pair.second.value) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool Scope::NonRecursiveMergeTo(Scope* dest,
                                 const MergeOptions& options,
                                 const ParseNode* node_for_err,
diff --git a/tools/gn/scope.h b/tools/gn/scope.h
index 5e9745a..585f151 100644
--- a/tools/gn/scope.h
+++ b/tools/gn/scope.h
@@ -220,6 +220,11 @@
   // scopes.
   void GetCurrentScopeValues(KeyValueMap* output) const;
 
+  // Returns true if the values in the current scope are the same as all
+  // values in the given scope, without going to the parent scopes. Returns
+  // false if not.
+  bool CheckCurrentScopeValuesEqual(const Scope* other) const;
+
   // Copies this scope's values into the destination. Values from the
   // containing scope(s) (normally shadowed into the current one) will not be
   // copied, neither will the reference to the containing scope (this is why
diff --git a/tools/gn/value.cc b/tools/gn/value.cc
index 04db7ee..aff4ab1 100644
--- a/tools/gn/value.cc
+++ b/tools/gn/value.cc
@@ -196,10 +196,7 @@
       }
       return true;
     case Value::SCOPE:
-      // Scopes are always considered not equal because there's currently
-      // no use case for comparing them, and it requires a bunch of complex
-      // iteration code.
-      return false;
+      return scope_value()->CheckCurrentScopeValuesEqual(other.scope_value());
     default:
       return false;
   }
diff --git a/tools/gn/value.h b/tools/gn/value.h
index 13beb6f..912717d 100644
--- a/tools/gn/value.h
+++ b/tools/gn/value.h
@@ -113,7 +113,9 @@
   // false and sets the error.
   bool VerifyTypeIs(Type t, Err* err) const;
 
-  // Compares values. Only the "value" is compared, not the origin.
+  // Compares values. Only the "value" is compared, not the origin. Scope
+  // values check only the contents of the current scope, and do not go to
+  // parent scopes.
   bool operator==(const Value& other) const;
   bool operator!=(const Value& other) const;
 
diff --git a/tools/gn/value_unittest.cc b/tools/gn/value_unittest.cc
index 40ddf38..dd9be95 100644
--- a/tools/gn/value_unittest.cc
+++ b/tools/gn/value_unittest.cc
@@ -33,11 +33,28 @@
 
   // Scopes.
   TestWithScope setup;
-  Scope* scope = new Scope(setup.scope());
+  Scope* scope = new Scope(setup.settings());
   Value scopeval(nullptr, std::unique_ptr<Scope>(scope));
   EXPECT_EQ("{ }", scopeval.ToString(false));
 
+  // Test that an empty scope equals an empty scope.
+  EXPECT_TRUE(scopeval == scopeval);
+
   scope->SetValue("a", Value(nullptr, static_cast<int64_t>(42)), nullptr);
   scope->SetValue("b", Value(nullptr, "hello, world"), nullptr);
   EXPECT_EQ("{\n  a = 42\n  b = \"hello, world\"\n}", scopeval.ToString(false));
+  EXPECT_TRUE(scopeval == scopeval);
+
+  Scope* inner_scope = new Scope(setup.settings());
+  Value inner_scopeval(nullptr, std::unique_ptr<Scope>(inner_scope));
+  inner_scope->SetValue("d", Value(nullptr, static_cast<int64_t>(42)), nullptr);
+  scope->SetValue("c", inner_scopeval, nullptr);
+
+  // Test inner scope equality.
+  EXPECT_TRUE(scopeval == scopeval);
+
+  // Nested scopes should not be equal.
+  Scope* nested_scope = new Scope(scope);
+  Value nested_scopeval(nullptr, std::unique_ptr<Scope>(nested_scope));
+  EXPECT_FALSE(nested_scopeval == nested_scopeval);
 }