arphaman created this revision. Herald added a subscriber: mgorny. This patch depends on https://reviews.llvm.org/D36075 and https://reviews.llvm.org/D36156.
It introduces the `clang-refactor` tool alongside the `local-rename` action which is uses the existing renaming engine used by `clang-rename`. The tool doesn't actually perform the source transformations yet, it just provides testing support. I've moved one test from `clang-rename` over right now, but will move the others if the general direction of this patch is accepted. The following options are supported by clang-refactor: - `-v`: use verbose output - `-dump`: dump results instead of applying them - `-no-dbs`: avoid searching/loading databases like compilation database and/or indexer stores/databases - `-selection`: The source range that corresponds to the portion of the source that's selected (currently only special command `test:<file>` is supported). The testing support provided by `clang-refactor` is described below: When `-selection=test:<file>` is given, `clang-refactor` will parse the selection commands from that file. The selection commands are grouped and the specified refactoring action invoked by the tool. Each command in a group is expected to produce an identical result. The precise syntax for the selection commands is described in a comment for `findTestSelectionRangesIn` in TestSupport.h. Thanks for taking a look! Repository: rL LLVM https://reviews.llvm.org/D36574 Files: include/clang/Basic/DiagnosticRefactoringKinds.td include/clang/Tooling/Refactoring/RefactoringAction.h include/clang/Tooling/Refactoring/RefactoringActionRegistry.def include/clang/Tooling/Refactoring/RefactoringActionRules.h include/clang/Tooling/Refactoring/RefactoringEngine.h include/clang/Tooling/Refactoring/RefactoringOperationController.h include/clang/Tooling/Refactoring/RefactoringResult.h include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h include/clang/Tooling/Refactoring/Rename/USRFindingAction.h include/clang/Tooling/Refactoring/SourceSelectionConstraints.h include/clang/module.modulemap lib/Tooling/Refactoring/CMakeLists.txt lib/Tooling/Refactoring/RefactoringEngine.cpp lib/Tooling/Refactoring/Rename/RenamingAction.cpp lib/Tooling/Refactoring/Rename/USRFindingAction.cpp test/Refactor/LocalRename/Field.cpp test/Refactor/tool-common-options.c test/Refactor/tool-test-support.c test/clang-rename/Field.cpp tools/CMakeLists.txt tools/clang-refactor/CMakeLists.txt tools/clang-refactor/ClangRefactor.cpp tools/clang-refactor/TestSupport.cpp tools/clang-refactor/TestSupport.h unittests/Tooling/RefactoringActionRulesTest.cpp
Index: unittests/Tooling/RefactoringActionRulesTest.cpp =================================================================== --- unittests/Tooling/RefactoringActionRulesTest.cpp +++ unittests/Tooling/RefactoringActionRulesTest.cpp @@ -56,7 +56,8 @@ // When the requirements are satisifed, the rule's function must be invoked. { - RefactoringOperationController Operation(Context.Sources); + RefactoringOperationController Operation(Context.Sources, + Context.Diagnostics); SourceLocation Cursor = Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()) .getLocWithOffset(10); @@ -87,7 +88,8 @@ // When one of the requirements is not satisfied, perform should return either // None or a valid diagnostic. { - RefactoringOperationController Operation(Context.Sources); + RefactoringOperationController Operation(Context.Sources, + Context.Diagnostics); DiagnosticOr<RefactoringResult> DiagOrResult = Rule->perform(Operation); // A failure to select returns the invalidSelectionError. @@ -107,7 +109,8 @@ apply(Func, requiredSelection( selection::identity<selection::SourceSelectionRange>())); - RefactoringOperationController Operation(Context.Sources); + RefactoringOperationController Operation(Context.Sources, + Context.Diagnostics); SourceLocation Cursor = Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); Operation.setSelectionRange({Cursor, Cursor}); @@ -122,7 +125,8 @@ TEST_F(RefactoringActionRulesTest, ReturnInitiationDiagnostic) { unsigned DiagID = Context.Diagnostics.getCustomDiagID( DiagnosticsEngine::Error, "Diagnostic: %0"); - RefactoringOperationController Operation(Context.Sources); + RefactoringOperationController Operation(Context.Sources, + Context.Diagnostics); PartialDiagnostic Diag(DiagID, Operation.getDiagnosticStorage()); class SelectionRequirement : public selection::Requirement { Index: tools/clang-refactor/TestSupport.h =================================================================== --- /dev/null +++ tools/clang-refactor/TestSupport.h @@ -0,0 +1,104 @@ +//===--- TestSupport.h - Clang-based refactoring tool -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Declares datatypes and routines that are used by test-specific code +/// in clang-refactor. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H +#define LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H + +#include "clang/Basic/DiagnosticOr.h" +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/RefactoringResult.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include <map> +#include <string> + +namespace clang { + +class SourceManager; + +namespace clang_refactor { + +/// A source selection range that's specified in a test file using an inline +/// command in the comment. These commands can take the following forms: +/// +/// - /*range=*/ will create an empty selection range in the default group +/// right after the comment. +/// - /*range a=*/ will create an empty selection range in the 'a' group right +/// after the comment. +/// - /*range = +1*/ will create an empty selection range at a location that's +/// right after the comment with one offset to the column. +/// - /*range= -> +2:3*/ will create a selection range that starts at the +/// location right after the comment, and ends at column 3 of the 2nd line +/// after the line of the starting location. +/// +/// Clang-refactor will expected all ranges in one test group to produce +/// identical results. +struct TestSelectionRange { + unsigned Begin, End; +}; + +/// A set of test selection ranges specified in one file. +struct TestSelectionRangesInFile { + std::string Filename; + std::map<std::string, SmallVector<TestSelectionRange, 8>> GroupedRanges; + + TestSelectionRangesInFile(TestSelectionRangesInFile &&) = default; + TestSelectionRangesInFile &operator=(TestSelectionRangesInFile &&) = default; + + bool dispatch( + const SourceManager &SM, DiagnosticsEngine &Diags, + llvm::function_ref<DiagnosticOr<tooling::RefactoringResult>(SourceRange)> + Producer) const; + + void dump(llvm::raw_ostream &OS) const; +}; + +/// Extracts the grouped selection ranges from the file that's specified in +/// the -selection=test:<filename> option. +/// +/// The grouped ranges are specified in comments using the following syntax: +/// "range" [ group-name ] "=" [ "+" starting-column-offset ] [ "->" +/// "+" ending-line-offset ":" +/// ending-column-position ] +/// +/// The selection range is then computed from this command by taking the ending +/// location of the comment, and adding 'starting-column-offset' to the column +/// for that location. That location in turns becomes the whole selection range, +/// unless 'ending-line-offset' and 'ending-column-position' are specified. If +/// they are specified, then the ending location of the selection range is +/// the starting location's line + 'ending-line-offset' and the +/// 'ending-column-position' column. +/// +/// All selection ranges in one group are expected to produce the same +/// refactoring result. +/// +/// When testing, zero is returned from clang-refactor even when a group +/// produces an initiation error, which is different from normal invocation +/// that returns a non-zero value. This is done on purpose, to ensure that group +/// consistency checks can return non-zero, but still dump the output of +/// the group. So even if a test matches the output of group, it will still fail +/// because clang-refactor should return zero on exit when the group results are +/// consistent. +/// +/// \returns None on failure (errors are emitted to stderr), or a set of +/// grouped source ranges in the given file otherwise. +Optional<TestSelectionRangesInFile> +findTestSelectionRangesIn(StringRef Filename); + +} // end namespace clang_refactor +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_CLANG_REFACTOR_TEST_SUPPORT_H Index: tools/clang-refactor/TestSupport.cpp =================================================================== --- /dev/null +++ tools/clang-refactor/TestSupport.cpp @@ -0,0 +1,209 @@ +//===--- TestSupport.cpp - Clang-based refactoring tool -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements routines that provide refactoring testing +/// utilities. +/// +//===----------------------------------------------------------------------===// + +#include "TestSupport.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; +using namespace tooling; +using namespace clang_refactor; + +void TestSelectionRangesInFile::dump(raw_ostream &OS) const { + for (const auto &Group : GroupedRanges) { + OS << "Test selection group '" << Group.first << "':\n"; + for (const auto &Range : Group.second) { + OS << " " << Range.Begin << "-" << Range.End << "\n"; + } + } +} + +bool TestSelectionRangesInFile::dispatch( + const SourceManager &SM, DiagnosticsEngine &Diags, + llvm::function_ref<DiagnosticOr<RefactoringResult>(SourceRange)> Producer) + const { + const FileEntry *FE = SM.getFileManager().getFile(Filename); + FileID FID = FE ? SM.translateFile(FE) : FileID(); + if (!FE || FID.isInvalid()) { + llvm::errs() << "error: -selection=test:" << Filename + << " : given file is not in the target TU"; + return true; + } + SourceLocation FileLoc = SM.getLocForStartOfFile(FID); + bool Failed = false; + for (const auto &Group : GroupedRanges) { + // All ranges in the group must produce the same result. + Optional<RefactoringResult> CanonicalResult; + Optional<std::string> CanonicalErrorMessage; + for (const TestSelectionRange &Range : Group.second) { + // Translate the offset pair to a true source range. + SourceLocation Start = + SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.Begin)); + SourceLocation End = + SM.getMacroArgExpandedLocation(FileLoc.getLocWithOffset(Range.End)); + assert(Start.isValid() && End.isValid() && "unexpected invalid range"); + + DiagnosticOr<RefactoringResult> Result = + Producer(SourceRange(Start, End)); + std::string ErrorMessage; + bool HasResult = !!Result; + if (!HasResult) { + SmallString<100> String; + // It's better to test location agnostic errors, so ignore the location. + Result.getDiagnostic().second.EmitToString(Diags, String); + ErrorMessage = String.c_str(); + } + if (!CanonicalResult && !CanonicalErrorMessage) { + if (HasResult) + CanonicalResult = std::move(*Result); + else + CanonicalErrorMessage = std::move(ErrorMessage); + continue; + } + + // Verify that this result corresponds to the canonical result. + if (CanonicalErrorMessage) { + // The error messages must match. + if (!HasResult && ErrorMessage == *CanonicalErrorMessage) + continue; + } else { + assert(CanonicalResult && "missing canonical result"); + // The results must match. + if (HasResult && *Result == *CanonicalResult) + continue; + } + Failed = true; + // Report the mismatch. + llvm::errs() + << "error: unexpected refactoring result for range starting at " + << SM.getLineNumber(FID, Range.Begin) << ':' + << SM.getColumnNumber(FID, Range.Begin) << " in group '" + << Group.first << "':\n "; + if (HasResult) + llvm::errs() << "valid result"; + else + llvm::errs() << "error '" << ErrorMessage << "'"; + llvm::errs() << " does not match initial "; + if (CanonicalErrorMessage) + llvm::errs() << "error '" << *CanonicalErrorMessage << "'\n"; + else + llvm::errs() << "valid result\n"; + if (HasResult && !CanonicalErrorMessage) { + llvm::errs() << " Expected to Produce:\n"; + CanonicalResult->dump(llvm::errs(), SM, /*Indent=*/4); + llvm::errs() << " Produced:\n"; + Result->dump(llvm::errs(), SM, /*Indent=*/4); + } + } + + // FIXME: Integrate with -dump. + if (!CanonicalResult) { + llvm::errs() << Group.second.size() << " '" << Group.first + << "' results:\n"; + llvm::errs() << *CanonicalErrorMessage << "\n"; + } else { + llvm::outs() << Group.second.size() << " '" << Group.first + << "' results:\n"; + CanonicalResult->dump(llvm::outs(), SM); + } + } + return Failed; +} + +/// Adds the \p ColumnOffset to file offset \p Offset, without going past a +/// newline. +static unsigned addColumnOffset(StringRef Source, unsigned Offset, + unsigned ColumnOffset) { + if (!ColumnOffset) + return Offset; + StringRef Substr = Source.drop_front(Offset).take_front(ColumnOffset); + size_t NewlinePos = Substr.find_first_of("\r\n"); + return Offset + + (NewlinePos == StringRef::npos ? ColumnOffset : (unsigned)NewlinePos); +} + +Optional<TestSelectionRangesInFile> +clang_refactor::findTestSelectionRangesIn(StringRef Filename) { + ErrorOr<std::unique_ptr<MemoryBuffer>> ErrOrFile = + llvm::MemoryBuffer::getFile(Filename); + if (!ErrOrFile) { + llvm::errs() << "error: -selection=test:" << Filename + << " : could not open the given file"; + return None; + } + StringRef Source = ErrOrFile.get()->getBuffer(); + + // FIXME (Alex L): 3rd capture groups for +line:column. + // See the doc comment for this function for the explanation of this + // syntax. + static Regex RangeRegex("range[[:blank:]]*([[:alpha:]_]*)?[[:blank:]]*=[[:" + "blank:]]*(\\+[[:digit:]]+)?"); + + TestSelectionRangesInFile TestRanges = {Filename.str(), {}}; + + LangOptions LangOpts; + LangOpts.CPlusPlus = 1; + LangOpts.CPlusPlus11 = 1; + Lexer Lex(SourceLocation::getFromRawEncoding(0), LangOpts, Source.begin(), + Source.begin(), Source.end()); + Lex.SetCommentRetentionState(true); + Token Tok; + for (Lex.LexFromRawLexer(Tok); Tok.isNot(tok::eof); + Lex.LexFromRawLexer(Tok)) { + if (Tok.isNot(tok::comment)) + continue; + StringRef Comment = + Source.substr(Tok.getLocation().getRawEncoding(), Tok.getLength()); + llvm::SmallVector<StringRef, 4> Matches; + if (!RangeRegex.match(Comment, &Matches)) { + // Try to detect mistyped 'range:' comments to ensure tests don't miss + // anything. + if (Comment.contains_lower("range") && !Comment.contains_lower("run")) { + llvm::errs() << "error: suspicious comment '" << Comment + << "' that " + "resembles the range command found\n"; + llvm::errs() << "note: please reword if this isn't a range command\n"; + return None; + } + continue; + } + unsigned Offset = Tok.getEndLoc().getRawEncoding(); + unsigned ColumnOffset = 0; + if (!Matches[2].empty()) { + // Don't forget to drop the '+'! + if (Matches[2].drop_front().getAsInteger(10, ColumnOffset)) + assert(false && "regex should have produced a number"); + } + // FIXME (Alex L): Support true ranges. + Offset = addColumnOffset(Source, Offset, ColumnOffset); + TestSelectionRange Range = {Offset, Offset}; + auto It = TestRanges.GroupedRanges.insert(std::make_pair( + Matches[1].str(), SmallVector<TestSelectionRange, 8>{Range})); + if (!It.second) + It.first->second.push_back(Range); + } + if (TestRanges.GroupedRanges.empty()) { + llvm::errs() << "error: -selection=test:" << Filename + << ": no 'range' commands"; + return None; + } + return std::move(TestRanges); +} Index: tools/clang-refactor/ClangRefactor.cpp =================================================================== --- /dev/null +++ tools/clang-refactor/ClangRefactor.cpp @@ -0,0 +1,336 @@ +//===--- ClangRefactor.cpp - Clang-based refactoring tool -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements a clang-refactor tool that performs various +/// source transformations. +/// +//===----------------------------------------------------------------------===// + +#include "TestSupport.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringEngine.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include <string> + +using namespace clang; +using namespace tooling; +using namespace clang_refactor; +namespace cl = llvm::cl; + +namespace opts { + +static cl::OptionCategory CommonRefactorOptions("Common refactoring options"); + +static cl::opt<bool> DumpResults( + "dump", + cl::desc("Dump the produced source replacements instead of applying them"), + cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands)); + +static cl::opt<bool> + NoDatabases("no-dbs", + cl::desc("Ignore external databases including Clang's " + "compilation database and indexer stores"), + cl::cat(CommonRefactorOptions), cl::sub(*cl::AllSubCommands)); + +static cl::opt<bool> Verbose("v", cl::desc("Use verbose output"), + cl::cat(CommonRefactorOptions), + cl::sub(*cl::AllSubCommands)); +} // end namespace opts + +namespace { + +/// A subcommand that corresponds to individual refactoring action. +class RefactoringActionSubcommand : public cl::SubCommand { +public: + RefactoringActionSubcommand(StringRef Name, StringRef Description, + RefactoringAction &Action, + cl::OptionCategory &Category, bool HasSelection) + : SubCommand(Name, Description), Action(&Action) { + Sources = llvm::make_unique<cl::list<std::string>>( + cl::Positional, cl::ZeroOrMore, cl::desc("<source0> [... <sourceN>]"), + cl::cat(Category), cl::sub(*this)); + if (HasSelection) { + Selection = llvm::make_unique<cl::opt<std::string>>( + "selection", cl::desc("Source selection range"), cl::cat(Category), + cl::sub(*this)); + } + } + + ~RefactoringActionSubcommand() { unregisterSubCommand(); } + + RefactoringAction &getAction() const { return *Action; } + + StringRef getSelection() const { + assert(Selection && "selection not supported!"); + return *Selection; + } + + /// Returns a pointer to the parsed -selection=find(<filename>) option, or + /// null if the selection was given in another format. + const TestSelectionRangesInFile *getTestSelection() { + parseSelection(); + return ParsedTestSelection ? ParsedTestSelection.getPointer() : nullptr; + } + + ArrayRef<std::string> getSources() const { return *Sources; } + +private: + void parseSelection() { + assert(Selection && "selection not supported!"); + if (IsSelectionParsed) + return; + IsSelectionParsed = true; + // FIXME: Support true selection ranges. + StringRef Value = *Selection; + if (Value.startswith("test:")) { + StringRef Filename = Value.drop_front(strlen("test:")); + ParsedTestSelection = findTestSelectionRangesIn(Filename); + if (!ParsedTestSelection) { + // A parsing error was already reported. + exit(1); + } + } else { + llvm::errs() << "error: '-selection' option must be specified using " + "<file>:<line>:<column> or " + "<file>:<line>:<column>-<line>:<column> format"; + exit(1); + } + } + + RefactoringAction *Action; + std::unique_ptr<cl::list<std::string>> Sources; + std::unique_ptr<cl::opt<std::string>> Selection; + + bool IsSelectionParsed; + /// The parsed -selection=test:<filename> option. + Optional<TestSelectionRangesInFile> ParsedTestSelection; +}; + +class ToolRefactoringEngine final : public RefactoringEngine { +public: + std::vector<std::unique_ptr<RefactoringActionSubcommand>> SubCommands; + PartialDiagnostic::StorageAllocator DiagnosticStorage; + + void initialize() override { + RefactoringEngine::initialize(); + + // Create subcommands and command-line options. + for (const auto &Action : Actions) { + const ActionState &ActionInfo = State[Action.get()]; + // Check if the selection option is supported. + bool HasSelection = false; + for (const auto &Rule : ActionInfo.Rules) { + if ((HasSelection = Rule->hasSelectionRequirement())) + break; + } + + SubCommands.push_back(llvm::make_unique<RefactoringActionSubcommand>( + Action->getCommand(), Action->getDescription(), *Action, + opts::CommonRefactorOptions, HasSelection)); + } + } + + using TUCallbackType = llvm::function_ref<void(ASTContext &)>; + + /// Parses the translation units that were given to the subcommand using + /// the 'sources' option and invokes the callback for each parsed + /// translation unit. + bool foreachTranslationUnit(RefactoringActionSubcommand &Subcommand, + TUCallbackType Callback) { + std::unique_ptr<CompilationDatabase> Compilations; + if (opts::NoDatabases) { + // FIXME (Alex L): Support compilation options. + Compilations = + llvm::make_unique<clang::tooling::FixedCompilationDatabase>( + ".", std::vector<std::string>()); + } else { + // FIXME (Alex L): Support compilation database. + llvm::errs() << "compilation databases are not supported yet!\n"; + return true; + } + + class ToolASTConsumer : public ASTConsumer { + public: + TUCallbackType Callback; + ToolASTConsumer(TUCallbackType Callback) : Callback(Callback) {} + + void HandleTranslationUnit(ASTContext &Context) override { + Callback(Context); + } + }; + class ActionWrapper { + public: + TUCallbackType Callback; + ActionWrapper(TUCallbackType Callback) : Callback(Callback) {} + + std::unique_ptr<ASTConsumer> newASTConsumer() { + return llvm::make_unique<ToolASTConsumer>(std::move(Callback)); + } + }; + + ClangTool Tool(*Compilations, Subcommand.getSources()); + ActionWrapper ToolAction(std::move(Callback)); + std::unique_ptr<tooling::FrontendActionFactory> Factory = + tooling::newFrontendActionFactory(&ToolAction); + return Tool.run(Factory.get()); + } + + /// Logs an individual refactoring action invocation to STDOUT. + void logInvocation(RefactoringActionSubcommand &Subcommand, + const RefactoringOperationController &Controller) { + if (!opts::Verbose) + return; + llvm::outs() << "invoking action '" << Subcommand.getName() << "':\n"; + if (Controller.getSelectionRange().isValid()) { + SourceRange R = Controller.getSelectionRange(); + llvm::outs() << " -selection="; + R.getBegin().print(llvm::outs(), Controller.getSources()); + llvm::outs() << " -> "; + R.getEnd().print(llvm::outs(), Controller.getSources()); + llvm::outs() << "\n"; + } + } + + static PartialDiagnosticAt + reportInitiationDiag(const RefactoringActionSubcommand &Subcommand, + RefactoringOperationController &Controller) { + return PartialDiagnosticAt( + Controller.getSelectionRange().getBegin(), + PartialDiagnostic(diag::err_refactor_selection_initiation_failed, + Controller.getDiagnosticStorage()) + << Subcommand.getName()); + } + + /// Testing support: invokes the selection action for each selection range in + /// the test file. + bool + runSelectionTestInvocations(RefactoringActionSubcommand &Subcommand, + RefactoringOperationController &Controller, + const TestSelectionRangesInFile &TestSelections, + ArrayRef<RefactoringActionRule *> MatchingRules) { + if (opts::Verbose) + TestSelections.dump(llvm::outs()); + if (!opts::DumpResults) { + llvm::errs() << "error: test refactoring results can't be applied." + " Did you forget -dump?\n"; + return true; + } + return TestSelections.dispatch( + Controller.getSources(), Controller.getDiagnostics(), + [&](SourceRange R) -> DiagnosticOr<RefactoringResult> { + Controller.setSelectionRange(R); + logInvocation(Subcommand, Controller); + for (RefactoringActionRule *Rule : MatchingRules) { + if (!Rule->hasSelectionRequirement()) + continue; + // FIXME (Alex L): Smarter handle for multiple matching rules. + DiagnosticOr<RefactoringResult> Result = Rule->perform(Controller); + if (!Result && !Result.getDiagnostic().second.getDiagID()) + return reportInitiationDiag(Subcommand, Controller); + return Result; + } + llvm_unreachable("The action must have at least one selection rule"); + }); + } + + bool invokeAction(RefactoringActionSubcommand &Subcommand) { + const RefactoringAction &Action = Subcommand.getAction(); + const ActionState &ActionInfo = State[&Action]; + + // Find a set of matching rules. + SmallVector<RefactoringActionRule *, 4> MatchingRules; + llvm::StringSet<> MissingOptions; + + bool HasSelection = false; + for (const auto &Rule : ActionInfo.Rules) { + if (Rule->hasSelectionRequirement()) { + HasSelection = true; + if (!Subcommand.getSelection().empty()) + MatchingRules.push_back(Rule.get()); + else + MissingOptions.insert("selection"); + } + // FIXME (Alex L): Support custom options. + } + if (MatchingRules.empty()) { + llvm::errs() << "error: '" << Subcommand.getName() + << "' can't be invoked with the given arguments:\n"; + for (const auto &Opt : MissingOptions) + llvm::errs() << " missing '-" << Opt.getKey() << "' option\n"; + return true; + } + + bool HasFailed = false; + if (foreachTranslationUnit(Subcommand, [&](ASTContext &Context) { + RefactoringOperationController Controller(Context.getSourceManager(), + Context.getDiagnostics()); + ASTRefactoringOperation Op(Context); + Controller.setASTRefactoringOperation(&Op); + + if (HasSelection && Subcommand.getTestSelection()) { + // Use a slightly different invocation path for tests. + HasFailed = runSelectionTestInvocations( + Subcommand, Controller, *Subcommand.getTestSelection(), + MatchingRules); + return; + } + // FIXME (Alex L): Implement non-test invocation path. + // FIXME (Alex L): If more than one initiation succeeded, then the + // rules are ambiguous. + })) + return true; + return HasFailed; + } +}; + +} // end anonymous namespace + +int main(int argc, const char **argv) { + ToolRefactoringEngine Engine; + Engine.initialize(); + + cl::HideUnrelatedOptions(opts::CommonRefactorOptions); + cl::ParseCommandLineOptions( + argc, argv, "Clang-based refactoring tool for C, C++ and Objective-C"); + cl::PrintOptionValues(); + + // Figure out which action is given by the user. + auto It = llvm::find_if( + Engine.SubCommands, + [](const std::unique_ptr<RefactoringActionSubcommand> &SubCommand) { + return !!(*SubCommand); + }); + if (It == Engine.SubCommands.end()) { + llvm::errs() << "error: no refactoring action given\n"; + llvm::errs() << "note: the following actions are supported:\n"; + for (const auto &Subcommand : Engine.SubCommands) + llvm::errs().indent(2) << Subcommand->getName() << "\n"; + return 1; + } + RefactoringActionSubcommand &ActionCommand = **It; + + ArrayRef<std::string> Sources = ActionCommand.getSources(); + // When -no-dbs is used, at least one file (TU) must be given to any + // subcommand. + if (opts::NoDatabases && Sources.empty()) { + llvm::errs() << "error: must provide paths to the source files when " + "'-no-dbs' is used\n"; + return 1; + } + if (Engine.invokeAction(ActionCommand)) + return 1; + + // FIXME (Alex L): Apply refactoring results to the source files. + return 0; +} Index: tools/clang-refactor/CMakeLists.txt =================================================================== --- /dev/null +++ tools/clang-refactor/CMakeLists.txt @@ -0,0 +1,20 @@ +set(LLVM_LINK_COMPONENTS + Option + Support + ) + +add_clang_executable(clang-refactor + ClangRefactor.cpp + TestSupport.cpp + ) + +target_link_libraries(clang-refactor + clangBasic + clangFrontend + clangRewrite + clangTooling + clangToolingCore + clangToolingRefactor + ) + +install(TARGETS clang-refactor RUNTIME DESTINATION bin) Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -12,6 +12,7 @@ add_clang_subdirectory(c-index-test) add_clang_subdirectory(clang-rename) +add_clang_subdirectory(clang-refactor) if(CLANG_ENABLE_ARCMT) add_clang_subdirectory(arcmt-test) Index: test/clang-rename/Field.cpp =================================================================== --- test/clang-rename/Field.cpp +++ /dev/null @@ -1,15 +0,0 @@ -class Baz { - int Foo; /* Test 1 */ // CHECK: int Bar; -public: - Baz(); -}; - -Baz::Baz() : Foo(0) /* Test 2 */ {} // CHECK: Baz::Baz() : Bar(0) - -// Test 1. -// RUN: clang-rename -offset=18 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s -// Test 2. -// RUN: clang-rename -offset=89 -new-name=Bar %s -- | sed 's,//.*,,' | FileCheck %s - -// To find offsets after modifying the file, use: -// grep -Ubo 'Foo.*' <file> Index: test/Refactor/tool-test-support.c =================================================================== --- /dev/null +++ test/Refactor/tool-test-support.c @@ -0,0 +1,34 @@ +// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -dump -v %s 2>&1 | FileCheck %s + +/*range=*/int test; + +/*range named=*/int test2; + +/*range= +1*/int test3; + +/* range = +100 */int test4; + +/*range named =+0*/int test5; + +// CHECK: Test selection group '': +// CHECK-NEXT: 106-106 +// CHECK-NEXT: 159-159 +// CHECK-NEXT: 198-198 +// CHECK-NEXT: Test selection group 'named': +// CHECK-NEXT: 133-133 +// CHECK-NEXT: 219-219 + +// CHECK: invoking action 'local-rename': +// CHECK-NEXT: -selection={{.*}}tool-test-support.c:3:11 + +// CHECK: invoking action 'local-rename': +// CHECK-NEXT: -selection={{.*}}tool-test-support.c:7:15 + +// CHECK: invoking action 'local-rename': +// CHECK-NEXT: -selection={{.*}}tool-test-support.c:9:29 + +// CHECK: invoking action 'local-rename': +// CHECK-NEXT: -selection={{.*}}tool-test-support.c:5:17 + +// CHECK: invoking action 'local-rename': +// CHECK-NEXT: -selection={{.*}}tool-test-support.c:11:20 Index: test/Refactor/tool-common-options.c =================================================================== --- /dev/null +++ test/Refactor/tool-common-options.c @@ -0,0 +1,6 @@ +// RUN: not clang-refactor 2>&1 | FileCheck --check-prefix=MISSING_ACTION %s +// MISSING_ACTION: error: no refactoring action given +// MISSING_ACTION-NEXT: note: the following actions are supported: + +// RUN: not clang-refactor local-rename -no-dbs 2>&1 | FileCheck --check-prefix=MISSING_SOURCES %s +// MISSING_SOURCES: error: must provide paths to the source files when '-no-dbs' is used Index: test/Refactor/LocalRename/Field.cpp =================================================================== --- /dev/null +++ test/Refactor/LocalRename/Field.cpp @@ -0,0 +1,9 @@ +// RUN: clang-refactor local-rename -selection=test:%s -no-dbs -dump %s | FileCheck %s + +class Baz { + int /*range=*/Foo; // CHECK: symbol [[@LINE]]:17 -> [[@LINE]]:20 +public: + Baz(); +}; + +Baz::Baz() : /*range=*/Foo(0) {} // CHECK: symbol [[@LINE]]:24 -> [[@LINE]]:27 Index: lib/Tooling/Refactoring/Rename/USRFindingAction.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/USRFindingAction.cpp +++ lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -154,6 +154,12 @@ }; } // namespace +std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND, + ASTContext &Context) { + AdditionalUSRFinder Finder(ND, Context); + return Finder.Find(); +} + class NamedDeclFindingConsumer : public ASTConsumer { public: NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets, Index: lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -22,17 +22,73 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" #include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include <string> #include <vector> using namespace llvm; +using namespace clang; +using namespace tooling; + +namespace { + +class LocalRename : public RefactoringAction { +public: + StringRef getCommand() const override { return "local-rename"; } + + StringRef getDescription() const override { + return "Finds and renames symbols in code with no indexer support"; + } + + static Expected<RefactoringResult> + findOccurrences(const ASTRefactoringOperation &Op, const NamedDecl *ND) { + std::vector<std::string> USRs = + getUSRsForDeclaration(ND, Op.getASTContext()); + std::string PrevName = ND->getNameAsString(); + return getOccurrencesOfUSRs(USRs, PrevName, + Op.getASTContext().getTranslationUnitDecl()); + // FIXME: Create the atomic changes when -new-name is given. + } + + class SymbolSelectionRequirement : public selection::Requirement { + public: + DiagnosticOr<const NamedDecl *> + evaluateSelection(const ASTRefactoringOperation &Op, + selection::SourceSelectionRange Selection) const { + const NamedDecl *ND = + getNamedDeclAt(Op.getASTContext(), Selection.getRange().getBegin()); + if (!ND) + return invalidSelectionError(); + return getCanonicalSymbolDeclaration(ND); + } + }; + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + using namespace refactoring_action_rules; + RefactoringActionRules Rules; + Rules.push_back(apply(findOccurrences, + requiredSelection(SymbolSelectionRequirement()))); + return Rules; + } +}; + +} // end anonymous namespace namespace clang { namespace tooling { +std::unique_ptr<RefactoringAction> createLocalRenameAction() { + return llvm::make_unique<LocalRename>(); +} + Expected<std::vector<AtomicChange>> createRenameReplacements(const SymbolOccurrences &Occurrences, const SourceManager &SM, Index: lib/Tooling/Refactoring/RefactoringEngine.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/RefactoringEngine.cpp @@ -0,0 +1,97 @@ +//===--- RefactoringEngine.cpp - Common refactoring engine code -----------===// +// +// 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/RefactoringEngine.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Errc.h" + +namespace clang { +namespace tooling { + +// Forward declare the individual create*Action functions. +#define REFACTORING_ACTION(Name) \ + std::unique_ptr<RefactoringAction> create##Name##Action(); +#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def" + +} // end namespace tooling +} // end namespace clang + +using namespace clang; +using namespace tooling; + +void RefactoringEngine::initialize() { +// Create the refactoring action. +#define REFACTORING_ACTION(Name) Actions.push_back(create##Name##Action()); +#include "clang/Tooling/Refactoring/RefactoringActionRegistry.def" + + // Create the refactoring action rules. + for (const auto &Action : Actions) { + RefactoringActionRules Rules = Action->createActionRules(); + // FIXME (Alex L): Drop unsupported rules. + State[Action.get()].Rules = std::move(Rules); + } +} + +void RefactoringResult::dump(llvm::raw_ostream &OS, const SourceManager &SM, + int Indent) const { + switch (getKind()) { + case AtomicChanges: + // FIXME (Alex L): Dump atomic changes. + break; + case SymbolOccurrencesKind: + for (const SymbolOccurrence &Occurrence : getSymbolOccurrences()) { + OS.indent(Indent); + switch (Occurrence.getKind()) { + case SymbolOccurrence::MatchingSymbol: + OS << "symbol "; + break; + } + for (const auto &I : llvm::enumerate(Occurrence.getNameRanges())) { + if (I.index()) + OS << ", "; + SourceRange Range = I.value(); + auto DumpLoc = [&](SourceLocation L) { + std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(L); + if (LocInfo.first.isInvalid()) { + OS << "<invalid>"; + return; + } + OS << SM.getLineNumber(LocInfo.first, LocInfo.second) << ':' + << SM.getColumnNumber(LocInfo.first, LocInfo.second); + }; + DumpLoc(Range.getBegin()); + OS << " -> "; + DumpLoc(Range.getEnd()); + OS << "\n"; + } + } + break; + } +} + +bool RefactoringResult::operator==(const RefactoringResult &Other) const { + if (getKind() != Other.getKind()) + return false; + switch (getKind()) { + case AtomicChanges: + // FIXME (Alex L): Compare atomic changes. + return false; + case SymbolOccurrencesKind: + if (getSymbolOccurrences().size() != Other.getSymbolOccurrences().size()) + return false; + for (const auto &I : + llvm::zip(getSymbolOccurrences(), Other.getSymbolOccurrences())) { + if (std::get<0>(I) != std::get<1>(I)) + return false; + } + break; + } + return true; +} Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_library(clangToolingRefactor AtomicChange.cpp + RefactoringEngine.cpp Rename/RenamingAction.cpp Rename/SymbolOccurrences.cpp Rename/USRFinder.cpp Index: include/clang/module.modulemap =================================================================== --- include/clang/module.modulemap +++ include/clang/module.modulemap @@ -138,6 +138,8 @@ // importing the AST matchers library gives a link dependency on the AST // matchers (and thus the AST), which clang-format should not have. exclude header "Tooling/RefactoringCallbacks.h" + + textual header "Tooling/Refactoring/RefactoringActionRegistry.def" } module Clang_ToolingCore { Index: include/clang/Tooling/Refactoring/SourceSelectionConstraints.h =================================================================== --- include/clang/Tooling/Refactoring/SourceSelectionConstraints.h +++ include/clang/Tooling/Refactoring/SourceSelectionConstraints.h @@ -15,6 +15,9 @@ namespace clang { namespace tooling { + +class ASTRefactoringOperation; + namespace selection { /// This constraint is satisfied when any portion of the source text is @@ -33,7 +36,11 @@ /// A custom selection requirement. class Requirement { - /// Subclasses must implement 'T evaluateSelection(SelectionConstraint) const' + /// Subclasses must implement + /// 'T evaluateSelection(const ASTRefactoringOperation &, + /// SelectionConstraint) const' + /// or + /// 'T evaluateSelection(SelectionConstraint) const' /// member function. \c T is used to determine the return type that is /// passed to the refactoring rule's function. /// If T is \c DiagnosticOr<S> , then \c S is passed to the rule's function @@ -57,9 +64,18 @@ template <typename T> struct EvaluateSelectionChecker : std::false_type {}; template <typename T, typename R, typename A> +struct EvaluateSelectionChecker<R (T::*)(const ASTRefactoringOperation &, A) + const> : std::true_type { + using ReturnType = R; + using ArgType = A; + using OperationType = ASTRefactoringOperation; +}; + +template <typename T, typename R, typename A> struct EvaluateSelectionChecker<R (T::*)(A) const> : std::true_type { using ReturnType = R; using ArgType = A; + using OperationType = void; }; template <typename T> class Identity : public Requirement { @@ -94,6 +110,16 @@ &T::evaluateSelection)>::ArgType>::value, std::true_type, std::false_type>::type {}; +/// A type trait that returns the type of the operation on which the selection +/// operates. +/// +/// Operation can be either \c ASTRefactoringOperation which requires parsed +/// AST, or \c void which doesn't require the parsed AST. +template <typename T> struct RequirementOperationType { + using Type = typename detail::EvaluateSelectionChecker<decltype( + &T::evaluateSelection)>::OperationType; +}; + } // end namespace traits } // end namespace selection } // end namespace tooling Index: include/clang/Tooling/Refactoring/Rename/USRFindingAction.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/USRFindingAction.h +++ include/clang/Tooling/Refactoring/Rename/USRFindingAction.h @@ -23,6 +23,7 @@ namespace clang { class ASTConsumer; +class ASTContext; class CompilerInstance; class NamedDecl; @@ -37,6 +38,10 @@ /// - A destructor is canonicalized to its class. const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl); +/// Returns the set of USRs that correspond to the given declaration. +std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND, + ASTContext &Context); + struct USRFindingAction { USRFindingAction(ArrayRef<unsigned> SymbolOffsets, ArrayRef<std::string> QualifiedNames, bool Force) Index: include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h +++ include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h @@ -77,6 +77,22 @@ return RangeOrNumRanges; } + bool operator==(const SymbolOccurrence &Other) const { + if (Kind != Other.Kind) + return false; + if (getNameRanges().size() != Other.getNameRanges().size()) + return false; + for (size_t I = 0, E = getNameRanges().size(); I != E; ++I) { + if (getNameRanges()[I] != Other.getNameRanges()[I]) + return false; + } + return true; + } + + bool operator!=(const SymbolOccurrence &Other) const { + return !(*this == Other); + } + private: OccurrenceKind Kind; std::unique_ptr<SourceRange[]> MultipleRanges; Index: include/clang/Tooling/Refactoring/RefactoringResult.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringResult.h +++ include/clang/Tooling/Refactoring/RefactoringResult.h @@ -11,6 +11,7 @@ #define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RESULT_H #include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h" namespace clang { namespace tooling { @@ -21,12 +22,16 @@ enum ResultKind { /// A set of source replacements represented using a vector of /// \c AtomicChanges. - AtomicChanges + AtomicChanges, + + SymbolOccurrencesKind }; RefactoringResult(AtomicChange Change) : Kind(AtomicChanges) { Changes.push_back(std::move(Change)); } + RefactoringResult(SymbolOccurrences Occurrences) + : Kind(SymbolOccurrencesKind), Occurrences(std::move(Occurrences)) {} RefactoringResult(RefactoringResult &&Other) = default; RefactoringResult &operator=(RefactoringResult &&Other) = default; @@ -38,9 +43,24 @@ return Changes; } + ArrayRef<SymbolOccurrence> getSymbolOccurrences() const { + assert(getKind() == SymbolOccurrencesKind && + "Refactoring didn't produce occurrences"); + return Occurrences; + } + + void dump(llvm::raw_ostream &OS, const SourceManager &SM, + int Indent = 0) const; + + bool operator==(const RefactoringResult &Other) const; + bool operator!=(const RefactoringResult &Other) const { + return !(*this == Other); + } + private: ResultKind Kind; std::vector<AtomicChange> Changes; + SymbolOccurrences Occurrences; }; } // end namespace tooling Index: include/clang/Tooling/Refactoring/RefactoringOperationController.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringOperationController.h +++ include/clang/Tooling/Refactoring/RefactoringOperationController.h @@ -14,32 +14,58 @@ #include "clang/Basic/SourceManager.h" namespace clang { + +class ASTContext; + namespace tooling { +/// A refactoring operation that requires the parsed AST. +class ASTRefactoringOperation { +public: + ASTRefactoringOperation(ASTContext &Context) : Context(Context) {} + + ASTContext &getASTContext() const { return Context; } + +private: + ASTContext &Context; +}; + /// Encapsulates all of the possible state that an individual refactoring /// operation might have. Controls the process of initiation of refactoring /// operations, by feeding the right information to the functions that /// evaluate the refactoring action rule requirements. class RefactoringOperationController { public: - RefactoringOperationController(const SourceManager &SM) : SM(SM) {} + RefactoringOperationController(const SourceManager &SM, + DiagnosticsEngine &Diags) + : SM(SM), Diags(Diags) {} PartialDiagnostic::StorageAllocator &getDiagnosticStorage() { return DiagnosticStorage; } const SourceManager &getSources() const { return SM; } + DiagnosticsEngine &getDiagnostics() const { return Diags; } + /// Returns the current source selection range as set by the /// refactoring engine. Can be invalid. SourceRange getSelectionRange() const { return SelectionRange; } void setSelectionRange(SourceRange R) { SelectionRange = R; } + void setASTRefactoringOperation(ASTRefactoringOperation *Op) { + ASTOperation = Op; + } + + ASTRefactoringOperation *getASTOperation() const { return ASTOperation; } + private: const SourceManager &SM; + DiagnosticsEngine &Diags; SourceRange SelectionRange; PartialDiagnostic::StorageAllocator DiagnosticStorage; + ASTRefactoringOperation *ASTOperation = nullptr; }; } // end namespace tooling Index: include/clang/Tooling/Refactoring/RefactoringEngine.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringEngine.h @@ -0,0 +1,42 @@ +//===--- RefactoringEngine.h - Clang refactoring library ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ENGINE_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ENGINE_H + +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include "llvm/ADT/DenseMap.h" +#include <vector> + +namespace clang { +namespace tooling { + +/// A base class for an editor or a tool refactoring engine. +/// +/// Contains the list of supported actions and refactoring action rules. +class RefactoringEngine { +public: + virtual ~RefactoringEngine() {} + + virtual void initialize(); + +protected: + struct ActionState { + RefactoringActionRules Rules; + }; + + std::vector<std::unique_ptr<RefactoringAction>> Actions; + llvm::DenseMap<const RefactoringAction *, ActionState> State; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h =================================================================== --- include/clang/Tooling/Refactoring/RefactoringActionRules.h +++ include/clang/Tooling/Refactoring/RefactoringActionRules.h @@ -35,6 +35,8 @@ /// rule's function runs successfully. virtual DiagnosticOr<RefactoringResult> perform(RefactoringOperationController &Controller) = 0; + + virtual bool hasSelectionRequirement() = 0; }; /// A set of refactoring action rules that should have unique initiation @@ -94,16 +96,52 @@ friend class BaseSpecializedRule; }; +/// A type trait that returns true when the given type has at least one source +/// selection requirement. +template <typename First, typename... Rest> +struct HasSelectionRequirement + : std::conditional<HasSelectionRequirement<First>::value || + HasSelectionRequirement<Rest...>::value, + std::true_type, std::false_type>::type {}; + +template <typename I, typename O, typename R> +struct HasSelectionRequirement<SourceSelectionRequirement<I, O, R>> + : std::true_type {}; + +template <typename T> struct HasSelectionRequirement<T> : std::false_type {}; + /// A wrapper class around \c RefactoringActionRule that defines some helper /// methods that are used by the subclasses. class BaseSpecializedRule : public RefactoringActionRule { protected: /// Evaluates a source selection action rule requirement. template <typename InputT, typename OutputT, typename RequirementT> - static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type> - evaluate(RefactoringOperationController &Controller, - const SourceSelectionRequirement<InputT, OutputT, RequirementT> - &SelectionRequirement) { + static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type> evaluate( + RefactoringOperationController &Controller, + const SourceSelectionRequirement<InputT, OutputT, RequirementT> + &SelectionRequirement, + typename std::enable_if< + std::is_same<typename selection::traits::RequirementOperationType< + RequirementT>::Type, + ASTRefactoringOperation>::value>::type * = nullptr) { + Optional<InputT> Value = evalSelection<InputT>(Controller); + if (!Value) + return invalidSelectionError(); + const ASTRefactoringOperation *Op = Controller.getASTOperation(); + assert(Op && "not an AST operation!"); + return std::move( + SelectionRequirement.Requirement.evaluateSelection(*Op, *Value)); + } + + template <typename InputT, typename OutputT, typename RequirementT> + static DiagnosticOr<typename DropDiagnosticOr<OutputT>::Type> evaluate( + RefactoringOperationController &Controller, + const SourceSelectionRequirement<InputT, OutputT, RequirementT> + &SelectionRequirement, + typename std::enable_if< + std::is_same<typename selection::traits::RequirementOperationType< + RequirementT>::Type, + void>::value>::type * = nullptr) { Optional<InputT> Value = evalSelection<InputT>(Controller); if (!Value) return invalidSelectionError(); @@ -164,7 +202,8 @@ /// A specialized refactoring action rule that calls the stored function once /// all the of the requirements are fullfilled. The values produced during the /// evaluation of requirements are passed to the stored function. -template <typename FunctionType, typename... RequirementTypes> +template <typename FunctionType, typename OperationType, + typename... RequirementTypes> class PlainFunctionRule final : public BaseSpecializedRule { public: PlainFunctionRule(FunctionType Function, @@ -177,20 +216,46 @@ llvm::index_sequence_for<RequirementTypes...>()); } + bool hasSelectionRequirement() override { + return HasSelectionRequirement<RequirementTypes...>::value; + } + private: + template <typename OpType, typename ValueType, size_t... Is> + Expected<RefactoringResult> performImplDispatch( + RefactoringOperationController &Controller, llvm::index_sequence<Is...>, + ValueType &Values, + typename std::enable_if< + std::is_same<OpType, ASTRefactoringOperation>::value>::type * = + nullptr) { + assert(Controller.getASTOperation() && "Not an AST-based operation!"); + return Function(*Controller.getASTOperation(), + removeDiagOr(std::move(std::get<Is>(Values)))...); + } + + template <typename OpType, typename ValueType, size_t... Is> + Expected<RefactoringResult> performImplDispatch( + RefactoringOperationController &, llvm::index_sequence<Is...>, + ValueType &Values, + typename std::enable_if<std::is_same<OpType, void>::value>::type * = + nullptr) { + return Function(removeDiagOr(std::move(std::get<Is>(Values)))...); + } + template <size_t... Is> DiagnosticOr<RefactoringResult> performImpl(RefactoringOperationController &Controller, - llvm::index_sequence<Is...>) { + llvm::index_sequence<Is...> Seq) { // Initiate the operation. auto Values = std::make_tuple(evaluate(Controller, std::get<Is>(Requirements))...); OptionalDiag InitiationFailure = findDiag(std::get<Is>(Values)...); if (InitiationFailure) return std::move(*InitiationFailure); // Perform the operation. Expected<RefactoringResult> Result = - Function(removeDiagOr(std::move(std::get<Is>(Values)))...); + performImplDispatch<OperationType, decltype(Values), Is...>( + Controller, Seq, Values); if (Result) return std::move(*Result); std::string Error = llvm::toString(Result.takeError()); @@ -225,12 +290,27 @@ template <typename... RequirementTypes> std::unique_ptr<RefactoringActionRule> apply(Expected<RefactoringResult> (*RefactoringFunction)( + const ASTRefactoringOperation &Op, + typename RequirementTypes::OutputType...), + const RequirementTypes &... Requirements) { + static_assert(traits::IsRequirement<RequirementTypes...>::value, + "invalid refactoring action rule requirement"); + return llvm::make_unique< + detail::PlainFunctionRule<decltype(RefactoringFunction), + ASTRefactoringOperation, RequirementTypes...>>( + RefactoringFunction, std::make_tuple(Requirements...)); +} + +template <typename... RequirementTypes> +std::unique_ptr<RefactoringActionRule> +apply(Expected<RefactoringResult> (*RefactoringFunction)( typename RequirementTypes::OutputType...), const RequirementTypes &... Requirements) { static_assert(traits::IsRequirement<RequirementTypes...>::value, "invalid refactoring action rule requirement"); - return llvm::make_unique<detail::PlainFunctionRule< - decltype(RefactoringFunction), RequirementTypes...>>( + return llvm::make_unique< + detail::PlainFunctionRule<decltype(RefactoringFunction), + /*OperationType=*/void, RequirementTypes...>>( RefactoringFunction, std::make_tuple(Requirements...)); } Index: include/clang/Tooling/Refactoring/RefactoringActionRegistry.def =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRegistry.def @@ -0,0 +1,7 @@ +#ifndef REFACTORING_ACTION +#define REFACTORING_ACTION(Name) +#endif + +REFACTORING_ACTION(LocalRename) + +#undef REFACTORING_ACTION Index: include/clang/Tooling/Refactoring/RefactoringAction.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringAction.h @@ -0,0 +1,38 @@ +//===--- RefactoringAction.h - Clang refactoring library ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include <vector> + +namespace clang { +namespace tooling { + +class RefactoringAction { +public: + /// Returns the name of the subcommand that's used by clang-refactor for this + /// action. + virtual StringRef getCommand() const = 0; + + virtual StringRef getDescription() const = 0; + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + virtual RefactoringActionRules createActionRules() const = 0; + + virtual ~RefactoringAction() {} +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_H Index: include/clang/Basic/DiagnosticRefactoringKinds.td =================================================================== --- include/clang/Basic/DiagnosticRefactoringKinds.td +++ include/clang/Basic/DiagnosticRefactoringKinds.td @@ -19,4 +19,11 @@ } +let CategoryName = "clang-refactor Issue" in { + +def err_refactor_selection_initiation_failed : Error< + "action '%0' can't be initiated with the specified selection range">; + +} + } // end of Refactoring diagnostics
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits