Support defined(foo["bar"]) syntax.

Since it is already possible to use a string subscript to
get the value of a scope member (i.e. foo["bar"] being the
same as foo.bar, but [] allowing expression evaluation),
this ensures that `defined(foo["bar"])` works as `defined(foo.bar)`
as well.

Bug: 328
Change-Id: I41c069b3e782745765f85aa0c1a6f7ac75f43c82
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15200
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index 59d97c4..e41d688 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -514,6 +514,13 @@
   named scope foo. It will throw an error if foo is not defined or is not a
   scope.
 
+  You can also check a named scope using a subscript string expression:
+    defined(foo[bar + "_name"])
+  Which will return true or false depending on whether the subscript
+  expression expands to the name of a member of the scope foo. It will
+  throw an error if foo is not defined or is not a scope, or if the
+  expression does not expand to a string, or if it is an empty string.
+
 Example
 
   template("mytemplate") {
@@ -551,27 +558,40 @@
 
   const AccessorNode* accessor = args_vector[0]->AsAccessor();
   if (accessor) {
-    // Passed an accessor "defined(foo.bar)".
-    if (accessor->member()) {
-      // The base of the accessor must be a scope if it's defined.
-      const Value* base = scope->GetValue(accessor->base().value());
-      if (!base) {
-        *err = Err(accessor, "Undefined identifier");
-        return Value();
-      }
-      if (!base->VerifyTypeIs(Value::SCOPE, err))
-        return Value();
+    // The base of the accessor must be a scope if it's defined.
+    const Value* base = scope->GetValue(accessor->base().value());
+    if (!base) {
+      *err = Err(accessor, "Undefined identifier");
+      return Value();
+    }
+    if (!base->VerifyTypeIs(Value::SCOPE, err))
+      return Value();
 
+    std::string scope_member;
+
+    if (accessor->member()) {
+      // Passed an accessor "defined(foo.bar)".
+      scope_member = accessor->member()->value().value();
+    } else if (accessor->subscript()) {
+      // Passed an accessor "defined(foo["bar"])".
+      Value subscript_value = accessor->subscript()->Execute(scope, err);
+      if (err->has_error())
+        return Value();
+      if (!subscript_value.VerifyTypeIs(Value::STRING, err))
+        return Value();
+      scope_member = subscript_value.string_value();
+    }
+    if (!scope_member.empty()) {
       // Check the member inside the scope to see if its defined.
-      if (base->scope_value()->GetValue(accessor->member()->value().value()))
-        return Value(function, true);
-      return Value(function, false);
+      bool result = base->scope_value()->GetValue(scope_member) != nullptr;
+      return Value(function, result);
     }
   }
 
   // Argument is invalid.
   *err = Err(function, "Bad thing passed to defined().",
-             "It should be of the form defined(foo) or defined(foo.bar).");
+             "It should be of the form defined(foo), defined(foo.bar) or "
+             "defined(foo[<string-expression>]).");
   return Value();
 }
 
diff --git a/src/gn/functions_unittest.cc b/src/gn/functions_unittest.cc
index 6498415..bb7b0a3 100644
--- a/src/gn/functions_unittest.cc
+++ b/src/gn/functions_unittest.cc
@@ -115,6 +115,21 @@
                                  &args_list_accessor_defined, &err);
   ASSERT_EQ(Value::BOOLEAN, result.type());
   EXPECT_FALSE(result.boolean_value());
+
+  // Should also work by pasing an accessor node so you can do
+  // "defined(def["foo"])" to see if foo is defined on the def scope.
+  std::unique_ptr<AccessorNode> subscript_accessor =
+      std::make_unique<AccessorNode>();
+  subscript_accessor->set_base(defined_token);
+  subscript_accessor->set_subscript(
+      std::make_unique<LiteralNode>(Token(Location(), Token::STRING, "foo")));
+  ListNode args_list_subscript_accessor_defined;
+  args_list_subscript_accessor_defined.append_item(
+      std::move(subscript_accessor));
+  result = functions::RunDefined(setup.scope(), &function_call,
+                                 &args_list_subscript_accessor_defined, &err);
+  ASSERT_EQ(Value::BOOLEAN, result.type());
+  EXPECT_FALSE(result.boolean_value());
 }
 
 // Tests that an error is thrown when a {} is supplied to a function that