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