[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