| // Copyright 2014 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gn/parse_tree.h" | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include "base/containers/span.h" | |
| #include "gn/input_file.h" | |
| #include "gn/scope.h" | |
| #include "gn/test_with_scope.h" | |
| #include "util/test/test.h" | |
| TEST(ParseTree, Accessor) { | |
| TestWithScope setup; | |
| // Make a pretend parse node with proper tracking that we can blame for the | |
| // given value. | |
| InputFile input_file(SourceFile("//foo")); | |
| Token base_token(Location(&input_file, 1, 1), Token::IDENTIFIER, "a"); | |
| Token member_token(Location(&input_file, 1, 1), Token::IDENTIFIER, "b"); | |
| AccessorNode accessor; | |
| accessor.set_base(base_token); | |
| std::unique_ptr<IdentifierNode> member_identifier = | |
| std::make_unique<IdentifierNode>(member_token); | |
| accessor.set_member(std::move(member_identifier)); | |
| // The access should fail because a is not defined. | |
| Err err; | |
| Value result = accessor.Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| err = Err(); | |
| EXPECT_EQ(Value::NONE, result.type()); | |
| // Define a as a Scope. It should still fail because b isn't defined. | |
| err = Err(); | |
| setup.scope()->SetValue( | |
| "a", Value(nullptr, std::make_unique<Scope>(setup.scope())), nullptr); | |
| result = accessor.Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ(Value::NONE, result.type()); | |
| // Define b, accessor should succeed now. | |
| const int64_t kBValue = 42; | |
| err = Err(); | |
| setup.scope() | |
| ->GetMutableValue("a", Scope::SEARCH_NESTED, false) | |
| ->scope_value() | |
| ->SetValue("b", Value(nullptr, kBValue), nullptr); | |
| result = accessor.Execute(setup.scope(), &err); | |
| EXPECT_SUCCESS(err); | |
| ASSERT_EQ(Value::INTEGER, result.type()); | |
| EXPECT_EQ(kBValue, result.int_value()); | |
| } | |
| TEST(ParseTree, SubscriptedAccess) { | |
| TestWithScope setup; | |
| Err err; | |
| TestParseInput values( | |
| "list = [ 2, 3 ]\n" | |
| "scope = {\n" | |
| " foo = 5\n" | |
| " bar = 8\n" | |
| "}\n" | |
| "bar_key = \"bar\"" | |
| "second_element_idx = 1"); | |
| values.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_SUCCESS(err); | |
| Value first = setup.ExecuteExpression("list[0]", &err); | |
| EXPECT_SUCCESS(err); | |
| EXPECT_EQ(first.type(), Value::INTEGER); | |
| EXPECT_EQ(first.int_value(), 2); | |
| Value second = setup.ExecuteExpression("list[second_element_idx]", &err); | |
| EXPECT_SUCCESS(err); | |
| 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_SUCCESS(err); | |
| 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_SUCCESS(err); | |
| 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()); | |
| err = Err(); | |
| EXPECT_EQ(invalid1.type(), Value::NONE); | |
| Value invalid2 = setup.ExecuteExpression("list[bar_key]", &err); | |
| EXPECT_TRUE(err.has_error()); | |
| err = Err(); | |
| EXPECT_EQ(invalid2.type(), Value::NONE); | |
| Value invalid3 = setup.ExecuteExpression("scope[\"baz\"]", &err); | |
| EXPECT_TRUE(err.has_error()); | |
| err = Err(); | |
| EXPECT_EQ(invalid3.type(), Value::NONE); | |
| } | |
| TEST(ParseTree, BlockUnusedVars) { | |
| TestWithScope setup; | |
| // Printing both values should be OK. | |
| // | |
| // The crazy template definition here is a way to execute a block without | |
| // defining a target. Templates require that both the target_name and the | |
| // invoker be used, which is what the assertion statement inside the template | |
| // does. | |
| TestParseInput input_all_used( | |
| "template(\"foo\") { assert(target_name != 0 && invoker != 0) }\n" | |
| "foo(\"a\") {\n" | |
| " a = 12\n" | |
| " b = 13\n" | |
| " print(\"$a $b\")\n" | |
| "}"); | |
| EXPECT_FALSE(input_all_used.has_error()); | |
| Err err; | |
| input_all_used.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_SUCCESS(err); | |
| // Skipping one should throw an unused var error. | |
| TestParseInput input_unused( | |
| "foo(\"a\") {\n" | |
| " a = 12\n" | |
| " b = 13\n" | |
| " print(\"$a\")\n" | |
| "}"); | |
| EXPECT_FALSE(input_unused.has_error()); | |
| input_unused.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| // Also verify that the unused variable has the correct origin set. The | |
| // origin will point to the value assigned to the variable (in this case, the | |
| // "13" assigned to "b". | |
| EXPECT_EQ(3, err.location().line_number()); | |
| EXPECT_EQ(7, err.location().column_number()); | |
| } | |
| TEST(ParseTree, OriginForDereference) { | |
| TestWithScope setup; | |
| TestParseInput input( | |
| "a = 6\n" | |
| "get_target_outputs(a)"); | |
| EXPECT_SUCCESS(input); | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| // The origin for the "not a string" error message should be where the value | |
| // was dereferenced (the "a" on the second line). | |
| EXPECT_EQ(2, err.location().line_number()); | |
| EXPECT_EQ(20, err.location().column_number()); | |
| } | |
| TEST(ParseTree, ShortenTargets) { | |
| TestParseInput input( | |
| "deps = [\n" | |
| " \":abc\",\n" | |
| " \"bcd/efg\",\n" | |
| " \"cde/fgh:fgh\",\n" | |
| " \"cde/fgh:fghz\",\n" | |
| " \"//def/ghi\",\n" | |
| " \"//efg/hij:hij\",\n" | |
| " \"//efg/hij:hihihi\",\n" | |
| "]\n"); | |
| EXPECT_SUCCESS(input); | |
| ASSERT_TRUE(input.parsed()->AsBlock()); | |
| ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp()); | |
| const BinaryOpNode* binop = | |
| input.parsed()->AsBlock()->statements()[0]->AsBinaryOp(); | |
| ASSERT_TRUE(binop->right()->AsList()); | |
| auto* list = const_cast<ListNode*>(binop->right()->AsList()); | |
| const auto& contents = list->contents(); | |
| ASSERT_EQ(7u, contents.size()); | |
| auto all_elements_are_literal_nodes = | |
| [](base::span<const std::unique_ptr<const ParseNode>> container) -> bool { | |
| return std::ranges::all_of( | |
| container, [](const std::unique_ptr<const ParseNode>& element) { | |
| return element->AsLiteral(); | |
| }); | |
| }; | |
| auto get_literal_value = [](const ParseNode& node) { | |
| return node.AsLiteral()->value().value(); | |
| }; | |
| ASSERT_TRUE(all_elements_are_literal_nodes(contents)); | |
| EXPECT_EQ("\":abc\"", get_literal_value(*contents[0])); | |
| EXPECT_EQ("\"bcd/efg\"", get_literal_value(*contents[1])); | |
| EXPECT_EQ("\"cde/fgh:fgh\"", get_literal_value(*contents[2])); | |
| EXPECT_EQ("\"cde/fgh:fghz\"", get_literal_value(*contents[3])); | |
| EXPECT_EQ("\"//def/ghi\"", get_literal_value(*contents[4])); | |
| EXPECT_EQ("\"//efg/hij:hij\"", get_literal_value(*contents[5])); | |
| EXPECT_EQ("\"//efg/hij:hihihi\"", get_literal_value(*contents[6])); | |
| list->ShortenTargets(); | |
| ASSERT_TRUE(all_elements_are_literal_nodes(contents)); | |
| EXPECT_EQ("\":abc\"", get_literal_value(*contents[0])); | |
| EXPECT_EQ("\"bcd/efg\"", get_literal_value(*contents[1])); | |
| EXPECT_EQ("\"cde/fgh\"", get_literal_value(*contents[2])); | |
| EXPECT_EQ("\"cde/fgh:fghz\"", get_literal_value(*contents[3])); | |
| EXPECT_EQ("\"//def/ghi\"", get_literal_value(*contents[4])); | |
| EXPECT_EQ("\"//efg/hij\"", get_literal_value(*contents[5])); | |
| EXPECT_EQ("\"//efg/hij:hihihi\"", get_literal_value(*contents[6])); | |
| } | |
| TEST(ParseTree, SortRangeExtraction) { | |
| TestWithScope setup; | |
| // Ranges are [begin, end). | |
| { | |
| TestParseInput input( | |
| "sources = [\n" | |
| " \"a\",\n" | |
| " \"b\",\n" | |
| " \n" | |
| " #\n" | |
| " # Block\n" | |
| " #\n" | |
| " \n" | |
| " \"c\"," | |
| " \"d\"," | |
| "]\n"); | |
| EXPECT_SUCCESS(input); | |
| ASSERT_TRUE(input.parsed()->AsBlock()); | |
| ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp()); | |
| const BinaryOpNode* binop = | |
| input.parsed()->AsBlock()->statements()[0]->AsBinaryOp(); | |
| ASSERT_TRUE(binop->right()->AsList()); | |
| const ListNode* list = binop->right()->AsList(); | |
| EXPECT_EQ(5u, list->contents().size()); | |
| auto ranges = list->GetSortRanges(); | |
| ASSERT_EQ(2u, ranges.size()); | |
| EXPECT_EQ(0u, ranges[0].begin); | |
| EXPECT_EQ(2u, ranges[0].end); | |
| EXPECT_EQ(3u, ranges[1].begin); | |
| EXPECT_EQ(5u, ranges[1].end); | |
| } | |
| { | |
| TestParseInput input( | |
| "sources = [\n" | |
| " \"a\",\n" | |
| " \"b\",\n" | |
| " \n" | |
| " # Attached comment.\n" | |
| " \"c\"," | |
| " \"d\"," | |
| "]\n"); | |
| EXPECT_SUCCESS(input); | |
| ASSERT_TRUE(input.parsed()->AsBlock()); | |
| ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp()); | |
| const BinaryOpNode* binop = | |
| input.parsed()->AsBlock()->statements()[0]->AsBinaryOp(); | |
| ASSERT_TRUE(binop->right()->AsList()); | |
| const ListNode* list = binop->right()->AsList(); | |
| EXPECT_EQ(4u, list->contents().size()); | |
| auto ranges = list->GetSortRanges(); | |
| ASSERT_EQ(2u, ranges.size()); | |
| EXPECT_EQ(0u, ranges[0].begin); | |
| EXPECT_EQ(2u, ranges[0].end); | |
| EXPECT_EQ(2u, ranges[1].begin); | |
| EXPECT_EQ(4u, ranges[1].end); | |
| } | |
| { | |
| TestParseInput input( | |
| "sources = [\n" | |
| " # At end of list.\n" | |
| " \"zzzzzzzzzzz.cc\"," | |
| "]\n"); | |
| EXPECT_SUCCESS(input); | |
| ASSERT_TRUE(input.parsed()->AsBlock()); | |
| ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp()); | |
| const BinaryOpNode* binop = | |
| input.parsed()->AsBlock()->statements()[0]->AsBinaryOp(); | |
| ASSERT_TRUE(binop->right()->AsList()); | |
| const ListNode* list = binop->right()->AsList(); | |
| EXPECT_EQ(1u, list->contents().size()); | |
| auto ranges = list->GetSortRanges(); | |
| ASSERT_EQ(1u, ranges.size()); | |
| EXPECT_EQ(0u, ranges[0].begin); | |
| EXPECT_EQ(1u, ranges[0].end); | |
| } | |
| { | |
| TestParseInput input( | |
| "sources = [\n" | |
| " # Block at start.\n" | |
| " \n" | |
| " \"z.cc\"," | |
| " \"y.cc\"," | |
| "]\n"); | |
| EXPECT_SUCCESS(input); | |
| ASSERT_TRUE(input.parsed()->AsBlock()); | |
| ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp()); | |
| const BinaryOpNode* binop = | |
| input.parsed()->AsBlock()->statements()[0]->AsBinaryOp(); | |
| ASSERT_TRUE(binop->right()->AsList()); | |
| const ListNode* list = binop->right()->AsList(); | |
| EXPECT_EQ(3u, list->contents().size()); | |
| auto ranges = list->GetSortRanges(); | |
| ASSERT_EQ(1u, ranges.size()); | |
| EXPECT_EQ(1u, ranges[0].begin); | |
| EXPECT_EQ(3u, ranges[0].end); | |
| } | |
| } | |
| TEST(ParseTree, Integers) { | |
| static const char* const kGood[] = { | |
| "0", | |
| "10", | |
| "-54321", | |
| "9223372036854775807", // INT64_MAX | |
| "-9223372036854775808", // INT64_MIN | |
| }; | |
| for (auto* s : kGood) { | |
| TestParseInput input(std::string("x = ") + s); | |
| EXPECT_SUCCESS(input); | |
| TestWithScope setup; | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_SUCCESS(err); | |
| } | |
| static const char* const kBad[] = { | |
| "-0", | |
| "010", | |
| "-010", | |
| "9223372036854775808", // INT64_MAX + 1 | |
| "-9223372036854775809", // INT64_MIN - 1 | |
| }; | |
| for (auto* s : kBad) { | |
| TestParseInput input(std::string("x = ") + s); | |
| EXPECT_SUCCESS(input); | |
| TestWithScope setup; | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| } | |
| } |