Index: include/clang/Basic/LLVM.h =================================================================== --- include/clang/Basic/LLVM.h +++ include/clang/Basic/LLVM.h @@ -35,6 +35,7 @@ template class SmallVector; template class SmallVectorImpl; template class Optional; + template class Expected; template struct SaveAndRestore; @@ -71,6 +72,9 @@ using llvm::SmallVectorImpl; using llvm::SaveAndRestore; + // Error handling. + using llvm::Expected; + // Reference counting. using llvm::IntrusiveRefCntPtr; using llvm::IntrusiveRefCntPtrInfo; Index: include/clang/Tooling/Refactoring/AtomicChange.h =================================================================== --- include/clang/Tooling/Refactoring/AtomicChange.h +++ include/clang/Tooling/Refactoring/AtomicChange.h @@ -45,6 +45,12 @@ AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key) : Key(Key), FilePath(FilePath) {} + AtomicChange(AtomicChange &&) = default; + AtomicChange(const AtomicChange &) = default; + + AtomicChange &operator=(AtomicChange &&) = default; + AtomicChange &operator=(const AtomicChange &) = default; + /// \brief Returns the atomic change as a YAML string. std::string toYAMLString(); Index: include/clang/Tooling/Refactoring/DiagOr.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/DiagOr.h @@ -0,0 +1,52 @@ +//===--- DiagOr.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_DIAG_OR_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_DIAG_OR_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LLVM.h" + +namespace clang { +namespace tooling { + +/// A variant that stores a value of given type or an optional diagnostic. +template struct DiagOr { + DiagOr(T &&Value) : Value(std::move(Value)) {} + DiagOr(llvm::NoneType) {} + DiagOr(Optional &&Diag) : Diag(std::move(Diag)) {} + + DiagOr(DiagOr &&) = default; + DiagOr &operator=(DiagOr &&) = default; + + Optional &getDiag() { + assert(!Value.hasValue() && "Diagnostic with a value"); + return Diag; + } + + explicit operator bool() const { return hasValue(); } + bool hasValue() const { return Value.hasValue(); } + const T &operator*() const { + assert(Value.hasValue() && "Dereference without a value"); + return *Value; + } + T &operator*() { + assert(Value.hasValue() && "Dereference without a value"); + return *Value; + } + +private: + Optional Diag; + Optional Value; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_DIAG_OR_H Index: include/clang/Tooling/Refactoring/RefactoringActionRules.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringActionRules.h @@ -0,0 +1,306 @@ +//===--- RefactoringActionRules.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_RULES_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H + +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/Refactoring/DiagOr.h" +#include "clang/Tooling/Refactoring/RefactoringOperationController.h" +#include "clang/Tooling/Refactoring/RefactoringResult.h" +#include "clang/Tooling/Refactoring/SourceSelectionConstraints.h" +#include "llvm/Support/Error.h" +#include + +namespace clang { +namespace tooling { + +/// A common refactoring action rule interface. +class RefactoringActionRule { +public: + virtual ~RefactoringActionRule() {} + + /// The refactoring engine calls this method when trying to perform a + /// refactoring operation. The specific rule must return a valid diagnostic + /// or \c None when the refactoring action couldn't be initiated, an error + /// when the rule fails after initiation, or a valid refactoring result when + /// the action runs successfully. + virtual DiagOr> + perform(RefactoringOperationController &Controller) = 0; +}; + +/// A set of refactoring action rules that should have unique initiation +/// requirements. +using RefactoringActionRules = + std::vector>; + +namespace refactoring_action_rules { + +namespace detail { + +struct RequirementBase {}; + +} // end namespace detail + +namespace traits { + +/// A type trait that returns true iff the given type is a valid rule +/// requirement. +template +struct IsRequirement : std::conditional::value && + IsRequirement::value, + std::true_type, std::false_type>::type { +}; + +template +struct IsRequirement + : std::conditional::value, + std::true_type, std::false_type>::type {}; + +} // end namespace traits + +namespace detail { + +/// Defines a type alias of type \T when given \c DiagOr, or \c T otherwise. +template struct DropDiagOr { using Type = T; }; + +template struct DropDiagOr> { using Type = T; }; + +/// The \c requiredSelection refactoring action requirement is represented +/// using this type. +template +struct SourceSelectionRequirement + : std::enable_if::value, + RequirementBase>::type { + using OutputType = typename DropDiagOr::Type; + using FunctionType = OutputT (*)(InputT); + + SourceSelectionRequirement(FunctionType Function) : Function(Function) {} + +private: + OutputT (*Function)(InputT); + friend class BaseSpecializedRule; +}; + +/// 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 + static DiagOr::Type> evaluate( + RefactoringOperationController &Controller, + const SourceSelectionRequirement &SelectionRequirement) { + Optional Value = evalSelection(Controller); + if (!Value) + return None; + return SelectionRequirement.Function(*Value); + } + + /// Returns \c T when given \c DiagOr, or \c T otherwise. + template static T &&removeDiagOr(DiagOr &&X) { + assert(X && "unexpected diagnostic!"); + return std::move(*X); + } + template static T &&removeDiagOr(T &&X) { return std::move(X); } + + using OptionalDiagOrNone = Optional>; + + /// Scans the tuple and returns a \c StoredDiagnostic or an \c Optional + /// from the first invalid \c DiagOr value. Returns \c None if all values + /// are valid. + template + static OptionalDiagOrNone findDiagOrNone(FirstT &First, RestT &... Rest) { + OptionalDiagOrNone Result = takeDiagOrNone(First); + if (Result.hasValue()) + return Result; + return findDiagOrNone(Rest...); + } + +private: + /// Evaluates a selection constraint. + template + static typename std::enable_if::value, + llvm::Optional>::type + evalSelection(RefactoringOperationController &Controller); + + static OptionalDiagOrNone findDiagOrNone() { return OptionalDiagOrNone(); } + template static OptionalDiagOrNone takeDiagOrNone(T &) { + return OptionalDiagOrNone(); + } + template + static OptionalDiagOrNone takeDiagOrNone(DiagOr &Diag) { + return Diag.hasValue() ? OptionalDiagOrNone() : std::move(Diag.getDiag()); + } +}; + +/// Evaluates the \c selection::SourceSelectionRange constraint. +template <> +llvm::Optional +BaseSpecializedRule::evalSelection( + RefactoringOperationController &Controller) { + SourceRange R = Controller.getSelectionRange(); + if (R.isInvalid()) + return None; + return selection::SourceSelectionRange(Controller.getSources(), R); +} + +/// 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 +class PlainFunctionRule final : public BaseSpecializedRule { +public: + PlainFunctionRule(FunctionType Function, + std::tuple &&Requirements) + : Function(Function), Requirements(std::move(Requirements)) {} + + DiagOr> + perform(RefactoringOperationController &Controller) override { + return performImpl(Controller, + llvm::index_sequence_for()); + } + +private: + template + DiagOr> + performImpl(RefactoringOperationController &Controller, + llvm::index_sequence) { + // Initiate the operation. + auto Values = + std::make_tuple(evaluate(Controller, std::get(Requirements))...); + OptionalDiagOrNone InitiationFailure = + findDiagOrNone(std::get(Values)...); + if (InitiationFailure) + return std::move(*InitiationFailure); + // Perform the operation. + return Function(removeDiagOr(std::move(std::get(Values)))...); + } + + FunctionType Function; + std::tuple Requirements; +}; + +} // end namespace detail + +/// Creates a new refactoring action rule that invokes the given function once +/// all of the requirements are satisfied. The values produced during the +/// evaluation of requirements are passed to the given function (in the order of +/// requirements). +/// +/// \param RefactoringFunction the function that will perform the refactoring +/// once the requirements are satisfied. +/// +/// \param Requirements a set of rule requirements that have to be satisfied. +/// Each requirement must be a valid requirement, i.e. the value of +/// \c traits::IsRequirement must be true. +template +std::unique_ptr +apply(Expected (*RefactoringFunction)( + typename RequirementTypes::OutputType...), + const RequirementTypes &... Requirements) { + static_assert(traits::IsRequirement::value, + "invalid refactoring action rule requirement"); + return llvm::make_unique>( + RefactoringFunction, std::make_tuple(Requirements...)); +} + +namespace detail { + +template struct lambdaDeducer; + +template +struct lambdaDeducer { + using ReturnType = R; + using ArgType = A; +}; + +} // end namespace detail + +#define DEDUCE_LAMBDA_ARG(Callable) \ + typename detail::lambdaDeducer::ArgType +#define DEDUCE_LAMBDA_RET(Callable) \ + typename detail::lambdaDeducer::ReturnType + +#ifdef _MSC_VER +// MSVC is unable to deduce to the return type that depends on lambda's argument +// and return types. Fallback to using a C++14's decltype(auto). Luckily +// it's supported by the minimum version of MSVC compiler that's allowed +// by LLVM. +#define DEDUCE_LAMBDA_TYPE(T, Callable) decltype(auto) +#define DEDUCE_LAMBDA_TYPE_WITH_ARGTYPE(T, Callable, Arg) decltype(auto) +#else +// C++11 compliant type deduction that works with other compilers. +#define DEDUCE_LAMBDA_TYPE(T, Callable) \ + T +#define DEDUCE_LAMBDA_TYPE_WITH_ARGTYPE(T, Callable, Arg) \ + T +#endif + +/// Creates a selection requirement from a function pointer. +template +detail::SourceSelectionRequirement selectionRequirement( + OutputT (*Constraint)(InputT), + typename std::enable_if< + selection::traits::IsConstraint::value>::type * = nullptr) { + return detail::SourceSelectionRequirement(Constraint); +} + +/// Create a selection requirement from a non-capturing lambda. +template +auto selectionRequirement( + Callable Constraint, + typename std::enable_if::value>::type * = nullptr) + -> DEDUCE_LAMBDA_TYPE(detail::SourceSelectionRequirement, Callable) { + return detail::SourceSelectionRequirement( + Constraint); +} + +// These overloads provide a helpful diagnostic when a lambda or a function that +// takes an invalid selection constraint is given to selectionRequirement. +template +auto selectionRequirement( + Callable C, typename std::enable_if::value>::type * = nullptr) + -> DEDUCE_LAMBDA_TYPE_WITH_ARGTYPE(detail::SourceSelectionRequirement, + Callable, + selection::SourceSelectionRange) { + using OutputT = DEDUCE_LAMBDA_RET(Callable); + static_assert(sizeof(OutputT) && false, + "The argument type of the function passed into " + "'selectionRequirement' must be a valid selection constraint"); + return detail::SourceSelectionRequirement(nullptr); +} +template +detail::SourceSelectionRequirement +selectionRequirement( + OutputT (*C)(InputT), + typename std::enable_if< + !selection::traits::IsConstraint::value>::type * = nullptr) { + static_assert(sizeof(OutputT) && false, + "The argument type of the function passed into " + "'selectionRequirement' must be a valid selection constraint"); + return detail::SourceSelectionRequirement(nullptr); +} + +#undef DEDUCE_LAMBDA_ARG +#undef DEDUCE_LAMBDA_RET +#undef DEDUCE_LAMBDA_TYPE +#undef DEDUCE_LAMBDA_TYPE_WITH_ARGTYPE + +} // end namespace refactoring_action_rules +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_ACTION_RULES_H Index: include/clang/Tooling/Refactoring/RefactoringOperationController.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringOperationController.h @@ -0,0 +1,42 @@ +//===--- RefactoringOperationController.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_OPERATION_CONTROLLER_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_CONTROLLER_H + +#include "clang/Basic/SourceManager.h" + +namespace clang { +namespace tooling { + +/// 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) {} + + const SourceManager &getSources() const { return SM; } + + /// 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; } + +private: + const SourceManager &SM; + SourceRange SelectionRange; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_OPERATION_CONTROLLER_H Index: include/clang/Tooling/Refactoring/RefactoringResult.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/RefactoringResult.h @@ -0,0 +1,49 @@ +//===--- RefactoringResult.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_RESULT_H +#define LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RESULT_H + +#include "clang/Tooling/Refactoring/AtomicChange.h" + +namespace clang { +namespace tooling { + +/// Refactoring result is a variant that stores a set of source changes or +/// a set of found symbol occurrences. +struct RefactoringResult { + enum ResultKind { + /// A set of source replacements represented using a vector of + /// \c AtomicChanges. + AtomicChanges + }; + + RefactoringResult(AtomicChange Change) : Kind(AtomicChanges) { + Changes.push_back(std::move(Change)); + } + RefactoringResult(RefactoringResult &&Other) = default; + RefactoringResult &operator=(RefactoringResult &&Other) = default; + + ResultKind getKind() const { return Kind; } + + llvm::MutableArrayRef getChanges() { + assert(getKind() == AtomicChanges && + "Refactoring didn't produce atomic changes"); + return Changes; + } + +private: + ResultKind Kind; + std::vector Changes; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_RESULT_H Index: include/clang/Tooling/Refactoring/SourceSelectionConstraints.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/SourceSelectionConstraints.h @@ -0,0 +1,61 @@ +//===--- SourceSelectionConstraints.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_SOURCE_SELECTION_CONSTRAINTS_H +#define LLVM_CLANG_TOOLING_REFACTOR_SOURCE_SELECTION_CONSTRAINTS_H + +#include "clang/Basic/SourceLocation.h" +#include + +namespace clang { +namespace tooling { +namespace selection { + +/// This constraint is satisfied when any portion of the source text is +/// selected. It can be used be used to obtain the raw source selection range. +struct SourceSelectionRange { + SourceSelectionRange(const SourceManager &SM, SourceRange Range) + : SM(SM), Range(Range) {} + + const SourceManager &getSources() const { return SM; } + SourceRange getRange() const { return Range; } + +private: + const SourceManager &SM; + SourceRange Range; +}; + +namespace traits { + +/// A type trait that returns true iff the given type is a valid selection +/// constraint. +template struct IsConstraint : public std::false_type {}; + +} // end namespace traits + +/// A identity function that returns the given selection constraint is provided +/// for convenience, as it can be passed to \c requiredSelection directly. +template T (*identity())(T) { + static_assert( + traits::IsConstraint::value, + "selection::identity can be used with selection constraints only"); + return [](T Value) { return std::move(Value); }; +} + +namespace traits { + +template <> +struct IsConstraint : public std::true_type {}; + +} // end namespace traits +} // end namespace selection +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_SELECTION_CONSTRAINTS_H Index: unittests/Tooling/CMakeLists.txt =================================================================== --- unittests/Tooling/CMakeLists.txt +++ unittests/Tooling/CMakeLists.txt @@ -23,6 +23,7 @@ RecursiveASTVisitorTestDeclVisitor.cpp RecursiveASTVisitorTestExprVisitor.cpp RecursiveASTVisitorTestTypeLocVisitor.cpp + RefactoringActionRulesTest.cpp RefactoringCallbacksTest.cpp RefactoringTest.cpp ReplacementsYamlTest.cpp Index: unittests/Tooling/RefactoringActionRulesTest.cpp =================================================================== --- /dev/null +++ unittests/Tooling/RefactoringActionRulesTest.cpp @@ -0,0 +1,148 @@ +//===- unittest/Tooling/RefactoringTestActionRulesTest.cpp ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplacementTest.h" +#include "RewriterTestContext.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace tooling; +using namespace refactoring_action_rules; + +namespace { + +class RefactoringActionRulesTest : public ::testing::Test { +protected: + void SetUp() override { + Context.Sources.setMainFileID( + Context.createInMemoryFile("input.cpp", DefaultCode)); + } + + RewriterTestContext Context; + std::string DefaultCode = std::string(100, 'a'); +}; + +TEST_F(RefactoringActionRulesTest, MyFirstRefactoringRule) { + auto ReplaceAWithB = + [](std::pair Selection) + -> Expected { + const SourceManager &SM = Selection.first.getSources(); + SourceLocation Loc = Selection.first.getRange().getBegin().getLocWithOffset( + Selection.second); + AtomicChange Change(SM, Loc); + llvm::Error E = Change.replace(SM, Loc, 1, "b"); + if (E) + return std::move(E); + return Change; + }; + auto Rule = + apply(ReplaceAWithB, + selectionRequirement([](selection::SourceSelectionRange Selection) { + return std::make_pair(Selection, 20); + })); + + // When the requirements are satisifed, the rule's function must be invoked. + { + RefactoringOperationController Operation(Context.Sources); + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()) + .getLocWithOffset(10); + Operation.setSelectionRange({Cursor, Cursor}); + + DiagOr> DiagOrResult = Rule->perform(Operation); + ASSERT_FALSE(!DiagOrResult); + Expected Result = std::move(*DiagOrResult); + ASSERT_FALSE(!Result); + ASSERT_EQ(Result->getKind(), RefactoringResult::AtomicChanges); + ASSERT_EQ(Result->getChanges().size(), 1u); + std::string YAMLString = Result->getChanges()[0].toYAMLString(); + + ASSERT_STREQ("---\n" + "Key: 'input.cpp:30'\n" + "FilePath: input.cpp\n" + "Error: ''\n" + "InsertedHeaders: \n" + "RemovedHeaders: \n" + "Replacements: \n" // Extra whitespace here! + " - FilePath: input.cpp\n" + " Offset: 30\n" + " Length: 1\n" + " ReplacementText: b\n" + "...\n", + YAMLString.c_str()); + } + + // When one of the requirements is not satisfied, perform should return either + // None or a valid diagnostic. + { + RefactoringOperationController Operation(Context.Sources); + DiagOr> DiagOrResult = Rule->perform(Operation); + + ASSERT_TRUE(!DiagOrResult); + auto DiagOrNone = DiagOrResult.getDiag(); + ASSERT_TRUE(!DiagOrNone); + } +} + +TEST_F(RefactoringActionRulesTest, ReturnError) { + Expected (*Func)(selection::SourceSelectionRange) = + [](selection::SourceSelectionRange) -> Expected { + return llvm::make_error( + "Error", std::make_error_code(std::errc::bad_message)); + }; + auto Rule = + apply(Func, selectionRequirement( + selection::identity())); + + RefactoringOperationController Operation(Context.Sources); + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); + Operation.setSelectionRange({Cursor, Cursor}); + DiagOr> Result = Rule->perform(Operation); + + ASSERT_FALSE(!Result); + ASSERT_TRUE(!*Result); + std::string Value; + llvm::handleAllErrors( + (*Result).takeError(), + [&](const llvm::StringError &Error) { Value = Error.getMessage(); }); + EXPECT_EQ(Value, "Error"); +} + +TEST_F(RefactoringActionRulesTest, ReturnInitiationDiagnostic) { + DiagOr (*SelectionConstraint)(selection::SourceSelectionRange) = + [](selection::SourceSelectionRange) -> DiagOr { + return DiagOr( + StoredDiagnostic(DiagnosticsEngine::Error, 0, "Diagnostic")); + }; + auto Rule = apply( + [](int) -> Expected { + llvm::report_fatal_error("Should not run!"); + }, + selectionRequirement(SelectionConstraint)); + + RefactoringOperationController Operation(Context.Sources); + SourceLocation Cursor = + Context.Sources.getLocForStartOfFile(Context.Sources.getMainFileID()); + Operation.setSelectionRange({Cursor, Cursor}); + DiagOr> Result = Rule->perform(Operation); + + ASSERT_TRUE(!Result); + auto DiagOrNone = Result.getDiag(); + ASSERT_FALSE(!DiagOrNone); + StoredDiagnostic Diag = *DiagOrNone; + EXPECT_EQ(Diag.getLevel(), DiagnosticsEngine::Error); + EXPECT_EQ(Diag.getID(), 0u); + EXPECT_EQ(Diag.getMessage(), "Diagnostic"); +} + +} // end anonymous namespace