//===- 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 // //===----------------------------------------------------------------------===// #include "clang/Tooling/Syntax/Tree.h" #include "TreeTestBase.h" #include "clang/Basic/SourceManager.h" #include "clang/Tooling/Syntax/BuildTree.h" #include "clang/Tooling/Syntax/Nodes.h" #include "llvm/ADT/STLExtras.h" #include "gtest/gtest.h" using namespace clang; using namespace clang::syntax; namespace { using testing::ElementsAre; class TreeTest : public SyntaxTreeTest { private: Tree *createTree(ArrayRef Children) { std::vector> ChildrenWithRoles; ChildrenWithRoles.reserve(Children.size()); for (const auto *Child : Children) { ChildrenWithRoles.push_back(std::make_pair( deepCopyExpandingMacros(*Arena, Child), NodeRole::Unknown)); } return clang::syntax::createTree(*Arena, ChildrenWithRoles, NodeKind::UnknownExpression); } // Generate Forests by combining `Children` into `ParentCount` Trees. // // We do this recursively. std::vector> generateAllForests(ArrayRef Children, unsigned ParentCount) { assert(ParentCount > 0); // If there is only one Parent node, then combine `Children` under // this Parent. if (ParentCount == 1) return {{createTree(Children)}}; // Otherwise, combine `ChildrenCount` children under the last parent and // solve the smaller problem without these children and this parent. Do this // for every `ChildrenCount` and combine the results. std::vector> AllForests; 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; } protected: // Generates all trees with a `Base` 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) { // We compute the solution per layer. A layer is a collection of bases, // where each base has the same number of nodes, given by // `NodeCountPerLayer`. auto GenerateNextLayer = [this](ArrayRef> Layer, unsigned NextLayerNodeCount) { std::vector> NextLayer; for (const auto &Base : Layer) { for (const auto &NextBase : generateAllForests(Base, NextLayerNodeCount)) { NextLayer.push_back( std::vector(NextBase.begin(), NextBase.end())); } } return NextLayer; }; std::vector> Layer = {Base}; for (auto NodeCount : NodeCountPerLayer) Layer = GenerateNextLayer(Layer, NodeCount); std::vector AllTrees; AllTrees.reserve(Layer.size()); for (const auto &Base : Layer) AllTrees.push_back(createTree(Base)); 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); } } TEST_F(TreeTest, Iterators) { buildTree("", allTestClangConfigs().front()); std::vector Children = {createLeaf(*Arena, tok::identifier, "a"), createLeaf(*Arena, tok::identifier, "b"), createLeaf(*Arena, tok::identifier, "c")}; auto *Tree = syntax::createTree(*Arena, {{Children[0], NodeRole::LeftHandSide}, {Children[1], NodeRole::OperatorToken}, {Children[2], NodeRole::RightHandSide}}, NodeKind::TranslationUnit); const auto *ConstTree = Tree; auto Range = Tree->getChildren(); EXPECT_THAT(Range, ElementsAre(role(NodeRole::LeftHandSide), role(NodeRole::OperatorToken), role(NodeRole::RightHandSide))); auto ConstRange = ConstTree->getChildren(); EXPECT_THAT(ConstRange, ElementsAre(role(NodeRole::LeftHandSide), role(NodeRole::OperatorToken), role(NodeRole::RightHandSide))); // FIXME: mutate and observe no invalidation. Mutations are private for now... auto It = Range.begin(); auto CIt = ConstRange.begin(); static_assert(std::is_same::value, "mutable range"); static_assert(std::is_same::value, "const range"); for (unsigned I = 0; I < 3; ++I) { EXPECT_EQ(It, CIt); EXPECT_TRUE(It); EXPECT_TRUE(CIt); EXPECT_EQ(It.asPointer(), Children[I]); EXPECT_EQ(CIt.asPointer(), Children[I]); EXPECT_EQ(&*It, Children[I]); EXPECT_EQ(&*CIt, Children[I]); ++It; ++CIt; } EXPECT_EQ(It, CIt); EXPECT_EQ(It, Tree::ChildIterator()); EXPECT_EQ(CIt, Tree::ConstChildIterator()); EXPECT_FALSE(It); EXPECT_FALSE(CIt); EXPECT_EQ(nullptr, It.asPointer()); EXPECT_EQ(nullptr, CIt.asPointer()); } class ListTest : public SyntaxTreeTest { private: std::string dumpQuotedTokensOrNull(const Node *N) { return N ? "'" + StringRef(N->dumpTokens(Arena->getSourceManager())) .trim() .str() + "'" : "null"; } protected: std::string dumpElementsAndDelimiters(ArrayRef> EDs) { std::string Storage; llvm::raw_string_ostream OS(Storage); OS << "["; llvm::interleaveComma( EDs, OS, [&OS, this](const List::ElementAndDelimiter &ED) { OS << "(" << dumpQuotedTokensOrNull(ED.element) << ", " << dumpQuotedTokensOrNull(ED.delimiter) << ")"; }); OS << "]"; return OS.str(); } std::string dumpNodes(ArrayRef Nodes) { std::string Storage; llvm::raw_string_ostream OS(Storage); OS << "["; llvm::interleaveComma(Nodes, OS, [&OS, this](const Node *N) { OS << dumpQuotedTokensOrNull(N); }); OS << "]"; return OS.str(); } }; INSTANTIATE_TEST_CASE_P(TreeTests, ListTest, ::testing::ValuesIn(allTestClangConfigs()), ); /// "a, b, c" <=> [("a", ","), ("b", ","), ("c", null)] TEST_P(ListTest, List_Separated_WellFormed) { buildTree("", GetParam()); // "a, b, c" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, }, NodeKind::CallArguments)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', ','), ('b', ','), ('c', null)]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); } /// "a, , c" <=> [("a", ","), (null, ","), ("c", null)] TEST_P(ListTest, List_Separated_MissingElement) { buildTree("", GetParam()); // "a, , c" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, }, NodeKind::CallArguments)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', ','), (null, ','), ('c', null)]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']"); } /// "a, b c" <=> [("a", ","), ("b", null), ("c", null)] TEST_P(ListTest, List_Separated_MissingDelimiter) { buildTree("", GetParam()); // "a, b c" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, }, NodeKind::CallArguments)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', ','), ('b', null), ('c', null)]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); } /// "a, b," <=> [("a", ","), ("b", ","), (null, null)] TEST_P(ListTest, List_Separated_MissingLastElement) { buildTree("", GetParam()); // "a, b, c" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, }, NodeKind::CallArguments)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', ','), ('b', ','), (null, null)]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', null]"); } /// "a:: b:: c::" <=> [("a", "::"), ("b", "::"), ("c", "::")] TEST_P(ListTest, List_Terminated_WellFormed) { if (!GetParam().isCXX()) { return; } buildTree("", GetParam()); // "a:: b:: c::" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, }, NodeKind::NestedNameSpecifier)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', '::'), ('b', '::'), ('c', '::')]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); } /// "a:: :: c::" <=> [("a", "::"), (null, "::"), ("c", "::")] TEST_P(ListTest, List_Terminated_MissingElement) { if (!GetParam().isCXX()) { return; } buildTree("", GetParam()); // "a:: b:: c::" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, }, NodeKind::NestedNameSpecifier)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', '::'), (null, '::'), ('c', '::')]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']"); } /// "a:: b c::" <=> [("a", "::"), ("b", null), ("c", "::")] TEST_P(ListTest, List_Terminated_MissingDelimiter) { if (!GetParam().isCXX()) { return; } buildTree("", GetParam()); // "a:: b c::" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, }, NodeKind::NestedNameSpecifier)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', '::'), ('b', null), ('c', '::')]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); } /// "a:: b:: c" <=> [("a", "::"), ("b", "::"), ("c", null)] TEST_P(ListTest, List_Terminated_MissingLastDelimiter) { if (!GetParam().isCXX()) { return; } buildTree("", GetParam()); // "a:: b:: c" auto *List = dyn_cast(syntax::createTree( *Arena, { {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, }, NodeKind::NestedNameSpecifier)); EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), "[('a', '::'), ('b', '::'), ('c', null)]"); EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); } } // namespace