summaryrefslogtreecommitdiffstats
path: root/lib/Tooling/Refactoring
diff options
context:
space:
mode:
authorAlex Lorenz <arphaman@gmail.com>2017-10-24 17:18:45 +0000
committerAlex Lorenz <arphaman@gmail.com>2017-10-24 17:18:45 +0000
commit58538f115416ae2ce6cc4cfa794d243bade5458d (patch)
treeec24dbb374fbc882e2168b3e71efa4ff0e9921fa /lib/Tooling/Refactoring
parent53710bdec511dfcfb69ee4d234b7f579b4955101 (diff)
[refactor] Initial outline of implementation of "extract function" refactoring
This commit adds an initial, skeleton outline of the "extract function" refactoring. The extracted function doesn't capture variables / rewrite code yet, it just basically does a simple copy-paste. The following initiation rules are specified: - extraction can only be done for executable code in a function/method/block. This means that you can't extract a global variable initialize into a function right now. - simple literals and references are not extractable. This commit also adds support for full source ranges to clang-refactor's test mode. Differential Revision: https://reviews.llvm.org/D38982 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@316465 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/Tooling/Refactoring')
-rw-r--r--lib/Tooling/Refactoring/ASTSelection.cpp37
-rw-r--r--lib/Tooling/Refactoring/ASTSelectionRequirements.cpp48
-rw-r--r--lib/Tooling/Refactoring/CMakeLists.txt2
-rw-r--r--lib/Tooling/Refactoring/Extract.cpp232
4 files changed, 319 insertions, 0 deletions
diff --git a/lib/Tooling/Refactoring/ASTSelection.cpp b/lib/Tooling/Refactoring/ASTSelection.cpp
index 2c9c42bfcb..9d0683a285 100644
--- a/lib/Tooling/Refactoring/ASTSelection.cpp
+++ b/lib/Tooling/Refactoring/ASTSelection.cpp
@@ -322,6 +322,10 @@ CodeRangeASTSelection::create(SourceRange SelectionRange,
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/false);
}
+ // FIXME (Alex L): First selected SwitchCase means that first case statement.
+ // is selected actually
+ // (See https://github.com/apple/swift-clang & CompoundStmtRange).
+
// FIXME (Alex L): Tweak selection rules for compound statements, see:
// https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/
// Refactor/ASTSlice.cpp#L513
@@ -330,3 +334,36 @@ CodeRangeASTSelection::create(SourceRange SelectionRange,
return CodeRangeASTSelection(Selected.Node, Selected.Parents,
/*AreChildrenSelected=*/true);
}
+
+bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const {
+ bool IsPrevCompound = false;
+ // Scan through the parents (bottom-to-top) and check if the selection is
+ // contained in a compound statement that's a body of a function/method
+ // declaration.
+ for (const auto &Parent : llvm::reverse(Parents)) {
+ const DynTypedNode &Node = Parent.get().Node;
+ if (const auto *D = Node.get<Decl>()) {
+ // FIXME (Alex L): Test for BlockDecl && ObjCMethodDecl.
+ if (isa<FunctionDecl>(D))
+ return IsPrevCompound;
+ // FIXME (Alex L): We should return false on top-level decls in functions
+ // e.g. we don't want to extract:
+ // function foo() { struct X {
+ // int m = /*selection:*/ 1 + 2 /*selection end*/; }; };
+ }
+ IsPrevCompound = Node.get<CompoundStmt>() != nullptr;
+ }
+ return false;
+}
+
+const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const {
+ for (const auto &Parent : llvm::reverse(Parents)) {
+ const DynTypedNode &Node = Parent.get().Node;
+ if (const auto *D = Node.get<Decl>()) {
+ // FIXME (Alex L): Test for BlockDecl && ObjCMethodDecl.
+ if (isa<FunctionDecl>(D))
+ return D;
+ }
+ }
+ return nullptr;
+}
diff --git a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp
new file mode 100644
index 0000000000..c0232c5da4
--- /dev/null
+++ b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp
@@ -0,0 +1,48 @@
+//===--- ASTSelectionRequirements.cpp - Clang refactoring library ---------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h"
+
+using namespace clang;
+using namespace tooling;
+
+Expected<SelectedASTNode>
+ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const {
+ // FIXME: Memoize so that selection is evaluated only once.
+ Expected<SourceRange> Range =
+ SourceRangeSelectionRequirement::evaluate(Context);
+ if (!Range)
+ return Range.takeError();
+
+ Optional<SelectedASTNode> Selection =
+ findSelectedASTNodes(Context.getASTContext(), *Range);
+ if (!Selection)
+ return Context.createDiagnosticError(
+ Range->getBegin(), diag::err_refactor_selection_invalid_ast);
+ return std::move(*Selection);
+}
+
+Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate(
+ RefactoringRuleContext &Context) const {
+ // FIXME: Memoize so that selection is evaluated only once.
+ Expected<SelectedASTNode> ASTSelection =
+ ASTSelectionRequirement::evaluate(Context);
+ if (!ASTSelection)
+ return ASTSelection.takeError();
+ std::unique_ptr<SelectedASTNode> StoredSelection =
+ llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection));
+ Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create(
+ Context.getSelectionRange(), *StoredSelection);
+ if (!CodeRange)
+ return Context.createDiagnosticError(
+ Context.getSelectionRange().getBegin(),
+ diag::err_refactor_selection_invalid_ast);
+ Context.setASTSelection(std::move(StoredSelection));
+ return std::move(*CodeRange);
+}
diff --git a/lib/Tooling/Refactoring/CMakeLists.txt b/lib/Tooling/Refactoring/CMakeLists.txt
index 43ea1d9c54..f30c679a92 100644
--- a/lib/Tooling/Refactoring/CMakeLists.txt
+++ b/lib/Tooling/Refactoring/CMakeLists.txt
@@ -2,7 +2,9 @@ set(LLVM_LINK_COMPONENTS Support)
add_clang_library(clangToolingRefactor
ASTSelection.cpp
+ ASTSelectionRequirements.cpp
AtomicChange.cpp
+ Extract.cpp
RefactoringActions.cpp
Rename/RenamingAction.cpp
Rename/SymbolOccurrences.cpp
diff --git a/lib/Tooling/Refactoring/Extract.cpp b/lib/Tooling/Refactoring/Extract.cpp
new file mode 100644
index 0000000000..616900c181
--- /dev/null
+++ b/lib/Tooling/Refactoring/Extract.cpp
@@ -0,0 +1,232 @@
+//===--- Extract.cpp - Clang refactoring library --------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Implements the "extract" refactoring that can pull code into
+/// new functions, methods or declare new variables.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Refactoring/RefactoringAction.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include "clang/Tooling/Refactoring/RefactoringOptions.h"
+
+namespace clang {
+namespace tooling {
+
+namespace {
+
+/// Returns true if \c E is a simple literal or a reference expression that
+/// should not be extracted.
+bool isSimpleExpression(const Expr *E) {
+ if (!E)
+ return false;
+ switch (E->IgnoreParenCasts()->getStmtClass()) {
+ case Stmt::DeclRefExprClass:
+ case Stmt::PredefinedExprClass:
+ case Stmt::IntegerLiteralClass:
+ case Stmt::FloatingLiteralClass:
+ case Stmt::ImaginaryLiteralClass:
+ case Stmt::CharacterLiteralClass:
+ case Stmt::StringLiteralClass:
+ return true;
+ default:
+ return false;
+ }
+}
+
+class ExtractableCodeSelectionRequirement final
+ : public CodeRangeASTSelectionRequirement {
+public:
+ Expected<CodeRangeASTSelection>
+ evaluate(RefactoringRuleContext &Context) const {
+ Expected<CodeRangeASTSelection> Selection =
+ CodeRangeASTSelectionRequirement::evaluate(Context);
+ if (!Selection)
+ return Selection.takeError();
+ CodeRangeASTSelection &Code = *Selection;
+
+ // We would like to extract code out of functions/methods/blocks.
+ // Prohibit extraction from things like global variable / field
+ // initializers and other top-level expressions.
+ if (!Code.isInFunctionLikeBodyOfCode())
+ return Context.createDiagnosticError(
+ diag::err_refactor_code_outside_of_function);
+
+ // Avoid extraction of simple literals and references.
+ if (Code.size() == 1 && isSimpleExpression(dyn_cast<Expr>(Code[0])))
+ return Context.createDiagnosticError(
+ diag::err_refactor_extract_simple_expression);
+
+ // FIXME (Alex L): Prohibit extraction of Objective-C property setters.
+ return Selection;
+ }
+};
+
+class ExtractFunction final : public SourceChangeRefactoringRule {
+public:
+ ExtractFunction(CodeRangeASTSelection Code, Optional<std::string> DeclName)
+ : Code(std::move(Code)),
+ DeclName(DeclName ? std::move(*DeclName) : "extracted") {}
+
+ Expected<AtomicChanges>
+ createSourceReplacements(RefactoringRuleContext &Context) override;
+
+private:
+ CodeRangeASTSelection Code;
+
+ // FIXME: Account for naming collisions:
+ // - error when name is specified by user.
+ // - rename to "extractedN" when name is implicit.
+ std::string DeclName;
+};
+
+SourceLocation computeFunctionExtractionLocation(const Decl *D) {
+ // FIXME (Alex L): Method -> function extraction should place function before
+ // C++ record if the method is defined inside the record.
+ return D->getLocStart();
+}
+
+// FIXME: Support C++ method extraction.
+// FIXME: Support Objective-C method extraction.
+Expected<AtomicChanges>
+ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) {
+ const Decl *ParentDecl = Code.getFunctionLikeNearestParent();
+ assert(ParentDecl && "missing parent");
+
+ // Compute the source range of the code that should be extracted.
+ SourceRange ExtractedRange(Code[0]->getLocStart(),
+ Code[Code.size() - 1]->getLocEnd());
+ // FIXME (Alex L): Add code that accounts for macro locations.
+
+ ASTContext &AST = Context.getASTContext();
+ SourceManager &SM = AST.getSourceManager();
+ const LangOptions &LangOpts = AST.getLangOpts();
+ Rewriter ExtractedCodeRewriter(SM, LangOpts);
+
+ // FIXME: Capture used variables.
+
+ // Compute the return type.
+ QualType ReturnType = AST.VoidTy;
+ // FIXME (Alex L): Account for the return statement in extracted code.
+ // FIXME (Alex L): Check for lexical expression instead.
+ bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]);
+ if (IsExpr) {
+ // FIXME (Alex L): Get a more user-friendly type if needed.
+ ReturnType = cast<Expr>(Code[0])->getType();
+ }
+
+ // FIXME: Rewrite the extracted code performing any required adjustments.
+
+ // FIXME: Capture any field if necessary (method -> function extraction).
+
+ // FIXME: Sort captured variables by name.
+
+ // FIXME: Capture 'this' / 'self' if necessary.
+
+ // FIXME: Compute the actual parameter types.
+
+ // Compute the location of the extracted declaration.
+ SourceLocation ExtractedDeclLocation =
+ computeFunctionExtractionLocation(ParentDecl);
+ // FIXME: Adjust the location to account for any preceding comments.
+
+ // FIXME: Adjust with PP awareness like in Sema to get correct 'bool'
+ // treatment.
+ PrintingPolicy PP = AST.getPrintingPolicy();
+ // FIXME: PP.UseStdFunctionForLambda = true;
+ PP.SuppressStrongLifetime = true;
+ PP.SuppressLifetimeQualifiers = true;
+ PP.SuppressUnwrittenScope = true;
+
+ AtomicChange Change(SM, ExtractedDeclLocation);
+ // Create the replacement for the extracted declaration.
+ {
+ std::string ExtractedCode;
+ llvm::raw_string_ostream OS(ExtractedCode);
+ // FIXME: Use 'inline' in header.
+ OS << "static ";
+ ReturnType.print(OS, PP, DeclName);
+ OS << '(';
+ // FIXME: Arguments.
+ OS << ')';
+
+ // Function body.
+ OS << " {\n";
+ if (IsExpr && !ReturnType->isVoidType())
+ OS << "return ";
+ OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange);
+ // FIXME: Compute the correct semicolon policy.
+ OS << ';';
+ OS << "\n}\n\n";
+ auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str());
+ if (Err)
+ return std::move(Err);
+ }
+
+ // Create the replacement for the call to the extracted declaration.
+ {
+ std::string ReplacedCode;
+ llvm::raw_string_ostream OS(ReplacedCode);
+
+ OS << DeclName << '(';
+ // FIXME: Forward arguments.
+ OS << ')';
+ // FIXME: Add semicolon if needed.
+
+ auto Err = Change.replace(
+ SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str());
+ if (Err)
+ return std::move(Err);
+ }
+
+ // FIXME: Add support for assocciated symbol location to AtomicChange to mark
+ // the ranges of the name of the extracted declaration.
+ return AtomicChanges{std::move(Change)};
+}
+
+class DeclNameOption final : public OptionalRefactoringOption<std::string> {
+public:
+ StringRef getName() const { return "name"; }
+ StringRef getDescription() const {
+ return "Name of the extracted declaration";
+ }
+};
+
+class ExtractRefactoring final : public RefactoringAction {
+public:
+ StringRef getCommand() const override { return "extract"; }
+
+ StringRef getDescription() const override {
+ return "(WIP action; use with caution!) Extracts code into a new function "
+ "/ method / variable";
+ }
+
+ /// Returns a set of refactoring actions rules that are defined by this
+ /// action.
+ RefactoringActionRules createActionRules() const override {
+ RefactoringActionRules Rules;
+ Rules.push_back(createRefactoringActionRule<ExtractFunction>(
+ ExtractableCodeSelectionRequirement(),
+ OptionRequirement<DeclNameOption>()));
+ return Rules;
+ }
+};
+
+} // end anonymous namespace
+
+std::unique_ptr<RefactoringAction> createExtractAction() {
+ return llvm::make_unique<ExtractRefactoring>();
+}
+
+} // end namespace tooling
+} // end namespace clang