summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYitzhak Mandelbaum <yitzhakm@google.com>2019-09-23 12:40:10 +0000
committerYitzhak Mandelbaum <yitzhakm@google.com>2019-09-23 12:40:10 +0000
commitac9b961a2f4e425a07e38eacfec47437ff813c31 (patch)
treeff0725b531bdd1d4a93ac92b8dd2b4fce89da44b
parent133ef3bf8b4d2854899d829e8ed53289dd03224a (diff)
downloadclang-ac9b961a2f4e425a07e38eacfec47437ff813c31.tar.gz
[libTooling] Introduce new library of source-code builders.
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
-rw-r--r--include/clang/Tooling/Refactoring/SourceCodeBuilders.h86
-rw-r--r--lib/Tooling/Refactoring/CMakeLists.txt1
-rw-r--r--lib/Tooling/Refactoring/SourceCodeBuilders.cpp160
-rw-r--r--unittests/Tooling/CMakeLists.txt1
-rw-r--r--unittests/Tooling/SourceCodeBuildersTest.cpp230
5 files changed, 478 insertions, 0 deletions
diff --git a/include/clang/Tooling/Refactoring/SourceCodeBuilders.h b/include/clang/Tooling/Refactoring/SourceCodeBuilders.h
new file mode 100644
index 0000000000..797046f3ec
--- /dev/null
+++ b/include/clang/Tooling/Refactoring/SourceCodeBuilders.h
@@ -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_
diff --git a/lib/Tooling/Refactoring/CMakeLists.txt b/lib/Tooling/Refactoring/CMakeLists.txt
index d1f092f261..e3961db284 100644
--- a/lib/Tooling/Refactoring/CMakeLists.txt
+++ b/lib/Tooling/Refactoring/CMakeLists.txt
@@ -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
index 0000000000..2499c0f1eb
--- /dev/null
+++ b/lib/Tooling/Refactoring/SourceCodeBuilders.cpp
@@ -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();
+}
diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt
index 3a4094a8b4..2b35302c7b 100644
--- a/unittests/Tooling/CMakeLists.txt
+++ b/unittests/Tooling/CMakeLists.txt
@@ -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
index 0000000000..2bf50ffad5
--- /dev/null
+++ b/unittests/Tooling/SourceCodeBuildersTest.cpp
@@ -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