[clang] b6c218d - [libTooling] Add "switch"-like Stencil combinator
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 14 09:46:28 PDT 2021
Author: Yitzhak Mandelbaum
Date: 2021-10-14T16:45:37Z
New Revision: b6c218d4fdb74c0ee467e078721438c3396dc599
URL: https://github.com/llvm/llvm-project/commit/b6c218d4fdb74c0ee467e078721438c3396dc599
DIFF: https://github.com/llvm/llvm-project/commit/b6c218d4fdb74c0ee467e078721438c3396dc599.diff
LOG: [libTooling] Add "switch"-like Stencil combinator
Adds `selectBound`, a `Stencil` combinator that allows the user to supply multiple alternative cases, discriminated by bound node IDs.
Differential Revision: https://reviews.llvm.org/D111708
Added:
Modified:
clang/include/clang/Tooling/Transformer/Stencil.h
clang/lib/Tooling/Transformer/Stencil.cpp
clang/unittests/Tooling/StencilTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Tooling/Transformer/Stencil.h b/clang/include/clang/Tooling/Transformer/Stencil.h
index 1b7495eb02622..249f95b7391df 100644
--- a/clang/include/clang/Tooling/Transformer/Stencil.h
+++ b/clang/include/clang/Tooling/Transformer/Stencil.h
@@ -117,6 +117,38 @@ inline Stencil ifBound(llvm::StringRef Id, llvm::StringRef TrueText,
detail::makeStencil(FalseText));
}
+/// Chooses between multiple stencils, based on the presence of bound nodes. \p
+/// CaseStencils takes a vector of (ID, \c Stencil) pairs and checks each ID in
+/// order to see if it's bound to a node. If so, the associated \c Stencil is
+/// run and all other cases are ignored. An optional \p DefaultStencil can be
+/// provided to be run if all cases are exhausted beacause none of the provided
+/// IDs are bound. If no default case is provided and all cases are exhausted,
+/// the stencil will fail with error `llvm::errc::result_out_of_range`.
+///
+/// For example, say one matches a statement's type with:
+/// anyOf(
+/// qualType(isInteger()).bind("int"),
+/// qualType(realFloatingPointType()).bind("float"),
+/// qualType(isAnyCharacter()).bind("char"),
+/// booleanType().bind("bool"))
+///
+/// Then, one can decide in a stencil how to construct a literal.
+/// cat("a = ",
+/// selectBound(
+/// {{"int", cat("0")},
+/// {"float", cat("0.0")},
+/// {"char", cat("'\\0'")},
+/// {"bool", cat("false")}}))
+///
+/// In addition, one could supply a default case for all other types:
+/// selectBound(
+/// {{"int", cat("0")},
+/// ...
+/// {"bool", cat("false")}},
+/// cat("{}"))
+Stencil selectBound(std::vector<std::pair<std::string, Stencil>> CaseStencils,
+ Stencil DefaultStencil = nullptr);
+
/// Wraps a \c MatchConsumer in a \c Stencil, so that it can be used in a \c
/// Stencil. This supports user-defined extensions to the \c Stencil language.
Stencil run(MatchConsumer<std::string> C);
diff --git a/clang/lib/Tooling/Transformer/Stencil.cpp b/clang/lib/Tooling/Transformer/Stencil.cpp
index 4dc3544bb06db..8b20ef34c3ff2 100644
--- a/clang/lib/Tooling/Transformer/Stencil.cpp
+++ b/clang/lib/Tooling/Transformer/Stencil.cpp
@@ -27,14 +27,15 @@
using namespace clang;
using namespace transformer;
+using ast_matchers::BoundNodes;
using ast_matchers::MatchFinder;
using llvm::errc;
using llvm::Error;
using llvm::Expected;
using llvm::StringError;
-static llvm::Expected<DynTypedNode>
-getNode(const ast_matchers::BoundNodes &Nodes, StringRef Id) {
+static llvm::Expected<DynTypedNode> getNode(const BoundNodes &Nodes,
+ StringRef Id) {
auto &NodesMap = Nodes.getMap();
auto It = NodesMap.find(Id);
if (It == NodesMap.end())
@@ -366,6 +367,73 @@ class IfBoundStencil : public StencilInterface {
}
};
+class SelectBoundStencil : public clang::transformer::StencilInterface {
+ static bool containsNoNullStencils(
+ const std::vector<std::pair<std::string, Stencil>> &Cases) {
+ for (const auto &S : Cases)
+ if (S.second == nullptr)
+ return false;
+ return true;
+ }
+
+public:
+ SelectBoundStencil(std::vector<std::pair<std::string, Stencil>> Cases,
+ Stencil Default)
+ : CaseStencils(std::move(Cases)), DefaultStencil(std::move(Default)) {
+ assert(containsNoNullStencils(CaseStencils) &&
+ "cases of selectBound may not be null");
+ }
+ ~SelectBoundStencil() override{};
+
+ llvm::Error eval(const MatchFinder::MatchResult &match,
+ std::string *result) const override {
+ const BoundNodes::IDToNodeMap &NodeMap = match.Nodes.getMap();
+ for (const auto &S : CaseStencils) {
+ if (NodeMap.count(S.first) > 0) {
+ return S.second->eval(match, result);
+ }
+ }
+
+ if (DefaultStencil != nullptr) {
+ return DefaultStencil->eval(match, result);
+ }
+
+ llvm::SmallVector<llvm::StringRef, 2> CaseIDs;
+ CaseIDs.reserve(CaseStencils.size());
+ for (const auto &S : CaseStencils)
+ CaseIDs.emplace_back(S.first);
+
+ return llvm::createStringError(
+ errc::result_out_of_range,
+ llvm::Twine("selectBound failed: no cases bound and no default: {") +
+ llvm::join(CaseIDs, ", ") + "}");
+ }
+
+ std::string toString() const override {
+ std::string Buffer;
+ llvm::raw_string_ostream Stream(Buffer);
+ Stream << "selectBound({";
+ bool First = true;
+ for (const auto &S : CaseStencils) {
+ if (First)
+ First = false;
+ else
+ Stream << "}, ";
+ Stream << "{\"" << S.first << "\", " << S.second->toString();
+ }
+ Stream << "}}";
+ if (DefaultStencil != nullptr) {
+ Stream << ", " << DefaultStencil->toString();
+ }
+ Stream << ")";
+ return Stream.str();
+ }
+
+private:
+ std::vector<std::pair<std::string, Stencil>> CaseStencils;
+ Stencil DefaultStencil;
+};
+
class SequenceStencil : public StencilInterface {
std::vector<Stencil> Stencils;
@@ -462,6 +530,13 @@ Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil,
std::move(FalseStencil));
}
+Stencil transformer::selectBound(
+ std::vector<std::pair<std::string, Stencil>> CaseStencils,
+ Stencil DefaultStencil) {
+ return std::make_shared<SelectBoundStencil>(std::move(CaseStencils),
+ std::move(DefaultStencil));
+}
+
Stencil transformer::run(MatchConsumer<std::string> Fn) {
return std::make_shared<RunStencil>(std::move(Fn));
}
diff --git a/clang/unittests/Tooling/StencilTest.cpp b/clang/unittests/Tooling/StencilTest.cpp
index 0b5b9691dadc5..6036f8f4fa381 100644
--- a/clang/unittests/Tooling/StencilTest.cpp
+++ b/clang/unittests/Tooling/StencilTest.cpp
@@ -201,6 +201,58 @@ TEST_F(StencilTest, IfBoundOpUnbound) {
testExpr(Id, "3;", ifBound("other", cat("5"), cat("7")), "7");
}
+static auto selectMatcher() {
+ // The `anything` matcher is not bound, to test for none of the cases
+ // matching.
+ return expr(anyOf(integerLiteral().bind("int"), cxxBoolLiteral().bind("bool"),
+ floatLiteral().bind("float"), anything()));
+}
+
+static auto selectStencil() {
+ return selectBound({
+ {"int", cat("I")},
+ {"bool", cat("B")},
+ {"bool", cat("redundant")},
+ {"float", cat("F")},
+ });
+}
+
+TEST_F(StencilTest, SelectBoundChooseDetectedMatch) {
+ std::string Input = "3;";
+ auto StmtMatch = matchStmt(Input, selectMatcher());
+ ASSERT_TRUE(StmtMatch);
+ EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result),
+ HasValue(std::string("I")));
+}
+
+TEST_F(StencilTest, SelectBoundChooseFirst) {
+ std::string Input = "true;";
+ auto StmtMatch = matchStmt(Input, selectMatcher());
+ ASSERT_TRUE(StmtMatch);
+ EXPECT_THAT_EXPECTED(selectStencil()->eval(StmtMatch->Result),
+ HasValue(std::string("B")));
+}
+
+TEST_F(StencilTest, SelectBoundDiesOnExhaustedCases) {
+ std::string Input = "\"string\";";
+ auto StmtMatch = matchStmt(Input, selectMatcher());
+ ASSERT_TRUE(StmtMatch);
+ EXPECT_THAT_EXPECTED(
+ selectStencil()->eval(StmtMatch->Result),
+ Failed<StringError>(testing::Property(
+ &StringError::getMessage,
+ AllOf(HasSubstr("selectBound failed"), HasSubstr("no default")))));
+}
+
+TEST_F(StencilTest, SelectBoundSucceedsWithDefault) {
+ std::string Input = "\"string\";";
+ auto StmtMatch = matchStmt(Input, selectMatcher());
+ ASSERT_TRUE(StmtMatch);
+ auto Stencil = selectBound({{"int", cat("I")}}, cat("D"));
+ EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
+ HasValue(std::string("D")));
+}
+
TEST_F(StencilTest, ExpressionOpNoParens) {
StringRef Id = "id";
testExpr(Id, "3;", expression(Id), "3");
@@ -674,6 +726,28 @@ TEST(StencilToStringTest, IfBoundOp) {
EXPECT_EQ(S->toString(), Expected);
}
+TEST(StencilToStringTest, SelectBoundOp) {
+ auto S = selectBound({
+ {"int", cat("I")},
+ {"float", cat("F")},
+ });
+ StringRef Expected = R"repr(selectBound({{"int", "I"}, {"float", "F"}}))repr";
+ EXPECT_EQ(S->toString(), Expected);
+}
+
+TEST(StencilToStringTest, SelectBoundOpWithOneCase) {
+ auto S = selectBound({{"int", cat("I")}});
+ StringRef Expected = R"repr(selectBound({{"int", "I"}}))repr";
+ EXPECT_EQ(S->toString(), Expected);
+}
+
+TEST(StencilToStringTest, SelectBoundOpWithDefault) {
+ auto S = selectBound({{"int", cat("I")}, {"float", cat("F")}}, cat("D"));
+ StringRef Expected =
+ R"cc(selectBound({{"int", "I"}, {"float", "F"}}, "D"))cc";
+ EXPECT_EQ(S->toString(), Expected);
+}
+
TEST(StencilToStringTest, RunOp) {
auto F1 = [](const MatchResult &R) { return "foo"; };
auto S1 = run(F1);
More information about the cfe-commits
mailing list