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());