diff --git a/clang/lib/Tooling/Syntax/Tree.cpp b/clang/lib/Tooling/Syntax/Tree.cpp --- a/clang/lib/Tooling/Syntax/Tree.cpp +++ b/clang/lib/Tooling/Syntax/Tree.cpp @@ -255,27 +255,24 @@ } syntax::Leaf *syntax::Tree::findFirstLeaf() { - auto *T = this; - while (auto *C = T->getFirstChild()) { + for (auto *C = getFirstChild(); C; C = C->getNextSibling()) { if (auto *L = dyn_cast(C)) return L; - T = cast(C); + if (auto *L = cast(C)->findFirstLeaf()) + return L; } return nullptr; } syntax::Leaf *syntax::Tree::findLastLeaf() { - auto *T = this; - while (auto *C = T->getFirstChild()) { - // Find the last child. - while (auto *Next = C->getNextSibling()) - C = Next; - + syntax::Leaf *Last = nullptr; + for (auto *C = getFirstChild(); C; C = C->getNextSibling()) { if (auto *L = dyn_cast(C)) - return L; - T = cast(C); + Last = L; + else if (auto *L = cast(C)->findLastLeaf()) + Last = L; } - return nullptr; + return Last; } syntax::Node *syntax::Tree::findChild(NodeRole R) { diff --git a/clang/unittests/Tooling/Syntax/CMakeLists.txt b/clang/unittests/Tooling/Syntax/CMakeLists.txt --- a/clang/unittests/Tooling/Syntax/CMakeLists.txt +++ b/clang/unittests/Tooling/Syntax/CMakeLists.txt @@ -7,6 +7,7 @@ BuildTreeTest.cpp MutationsTest.cpp SynthesisTest.cpp + TreeTest.cpp TokensTest.cpp ) diff --git a/clang/unittests/Tooling/Syntax/TreeTest.cpp b/clang/unittests/Tooling/Syntax/TreeTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Tooling/Syntax/TreeTest.cpp @@ -0,0 +1,141 @@ +//===- TreeTest.cpp ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file tests the Syntax Tree base API. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Syntax/Tree.h" +#include "TreeTestBase.h" +#include "clang/Tooling/Syntax/BuildTree.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace clang::syntax; + +namespace { + +class TreeTest : public SyntaxTreeTest { +protected: + ::testing::AssertionResult treeDumpEqual(const syntax::Node *Root, + StringRef Dump) { + if (!Root) + return ::testing::AssertionFailure() + << "Root was not built successfully."; + + auto Actual = StringRef(Root->dump(Arena->getSourceManager())).trim().str(); + auto Expected = Dump.trim().str(); + // EXPECT_EQ shows the diff between the two strings if they are different. + EXPECT_EQ(Expected, Actual); + if (Actual != Expected) { + return ::testing::AssertionFailure(); + } + return ::testing::AssertionSuccess(); + } + + Tree *createTree(ArrayRef Children) { + auto ChildrenWithRoles = std::vector>(); + ChildrenWithRoles.reserve(Children.size()); + for (const auto *Child : Children) { + ChildrenWithRoles.push_back( + std::make_pair(deepCopy(*Arena, Child), NodeRole::Unknown)); + } + return clang::syntax::createTree(*Arena, ChildrenWithRoles, + NodeKind::UnknownExpression); + } + + // Generate Forests by combining `Children` under `ParentCount` Trees. + // + // We do this recursively. + std::vector> + generateAllForests(ArrayRef Children, unsigned ParentCount) { + assert(ParentCount > 0); + // If there is only one Parent node, we need to combine `Children` under + // this Parent. + if (ParentCount == 1) + return {{createTree(Children)}}; + + // Otherwise, we combine `ChildrenCount` children under the last parent and + // solve the smaller problem without these children and this parent. Finally + // we combine the results for every possible `ChildrenCount`. + auto AllForests = std::vector>(); + for (unsigned ChildrenCount = 0; ChildrenCount <= Children.size(); + ++ChildrenCount) { + auto *LastParent = createTree(Children.take_back(ChildrenCount)); + for (auto &Forest : generateAllForests(Children.drop_back(ChildrenCount), + ParentCount - 1)) { + Forest.push_back(LastParent); + AllForests.push_back(Forest); + } + } + return AllForests; + } + + // Generates all trees with a `Base` layer of `Node`s and `NodeCountPerLayer` + // `Node`s per layer. An example of Tree with `Base` = {`(`, `)`} and + // `NodeCountPerLayer` = {2, 2}: + // Tree + // |-Tree + // `-Tree + // |-Tree + // | `-'(' + // `-Tree + // `-')' + std::vector + generateAllTreesWithShape(ArrayRef Base, + ArrayRef NodeCountPerLayer) { + auto GenerateNextLayers = [this](ArrayRef> Bases, + unsigned NodeCount) { + auto NextLayers = std::vector>(); + for (const auto &Base : Bases) { + for (const auto &NextLayer : generateAllForests(Base, NodeCount)) { + NextLayers.push_back( + std::vector(NextLayer.begin(), NextLayer.end())); + } + } + return NextLayers; + }; + + auto Layers = std::vector>({Base}); + for (auto NodeCount : NodeCountPerLayer) { + Layers = GenerateNextLayers(Layers, NodeCount); + } + + auto AllTrees = std::vector(); + AllTrees.reserve(Layers.size()); + for (const auto &Layer : Layers) { + AllTrees.push_back(createTree(Layer)); + } + return AllTrees; + } +}; + +INSTANTIATE_TEST_CASE_P(TreeTests, TreeTest, + ::testing::ValuesIn(allTestClangConfigs()), ); + +TEST_P(TreeTest, FirstLeaf) { + buildTree("", GetParam()); + std::vector Leafs = {createLeaf(*Arena, tok::l_paren), + createLeaf(*Arena, tok::r_paren)}; + for (const auto *Tree : generateAllTreesWithShape(Leafs, {3u})) { + ASSERT_TRUE(Tree->findFirstLeaf() != nullptr); + EXPECT_EQ(Tree->findFirstLeaf()->getToken()->kind(), tok::l_paren); + } +} + +TEST_P(TreeTest, LastLeaf) { + buildTree("", GetParam()); + std::vector Leafs = {createLeaf(*Arena, tok::l_paren), + createLeaf(*Arena, tok::r_paren)}; + for (const auto *Tree : generateAllTreesWithShape(Leafs, {3u})) { + ASSERT_TRUE(Tree->findLastLeaf() != nullptr); + EXPECT_EQ(Tree->findLastLeaf()->getToken()->kind(), tok::r_paren); + } +} + +} // namespace