diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt --- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -16,6 +16,7 @@ DumpAST.cpp ExpandAutoType.cpp ExpandMacro.cpp + ExtractFunction.cpp ExtractVariable.cpp RawStringLiteral.cpp SwapIfBranches.cpp diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp @@ -0,0 +1,546 @@ +//===--- ExtractFunction.cpp -------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "ClangdUnit.h" +#include "Logger.h" +#include "Selection.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Refactoring/Extract/SourceExtraction.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { + +using Node = SelectionTree::Node; + +// Source is the part of code that is being extracted. +// EnclosingFunction is the function/method inside which the source lies. +// PreSource is everything before source and part of EnclosingFunction. +// PostSource is everything after source and part of EnclosingFunction. +// We split the file into 4 parts w.r.t. the Source. +enum LocType { PRESOURCE, INSOURCE, POSTSOURCE, OUTSIDEFUNC }; + +// ExtractionSourceView forms a view of the code wrt to Source. +class ExtractionSourceView { +public: + ExtractionSourceView(const Node *N, const SourceManager &SM, + const LangOptions &LangOpts); + // Get the location type for a given location. + LocType getLocType(SourceLocation Loc) const; + // Parent of RootStatements being extracted. + const Node *Parent = nullptr; + // The half-open file range of the code being extracted. + SourceRange SourceRng; + // The function inside which our source resides. + const Node *EnclosingFunction; + // The half-open file range of the enclosing function. + SourceRange EnclosingFunctionRng; + const Node *LastRootStmt = nullptr; + bool isEligibleForExtraction() const { return IsEligibleForExtraction; } + SourceLocation getInsertionPoint() const { + return EnclosingFunctionRng.getBegin(); + } + +private: + // Check if all child nodes of Parent are Root Stmts. + bool hasOnlyRootStmtChildren(); + // We only support extraction of RootStmts. A RootStmt as a statement that is + // fully selected including all of it's children. + // Returns the (unselected) parent of all RootStmts. + static const Node *getParentOfRootStmts(const Node *CommonAnc); + // Find the union of source ranges of all child nodes of Parent. Returns an + // inavlid SourceRange if it fails to do so. + SourceRange computeSourceRng(); + // Finds the function in which the source lies. + static const Node *computeEnclosingFunction(const Node *CommonAnc); + // Compute the range spanned by the enclosing function. + // For non-template functions, it is the range of the FunctionDecl. + // For templated functions, it is the range of the FunctionTemplateDecl. + static SourceRange computeEnclosingFunctionRng(const Node *EnclosingFunction, + const SourceManager &SM, + const LangOptions &LangOpts); + const SourceManager &SM; + const LangOptions &LangOpts; + // Source is extractable if it lies inside a function and + bool IsEligibleForExtraction = false; +}; + +const Node * +ExtractionSourceView::computeEnclosingFunction(const Node *CommonAnc) { + // Walk up the SelectionTree until we find a function Decl + for (const Node *CurNode = CommonAnc; CurNode; CurNode = CurNode->Parent) { + if (CurNode->ASTNode.get()) { + // FIXME: Support extraction from methods. + if (CurNode->ASTNode.get()) + return nullptr; + return CurNode; + } + } + return nullptr; +} + +SourceRange +ExtractionSourceView::computeEnclosingFunctionRng(const Node *EnclosingFunction, + const SourceManager &SM, + const LangOptions &LangOpts) { + const Node *N = EnclosingFunction->Parent->ASTNode.get() + ? EnclosingFunction->Parent + : EnclosingFunction; + return *toHalfOpenFileRange(SM, LangOpts, N->ASTNode.getSourceRange()); +} + +const Node *ExtractionSourceView::getParentOfRootStmts(const Node *CommonAnc) { + if (!CommonAnc) + return nullptr; + switch (CommonAnc->Selected) { + case SelectionTree::Selection::Unselected: + return CommonAnc; + case SelectionTree::Selection::Partial: + // Treat Partially selected VarDecl as completely selected since + // SelectionTree doesn't always select VarDecls correctly. + if (!CommonAnc->ASTNode.get()) + return nullptr; + [[clang::fallthrough]]; + case SelectionTree::Selection::Complete: + const Node *Parent = CommonAnc->Parent; + // If parent is a DeclStmt, we consider it a root statement and return its + // parent. + return Parent->ASTNode.get() ? Parent->Parent : Parent; + } +} + +// FIXME: Check we're not extracting from the initializer/condition of a control +// flow structure. +// FIXME: Check that we don't extract the compound statement of the +// enclosingFunction. +ExtractionSourceView::ExtractionSourceView(const Node *CommonAnc, + const SourceManager &SM, + const LangOptions &LangOpts) + : SM(SM), LangOpts(LangOpts) { + Parent = getParentOfRootStmts(CommonAnc); + if (!Parent || Parent->Children.empty()) + return; + // FIXME: check if EnclosingFunction has any attributes + EnclosingFunction = computeEnclosingFunction(Parent); + if (!EnclosingFunction) + return; + EnclosingFunctionRng = + computeEnclosingFunctionRng(EnclosingFunction, SM, LangOpts); + if (!hasOnlyRootStmtChildren()) + return; + LastRootStmt = Parent->Children.back(); + // Don't extract expressions. + if (Parent->Children.size() == 1 && LastRootStmt->ASTNode.get()) + return; + SourceRng = computeSourceRng(); + IsEligibleForExtraction = true; +} + +SourceRange ExtractionSourceView::computeSourceRng() { + SourceRange SR; + for (const Node *Child : Parent->Children) { + auto ChildFileRange = + toHalfOpenFileRange(SM, LangOpts, Child->ASTNode.getSourceRange()); + if (!ChildFileRange) + return SourceRange(); + if (SR.isInvalid()) + SR = *ChildFileRange; + else + SR.setEnd(ChildFileRange->getEnd()); + } + return SR; +} + +bool ExtractionSourceView::hasOnlyRootStmtChildren() { + for (const Node *Child : Parent->Children) { + // Ensure every child is a statement. + if (!Child->ASTNode.get()) + return false; + // We don't want to extract a partially selected subtree. + if (Child->Selected == SelectionTree::Partial) + return false; + // Only DeclStmt can be an unselected child since VarDecls claim the entire + // selection range in selectionTree. + if (Child->Selected == SelectionTree::Selection::Unselected && + !Child->ASTNode.get()) + return false; + } + return true; +} + +// TODO: check if this works properly with macros. +LocType ExtractionSourceView::getLocType(SourceLocation Loc) const { + if (!SM.isPointWithin(Loc, EnclosingFunctionRng.getBegin(), + EnclosingFunctionRng.getEnd())) + return OUTSIDEFUNC; + if (Loc < SourceRng.getBegin()) + return PRESOURCE; + if (Loc < SourceRng.getEnd()) + return INSOURCE; + return POSTSOURCE; +} + +// Stores information about the extracted function and provides methods for +// rendering it. +class NewFunction { +private: + const SourceManager &SM; + struct Parameter { + std::string Name; + std::string Type; + bool PassByReference = true; + bool IsConst; + Parameter(std::string Name, std::string Type, bool IsConst) + : Name(Name), Type(Type), IsConst(IsConst) {} + std::string render(bool IsDefinition) const; + // FIXME: Better priority heuristics + bool operator<(const Parameter &Other) const { return Name < Other.Name; } + bool operator==(const Parameter &Other) const { return Name == Other.Name; } + }; + +public: + std::string FuncName = "extracted"; + std::string ReturnType = "void"; + std::set Parameters; + std::vector DeclsToHoist; + SourceRange BodyRange; + SourceLocation InsertionPoint; + tooling::ExtractionSemicolonPolicy SemicolonPolicy; + NewFunction(const SourceManager &SM, SourceRange BodyRange, + SourceLocation InsertionPoint, + tooling::ExtractionSemicolonPolicy SemicolonPolicy) + : SM(SM), BodyRange(BodyRange), InsertionPoint(InsertionPoint), + SemicolonPolicy(SemicolonPolicy){}; + std::string render(bool IsDefinition) const; + std::string getFuncBody() const; + void addParam(llvm::StringRef Name, llvm::StringRef Type, bool IsConst); +}; + +std::string NewFunction::render(bool IsDefinition) const { + std::string Result; + if (IsDefinition) { + Result += ReturnType + " "; + } + Result += FuncName + "("; + bool Comma = false; + for (const Parameter &P : Parameters) { + if (Comma) + Result += ", "; + Result += P.render(IsDefinition); + Comma = true; + } + Result += ")"; + if (IsDefinition) + Result += " {\n" + getFuncBody() + "\n}\n"; + else if (SemicolonPolicy.isNeededInOriginalFunction()) + Result += ";"; + return Result; +} + +std::string NewFunction::getFuncBody() const { + // TODO: Hoist Decls + // TODO: Add return statement. + return toSourceCode(SM, BodyRange).str() + + (SemicolonPolicy.isNeededInExtractedFunction() ? ";" : ""); +} + +void NewFunction::addParam(llvm::StringRef Name, llvm::StringRef Type, + bool IsConst) { + Parameters.insert(Parameter(Name, Type, IsConst)); +} + +std::string NewFunction::Parameter::render(bool IsDefinition) const { + std::string Result; + if (IsDefinition) { + if (IsConst) + Result += "const "; + Result += Type + " "; + if (PassByReference) + Result += "&"; + } + Result += Name; + return Result; +} + +// Captures information about the source. +class CapturedSourceInfo { +public: + struct DeclInformation { + const Decl *TheDecl = nullptr; + LocType DeclaredIn; + bool IsReferencedInSource = false; + bool IsReferencedInPostSource = false; + bool IsAssigned = false; + bool MaybeModifiedOutside = false; + DeclInformation(){}; + DeclInformation(const Decl *TheDecl, LocType DeclaredIn) + : TheDecl(TheDecl), DeclaredIn(DeclaredIn){}; + void markOccurence(LocType ReferenceLoc); + }; + llvm::DenseMap DeclInfoMap; + bool HasReturnStmt = false; + // For now we just care whether there exists a break/continue or not. + bool HasBreakOrContinue = false; + // FIXME: capture TypeAliasDecl and UsingDirectiveDecl + // FIXME: Capture type information as well. +private: + // Return reference for a Decl, adding it if not already present. + DeclInformation &getDeclInformationFor(const Decl *D); + const ExtractionSourceView &SrcView; + CapturedSourceInfo(const ExtractionSourceView &SrcView) : SrcView(SrcView) {} + +public: + static CapturedSourceInfo captureInfo(const ExtractionSourceView &SrcView); + void captureBreakOrContinue(const Stmt *BreakOrContinue); + void captureReturn(const Stmt *Return); + void captureReference(const DeclRefExpr *DRE); +}; + +CapturedSourceInfo +CapturedSourceInfo::captureInfo(const ExtractionSourceView &SrcView) { + // We use the ASTVisitor instead of using the selection tree since we need to + // find references in the PostSource as well. + // FIXME: Check which statements we don't allow to extract. + class ExtractionSourceViewVisitor + : public clang::RecursiveASTVisitor { + public: + ExtractionSourceViewVisitor(CapturedSourceInfo &Info) : Info(Info) {} + bool VisitDecl(Decl *D) { // NOLINT + Info.getDeclInformationFor(D); + return true; + } + bool VisitDeclRefExpr(DeclRefExpr *DRE) { // NOLINT + Info.captureReference(DRE); + return true; + } + bool VisitReturnStmt(ReturnStmt *Return) { // NOLINT + Info.captureReturn(Return); + return true; + } + bool VisitBreakStmt(BreakStmt *Break) { // NOLINT + Info.captureBreakOrContinue(Break); + return true; + } + bool VisitContinueStmt(ContinueStmt *Continue) { // NOLINT + Info.captureBreakOrContinue(Continue); + return true; + } + + private: + CapturedSourceInfo &Info; + }; + CapturedSourceInfo Info(SrcView); + ExtractionSourceViewVisitor Visitor(Info); + Visitor.TraverseDecl(const_cast( + SrcView.EnclosingFunction->ASTNode.get())); + return Info; +} + +CapturedSourceInfo::DeclInformation & +CapturedSourceInfo::getDeclInformationFor(const Decl *D) { + if (DeclInfoMap.find(D) == DeclInfoMap.end()) + DeclInfoMap.insert( + {D, DeclInformation(D, SrcView.getLocType(D->getLocation()))}); + return DeclInfoMap[D]; +} + +// FIXME: check if reference mutates the Decl being referred. +void CapturedSourceInfo::captureReference(const DeclRefExpr *DRE) { + DeclInformation &DeclInfo = getDeclInformationFor(DRE->getDecl()); + DeclInfo.markOccurence(SrcView.getLocType(DRE->getLocation())); +} + +void CapturedSourceInfo::captureReturn(const Stmt *Return) { + if (SrcView.getLocType(Return->getBeginLoc()) == INSOURCE) + HasReturnStmt = true; +} + +// FIXME: check for broken break/continue only. +void CapturedSourceInfo::captureBreakOrContinue(const Stmt *BreakOrContinue) { + if (SrcView.getLocType(BreakOrContinue->getBeginLoc()) == INSOURCE) + HasBreakOrContinue = true; +} + +void CapturedSourceInfo::DeclInformation::markOccurence(LocType ReferenceLoc) { + switch (ReferenceLoc) { + case INSOURCE: + IsReferencedInSource = true; + break; + case POSTSOURCE: + IsReferencedInPostSource = true; + break; + default: + break; + } +} + +// Adds parameters to ExtractedFunc and finds the corresponding decls that needs +// to be hoisted. Returns true if able to find the parameters successfully. +bool createParametersAndGetDeclsToHoist( + NewFunction &ExtractedFunc, const CapturedSourceInfo &CapturedInfo) { + // FIXME: Generate better types names. e.g. remove class from object types. + auto GetDeclType = [](const ValueDecl *D) { + return D->getType() + .getUnqualifiedType() + .getNonReferenceType() + .getAsString(); + }; + auto DependsOnTemplate = [](const ValueDecl *D) { + if (!D) + return false; + if (D->isTemplateParameter()) + return true; + const Type *T = D->getType().getTypePtr(); + return T && T->isDependentType(); + }; + + for (const auto &KeyVal : CapturedInfo.DeclInfoMap) { + const auto &DeclInfo = KeyVal.second; + const ValueDecl *VD = dyn_cast_or_null(DeclInfo.TheDecl); + // FIXME: Add support for extracting Template dependent Decls. + bool WillBeParameter = + (DeclInfo.DeclaredIn == PRESOURCE && DeclInfo.IsReferencedInSource) || + (DeclInfo.DeclaredIn == INSOURCE && DeclInfo.IsReferencedInPostSource); + bool TemplateDependent = DependsOnTemplate(VD); + // Don't extract template dependent Decls that are in source. + if (DeclInfo.DeclaredIn == INSOURCE && TemplateDependent) + return false; + // Check if the Decl will become a parameter. + if (!WillBeParameter) + continue; + // Parameter specific checks. + // Can't parameterise if the Decl isn't a ValueDecl, is a Template Dependent + // decl or a FunctionDecl(this includes the case of recursive call to + // EnclosingFunc in Source). + if (!VD || TemplateDependent || + dyn_cast_or_null(DeclInfo.TheDecl)) + return false; + // FIXME: Need better qualifier checks: check mutated status for + // DeclInformation + // FIXME: check if parameter will be a non l-value reference. + // FIXME: We don't want to always pass variables of types like int, + // pointers, etc by reference. + bool IsConstDecl = VD->getType().isConstQualified(); + ExtractedFunc.addParam(VD->getName(), GetDeclType(VD), IsConstDecl); + // If a Decl was Declared in source and referenced in post source, it needs + // to be hoisted. + if (DeclInfo.DeclaredIn == INSOURCE && DeclInfo.IsReferencedInPostSource) + ExtractedFunc.DeclsToHoist.push_back(DeclInfo.TheDecl); + } + return true; +} + +// Get semicolon extraction policy (may change SrcRange in SrcView) +tooling::ExtractionSemicolonPolicy +getSemicolonPolicy(ExtractionSourceView &SrcView, const SourceManager &SM, + const LangOptions &LangOpts) { + // get closed SourceRng. + SourceRange FuncBodyRange = {SrcView.SourceRng.getBegin(), + SrcView.SourceRng.getEnd().getLocWithOffset(-1)}; + auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute( + SrcView.LastRootStmt->ASTNode.get(), FuncBodyRange, SM, LangOpts); + SrcView.SourceRng.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1)); + return SemicolonPolicy; +} +// FIXME: add support for adding other function return types besides void. +// FIXME: assign the value returned by non void extracted function. +llvm::Optional +getExtractedFunction(ExtractionSourceView &SrcView, + const SourceManager &SM, const LangOptions &LangOpts) { + CapturedSourceInfo CapturedInfo = CapturedSourceInfo::captureInfo(SrcView); + // If there is any reference or source has a recursive call, we don't extract + // for now. + // FIXME: Bail out for Return only if we are extracting a return as well as + // want to return a Hoisted decl value. + if (CapturedInfo.HasReturnStmt || CapturedInfo.HasBreakOrContinue) + return llvm::None; + auto SemicolonPolicy = getSemicolonPolicy(SrcView, SM, LangOpts); + NewFunction ExtractedFunc(SM, SrcView.SourceRng, SrcView.getInsertionPoint(), + std::move(SemicolonPolicy)); + if (!createParametersAndGetDeclsToHoist(ExtractedFunc, CapturedInfo)) + return llvm::None; + // For now, we give up if any Decl needs to be hoisted. + // FIXME: Use the hoisted decls + if (!ExtractedFunc.DeclsToHoist.empty()) + return llvm::None; + return ExtractedFunc; +} + +/// Extracts statements to a new function and replaces the statements with a +/// call to the new function. +class ExtractFunction : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override { return "Extract to function"; } + Intent intent() const override { return Refactor; } + +private: + std::unique_ptr SrcView; +}; + +REGISTER_TWEAK(ExtractFunction) +tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc, + const SourceManager &SM, + const LangOptions &LangOpts) { + std::string FuncCall = ExtractedFunc.render(false); + return tooling::Replacement( + SM, CharSourceRange(ExtractedFunc.BodyRange, false), FuncCall, LangOpts); +} + +tooling::Replacement createFunctionDefinition(const NewFunction &ExtractedFunc, + const SourceManager &SM) { + std::string FunctionDef = ExtractedFunc.render(true); + return tooling::Replacement(SM, ExtractedFunc.InsertionPoint, 0, FunctionDef); +} + +bool ExtractFunction::prepare(const Selection &Inputs) { + const Node *CommonAnc = Inputs.ASTSelection.commonAncestor(); + const SourceManager &SM = Inputs.AST.getSourceManager(); + const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts(); + SrcView = llvm::make_unique(CommonAnc, SM, LangOpts); + return SrcView->isEligibleForExtraction(); +} + +Expected ExtractFunction::apply(const Selection &Inputs) { + const SourceManager &SM = Inputs.AST.getSourceManager(); + const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts(); + auto ExtractedFunc = getExtractedFunction(*SrcView, SM, LangOpts); + // FIXME: Add more types of errors. + if (!ExtractedFunc) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + +"Too complex to extract."); + tooling::Replacements Result; + if (auto Err = Result.add(createFunctionDefinition(*ExtractedFunc, SM))) + return std::move(Err); + if (auto Err = Result.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts))) + return std::move(Err); + return Effect::applyEdit(Result); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TweakTests.cpp b/clang-tools-extra/clangd/unittests/TweakTests.cpp --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -599,6 +599,117 @@ R"cpp(const char * x = "test")cpp"); } +TWEAK_TEST(ExtractFunction); +TEST_F(ExtractFunctionTest, PrepareFunctionTest) { + Header = R"cpp( + #define F(BODY) void FFF() { BODY } + )cpp"; + Context = Function; + EXPECT_AVAILABLE(R"cpp( + [[int sum = 0; + for(;;) + sum++;]] + for(int i = 0; i < 5; i++) { + sum += i; + } + )cpp"); + EXPECT_AVAILABLE(R"cpp([[int x;]])cpp"); + // TODO: Add tests for macros after selectionTree works properly for macros. + // EXPECT_AVAILABLE(R"cpp( F (int x = 0; [[x = 1;]])cpp"); + // FIXME: This should be unavailable since partially selected but + // selectionTree doesn't always work correctly for VarDecls. + //EXPECT_UNAVAILABLE(R"cpp(int [[x = 0]];)cpp"); + EXPECT_UNAVAILABLE(R"cpp( + int sum = 0; + for(;;) + [[sum++; + sum++;]] + )cpp"); + // Expressions aren't extracted. + EXPECT_UNAVAILABLE(R"cpp(int x = 0; [[x++;]])cpp"); + // FIXME: ExtractFunction should be unavailable inside loop construct + // initalizer/condition. + // EXPECT_UNAVAILABLE(R"cpp( for([[int i = 0; i < 5;]] i++) )cpp"); +} + +TEST_F(ExtractFunctionTest, PrepareMethodTest) { + EXPECT_UNAVAILABLE(R"cpp( + class T { + void f() { + [[int x;]] + } + }; + )cpp"); +} + +TEST_F(ExtractFunctionTest, ApplyTest) { + // We can extract from templated functions as long as no reference in the + // extraction depends on the template. + // Checks const qualifier and extraction parameters. + Header = R"cpp( + )cpp"; + EXPECT_EQ(apply( + R"cpp( +struct FOO { + int x; +}; +template +void f(int a) { + int b = N; T t; const int c; FOO foo; + int *ptr = &a; + [[a += foo.x; + b = c; + *ptr++; + for(;;) + int d = 5 /* check if comment is extracted */ ;]] +})cpp"), + R"cpp( +struct FOO { + int x; +}; +void extracted(int &a, int &b, const int &c, struct FOO &foo, int * &ptr) { +a += foo.x; + b = c; + *ptr++; + for(;;) + int d = 5 /* check if comment is extracted */ ; +} +template +void f(int a) { + int b = N; T t; const int c; FOO foo; + int *ptr = &a; + extracted(a, b, c, foo, ptr); +})cpp"); + auto ShouldSucceed = [&](llvm::StringRef Code) { + EXPECT_THAT(apply(Code), HasSubstr("extracted")); + }; + auto ShouldFail = [&](llvm::StringRef Code) { + EXPECT_THAT(apply(Code), StartsWith("fail:")); + }; + // Ensure the last break isn't included in the Source since the end of source + // and beginning of break are adjacent. + ShouldSucceed(R"cpp( + void f() { + for(;;) { + [[{}]]break; + } + } + )cpp"); + // Don't extract because needs hoisting. + ShouldFail(R"cpp( void f() { [[int a = 5;]] a++;})cpp"); + // Don't extract parameters that depend on template. + ShouldFail(R"cpp( template void f() { [[int a = N;]] })cpp"); + ShouldFail(R"cpp( template void f() { [[T t;]] })cpp"); + // Don't extract return + ShouldFail(R"cpp( int f() { int a = 5; [[return a;]]})cpp"); + // Don't extract break and continue. + // FIXME: We should be able to extract this. + ShouldFail(R"cpp( int f() { [[for(;;) break;]]})cpp"); + ShouldFail(R"cpp( int f() { for(;;) [[continue;]]})cpp"); + // Don't extract when we need to make a function as a parameter. + ShouldFail(R"cpp( void f() { [[int a; f();]] } )cpp"); +} + } // namespace } // namespace clangd } // namespace clang