407 lines
15 KiB
C++
407 lines
15 KiB
C++
|
//===- 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<const Node *> Children) {
|
||
|
std::vector<std::pair<Node *, NodeRole>> 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<std::vector<const Tree *>>
|
||
|
generateAllForests(ArrayRef<const Node *> 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<std::vector<const Tree *>> 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<const Tree *>
|
||
|
generateAllTreesWithShape(ArrayRef<const Node *> Base,
|
||
|
ArrayRef<unsigned> 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<std::vector<const Node *>> Layer,
|
||
|
unsigned NextLayerNodeCount) {
|
||
|
std::vector<std::vector<const Node *>> NextLayer;
|
||
|
for (const auto &Base : Layer) {
|
||
|
for (const auto &NextBase :
|
||
|
generateAllForests(Base, NextLayerNodeCount)) {
|
||
|
NextLayer.push_back(
|
||
|
std::vector<const Node *>(NextBase.begin(), NextBase.end()));
|
||
|
}
|
||
|
}
|
||
|
return NextLayer;
|
||
|
};
|
||
|
|
||
|
std::vector<std::vector<const Node *>> Layer = {Base};
|
||
|
for (auto NodeCount : NodeCountPerLayer)
|
||
|
Layer = GenerateNextLayer(Layer, NodeCount);
|
||
|
|
||
|
std::vector<const Tree *> 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<const Node *> 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<const Node *> 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<Node *> 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<decltype(*It), syntax::Node &>::value,
|
||
|
"mutable range");
|
||
|
static_assert(std::is_same<decltype(*CIt), const syntax::Node &>::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<List::ElementAndDelimiter<Node>> EDs) {
|
||
|
std::string Storage;
|
||
|
llvm::raw_string_ostream OS(Storage);
|
||
|
|
||
|
OS << "[";
|
||
|
|
||
|
llvm::interleaveComma(
|
||
|
EDs, OS, [&OS, this](const List::ElementAndDelimiter<Node> &ED) {
|
||
|
OS << "(" << dumpQuotedTokensOrNull(ED.element) << ", "
|
||
|
<< dumpQuotedTokensOrNull(ED.delimiter) << ")";
|
||
|
});
|
||
|
|
||
|
OS << "]";
|
||
|
|
||
|
return OS.str();
|
||
|
}
|
||
|
|
||
|
std::string dumpNodes(ArrayRef<Node *> 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::List>(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::List>(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::List>(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::List>(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::List>(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::List>(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::List>(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::List>(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
|