diff options
author | Alex Lorenz <arphaman@gmail.com> | 2017-10-24 17:18:45 +0000 |
---|---|---|
committer | Alex Lorenz <arphaman@gmail.com> | 2017-10-24 17:18:45 +0000 |
commit | 58538f115416ae2ce6cc4cfa794d243bade5458d (patch) | |
tree | ec24dbb374fbc882e2168b3e71efa4ff0e9921fa /lib/Tooling/Refactoring | |
parent | 53710bdec511dfcfb69ee4d234b7f579b4955101 (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.cpp | 37 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/ASTSelectionRequirements.cpp | 48 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/CMakeLists.txt | 2 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Extract.cpp | 232 |
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 |