diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp @@ -59,6 +59,26 @@ namespace clangd { namespace { +// Lexes from end of \p FD until it finds a semicolon. +llvm::Optional getSemiColonForDecl(const FunctionDecl *FD) { + const SourceManager &SM = FD->getASTContext().getSourceManager(); + const LangOptions &LangOpts = FD->getASTContext().getLangOpts(); + + SourceLocation SemiColon = + Lexer::getLocForEndOfToken(FD->getEndLoc(), 0, SM, LangOpts); + llvm::StringRef BufData = SM.getBufferData(SM.getFileID(SemiColon)); + Lexer RawLexer(SM.getLocForStartOfFile(SM.getFileID(SemiColon)), LangOpts, + BufData.begin(), SM.getCharacterData(SemiColon), + BufData.end()); + Token CurToken; + while (!CurToken.is(tok::semi)) { + if (RawLexer.LexFromRawLexer(CurToken)) + return llvm::None; + } + SemiColon = CurToken.getLocation(); + return SemiColon; +} + // Deduces the FunctionDecl from a selection. Requires either the function body // or the function decl to be selected. Returns null if none of the above // criteria is met. @@ -74,6 +94,251 @@ return nullptr; } +// There are three types of spellings that needs to be qualified in a function +// body: +// - Types: Foo -> ns::Foo +// - DeclRefExpr: ns2::foo() -> ns1::ns2::foo(); +// - UsingDecls: +// using ns2::foo -> using ns1::ns2::foo +// using namespace ns2 -> using namespace ns1::ns2 +// using ns3 = ns2 -> using ns3 = ns1::ns2 +// +// Goes over all DeclRefExprs, TypeLocs, and Using{,Directive}Decls inside a +// function body to generate replacements that will fully qualify those. So that +// body can be moved into an arbitrary file. +// We perform the qualification by qualyfying the last type/decl in a +// (un)qualified name. e.g: +// namespace a { namespace b { class Bar{}; void foo(); } } +// b::Bar x; -> a::b::Bar x; +// foo(); -> a::b::foo(); +// Currently there is no way to know whether a given TypeLoc is the last one or +// not, therefore we generate replacements for all the TypeLocs we see in a +// given name and pick only the longest one. +// FIXME: Instead of fully qualyfying we should try deducing visible scopes at +// target location and generate minimal edits. +class QualifyingVisitor : public RecursiveASTVisitor { +public: + QualifyingVisitor(const FunctionDecl *FD) + : LangOpts(FD->getASTContext().getLangOpts()), + SM(FD->getASTContext().getSourceManager()), + BodyBegin(SM.getFileOffset(FD->getBody()->getBeginLoc())) { + TraverseStmt(FD->getBody()); + } + + // We override traversals for DeclRefExprs and TypeLocs to generate an edit + // only for the last Type/Decl in a written name. Also it enables us to catch + // current range of the name as written, since the last Type/Decl doesn't + // contain that information. + bool TraverseDeclRefExpr(DeclRefExpr *DRE) { + maybeUpdateCurrentRange(DRE->getSourceRange()); + return RecursiveASTVisitor::TraverseDeclRefExpr(DRE); + } + + bool TraverseTypeLoc(TypeLoc TL) { + maybeUpdateCurrentRange(TL.getSourceRange()); + return RecursiveASTVisitor::TraverseTypeLoc(TL); + } + + // Generates a replacement that will qualify templated name and arguments. + bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TSTL) { + std::string Qualified; + llvm::raw_string_ostream OS(Qualified); + + QualType Ty = TSTL.getType(); + if (Ty->isDependentType()) { + // We don't have a decl if type is dependent, use the TemplateDecl + // instead. + const TemplateDecl *TD = + TSTL.getTypePtr()->getTemplateName().getAsTemplateDecl(); + + TD->printQualifiedName(OS); + // FIXME: For some reason this prints types as written in source code, + // instead of fully qualified version, + // i.e: a::Bar> instead of a::Bar> + printTemplateArgumentList(OS, TSTL.getTypePtr()->template_arguments(), + TD->getASTContext().getPrintingPolicy()); + } else if (const auto *CTSD = + llvm::dyn_cast( + TSTL.getType()->getAsTagDecl())) { + + CTSD->printQualifiedName(OS); + printTemplateArgumentList(OS, CTSD->getTemplateArgs().asArray(), + CTSD->getASTContext().getPrintingPolicy()); + } + OS.flush(); + + maybeUpdateReplacement(Qualified); + return true; + } + + // Qualifies TagTypes, we are not interested in other TypeLocs since they + // can't have nested name specifiers. + bool VisitTagTypeLoc(TagTypeLoc TTL) { + const TagDecl *TD = TTL.getDecl(); + if (index::isFunctionLocalSymbol(TD)) + return true; + + // FIXME: Instead of fully qualifying, try to drop visible scopes. + maybeUpdateReplacement(TD->getQualifiedNameAsString()); + return true; + } + + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + NamedDecl *ND = DRE->getFoundDecl(); + // UsingShadowDecls are considered local, but we might need to qualify them + // if the UsingDecl is not inside function body. + if (auto USD = llvm::dyn_cast(ND)) + ND = USD->getTargetDecl(); + // No need to re-write local symbols. + if (index::isFunctionLocalSymbol(ND)) + return true; + + std::string Qualified; + // For templates, in addition to decl name, also print the argument list. + if (auto *VTSD = llvm::dyn_cast(ND)) { + llvm::raw_string_ostream OS(Qualified); + VTSD->printQualifiedName(OS); + printTemplateArgumentList(OS, VTSD->getTemplateArgs().asArray(), + ND->getASTContext().getPrintingPolicy()); + } else if (auto *FTD = llvm::dyn_cast(ND)) { + llvm::raw_string_ostream OS(Qualified); + FTD->printQualifiedName(OS); + printTemplateArgumentList(OS, + llvm::dyn_cast(DRE->getDecl()) + ->getTemplateSpecializationArgs() + ->asArray(), + ND->getASTContext().getPrintingPolicy()); + } else { + Qualified = ND->getQualifiedNameAsString(); + } + maybeUpdateReplacement(Qualified); + return true; + } + + // For using decls, we chose the first shadow decls. This should not make + // any difference, since all of the shadow decls should have the same + // fully-qualified name. + bool VisitUsingDecl(UsingDecl *UD) { + flushCurrentReplacement(); + maybeUpdateCurrentRange( + SourceRange(UD->getQualifierLoc().getBeginLoc(), UD->getEndLoc())); + maybeUpdateReplacement( + UD->shadow_begin()->getTargetDecl()->getQualifiedNameAsString()); + return true; + } + + // Using-directives are "special" NamedDecls, they span the whole + // declaration and their names are not directly useful, we need to peek + // into underlying NamespaceDecl for qualified name and start location. + bool VisitUsingDirectiveDecl(UsingDirectiveDecl *UDD) { + flushCurrentReplacement(); + + SourceRange RepRange; + if (auto NNSL = UDD->getQualifierLoc()) + RepRange.setBegin(NNSL.getBeginLoc()); + else + RepRange.setBegin(UDD->getIdentLocation()); + RepRange.setEnd(UDD->getEndLoc()); + + maybeUpdateCurrentRange(RepRange); + maybeUpdateReplacement( + UDD->getNominatedNamespaceAsWritten()->getQualifiedNameAsString()); + return true; + } + + // Qualifies namespace alias decls of the form: + // using newns = ns2 -> using newns = ns1::ns2 + bool VisitNamespaceAliasDecl(NamespaceAliasDecl *NAD) { + flushCurrentReplacement(); + + SourceRange RepRange; + if (auto NNSL = NAD->getQualifierLoc()) + RepRange.setBegin(NNSL.getBeginLoc()); + else + RepRange.setBegin(NAD->getTargetNameLoc()); + RepRange.setEnd(NAD->getEndLoc()); + + maybeUpdateCurrentRange(RepRange); + maybeUpdateReplacement( + NAD->getAliasedNamespace()->getQualifiedNameAsString()); + return true; + } + + tooling::Replacements getReplacements() { + // flush the last replacement. + flushCurrentReplacement(); + return Reps; + } + +private: + // Updates the CurrentRange to NewRange if it is not overlapping with + // CurrentRange. Also flushes the replacement for CurrentRange before updating + // it. + void maybeUpdateCurrentRange(SourceRange NewRange) { + if (NewRange.isInvalid()) + return; + + if (CurrentChange && + SM.isBeforeInSLocAddrSpace( + NewRange.getBegin(), + // We use EndLoc+1 since CurrentChange also owns the token + // starting at EndLoc. + CurrentChange->Range.getEnd().getLocWithOffset(1))) { + return; + } + + flushCurrentReplacement(); + CurrentChange.emplace(); + CurrentChange->Range = NewRange; + } + + // Changes CurReplacement to contain RepText if it is longer. + void maybeUpdateReplacement(llvm::StringRef RepText) { + assert(CurrentChange && "Updating non-existing replacement"); + if (CurrentChange->Text.size() > RepText.size()) + return; + CurrentChange->Text = RepText; + } + + // Adds current replacement, the longest one for the current range, to the + // Reps. + void flushCurrentReplacement() { + if (!CurrentChange || CurrentChange->Text.empty()) + return; + SourceLocation Begin = CurrentChange->Range.getBegin(); + SourceLocation End = Lexer::getLocForEndOfToken( + CurrentChange->Range.getEnd(), 0, SM, LangOpts); + + unsigned int BeginOff = SM.getFileOffset(Begin) - BodyBegin; + unsigned int EndOff = SM.getFileOffset(End) - BodyBegin; + const tooling::Replacement Replacement("", BeginOff, EndOff - BeginOff, + CurrentChange->Text); + CurrentChange.reset(); + + if (auto Err = Reps.add(Replacement)) { + handleAllErrors(std::move(Err), [&](const llvm::ErrorInfoBase &EI) { + std::string ErrMsg; + llvm::raw_string_ostream OS(ErrMsg); + EI.log(OS); + elog("Failed to add replacement:{0}", ErrMsg); + }); + } + } + + const LangOptions LangOpts; + const SourceManager &SM; + // Used as reference points for replacements. + const unsigned int BodyBegin; + tooling::Replacements Reps; + + struct Change { + SourceRange Range; // to be replaced in the original source. + std::string Text; // to replace the range. + }; + // Change to apply for ast node currently being visited. + llvm::Optional CurrentChange; +}; + // Runs clang indexing api to get a list of declarations referenced inside // function decl. Skips local symbols. llvm::DenseSet getNonLocalDeclRefs(const FunctionDecl *FD, @@ -156,6 +421,20 @@ return true; } +// Rewrites body of FD to fully-qualify all of the decls inside. +llvm::Expected qualifyAllDecls(const FunctionDecl *FD) { + SourceRange OrigFuncRange = FD->getBody()->getSourceRange(); + // toSourceCode expects an halfOpenRange, but FuncBody is closed. + OrigFuncRange.setEnd(OrigFuncRange.getEnd().getLocWithOffset(1)); + // applyAllReplacements expects a null terminated string, therefore we make a + // copy here. + std::string OrigFuncBody = + toSourceCode(FD->getASTContext().getSourceManager(), OrigFuncRange); + + return tooling::applyAllReplacements(OrigFuncBody, + QualifyingVisitor(FD).getReplacements()); +} + // Returns the canonical declaration for the given FunctionDecl. This will // usually be the first declaration in current translation unit with the // exception of template specialization. For those we return the previous @@ -170,6 +449,15 @@ return FD->getCanonicalDecl(); } +// Returns the begining location for a FunctionDecl. Returns location of +// template keyword for templated functions. +const SourceLocation getBeginLoc(const FunctionDecl *FD) { + // Include template parameter list. + if (auto *FTD = FD->getDescribedFunctionTemplate()) + return FTD->getBeginLoc(); + return FD->getBeginLoc(); +} + /// Moves definition of a function to its declaration location. /// Before: /// a.h: @@ -223,8 +511,47 @@ } Expected apply(const Selection &Sel) override { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Not implemented yet"); + const ASTContext &AST = Sel.AST.getASTContext(); + const SourceManager &SM = AST.getSourceManager(); + + auto QualifiedBody = qualifyAllDecls(Source); + if (!QualifiedBody) + return QualifiedBody.takeError(); + + auto SemiColon = getSemiColonForDecl(Target); + if (!SemiColon) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Couldn't find semicolon for target declaration"); + } + + tooling::Replacements Replacements; + auto Err = Replacements.add( + tooling::Replacement(SM, *SemiColon, 1, *QualifiedBody)); + if (Err) + return std::move(Err); + Effect E; + E.ApplyEdits.emplace(); + + // We need to generate two edits if the Source and Target are in different + // files. + if (!SM.isWrittenInSameFile(Source->getBeginLoc(), Target->getBeginLoc())) { + E.addEdit(SM.getFilename(*SemiColon), + Edit::generateEdit(SM.getBufferData(SM.getFileID(*SemiColon)), + std::move(Replacements))); + Replacements.clear(); + } + + SourceLocation BeginLoc = getBeginLoc(Source); + unsigned int SourceLen = + SM.getFileOffset(Source->getEndLoc()) - SM.getFileOffset(BeginLoc) + 1; + Err = Replacements.add(tooling::Replacement(SM, BeginLoc, SourceLen, "")); + if (Err) + return std::move(Err); + + E.addEdit(SM.getFilename(Sel.Cursor), + Edit::generateEdit(Sel.Code, std::move(Replacements))); + return E; } private: 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 @@ -15,6 +15,7 @@ #include "clang/AST/Expr.h" #include "clang/Basic/LLVM.h" #include "clang/Rewrite/Core/Rewriter.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -75,10 +76,12 @@ auto T = prepareTweak(ID, Sel); if (Available) EXPECT_THAT_EXPECTED(T, Succeeded()) - << "code is " << markRange(Code.code(), Selection); + << "code is " << markRange(Code.code(), Selection) + << Sel.ASTSelection; else EXPECT_THAT_EXPECTED(T, Failed()) - << "code is " << markRange(Code.code(), Selection); + << "code is " << markRange(Code.code(), Selection) + << Sel.ASTSelection; }; for (auto P : Code.points()) CheckOver(Range{P, P}); @@ -101,7 +104,9 @@ std::move(AdditionalFiles)); } -llvm::Expected apply(StringRef ID, llvm::StringRef Input) { +llvm::Expected +apply(StringRef ID, llvm::StringRef Input, + const llvm::StringMap &AdditionalFiles = {}) { Annotations Code(Input); Range SelectionRng; if (Code.points().size() != 0) { @@ -114,6 +119,9 @@ TestTU TU; TU.Filename = "foo.cpp"; TU.Code = Code.code(); + for (auto &InpFile : AdditionalFiles) { + TU.AdditionalFiles[InpFile.first()] = InpFile.second.InitialText; + } ParsedAST AST = TU.build(); unsigned Begin = cantFail(positionToOffset(Code.code(), SelectionRng.start)); @@ -126,26 +134,44 @@ return (*T)->apply(S); } -llvm::Expected applyEdit(StringRef ID, llvm::StringRef Input) { - auto Effect = apply(ID, Input); +llvm::Expected> +applyEdit(StringRef ID, llvm::StringRef Input, + const llvm::StringMap &AdditionalFiles = {}) { + auto Effect = apply(ID, Input, AdditionalFiles); if (!Effect) return Effect.takeError(); if (!Effect->ApplyEdits) return llvm::createStringError(llvm::inconvertibleErrorCode(), "No replacements"); - auto Edits = *Effect->ApplyEdits; - if (Edits.size() > 1) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Multi-file edits"); - Annotations Code(Input); - return applyAllReplacements(Code.code(), Edits.begin()->second.Reps); + llvm::StringMap Output; + for (const auto &It : *Effect->ApplyEdits) { + llvm::StringRef FilePath = It.first(); + FilePath.consume_front(testRoot()); + FilePath.consume_front("/"); + auto InpIt = AdditionalFiles.find(FilePath); + if (InpIt == AdditionalFiles.end()) + FilePath = ""; + Annotations Code(InpIt == AdditionalFiles.end() + ? Input + : llvm::StringRef(InpIt->second.InitialText)); + auto Replaced = applyAllReplacements(Code.code(), It.second.Reps); + if (!Replaced) + return Replaced.takeError(); + Output[FilePath] = *Replaced; + } + return Output; } void checkTransform(llvm::StringRef ID, llvm::StringRef Input, - std::string Output) { - auto Result = applyEdit(ID, Input); + std::string Output, + llvm::StringMap AdditionalFiles = {}) { + auto Result = applyEdit(ID, Input, AdditionalFiles); ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input; - EXPECT_EQ(Output, std::string(*Result)) << Input; + EXPECT_EQ(Output, Result->lookup("")) << Input; + for (const auto &File : AdditionalFiles) { + llvm::errs() << "Looking for: " << File.first() << '\n'; + EXPECT_EQ(File.second.ModifiedText, Result->lookup(File.first())) << Input; + } } TWEAK_TEST(SwapIfBranches); @@ -154,7 +180,8 @@ EXPECT_EQ(apply("^if (true) {return 100;} else {continue;}"), "if (true) {continue;} else {return 100;}"); EXPECT_EQ(apply("^if () {return 100;} else {continue;}"), - "if () {continue;} else {return 100;}") << "broken condition"; + "if () {continue;} else {return 100;}") + << "broken condition"; EXPECT_AVAILABLE("^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; }"); EXPECT_UNAVAILABLE("if (true) {^return ^100;^ } else { ^continue^;^ }"); // Available in subexpressions of the condition; @@ -185,7 +212,7 @@ EXPECT_UNAVAILABLE(R"cpp(R"(multi )" ^"token " u8"str\ning")cpp"); // nonascii EXPECT_UNAVAILABLE(R"cpp(^R^"^(^multi )" "token " "str\ning")cpp"); // raw EXPECT_UNAVAILABLE(R"cpp(^"token\n" __FILE__)cpp"); // chunk is macro - EXPECT_UNAVAILABLE(R"cpp(^"a\r\n";)cpp"); // forbidden escape char + EXPECT_UNAVAILABLE(R"cpp(^"a\r\n";)cpp"); // forbidden escape char const char *Input = R"cpp(R"(multi token)" "\nst^ring\n" "literal")cpp"; @@ -366,11 +393,11 @@ void f(int a) { int y = PLUS([[1+a]]); })cpp", - /*FIXME: It should be extracted like this. - R"cpp(#define PLUS(x) x++ - void f(int a) { - auto dummy = 1+a; int y = PLUS(dummy); - })cpp"},*/ + /*FIXME: It should be extracted like this. + R"cpp(#define PLUS(x) x++ + void f(int a) { + auto dummy = 1+a; int y = PLUS(dummy); + })cpp"},*/ R"cpp(#define PLUS(x) x++ void f(int a) { auto dummy = PLUS(1+a); int y = dummy; @@ -381,13 +408,13 @@ if(1) LOOP(5 + [[3]]) })cpp", - /*FIXME: It should be extracted like this. SelectionTree needs to be - * fixed for macros. - R"cpp(#define LOOP(x) while (1) {a = x;} - void f(int a) { - auto dummy = 3; if(1) - LOOP(5 + dummy) - })cpp"},*/ + /*FIXME: It should be extracted like this. SelectionTree needs to be + * fixed for macros. + R"cpp(#define LOOP(x) while (1) {a = x;} + void f(int a) { + auto dummy = 3; if(1) + LOOP(5 + dummy) + })cpp"},*/ R"cpp(#define LOOP(x) while (1) {a = x;} void f(int a) { auto dummy = LOOP(5 + 3); if(1) @@ -483,8 +510,8 @@ void f() { auto dummy = S(2) + S(3) + S(4); S x = S(1) + dummy + S(5); })cpp"}, - // Don't try to analyze across macro boundaries - // FIXME: it'd be nice to do this someday (in a safe way) + // Don't try to analyze across macro boundaries + // FIXME: it'd be nice to do this someday (in a safe way) {R"cpp(#define ECHO(X) X void f() { int x = 1 + [[ECHO(2 + 3) + 4]] + 5; @@ -512,26 +539,27 @@ checkAvailable(ID, "^vo^id^ ^f(^) {^}^"); // available everywhere. checkAvailable(ID, "[[int a; int b;]]"); const char *Input = "void ^f() {}"; - const char *Output = "/* storage.type.primitive.cpp */void /* entity.name.function.cpp */f() {}"; + const char *Output = "/* storage.type.primitive.cpp */void /* " + "entity.name.function.cpp */f() {}"; checkTransform(ID, Input, Output); checkTransform(ID, - R"cpp( + R"cpp( [[void f1(); void f2();]] )cpp", - R"cpp( + R"cpp( /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f1(); /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f2(); )cpp"); - checkTransform(ID, - R"cpp( + checkTransform(ID, + R"cpp( void f1(); void f2() {^}; )cpp", - R"cpp( + R"cpp( void f1(); /* storage.type.primitive.cpp */void /* entity.name.function.cpp */f2() {}; )cpp"); @@ -611,7 +639,7 @@ StartsWith("fail: Could not expand type of lambda expression")); // inline namespaces EXPECT_EQ(apply("au^to x = inl_ns::Visible();"), - "Visible x = inl_ns::Visible();"); + "Visible x = inl_ns::Visible();"); // local class EXPECT_EQ(apply("namespace x { void y() { struct S{}; ^auto z = S(); } }"), "namespace x { void y() { struct S{}; S z = S(); } }"); @@ -725,6 +753,469 @@ {{"b.h", "void foo(int test);"}, {"a.h", "void bar();"}}); } +TEST(DefineInline, TransformUsings) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + namespace a { + void bar(); + namespace b { + void baz(); + namespace c { + void aux(); + } + } + } + + void foo(); + void f^oo() { + using namespace a; + + using namespace b; + using namespace a::b; + + using namespace c; + using namespace b::c; + using namespace a::b::c; + + using a::bar; + + using b::baz; + using a::b::baz; + + using c::aux; + using b::c::aux; + using a::b::c::aux; + + namespace d = c; + namespace d = b::c; + } + )cpp", + R"cpp( + namespace a { + void bar(); + namespace b { + void baz(); + namespace c { + void aux(); + } + } + } + + void foo(){ + using namespace a; + + using namespace a::b; + using namespace a::b; + + using namespace a::b::c; + using namespace a::b::c; + using namespace a::b::c; + + using a::bar; + + using a::b::baz; + using a::b::baz; + + using a::b::c::aux; + using a::b::c::aux; + using a::b::c::aux; + + namespace d = a::b::c; + namespace d = a::b::c; + } + + )cpp"); +} + +TEST(DefineInline, TransformDecls) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + void foo() /*Comment -_-*/ ; + + void f^oo() { + class Foo { + public: + void foo(); + int x; + static int y; + }; + Foo::y = 0; + + enum En { Zero, One }; + En x = Zero; + + enum class EnClass { Zero, One }; + EnClass y = EnClass::Zero; + + template class Bar {}; + Bar z; + } + )cpp", + R"cpp( + void foo() /*Comment -_-*/ { + class Foo { + public: + void foo(); + int x; + static int y; + }; + Foo::y = 0; + + enum En { Zero, One }; + En x = Zero; + + enum class EnClass { Zero, One }; + EnClass y = EnClass::Zero; + + template class Bar {}; + Bar z; + } + + + )cpp"); +} + +TEST(DefineInline, TransformTemplDecls) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + namespace a { + template class Bar { + public: + void bar(); + }; + template T bar; + template void aux() {} + } + + void foo() /*Comment -_-*/ ; + + void f^oo() { + using namespace a; + bar>.bar(); + aux>(); + } + )cpp", + R"cpp( + namespace a { + template class Bar { + public: + void bar(); + }; + template T bar; + template void aux() {} + } + + void foo() /*Comment -_-*/ { + using namespace a; + a::bar >.bar(); + a::aux >(); + } + + + )cpp"); +} + +TEST(DefineInline, TransformMembers) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + class Foo { + void foo() /*Comment -_-*/ ; + }; + + void Foo::f^oo() { + return; + } + )cpp", + R"cpp( + class Foo { + void foo() /*Comment -_-*/ { + return; + } + }; + + + )cpp"); + + checkTransform(ID, R"cpp( + #include "a.h" + void Foo::f^oo() { + return; + } + )cpp", + R"cpp( + #include "a.h" + + )cpp", + {{"a.h", + {R"cpp( + class Foo { + void foo() /*Comment -_-*/ ; + }; + + + )cpp", + R"cpp( + class Foo { + void foo() /*Comment -_-*/ { + return; + } + }; + + + )cpp"}}}); +} + +TEST(DefineInline, TransformDependentTypes) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + namespace a { + template class Bar {}; + } + using namespace a; + + template + void foo() /*Comment -_-*/ ; + + template + void f^oo() { + Bar B; + // FIXME: This should be a::Bar > + Bar> q; + } + )cpp", + R"cpp( + namespace a { + template class Bar {}; + } + using namespace a; + + template + void foo() /*Comment -_-*/ { + a::Bar B; + // FIXME: This should be a::Bar > + a::Bar > q; + } + + + )cpp"); +} + +TEST(DefineInline, TransformFunctionTempls) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + template + void foo(T p); + + template <> + void foo(int p); + + template <> + void foo(char p); + + template <> + void fo^o(int p) { + return; + } + )cpp", + R"cpp( + template + void foo(T p); + + template <> + void foo(int p){ + return; + } + + template <> + void foo(char p); + + + )cpp"); + + checkTransform(ID, R"cpp( + template + void foo(T p); + + template <> + void foo(int p); + + template <> + void foo(char p); + + template <> + void fo^o(char p) { + return; + } + )cpp", + R"cpp( + template + void foo(T p); + + template <> + void foo(int p); + + template <> + void foo(char p){ + return; + } + + + )cpp"); + + checkTransform(ID, R"cpp( + template + void foo(T p); + + template <> + void foo(int p); + + template + void fo^o(T p) { + return; + } + )cpp", + R"cpp( + template + void foo(T p){ + return; + } + + template <> + void foo(int p); + + + )cpp"); +} + +TEST(DefineInline, TransformTypeLocs) { + llvm::StringLiteral ID = "DefineInline"; + + checkTransform(ID, R"cpp( + namespace a { + template class Bar { + public: + template class Baz {}; + }; + namespace b{ + class Foo{}; + }; + } + using namespace a; + using namespace b; + using namespace c; + + void foo() /*Comment -_-*/ ; + + void f^oo() { + Bar B; + b::Foo foo; + a::Bar>::Baz> q; + } + )cpp", + R"cpp( + namespace a { + template class Bar { + public: + template class Baz {}; + }; + namespace b{ + class Foo{}; + }; + } + using namespace a; + using namespace b; + using namespace c; + + void foo() /*Comment -_-*/ { + a::Bar B; + a::b::Foo foo; + a::Bar >::Baz > q; + } + + + )cpp"); +} + +TEST(DefineInline, TransformDeclRefs) { + llvm::StringLiteral ID = "DefineInline"; + checkTransform(ID, R"cpp( + namespace a { + template class Bar { + public: + void foo(); + static void bar(); + int x; + static int y; + }; + void bar(); + namespace b { + class Foo{}; + namespace c { + void test(); + } + } + } + using namespace a; + using namespace b; + using namespace c; + + void foo() /*Comment -_-*/ ; + + void f^oo() { + a::Bar B; + B.foo(); + a::bar(); + Bar>::bar(); + a::Bar::bar(); + B.x = Bar::y; + Bar::y = 3; + bar(); + c::test(); + } + )cpp", + R"cpp( + namespace a { + template class Bar { + public: + void foo(); + static void bar(); + int x; + static int y; + }; + void bar(); + namespace b { + class Foo{}; + namespace c { + void test(); + } + } + } + using namespace a; + using namespace b; + using namespace c; + + void foo() /*Comment -_-*/ { + a::Bar B; + B.foo(); + a::bar(); + a::Bar >::bar(); + a::Bar::bar(); + B.x = a::Bar::y; + a::Bar::y = 3; + a::bar(); + a::b::c::test(); + } + + + )cpp"); +} + } // namespace } // namespace clangd } // namespace clang