diff --git a/clang/include/clang/Tooling/Refactoring/SourceCode.h b/clang/include/clang/Tooling/Refactoring/SourceCode.h --- a/clang/include/clang/Tooling/Refactoring/SourceCode.h +++ b/clang/include/clang/Tooling/Refactoring/SourceCode.h @@ -72,6 +72,19 @@ ASTContext &Context) { return getText(getExtendedRange(Node, Next, Context), Context); } + +// Attempts to resolve the given range to one that can be edited by a rewrite; +// generally, one that starts and ends within a particular file. It supports +// a limited set of cases involving source locations in macro expansions. +llvm::Optional +getRangeForEdit(const CharSourceRange &EditRange, const SourceManager &SM, + const LangOptions &LangOpts); + +inline llvm::Optional +getRangeForEdit(const CharSourceRange &EditRange, const ASTContext &Context) { + return getRangeForEdit(EditRange, Context.getSourceManager(), + Context.getLangOpts()); +} } // namespace tooling } // namespace clang #endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_H diff --git a/clang/lib/Tooling/Refactoring/SourceCode.cpp b/clang/lib/Tooling/Refactoring/SourceCode.cpp --- a/clang/lib/Tooling/Refactoring/SourceCode.cpp +++ b/clang/lib/Tooling/Refactoring/SourceCode.cpp @@ -29,3 +29,37 @@ return Range; return CharSourceRange::getTokenRange(Range.getBegin(), Tok->getLocation()); } + +llvm::Optional +clang::tooling::getRangeForEdit(const CharSourceRange &EditRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + // FIXME: makeFileCharRange() has the disadvantage of stripping off "identity" + // macros. For example, if we're looking to rewrite the int literal 3 to 6, + // and we have the following definition: + // #define DO_NOTHING(x) x + // then + // foo(DO_NOTHING(3)) + // will be rewritten to + // foo(6) + // rather than the arguably better + // foo(DO_NOTHING(6)) + // Decide whether the current behavior is desirable and modify if not. + CharSourceRange Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); + if (Range.isInvalid()) + return None; + + if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) + return None; + if (SM.isInSystemHeader(Range.getBegin()) || + SM.isInSystemHeader(Range.getEnd())) + return None; + + std::pair BeginInfo = SM.getDecomposedLoc(Range.getBegin()); + std::pair EndInfo = SM.getDecomposedLoc(Range.getEnd()); + if (BeginInfo.first != EndInfo.first || + BeginInfo.second > EndInfo.second) + return None; + + return Range; +} diff --git a/clang/unittests/Tooling/SourceCodeTest.cpp b/clang/unittests/Tooling/SourceCodeTest.cpp --- a/clang/unittests/Tooling/SourceCodeTest.cpp +++ b/clang/unittests/Tooling/SourceCodeTest.cpp @@ -6,17 +6,32 @@ // //===----------------------------------------------------------------------===// +#include "clang/Tooling/Refactoring/SourceCode.h" #include "TestVisitor.h" #include "clang/Basic/Diagnostic.h" -#include "clang/Tooling/Refactoring/SourceCode.h" +#include "llvm/Testing/Support/Annotations.h" +#include "llvm/Testing/Support/SupportHelpers.h" +#include +#include using namespace clang; -using tooling::getText; +using llvm::ValueIs; using tooling::getExtendedText; +using tooling::getRangeForEdit; +using tooling::getText; namespace { +struct IntLitVisitor : TestVisitor { + bool VisitIntegerLiteral(IntegerLiteral *Expr) { + OnIntLit(Expr, Context); + return true; + } + + std::function OnIntLit; +}; + struct CallsVisitor : TestVisitor { bool VisitCallExpr(CallExpr *Expr) { OnCall(Expr, Context); @@ -26,6 +41,19 @@ std::function OnCall; }; +// Equality matcher for `clang::CharSourceRange`, which lacks `operator==`. +MATCHER_P(EqualsRange, R, "") { + return arg.isTokenRange() == R.isTokenRange() && + arg.getBegin() == R.getBegin() && arg.getEnd() == R.getEnd(); +} + +static ::testing::Matcher AsRange(const SourceManager &SM, + llvm::Annotations::Range R) { + return EqualsRange(CharSourceRange::getCharRange( + SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(R.Begin), + SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(R.End))); +} + TEST(SourceCodeTest, getText) { CallsVisitor Visitor; @@ -94,4 +122,82 @@ Visitor.runOver("int foo() { return foo() + 3; }"); } +TEST(SourceCodeTest, EditRangeWithMacroExpansionsShouldSucceed) { + // The call expression, whose range we are extracting, includes two macro + // expansions. + llvm::Annotations Code(R"cpp( +#define M(a) a * 13 +int foo(int x, int y); +int a = $r[[foo(M(1), M(2))]]; +)cpp"); + + CallsVisitor Visitor; + + Visitor.OnCall = [&Code](CallExpr *CE, ASTContext *Context) { + auto Range = CharSourceRange::getTokenRange(CE->getSourceRange()); + EXPECT_THAT(getRangeForEdit(Range, *Context), + ValueIs(AsRange(Context->getSourceManager(), Code.range("r")))); + }; + Visitor.runOver(Code.code()); +} + +TEST(SourceCodeTest, EditWholeMacroExpansionShouldSucceed) { + llvm::Annotations Code(R"cpp( +#define FOO 10 +int a = $r[[FOO]]; +)cpp"); + + IntLitVisitor Visitor; + Visitor.OnIntLit = [&Code](IntegerLiteral *Expr, ASTContext *Context) { + auto Range = CharSourceRange::getTokenRange(Expr->getSourceRange()); + EXPECT_THAT(getRangeForEdit(Range, *Context), + ValueIs(AsRange(Context->getSourceManager(), Code.range("r")))); + }; + Visitor.runOver(Code.code()); +} + +TEST(SourceCodeTest, EditPartialMacroExpansionShouldFail) { + std::string Code = R"cpp( +#define BAR 10+ +int c = BAR 3.0; +)cpp"; + + IntLitVisitor Visitor; + Visitor.OnIntLit = [](IntegerLiteral *Expr, ASTContext *Context) { + auto Range = CharSourceRange::getTokenRange(Expr->getSourceRange()); + EXPECT_FALSE(getRangeForEdit(Range, *Context).hasValue()); + }; + Visitor.runOver(Code); +} + +TEST(SourceCodeTest, EditWholeMacroArgShouldSucceed) { + llvm::Annotations Code(R"cpp( +#define FOO(a) a + 7.0; +int a = FOO($r[[10]]); +)cpp"); + + IntLitVisitor Visitor; + Visitor.OnIntLit = [&Code](IntegerLiteral *Expr, ASTContext *Context) { + auto Range = CharSourceRange::getTokenRange(Expr->getSourceRange()); + EXPECT_THAT(getRangeForEdit(Range, *Context), + ValueIs(AsRange(Context->getSourceManager(), Code.range("r")))); + }; + Visitor.runOver(Code.code()); +} + +TEST(SourceCodeTest, EditPartialMacroArgShouldSucceed) { + llvm::Annotations Code(R"cpp( +#define FOO(a) a + 7.0; +int a = FOO($r[[10]] + 10.0); +)cpp"); + + IntLitVisitor Visitor; + Visitor.OnIntLit = [&Code](IntegerLiteral *Expr, ASTContext *Context) { + auto Range = CharSourceRange::getTokenRange(Expr->getSourceRange()); + EXPECT_THAT(getRangeForEdit(Range, *Context), + ValueIs(AsRange(Context->getSourceManager(), Code.range("r")))); + }; + Visitor.runOver(Code.code()); +} + } // end anonymous namespace