Fix variable use tracking for scope subscript accesses.

Accessing a scope field with a subscript expression as in:

  scope["foo"]

Did not mark the 'foo' field in the scope as used, unlike
the corresponding expression `scope.foo`.

CL fixes that sharing the logic for scope member evaluation
between AccessorNode::ExecuteScopeSubscriptAccess() and
AccessorNode::ExecuteScopeAccess().

Change-Id: I97b0efa5a2d24c6a99b1a70a33626d1860747909
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15920
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/parse_tree.cc b/src/gn/parse_tree.cc
index 28d5313..0f1c27e 100644
--- a/src/gn/parse_tree.cc
+++ b/src/gn/parse_tree.cc
@@ -391,7 +391,7 @@
   if (!key_value.VerifyTypeIs(Value::STRING, err))
     return Value();
   const Value* result =
-      base_value->scope_value()->GetValue(key_value.string_value());
+      ExecuteScopeAccessForMember(scope, key_value.string_value(), err);
   if (!result) {
     *err =
         Err(subscript_.get(), "No value named \"" + key_value.string_value() +
@@ -402,6 +402,21 @@
 }
 
 Value AccessorNode::ExecuteScopeAccess(Scope* scope, Err* err) const {
+  const Value* result =
+      ExecuteScopeAccessForMember(scope, member_->value().value(), err);
+
+  if (!result) {
+    *err = Err(member_.get(), "No value named \"" + member_->value().value() +
+                                  "\" in scope \"" + base_.value() + "\"");
+    return Value();
+  }
+  return *result;
+}
+
+const Value* AccessorNode::ExecuteScopeAccessForMember(
+    Scope* scope,
+    std::string_view member_str,
+    Err* err) const {
   // We jump through some hoops here since ideally a.b will count "b" as
   // accessed in the given scope. The value "a" might be in some normal nested
   // scope and we can modify it, but it might also be inherited from the
@@ -418,30 +433,22 @@
     // Common case: base value is mutable so we can track variable accesses
     // for unused value warnings.
     if (!mutable_base_value->VerifyTypeIs(Value::SCOPE, err))
-      return Value();
-    result = mutable_base_value->scope_value()->GetValue(
-        member_->value().value(), true);
+      return nullptr;
+    result = mutable_base_value->scope_value()->GetValue(member_str, true);
   } else {
     // Fall back to see if the value is on a read-only scope.
     const Value* const_base_value = scope->GetValue(base_.value(), true);
     if (const_base_value) {
       // Read only value, don't try to mark the value access as a "used" one.
       if (!const_base_value->VerifyTypeIs(Value::SCOPE, err))
-        return Value();
-      result =
-          const_base_value->scope_value()->GetValue(member_->value().value());
+        return nullptr;
+      result = const_base_value->scope_value()->GetValue(member_str);
     } else {
       *err = Err(base_, "Undefined identifier.");
-      return Value();
+      return nullptr;
     }
   }
-
-  if (!result) {
-    *err = Err(member_.get(), "No value named \"" + member_->value().value() +
-                                  "\" in scope \"" + base_.value() + "\"");
-    return Value();
-  }
-  return *result;
+  return result;
 }
 
 void AccessorNode::SetNewLocation(int line_number) {
diff --git a/src/gn/parse_tree.h b/src/gn/parse_tree.h
index 7922cd6..2ed8c54 100644
--- a/src/gn/parse_tree.h
+++ b/src/gn/parse_tree.h
@@ -206,6 +206,9 @@
                                     const Value* base_value,
                                     Err* err) const;
   Value ExecuteScopeAccess(Scope* scope, Err* err) const;
+  const Value* ExecuteScopeAccessForMember(Scope* scope,
+                                           std::string_view member_str,
+                                           Err* err) const;
 
   static constexpr const char* kDumpAccessorKind = "accessor_kind";
   static constexpr const char* kDumpAccessorKindSubscript = "subscript";
diff --git a/src/gn/parse_tree_unittest.cc b/src/gn/parse_tree_unittest.cc
index cc49848..9b684f0 100644
--- a/src/gn/parse_tree_unittest.cc
+++ b/src/gn/parse_tree_unittest.cc
@@ -83,15 +83,20 @@
   EXPECT_EQ(second.type(), Value::INTEGER);
   EXPECT_EQ(second.int_value(), 3);
 
+  const Scope* scope_var = setup.scope()->GetValue("scope")->scope_value();
+  EXPECT_TRUE(scope_var->IsSetButUnused("foo"));
   Value foo = setup.ExecuteExpression("scope[\"foo\"]", &err);
   EXPECT_FALSE(err.has_error());
   EXPECT_EQ(foo.type(), Value::INTEGER);
   EXPECT_EQ(foo.int_value(), 5);
+  EXPECT_FALSE(scope_var->IsSetButUnused("foo"));
 
+  EXPECT_TRUE(scope_var->IsSetButUnused("bar"));
   Value bar = setup.ExecuteExpression("scope[bar_key]", &err);
   EXPECT_FALSE(err.has_error());
   EXPECT_EQ(bar.type(), Value::INTEGER);
   EXPECT_EQ(bar.int_value(), 8);
+  EXPECT_FALSE(scope_var->IsSetButUnused("bar"));
 
   Value invalid1 = setup.ExecuteExpression("scope[second_element_idx]", &err);
   EXPECT_TRUE(err.has_error());