diff --git a/clang/include/clang/Tooling/Syntax/BuildTree.h b/clang/include/clang/Tooling/Syntax/BuildTree.h --- a/clang/include/clang/Tooling/Syntax/BuildTree.h +++ b/clang/include/clang/Tooling/Syntax/BuildTree.h @@ -24,8 +24,17 @@ // Create syntax trees from subtrees not backed by the source code. -clang::syntax::Leaf *createPunctuation(clang::syntax::Arena &A, - clang::tok::TokenKind K); +// Synthesis of Leafs +/// Create `Leaf` from token with `Spelling` and assert it has the desired +/// `TokenKind`. +syntax::Leaf *createLeaf(syntax::Arena &A, tok::TokenKind K, + StringRef Spelling); + +/// Infer the token spelling from its `TokenKind`, then create `Leaf` from +/// this token +syntax::Leaf *createLeaf(syntax::Arena &A, tok::TokenKind K); + +// Synthesis of Syntax Nodes clang::syntax::EmptyStatement *createEmptyStatement(clang::syntax::Arena &A); } // namespace syntax diff --git a/clang/lib/Tooling/Syntax/Synthesis.cpp b/clang/lib/Tooling/Syntax/Synthesis.cpp --- a/clang/lib/Tooling/Syntax/Synthesis.cpp +++ b/clang/lib/Tooling/Syntax/Synthesis.cpp @@ -5,13 +5,14 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// +#include "clang/Basic/TokenKinds.h" #include "clang/Tooling/Syntax/BuildTree.h" using namespace clang; /// Exposes private syntax tree APIs required to implement node synthesis. /// Should not be used for anything else. -class syntax::FactoryImpl { +class clang::syntax::FactoryImpl { public: static void setCanModify(syntax::Node *N) { N->CanModify = true; } @@ -21,24 +22,32 @@ } }; -clang::syntax::Leaf *syntax::createPunctuation(clang::syntax::Arena &A, - clang::tok::TokenKind K) { - auto Tokens = A.lexBuffer(llvm::MemoryBuffer::getMemBuffer( - clang::tok::getPunctuatorSpelling(K))) - .second; +syntax::Leaf *clang::syntax::createLeaf(syntax::Arena &A, tok::TokenKind K, + StringRef Spelling) { + auto Tokens = A.lexBuffer(llvm::MemoryBuffer::getMemBuffer(Spelling)).second; assert(Tokens.size() == 1); - assert(Tokens.front().kind() == K); - auto *L = new (A.getAllocator()) clang::syntax::Leaf(Tokens.begin()); - FactoryImpl::setCanModify(L); - L->assertInvariants(); - return L; + assert(Tokens.front().kind() == K && + "spelling is not lexed into the expected kind of token"); + + auto *Leaf = new (A.getAllocator()) syntax::Leaf(Tokens.begin()); + syntax::FactoryImpl::setCanModify(Leaf); + Leaf->assertInvariants(); + return Leaf; +} + +syntax::Leaf *clang::syntax::createLeaf(syntax::Arena &A, tok::TokenKind K) { + const auto *Spelling = tok::getPunctuatorSpelling(K); + if (!Spelling) + Spelling = tok::getKeywordSpelling(K); + assert(Spelling && + "Cannot infer the spelling of the token from its token kind."); + return createLeaf(A, K, Spelling); } -clang::syntax::EmptyStatement * -syntax::createEmptyStatement(clang::syntax::Arena &A) { - auto *S = new (A.getAllocator()) clang::syntax::EmptyStatement; +syntax::EmptyStatement *clang::syntax::createEmptyStatement(syntax::Arena &A) { + auto *S = new (A.getAllocator()) syntax::EmptyStatement; FactoryImpl::setCanModify(S); - FactoryImpl::prependChildLowLevel(S, createPunctuation(A, clang::tok::semi), + FactoryImpl::prependChildLowLevel(S, createLeaf(A, tok::semi), NodeRole::Unknown); S->assertInvariants(); return S; diff --git a/clang/unittests/Tooling/Syntax/SynthesisTest.cpp b/clang/unittests/Tooling/Syntax/SynthesisTest.cpp --- a/clang/unittests/Tooling/Syntax/SynthesisTest.cpp +++ b/clang/unittests/Tooling/Syntax/SynthesisTest.cpp @@ -12,33 +12,81 @@ #include "TreeTestBase.h" #include "clang/Tooling/Syntax/BuildTree.h" +#include "gtest/gtest.h" using namespace clang; using namespace clang::syntax; namespace { -INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest, +class SynthesisTest : public SyntaxTreeTest { +protected: + ::testing::AssertionResult treeDumpEqual(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(); + } +}; + +INSTANTIATE_TEST_CASE_P(SynthesisTests, SynthesisTest, ::testing::ValuesIn(allTestClangConfigs()), ); -TEST_P(SyntaxTreeTest, Leaf_Punctuation) { +TEST_P(SynthesisTest, Leaf_Punctuation) { + buildTree("", GetParam()); + + auto *Leaf = createLeaf(*Arena, tok::comma); + + EXPECT_TRUE(treeDumpEqual(Leaf, R"txt( +',' Detached synthesized + )txt")); +} + +TEST_P(SynthesisTest, Leaf_Keyword) { + buildTree("", GetParam()); + + auto *Leaf = createLeaf(*Arena, tok::kw_if); + + EXPECT_TRUE(treeDumpEqual(Leaf, R"txt( +'if' Detached synthesized + )txt")); +} + +TEST_P(SynthesisTest, Leaf_Identifier) { buildTree("", GetParam()); - auto *C = syntax::createPunctuation(*Arena, tok::comma); - ASSERT_NE(C, nullptr); - EXPECT_EQ(C->getToken()->kind(), tok::comma); - EXPECT_TRUE(C->canModify()); - EXPECT_FALSE(C->isOriginal()); - EXPECT_TRUE(C->isDetached()); + auto *Leaf = createLeaf(*Arena, tok::identifier, "a"); + + EXPECT_TRUE(treeDumpEqual(Leaf, R"txt( +'a' Detached synthesized + )txt")); +} + +TEST_P(SynthesisTest, Leaf_Number) { + buildTree("", GetParam()); + + auto *Leaf = createLeaf(*Arena, tok::numeric_constant, "1"); + + EXPECT_TRUE(treeDumpEqual(Leaf, R"txt( +'1' Detached synthesized + )txt")); } -TEST_P(SyntaxTreeTest, Statement_Empty) { +TEST_P(SynthesisTest, Statement_EmptyStatement) { buildTree("", GetParam()); - auto *S = syntax::createEmptyStatement(*Arena); - ASSERT_NE(S, nullptr); - EXPECT_TRUE(S->canModify()); - EXPECT_FALSE(S->isOriginal()); - EXPECT_TRUE(S->isDetached()); + auto *S = createEmptyStatement(*Arena); + EXPECT_TRUE(treeDumpEqual(S, R"txt( +EmptyStatement Detached synthesized +`-';' synthesized + )txt")); } } // namespace