From e6bc4a71e3450d7230205683f63d6e22fbf9bf05 Mon Sep 17 00:00:00 2001 From: Yitzhak Mandelbaum Date: Fri, 4 Dec 2020 15:23:34 +0000 Subject: [PATCH] [libTooling] Add `describe` combinator for formatting AST nodes for diagnostics. This new stencil combinator is intended for use in diagnostics and the like. Differential Revision: https://reviews.llvm.org/D92658 --- .../clang/Tooling/Transformer/Stencil.h | 9 ++ clang/lib/Tooling/Transformer/Stencil.cpp | 27 +++++- clang/unittests/Tooling/StencilTest.cpp | 82 ++++++++++++++++++- 3 files changed, 111 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/Tooling/Transformer/Stencil.h b/clang/include/clang/Tooling/Transformer/Stencil.h index 1b50a670f70b..b729c826c808 100644 --- a/clang/include/clang/Tooling/Transformer/Stencil.h +++ b/clang/include/clang/Tooling/Transformer/Stencil.h @@ -123,6 +123,15 @@ inline Stencil ifBound(llvm::StringRef Id, llvm::StringRef TrueText, /// Stencil. This supports user-defined extensions to the \c Stencil language. Stencil run(MatchConsumer C); +/// Produces a human-readable rendering of the node bound to `Id`, suitable for +/// diagnostics and debugging. This operator can be applied to any node, but is +/// targeted at those whose source cannot be printed directly, including: +/// +/// * Types. represented based on their structure. Note that namespace +/// qualifiers are always printed, with the anonymous namespace represented +/// explicitly. No desugaring or canonicalization is applied. +Stencil describe(llvm::StringRef Id); + /// For debug use only; semantics are not guaranteed. /// /// \returns the string resulting from calling the node's print() method. diff --git a/clang/lib/Tooling/Transformer/Stencil.cpp b/clang/lib/Tooling/Transformer/Stencil.cpp index 2670bf7adabf..56f145393691 100644 --- a/clang/lib/Tooling/Transformer/Stencil.cpp +++ b/clang/lib/Tooling/Transformer/Stencil.cpp @@ -63,6 +63,7 @@ enum class UnaryNodeOperator { MaybeDeref, AddressOf, MaybeAddressOf, + Describe, }; // Generic container for stencil operations with a (single) node-id argument. @@ -133,6 +134,9 @@ std::string toStringData(const UnaryOperationData &Data) { case UnaryNodeOperator::MaybeAddressOf: OpName = "maybeAddressOf"; break; + case UnaryNodeOperator::Describe: + OpName = "describe"; + break; } return (OpName + "(\"" + Data.Id + "\")").str(); } @@ -174,11 +178,11 @@ Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &, return Error::success(); } -Error evalData(const DebugPrintNodeData &Data, - const MatchFinder::MatchResult &Match, std::string *Result) { +static Error printNode(StringRef Id, const MatchFinder::MatchResult &Match, + std::string *Result) { std::string Output; llvm::raw_string_ostream Os(Output); - auto NodeOrErr = getNode(Match.Nodes, Data.Id); + auto NodeOrErr = getNode(Match.Nodes, Id); if (auto Err = NodeOrErr.takeError()) return Err; NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts())); @@ -186,8 +190,18 @@ Error evalData(const DebugPrintNodeData &Data, return Error::success(); } +Error evalData(const DebugPrintNodeData &Data, + const MatchFinder::MatchResult &Match, std::string *Result) { + return printNode(Data.Id, Match, Result); +} + Error evalData(const UnaryOperationData &Data, const MatchFinder::MatchResult &Match, std::string *Result) { + // The `Describe` operation can be applied to any node, not just expressions, + // so it is handled here, separately. + if (Data.Op == UnaryNodeOperator::Describe) + return printNode(Data.Id, Match, Result); + const auto *E = Match.Nodes.getNodeAs(Data.Id); if (E == nullptr) return llvm::make_error( @@ -217,6 +231,8 @@ Error evalData(const UnaryOperationData &Data, } Source = tooling::buildAddressOf(*E, *Match.Context); break; + case UnaryNodeOperator::Describe: + llvm_unreachable("This case is handled at the start of the function"); } if (!Source) return llvm::make_error( @@ -359,6 +375,11 @@ Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) { UnaryNodeOperator::MaybeAddressOf, std::string(ExprId)); } +Stencil transformer::describe(StringRef Id) { + return std::make_shared>( + UnaryNodeOperator::Describe, std::string(Id)); +} + Stencil transformer::access(StringRef BaseId, Stencil Member) { return std::make_shared>(BaseId, std::move(Member)); } diff --git a/clang/unittests/Tooling/StencilTest.cpp b/clang/unittests/Tooling/StencilTest.cpp index c843e33dd0da..56e81431e558 100644 --- a/clang/unittests/Tooling/StencilTest.cpp +++ b/clang/unittests/Tooling/StencilTest.cpp @@ -30,7 +30,9 @@ using MatchResult = MatchFinder::MatchResult; // Create a valid translation-unit from a statement. static std::string wrapSnippet(StringRef StatementCode) { - return ("struct S { int field; }; auto stencil_test_snippet = []{" + + return ("namespace N { class C {}; } " + "namespace { class AnonC {}; } " + "struct S { int field; }; auto stencil_test_snippet = []{" + StatementCode + "};") .str(); } @@ -55,14 +57,14 @@ struct TestMatch { // `StatementCode` may contain other statements not described by `Matcher`. static llvm::Optional matchStmt(StringRef StatementCode, StatementMatcher Matcher) { - auto AstUnit = tooling::buildASTFromCode(wrapSnippet(StatementCode)); + auto AstUnit = tooling::buildASTFromCodeWithArgs(wrapSnippet(StatementCode), + {"-Wno-unused-value"}); if (AstUnit == nullptr) { ADD_FAILURE() << "AST construction failed"; return llvm::None; } ASTContext &Context = AstUnit->getASTContext(); - auto Matches = ast_matchers::match( - traverse(ast_type_traits::TK_AsIs, wrapMatcher(Matcher)), Context); + auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); // We expect a single, exact match for the statement. if (Matches.size() != 1) { ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); @@ -365,6 +367,66 @@ TEST_F(StencilTest, AccessOpImplicitThis) { EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field")); } +TEST_F(StencilTest, DescribeType) { + std::string Snippet = "int *x; x;"; + std::string Expected = "int *"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + +TEST_F(StencilTest, DescribeSugaredType) { + std::string Snippet = "using Ty = int; Ty *x; x;"; + std::string Expected = "Ty *"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + +TEST_F(StencilTest, DescribeDeclType) { + std::string Snippet = "S s; s;"; + std::string Expected = "S"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + +TEST_F(StencilTest, DescribeQualifiedType) { + std::string Snippet = "N::C c; c;"; + std::string Expected = "N::C"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + +TEST_F(StencilTest, DescribeUnqualifiedType) { + std::string Snippet = "using N::C; C c; c;"; + std::string Expected = "N::C"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + +TEST_F(StencilTest, DescribeAnonNamespaceType) { + std::string Snippet = "AnonC c; c;"; + std::string Expected = "(anonymous namespace)::AnonC"; + auto StmtMatch = + matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), + HasValue(std::string(Expected))); +} + TEST_F(StencilTest, RunOp) { StringRef Id = "id"; auto SimpleFn = [Id](const MatchResult &R) { @@ -436,6 +498,12 @@ TEST_F(StencilTest, CatOfInvalidRangeFails) { }); } +// The `StencilToStringTest` tests verify that the string representation of the +// stencil combinator matches (as best possible) the spelling of the +// combinator's construction. Exceptions include those combinators that have no +// explicit spelling (like raw text) and those supporting non-printable +// arguments (like `run`, `selection`). + TEST(StencilToStringTest, RawTextOp) { auto S = cat("foo bar baz"); StringRef Expected = R"("foo bar baz")"; @@ -448,6 +516,12 @@ TEST(StencilToStringTest, RawTextOpEscaping) { EXPECT_EQ(S->toString(), Expected); } +TEST(StencilToStringTest, DescribeOp) { + auto S = describe("Id"); + StringRef Expected = R"repr(describe("Id"))repr"; + EXPECT_EQ(S->toString(), Expected); +} + TEST(StencilToStringTest, DebugPrintNodeOp) { auto S = dPrint("Id"); StringRef Expected = R"repr(dPrint("Id"))repr";