[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
This commit is contained in:
Yitzhak Mandelbaum 2020-12-04 15:23:34 +00:00
parent 438682de6a
commit e6bc4a71e3
3 changed files with 111 additions and 7 deletions

View File

@ -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<std::string> 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.

View File

@ -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<Expr>(Data.Id);
if (E == nullptr)
return llvm::make_error<StringError>(
@ -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<StringError>(
@ -359,6 +375,11 @@ Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) {
UnaryNodeOperator::MaybeAddressOf, std::string(ExprId));
}
Stencil transformer::describe(StringRef Id) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::Describe, std::string(Id));
}
Stencil transformer::access(StringRef BaseId, Stencil Member) {
return std::make_shared<StencilImpl<AccessData>>(BaseId, std::move(Member));
}

View File

@ -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<TestMatch> 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";