diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp @@ -16,6 +16,7 @@ #include "SourceCode.h" #include "refactor/Tweak.h" #include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -156,7 +157,7 @@ "define outline: couldn't find a context for target"); llvm::Error Errors = llvm::Error::success(); - tooling::Replacements QualifierInsertions; + tooling::Replacements DeclarationCleanups; // Finds the first unqualified name in function return type and name, then // qualifies those to be valid in TargetContext. @@ -181,7 +182,7 @@ const NamedDecl *ND = Ref.Targets.front(); const std::string Qualifier = getQualification( AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND); - if (auto Err = QualifierInsertions.add( + if (auto Err = DeclarationCleanups.add( tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); }); @@ -206,14 +207,62 @@ assert(Tok != Tokens.rend()); DelRange.setBegin(Tok->location()); if (auto Err = - QualifierInsertions.add(tooling::Replacement(SM, DelRange, ""))) + DeclarationCleanups.add(tooling::Replacement(SM, DelRange, ""))) Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); } } + auto DelAttr = [&](const Attr *A) { + if (!A) + return; + assert(A->getLocation().isValid()); + if (A->getLocation().isMacroID()) { + Errors = llvm::joinErrors( + std::move(Errors), + llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::StringRef("define outline: Can't move out of line as " + "function has a macro `") + + A->getSpelling() + "` specifier.")); + return; + } + if (auto Err = DeclarationCleanups.add(tooling::Replacement( + SM, CharSourceRange::getTokenRange(A->getRange()), ""))) + Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); + }; + + DelAttr(FD->getAttr()); + DelAttr(FD->getAttr()); + + if (FD->isVirtualAsWritten()) { + SourceRange SpecRange{FD->getBeginLoc(), FD->getLocation()}; + bool Any = false; + // Clang allows duplicating virtual specifiers so check for multiple + // occurances. + for (const syntax::Token &Tok : TokBuf.expandedTokens(SpecRange)) { + if (Tok.kind() != tok::kw_virtual) + continue; + assert(Tok.location().isValid()); + if (Tok.location().isMacroID()) { + Errors = + llvm::joinErrors(std::move(Errors), + llvm::createStringError( + llvm::inconvertibleErrorCode(), + "define outline: Can't move out of line as " + "function has a macro `virtual` specifier.")); + continue; + } + Any = true; + if (auto Err = DeclarationCleanups.add( + tooling::Replacement(SM, Tok.range(SM).toCharRange(SM), ""))) + Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); + } + assert(Any); // Ensure at least one virtual was found + } + if (Errors) return std::move(Errors); - return getFunctionSourceAfterReplacements(FD, QualifierInsertions); + return getFunctionSourceAfterReplacements(FD, DeclarationCleanups); } struct InsertionPoint { 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 @@ -2068,6 +2068,69 @@ };)cpp", "Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n", }, + // Virt specifiers. + { + R"cpp( + struct A { + virtual void f^oo() {} + };)cpp", + R"cpp( + struct A { + virtual void foo() ; + };)cpp", + " void A::foo() {}\n", + }, + { + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void fo^o() override {} + };)cpp", + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void foo() override ; + };)cpp", + "void B::foo() {}\n", + }, + { + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void fo^o() final {} + };)cpp", + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void foo() final ; + };)cpp", + "void B::foo() {}\n", + }, + { + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void fo^o() final override {} + };)cpp", + R"cpp( + struct A { + virtual void foo() = 0; + }; + struct B : A { + void foo() final override ; + };)cpp", + "void B::foo() {}\n", + }, }; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Test); @@ -2229,6 +2292,61 @@ << Case.TestHeader; } } + +TEST_F(DefineOutlineTest, FailsMacroSpecifier) { + FileName = "Test.hpp"; + ExtraFiles["Test.cpp"] = ""; + + std::pair Cases[] = { + { + R"cpp( + #define VIRT virtual + struct A { + VIRT void fo^o() {} + };)cpp", + "fail: define outline: Can't move out of line as function has a " + "macro `virtual` specifier."}, + { + R"cpp( + #define OVER override + struct A { + virtual void foo() {} + }; + struct B : A { + void fo^o() OVER {} + };)cpp", + "fail: define outline: Can't move out of line as function has a " + "macro `override` specifier."}, + { + R"cpp( + #define FINAL final + #define OVER override + struct A { + virtual void foo() {} + }; + struct B : A { + void fo^o() FINAL OVER {} + };)cpp", + "fail: define outline: Can't move out of line as function has a " + "macro `override` specifier.\ndefine outline: Can't move out of line " + "as function has a macro `final` specifier."}, + { + R"cpp( + #define FINALOVER final override + struct A { + virtual void foo() {} + }; + struct B : A { + void fo^o() FINALOVER {} + };)cpp", + "fail: define outline: Can't move out of line as function has a " + "macro `override` specifier.\ndefine outline: Can't move out of line " + "as function has a macro `final` specifier."}, + }; + for (const auto &Case : Cases) { + EXPECT_EQ(apply(Case.first), Case.second); + } +} } // namespace } // namespace clangd } // namespace clang