]> granicus.if.org Git - clang/commitdiff
[libTooling] Introduce new library of source-code builders.
authorYitzhak Mandelbaum <yitzhakm@google.com>
Mon, 23 Sep 2019 12:40:10 +0000 (12:40 +0000)
committerYitzhak Mandelbaum <yitzhakm@google.com>
Mon, 23 Sep 2019 12:40:10 +0000 (12:40 +0000)
Summary:
Introduces facilities for easily building source-code strings, including
idiomatic use of parentheses and the address-of, dereference and member-access
operators (dot and arrow) and queries about need for parentheses.

Reviewers: gribozavr

Subscribers: mgorny, cfe-commits, ilya-biryukov

Tags: #clang

Differential Revision: https://reviews.llvm.org/D67632

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@372595 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Tooling/Refactoring/SourceCodeBuilders.h [new file with mode: 0644]
lib/Tooling/Refactoring/CMakeLists.txt
lib/Tooling/Refactoring/SourceCodeBuilders.cpp [new file with mode: 0644]
unittests/Tooling/CMakeLists.txt
unittests/Tooling/SourceCodeBuildersTest.cpp [new file with mode: 0644]

diff --git a/include/clang/Tooling/Refactoring/SourceCodeBuilders.h b/include/clang/Tooling/Refactoring/SourceCodeBuilders.h
new file mode 100644 (file)
index 0000000..797046f
--- /dev/null
@@ -0,0 +1,86 @@
+//===--- SourceCodeBuilders.h - Source-code building facilities -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file collects facilities for generating source code strings.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_
+#define LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include <string>
+
+namespace clang {
+namespace tooling {
+
+/// \name Code analysis utilities.
+/// @{
+/// Ignores implicit object-construction expressions in addition to the normal
+/// implicit expressions that are ignored.
+const Expr *reallyIgnoreImplicit(const Expr &E);
+
+/// Determines whether printing this expression in *any* expression requires
+/// parentheses to preserve its meaning. This analyses is necessarily
+/// conservative because it lacks information about the target context.
+bool mayEverNeedParens(const Expr &E);
+
+/// Determines whether printing this expression to the left of a dot or arrow
+/// operator requires a parentheses to preserve its meaning. Given that
+/// dot/arrow are (effectively) the highest precedence, this is equivalent to
+/// asking whether it ever needs parens.
+inline bool needParensBeforeDotOrArrow(const Expr &E) {
+  return mayEverNeedParens(E);
+}
+
+/// Determines whether printing this expression to the right of a unary operator
+/// requires a parentheses to preserve its meaning.
+bool needParensAfterUnaryOperator(const Expr &E);
+/// @}
+
+/// \name Basic code-string generation utilities.
+/// @{
+
+/// Builds source for an expression, adding parens if needed for unambiguous
+/// parsing.
+llvm::Optional<std::string> buildParens(const Expr &E,
+                                        const ASTContext &Context);
+
+/// Builds idiomatic source for the dereferencing of `E`: prefix with `*` but
+/// simplify when it already begins with `&`.  \returns empty string on failure.
+llvm::Optional<std::string> buildDereference(const Expr &E,
+                                             const ASTContext &Context);
+
+/// Builds idiomatic source for taking the address of `E`: prefix with `&` but
+/// simplify when it already begins with `*`.  \returns empty string on failure.
+llvm::Optional<std::string> buildAddressOf(const Expr &E,
+                                           const ASTContext &Context);
+
+/// Adds a dot to the end of the given expression, but adds parentheses when
+/// needed by the syntax, and simplifies to `->` when possible, e.g.:
+///
+///  `x` becomes `x.`
+///  `*a` becomes `a->`
+///  `a+b` becomes `(a+b).`
+llvm::Optional<std::string> buildDot(const Expr &E, const ASTContext &Context);
+
+/// Adds an arrow to the end of the given expression, but adds parentheses
+/// when needed by the syntax, and simplifies to `.` when possible, e.g.:
+///
+///  `x` becomes `x->`
+///  `&a` becomes `a.`
+///  `a+b` becomes `(a+b)->`
+llvm::Optional<std::string> buildArrow(const Expr &E,
+                                       const ASTContext &Context);
+/// @}
+
+} // namespace tooling
+} // namespace clang
+#endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_
index d1f092f261c9fedabccda28a9852f1e3050bd08a..e3961db2841ec9b8ddd1cf02329e722c87aa34d5 100644 (file)
@@ -14,6 +14,7 @@ add_clang_library(clangToolingRefactoring
   Rename/USRFindingAction.cpp
   Rename/USRLocFinder.cpp
   SourceCode.cpp
+  SourceCodeBuilders.cpp
   Stencil.cpp
   Transformer.cpp
 
diff --git a/lib/Tooling/Refactoring/SourceCodeBuilders.cpp b/lib/Tooling/Refactoring/SourceCodeBuilders.cpp
new file mode 100644 (file)
index 0000000..2499c0f
--- /dev/null
@@ -0,0 +1,160 @@
+//===--- SourceCodeBuilder.cpp ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/SourceCodeBuilders.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/Tooling/Refactoring/SourceCode.h"
+#include "llvm/ADT/Twine.h"
+#include <string>
+
+using namespace clang;
+using namespace tooling;
+
+const Expr *tooling::reallyIgnoreImplicit(const Expr &E) {
+  const Expr *Expr = E.IgnoreImplicit();
+  if (const auto *CE = dyn_cast<CXXConstructExpr>(Expr)) {
+    if (CE->getNumArgs() > 0 &&
+        CE->getArg(0)->getSourceRange() == Expr->getSourceRange())
+      return CE->getArg(0)->IgnoreImplicit();
+  }
+  return Expr;
+}
+
+bool tooling::mayEverNeedParens(const Expr &E) {
+  const Expr *Expr = reallyIgnoreImplicit(E);
+  // We always want parens around unary, binary, and ternary operators, because
+  // they are lower precedence.
+  if (isa<UnaryOperator>(Expr) || isa<BinaryOperator>(Expr) ||
+      isa<AbstractConditionalOperator>(Expr))
+    return true;
+
+  // We need parens around calls to all overloaded operators except: function
+  // calls, subscripts, and expressions that are already part of an (implicit)
+  // call to operator->. These latter are all in the same precedence level as
+  // dot/arrow and that level is left associative, so they don't need parens
+  // when appearing on the left.
+  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr))
+    return Op->getOperator() != OO_Call && Op->getOperator() != OO_Subscript &&
+           Op->getOperator() != OO_Arrow;
+
+  return false;
+}
+
+bool tooling::needParensAfterUnaryOperator(const Expr &E) {
+  const Expr *Expr = reallyIgnoreImplicit(E);
+  if (isa<BinaryOperator>(Expr) || isa<AbstractConditionalOperator>(Expr))
+    return true;
+
+  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr))
+    return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
+           Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
+           Op->getOperator() != OO_Subscript;
+
+  return false;
+}
+
+llvm::Optional<std::string> tooling::buildParens(const Expr &E,
+                                                 const ASTContext &Context) {
+  StringRef Text = getText(E, Context);
+  if (Text.empty())
+    return llvm::None;
+  if (mayEverNeedParens(E))
+    return ("(" + Text + ")").str();
+  return Text.str();
+}
+
+llvm::Optional<std::string>
+tooling::buildDereference(const Expr &E, const ASTContext &Context) {
+  if (const auto *Op = dyn_cast<UnaryOperator>(&E))
+    if (Op->getOpcode() == UO_AddrOf) {
+      // Strip leading '&'.
+      StringRef Text =
+          getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context);
+      if (Text.empty())
+        return llvm::None;
+      return Text.str();
+    }
+
+  StringRef Text = getText(E, Context);
+  if (Text.empty())
+    return llvm::None;
+  // Add leading '*'.
+  if (needParensAfterUnaryOperator(E))
+    return ("*(" + Text + ")").str();
+  return ("*" + Text).str();
+}
+
+llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E,
+                                                    const ASTContext &Context) {
+  if (const auto *Op = dyn_cast<UnaryOperator>(&E))
+    if (Op->getOpcode() == UO_Deref) {
+      // Strip leading '*'.
+      StringRef Text =
+          getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context);
+      if (Text.empty())
+        return llvm::None;
+      return Text.str();
+    }
+  // Add leading '&'.
+  StringRef Text = getText(E, Context);
+  if (Text.empty())
+    return llvm::None;
+  if (needParensAfterUnaryOperator(E)) {
+    return ("&(" + Text + ")").str();
+  }
+  return ("&" + Text).str();
+}
+
+llvm::Optional<std::string> tooling::buildDot(const Expr &E,
+                                              const ASTContext &Context) {
+  if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E))
+    if (Op->getOpcode() == UO_Deref) {
+      // Strip leading '*', add following '->'.
+      const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
+      StringRef DerefText = getText(*SubExpr, Context);
+      if (DerefText.empty())
+        return llvm::None;
+      if (needParensBeforeDotOrArrow(*SubExpr))
+        return ("(" + DerefText + ")->").str();
+      return (DerefText + "->").str();
+    }
+
+  // Add following '.'.
+  StringRef Text = getText(E, Context);
+  if (Text.empty())
+    return llvm::None;
+  if (needParensBeforeDotOrArrow(E)) {
+    return ("(" + Text + ").").str();
+  }
+  return (Text + ".").str();
+}
+
+llvm::Optional<std::string> tooling::buildArrow(const Expr &E,
+                                                const ASTContext &Context) {
+  if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E))
+    if (Op->getOpcode() == UO_AddrOf) {
+      // Strip leading '&', add following '.'.
+      const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts();
+      StringRef DerefText = getText(*SubExpr, Context);
+      if (DerefText.empty())
+        return llvm::None;
+      if (needParensBeforeDotOrArrow(*SubExpr))
+        return ("(" + DerefText + ").").str();
+      return (DerefText + ".").str();
+    }
+
+  // Add following '->'.
+  StringRef Text = getText(E, Context);
+  if (Text.empty())
+    return llvm::None;
+  if (needParensBeforeDotOrArrow(E))
+    return ("(" + Text + ")->").str();
+  return (Text + "->").str();
+}
index 3a4094a8b4051693ca4fc159675741c30c9efca8..2b35302c7b1faa58fc3d45306157da6960c87ebe 100644 (file)
@@ -54,6 +54,7 @@ add_clang_unittest(ToolingTests
   RefactoringTest.cpp
   ReplacementsYamlTest.cpp
   RewriterTest.cpp
+  SourceCodeBuildersTest.cpp
   SourceCodeTest.cpp
   StencilTest.cpp
   ToolingTest.cpp
diff --git a/unittests/Tooling/SourceCodeBuildersTest.cpp b/unittests/Tooling/SourceCodeBuildersTest.cpp
new file mode 100644 (file)
index 0000000..2bf50ff
--- /dev/null
@@ -0,0 +1,230 @@
+//===- unittest/Tooling/SourceCodeBuildersTest.cpp ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/SourceCodeBuilders.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Testing/Support/SupportHelpers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace tooling;
+using namespace ast_matchers;
+
+namespace {
+using MatchResult = MatchFinder::MatchResult;
+using llvm::ValueIs;
+
+// Create a valid translation unit from a statement.
+static std::string wrapSnippet(StringRef StatementCode) {
+  return ("struct S { S(); S(int); int field; };\n"
+          "S operator+(const S &a, const S &b);\n"
+          "auto test_snippet = []{" +
+          StatementCode + "};")
+      .str();
+}
+
+static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) {
+  return varDecl(hasName("test_snippet"),
+                 hasDescendant(compoundStmt(hasAnySubstatement(Matcher))));
+}
+
+struct TestMatch {
+  // The AST unit from which `result` is built. We bundle it because it backs
+  // the result. Users are not expected to access it.
+  std::unique_ptr<ASTUnit> AstUnit;
+  // The result to use in the test. References `ast_unit`.
+  MatchResult Result;
+};
+
+// Matches `Matcher` against the statement `StatementCode` and returns the
+// result. Handles putting the statement inside a function and modifying the
+// matcher correspondingly. `Matcher` should match one of the statements in
+// `StatementCode` exactly -- that is, produce exactly one match. However,
+// `StatementCode` may contain other statements not described by `Matcher`.
+static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode,
+                                           StatementMatcher Matcher) {
+  auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode));
+  if (AstUnit == nullptr) {
+    ADD_FAILURE() << "AST construction failed";
+    return llvm::None;
+  }
+  ASTContext &Context = AstUnit->getASTContext();
+  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();
+    return llvm::None;
+  }
+  return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
+}
+
+static void testPredicate(bool (*Pred)(const Expr &), StringRef Snippet,
+                          bool Expected) {
+  auto StmtMatch = matchStmt(Snippet, expr().bind("expr"));
+  ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet;
+  EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr")))
+      << "Snippet: " << Snippet;
+}
+
+// Tests the predicate on the call argument, assuming `Snippet` is a function
+// call.
+static void testPredicateOnArg(bool (*Pred)(const Expr &), StringRef Snippet,
+                               bool Expected) {
+  auto StmtMatch = matchStmt(
+      Snippet, expr(ignoringImplicit(callExpr(hasArgument(
+                   0, ignoringElidableConstructorCall(expr().bind("arg")))))));
+  ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet;
+  EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs<Expr>("arg")))
+      << "Snippet: " << Snippet;
+}
+
+TEST(SourceCodeBuildersTest, needParensAfterUnaryOperator) {
+  testPredicate(needParensAfterUnaryOperator, "3 + 5;", true);
+  testPredicate(needParensAfterUnaryOperator, "true ? 3 : 5;", true);
+  testPredicate(needParensAfterUnaryOperator, "S(3) + S(5);", true);
+
+  testPredicate(needParensAfterUnaryOperator, "int x; x;", false);
+  testPredicate(needParensAfterUnaryOperator, "int(3.0);", false);
+  testPredicate(needParensAfterUnaryOperator, "void f(); f();", false);
+  testPredicate(needParensAfterUnaryOperator, "int a[3]; a[0];", false);
+  testPredicate(needParensAfterUnaryOperator, "S x; x.field;", false);
+  testPredicate(needParensAfterUnaryOperator, "int x = 1; --x;", false);
+  testPredicate(needParensAfterUnaryOperator, "int x = 1; -x;", false);
+}
+
+TEST(SourceCodeBuildersTest, needParensAfterUnaryOperatorInImplicitConversion) {
+  // The binary operation will be embedded in various implicit
+  // expressions. Verify they are ignored.
+  testPredicateOnArg(needParensAfterUnaryOperator, "void f(S); f(3 + 5);",
+                     true);
+}
+
+TEST(SourceCodeBuildersTest, mayEverNeedParens) {
+  testPredicate(mayEverNeedParens, "3 + 5;", true);
+  testPredicate(mayEverNeedParens, "true ? 3 : 5;", true);
+  testPredicate(mayEverNeedParens, "int x = 1; --x;", true);
+  testPredicate(mayEverNeedParens, "int x = 1; -x;", true);
+
+  testPredicate(mayEverNeedParens, "int x; x;", false);
+  testPredicate(mayEverNeedParens, "int(3.0);", false);
+  testPredicate(mayEverNeedParens, "void f(); f();", false);
+  testPredicate(mayEverNeedParens, "int a[3]; a[0];", false);
+  testPredicate(mayEverNeedParens, "S x; x.field;", false);
+}
+
+TEST(SourceCodeBuildersTest, mayEverNeedParensInImplictConversion) {
+  // The binary operation will be embedded in various implicit
+  // expressions. Verify they are ignored.
+  testPredicateOnArg(mayEverNeedParens, "void f(S); f(3 + 5);", true);
+}
+
+static void testBuilder(
+    llvm::Optional<std::string> (*Builder)(const Expr &, const ASTContext &),
+    StringRef Snippet, StringRef Expected) {
+  auto StmtMatch = matchStmt(Snippet, expr().bind("expr"));
+  ASSERT_TRUE(StmtMatch);
+  EXPECT_THAT(Builder(*StmtMatch->Result.Nodes.getNodeAs<Expr>("expr"),
+                      *StmtMatch->Result.Context),
+              ValueIs(Expected));
+}
+
+TEST(SourceCodeBuildersTest, BuildParensUnaryOp) {
+  testBuilder(buildParens, "-4;", "(-4)");
+}
+
+TEST(SourceCodeBuildersTest, BuildParensBinOp) {
+  testBuilder(buildParens, "4 + 4;", "(4 + 4)");
+}
+
+TEST(SourceCodeBuildersTest, BuildParensValue) {
+  testBuilder(buildParens, "4;", "4");
+}
+
+TEST(SourceCodeBuildersTest, BuildParensSubscript) {
+  testBuilder(buildParens, "int a[3]; a[0];", "a[0]");
+}
+
+TEST(SourceCodeBuildersTest, BuildParensCall) {
+  testBuilder(buildParens, "int f(int); f(4);", "f(4)");
+}
+
+TEST(SourceCodeBuildersTest, BuildAddressOfValue) {
+  testBuilder(buildAddressOf, "S x; x;", "&x");
+}
+
+TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereference) {
+  testBuilder(buildAddressOf, "S *x; *x;", "x");
+}
+
+TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereferenceIgnoresParens) {
+  testBuilder(buildAddressOf, "S *x; *(x);", "x");
+}
+
+TEST(SourceCodeBuildersTest, BuildAddressOfBinaryOperation) {
+  testBuilder(buildAddressOf, "S x; x + x;", "&(x + x)");
+}
+
+TEST(SourceCodeBuildersTest, BuildDereferencePointer) {
+  testBuilder(buildDereference, "S *x; x;", "*x");
+}
+
+TEST(SourceCodeBuildersTest, BuildDereferenceValueAddress) {
+  testBuilder(buildDereference, "S x; &x;", "x");
+}
+
+TEST(SourceCodeBuildersTest, BuildDereferenceValueAddressIgnoresParens) {
+  testBuilder(buildDereference, "S x; &(x);", "x");
+}
+
+TEST(SourceCodeBuildersTest, BuildDereferenceBinaryOperation) {
+  testBuilder(buildDereference, "S *x; x + 1;", "*(x + 1)");
+}
+
+TEST(SourceCodeBuildersTest, BuildDotValue) {
+  testBuilder(buildDot, "S x; x;", "x.");
+}
+
+TEST(SourceCodeBuildersTest, BuildDotPointerDereference) {
+  testBuilder(buildDot, "S *x; *x;", "x->");
+}
+
+TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceIgnoresParens) {
+  testBuilder(buildDot, "S *x; *(x);", "x->");
+}
+
+TEST(SourceCodeBuildersTest, BuildDotBinaryOperation) {
+  testBuilder(buildDot, "S x; x + x;", "(x + x).");
+}
+
+TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceExprWithParens) {
+  testBuilder(buildDot, "S *x; *(x + 1);", "(x + 1)->");
+}
+
+TEST(SourceCodeBuildersTest, BuildArrowPointer) {
+  testBuilder(buildArrow, "S *x; x;", "x->");
+}
+
+TEST(SourceCodeBuildersTest, BuildArrowValueAddress) {
+  testBuilder(buildArrow, "S x; &x;", "x.");
+}
+
+TEST(SourceCodeBuildersTest, BuildArrowValueAddressIgnoresParens) {
+  testBuilder(buildArrow, "S x; &(x);", "x.");
+}
+
+TEST(SourceCodeBuildersTest, BuildArrowBinaryOperation) {
+  testBuilder(buildArrow, "S *x; x + 1;", "(x + 1)->");
+}
+
+TEST(SourceCodeBuildersTest, BuildArrowValueAddressWithParens) {
+  testBuilder(buildArrow, "S x; &(true ? x : x);", "(true ? x : x).");
+}
+} // namespace