diff options
author | Yitzhak Mandelbaum <yitzhakm@google.com> | 2019-09-23 12:40:10 +0000 |
---|---|---|
committer | Yitzhak Mandelbaum <yitzhakm@google.com> | 2019-09-23 12:40:10 +0000 |
commit | ac9b961a2f4e425a07e38eacfec47437ff813c31 (patch) | |
tree | ff0725b531bdd1d4a93ac92b8dd2b4fce89da44b | |
parent | 133ef3bf8b4d2854899d829e8ed53289dd03224a (diff) | |
download | clang-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.h | 86 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/SourceCodeBuilders.cpp | 160 | ||||
-rw-r--r-- | unittests/Tooling/CMakeLists.txt | 1 | ||||
-rw-r--r-- | unittests/Tooling/SourceCodeBuildersTest.cpp | 230 |
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 |