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