//===-- DefineInlineTests.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 "TestTU.h" #include "TweakTesting.h" #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::ElementsAre; namespace clang { namespace clangd { namespace { TWEAK_TEST(DefineInline); TEST_F(DefineInlineTest, TriggersOnFunctionDecl) { // Basic check for function body and signature. EXPECT_AVAILABLE(R"cpp( class Bar { void baz(); }; [[void [[Bar::[[b^a^z]]]]() [[{ return; }]]]] void foo(); [[void [[f^o^o]]() [[{ return; }]]]] )cpp"); EXPECT_UNAVAILABLE(R"cpp( // Not a definition vo^i[[d^ ^f]]^oo(); [[vo^id ]]foo[[()]] {[[ [[(void)(5+3); return;]] }]] // Definition with no body. class Bar { Bar() = def^ault; }; )cpp"); } TEST_F(DefineInlineTest, NoForwardDecl) { Header = "void bar();"; EXPECT_UNAVAILABLE(R"cpp( void bar() { return; } // FIXME: Generate a decl in the header. void fo^o() { return; })cpp"); } TEST_F(DefineInlineTest, ReferencedDecls) { EXPECT_AVAILABLE(R"cpp( void bar(); void foo(int test); void fo^o(int baz) { int x = 10; bar(); })cpp"); // Internal symbol usage. Header = "void foo(int test);"; EXPECT_UNAVAILABLE(R"cpp( void bar(); void fo^o(int baz) { int x = 10; bar(); })cpp"); // Becomes available after making symbol visible. Header = "void bar();" + Header; EXPECT_AVAILABLE(R"cpp( void fo^o(int baz) { int x = 10; bar(); })cpp"); // FIXME: Move declaration below bar to make it visible. Header.clear(); EXPECT_UNAVAILABLE(R"cpp( void foo(); void bar(); void fo^o() { bar(); })cpp"); // Order doesn't matter within a class. EXPECT_AVAILABLE(R"cpp( class Bar { void foo(); void bar(); }; void Bar::fo^o() { bar(); })cpp"); // FIXME: Perform include insertion to make symbol visible. ExtraFiles["a.h"] = "void bar();"; Header = "void foo(int test);"; EXPECT_UNAVAILABLE(R"cpp( #include "a.h" void fo^o(int baz) { int x = 10; bar(); })cpp"); } TEST_F(DefineInlineTest, TemplateSpec) { EXPECT_UNAVAILABLE(R"cpp( template void foo(); template<> void foo(); template<> void f^oo() { })cpp"); EXPECT_UNAVAILABLE(R"cpp( template void foo(); template<> void f^oo() { })cpp"); EXPECT_UNAVAILABLE(R"cpp( template struct Foo { void foo(); }; template void Foo::f^oo() { })cpp"); EXPECT_AVAILABLE(R"cpp( template void foo(); void bar(); template <> void foo(); template<> void f^oo() { bar(); })cpp"); EXPECT_UNAVAILABLE(R"cpp( namespace bar { template void f^oo() {} template void foo(); })cpp"); } TEST_F(DefineInlineTest, CheckForCanonDecl) { EXPECT_UNAVAILABLE(R"cpp( void foo(); void bar() {} void f^oo() { // This bar normally refers to the definition just above, but it is not // visible from the forward declaration of foo. bar(); })cpp"); // Make it available with a forward decl. EXPECT_AVAILABLE(R"cpp( void bar(); void foo(); void bar() {} void f^oo() { bar(); })cpp"); } TEST_F(DefineInlineTest, UsingShadowDecls) { // Template body is not parsed until instantiation time on windows, which // results in arbitrary failures as function body becomes NULL. ExtraArgs.push_back("-fno-delayed-template-parsing"); EXPECT_UNAVAILABLE(R"cpp( namespace ns1 { void foo(int); } namespace ns2 { void foo(int*); } template void bar(); using ns1::foo; using ns2::foo; template void b^ar() { foo(T()); })cpp"); } TEST_F(DefineInlineTest, TransformNestedNamespaces) { auto Test = R"cpp( namespace a { void bar(); namespace b { void baz(); namespace c { void aux(); } } } void foo(); using namespace a; using namespace b; using namespace c; void f^oo() { bar(); a::bar(); baz(); b::baz(); a::b::baz(); aux(); c::aux(); b::c::aux(); a::b::c::aux(); })cpp"; auto Expected = R"cpp( namespace a { void bar(); namespace b { void baz(); namespace c { void aux(); } } } void foo(){ a::bar(); a::bar(); a::b::baz(); a::b::baz(); a::b::baz(); a::b::c::aux(); a::b::c::aux(); a::b::c::aux(); a::b::c::aux(); } using namespace a; using namespace b; using namespace c; )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformUsings) { auto Test = R"cpp( namespace a { namespace b { namespace c { void aux(); } } } void foo(); void f^oo() { using namespace a; using namespace b; using namespace c; using c::aux; namespace d = c; })cpp"; auto Expected = R"cpp( namespace a { namespace b { namespace c { void aux(); } } } void foo(){ using namespace a; using namespace a::b; using namespace a::b::c; using a::b::c::aux; namespace d = a::b::c; } )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformDecls) { auto Test = R"cpp( void foo(); void f^oo() { class Foo { public: void foo(); int x; }; enum En { Zero, One }; En x = Zero; enum class EnClass { Zero, One }; EnClass y = EnClass::Zero; })cpp"; auto Expected = R"cpp( void foo(){ class Foo { public: void foo(); int x; }; enum En { Zero, One }; En x = Zero; enum class EnClass { Zero, One }; EnClass y = EnClass::Zero; } )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformTemplDecls) { auto Test = R"cpp( namespace a { template class Bar { public: void bar(); }; template T bar; template void aux() {} } void foo(); using namespace a; void f^oo() { bar>.bar(); aux>(); })cpp"; auto Expected = R"cpp( namespace a { template class Bar { public: void bar(); }; template T bar; template void aux() {} } void foo(){ a::bar>.bar(); a::aux>(); } using namespace a; )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformMembers) { auto Test = R"cpp( class Foo { void foo(); }; void Foo::f^oo() { return; })cpp"; auto Expected = R"cpp( class Foo { void foo(){ return; } }; )cpp"; EXPECT_EQ(apply(Test), Expected); ExtraFiles["a.h"] = R"cpp( class Foo { void foo(); };)cpp"; llvm::StringMap EditedFiles; Test = R"cpp( #include "a.h" void Foo::f^oo() { return; })cpp"; Expected = R"cpp( #include "a.h" )cpp"; EXPECT_EQ(apply(Test, &EditedFiles), Expected); Expected = R"cpp( class Foo { void foo(){ return; } };)cpp"; EXPECT_THAT(EditedFiles, ElementsAre(FileWithContents(testPath("a.h"), Expected))); } TEST_F(DefineInlineTest, TransformDependentTypes) { auto Test = R"cpp( namespace a { template class Bar {}; } template void foo(); using namespace a; template void f^oo() { Bar B; Bar> q; })cpp"; auto Expected = R"cpp( namespace a { template class Bar {}; } template void foo(){ a::Bar B; a::Bar> q; } using namespace a; )cpp"; // Template body is not parsed until instantiation time on windows, which // results in arbitrary failures as function body becomes NULL. ExtraArgs.push_back("-fno-delayed-template-parsing"); EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformFunctionTempls) { // Check we select correct specialization decl. std::pair Cases[] = { {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"}, {// Make sure we are not selecting the first specialization all the time. 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"}, {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"}, }; // Template body is not parsed until instantiation time on windows, which // results in arbitrary failures as function body becomes NULL. ExtraArgs.push_back("-fno-delayed-template-parsing"); for (const auto &Case : Cases) EXPECT_EQ(apply(Case.first), Case.second) << Case.first; } TEST_F(DefineInlineTest, TransformTypeLocs) { auto Test = R"cpp( namespace a { template class Bar { public: template class Baz {}; }; class Foo{}; } void foo(); using namespace a; void f^oo() { Bar B; Foo foo; a::Bar>::Baz> q; })cpp"; auto Expected = R"cpp( namespace a { template class Bar { public: template class Baz {}; }; class Foo{}; } void foo(){ a::Bar B; a::Foo foo; a::Bar>::Baz> q; } using namespace a; )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformDeclRefs) { auto Test = R"cpp( namespace a { template class Bar { public: void foo(); static void bar(); int x; static int y; }; void bar(); void test(); } void foo(); using namespace a; void f^oo() { a::Bar B; B.foo(); a::bar(); Bar>::bar(); a::Bar::bar(); B.x = Bar::y; Bar::y = 3; bar(); a::test(); })cpp"; auto Expected = R"cpp( namespace a { template class Bar { public: void foo(); static void bar(); int x; static int y; }; void bar(); void test(); } void foo(){ 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::test(); } using namespace a; )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, StaticMembers) { auto Test = R"cpp( namespace ns { class X { static void foo(); void bar(); }; } void ns::X::b^ar() { foo(); })cpp"; auto Expected = R"cpp( namespace ns { class X { static void foo(); void bar(){ foo(); } }; } )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformParamNames) { std::pair Cases[] = { {R"cpp( void foo(int, bool b, int T\ est); void ^foo(int f, bool x, int z) {})cpp", R"cpp( void foo(int f, bool x, int z){} )cpp"}, {R"cpp( #define PARAM int Z void foo(PARAM); void ^foo(int X) {})cpp", "fail: Cant rename parameter inside macro body."}, {R"cpp( #define TYPE int #define PARAM TYPE Z #define BODY(x) 5 * (x) + 2 template void foo(PARAM, TYPE Q, TYPE, TYPE W = BODY(P)); template void ^foo(int Z, int b, int c, int d) {})cpp", R"cpp( #define TYPE int #define PARAM TYPE Z #define BODY(x) 5 * (x) + 2 template void foo(PARAM, TYPE b, TYPE c, TYPE d = BODY(x)){} )cpp"}, }; ExtraArgs.push_back("-fno-delayed-template-parsing"); for (const auto &Case : Cases) EXPECT_EQ(apply(Case.first), Case.second) << Case.first; } TEST_F(DefineInlineTest, TransformTemplParamNames) { auto Test = R"cpp( struct Foo { struct Bar { template class, template class Y, int, int Z> void foo(X, Y, int W = 5 * Z + 2); }; }; template class V, template class W, int X, int Y> void Foo::Bar::f^oo(U, W, int Q) {})cpp"; auto Expected = R"cpp( struct Foo { struct Bar { template class V, template class W, int X, int Y> void foo(U, W, int Q = 5 * Y + 2){} }; }; )cpp"; ExtraArgs.push_back("-fno-delayed-template-parsing"); EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TransformInlineNamespaces) { auto Test = R"cpp( namespace a { inline namespace b { namespace { struct Foo{}; } } } void foo(); using namespace a; void ^foo() {Foo foo;})cpp"; auto Expected = R"cpp( namespace a { inline namespace b { namespace { struct Foo{}; } } } void foo(){a::Foo foo;} using namespace a; )cpp"; EXPECT_EQ(apply(Test), Expected); } TEST_F(DefineInlineTest, TokensBeforeSemicolon) { std::pair Cases[] = { {R"cpp( void foo() /*Comment -_-*/ /*Com 2*/ ; void fo^o() { return ; })cpp", R"cpp( void foo() /*Comment -_-*/ /*Com 2*/ { return ; } )cpp"}, {R"cpp( void foo(); void fo^o() { return ; })cpp", R"cpp( void foo(){ return ; } )cpp"}, {R"cpp( #define SEMI ; void foo() SEMI void fo^o() { return ; })cpp", "fail: Couldn't find semicolon for target declaration."}, }; for (const auto &Case : Cases) EXPECT_EQ(apply(Case.first), Case.second) << Case.first; } TEST_F(DefineInlineTest, HandleMacros) { EXPECT_UNAVAILABLE(R"cpp( #define BODY { return; } void foo(); void f^oo()BODY)cpp"); EXPECT_UNAVAILABLE(R"cpp( #define BODY void foo(){ return; } void foo(); [[BODY]])cpp"); std::pair Cases[] = { // We don't qualify declarations coming from macros. {R"cpp( #define BODY Foo namespace a { class Foo{}; } void foo(); using namespace a; void f^oo(){BODY();})cpp", R"cpp( #define BODY Foo namespace a { class Foo{}; } void foo(){BODY();} using namespace a; )cpp"}, // Macro is not visible at declaration location, but we proceed. {R"cpp( void foo(); #define BODY return; void f^oo(){BODY})cpp", R"cpp( void foo(){BODY} #define BODY return; )cpp"}, {R"cpp( #define TARGET void foo() TARGET; void f^oo(){ return; })cpp", R"cpp( #define TARGET void foo() TARGET{ return; } )cpp"}, {R"cpp( #define TARGET foo void TARGET(); void f^oo(){ return; })cpp", R"cpp( #define TARGET foo void TARGET(){ return; } )cpp"}, }; for (const auto &Case : Cases) EXPECT_EQ(apply(Case.first), Case.second) << Case.first; } TEST_F(DefineInlineTest, DropCommonNameSpecifiers) { struct { llvm::StringRef Test; llvm::StringRef Expected; } Cases[] = { {R"cpp( namespace a { namespace b { void aux(); } } namespace ns1 { void foo(); namespace qq { void test(); } namespace ns2 { void bar(); namespace ns3 { void baz(); } } } using namespace a; using namespace a::b; using namespace ns1::qq; void ns1::ns2::ns3::b^az() { foo(); bar(); baz(); ns1::ns2::ns3::baz(); aux(); test(); })cpp", R"cpp( namespace a { namespace b { void aux(); } } namespace ns1 { void foo(); namespace qq { void test(); } namespace ns2 { void bar(); namespace ns3 { void baz(){ foo(); bar(); baz(); ns1::ns2::ns3::baz(); a::b::aux(); qq::test(); } } } } using namespace a; using namespace a::b; using namespace ns1::qq; )cpp"}, {R"cpp( namespace ns1 { namespace qq { struct Foo { struct Bar {}; }; using B = Foo::Bar; } namespace ns2 { void baz(); } } using namespace ns1::qq; void ns1::ns2::b^az() { Foo f; B b; })cpp", R"cpp( namespace ns1 { namespace qq { struct Foo { struct Bar {}; }; using B = Foo::Bar; } namespace ns2 { void baz(){ qq::Foo f; qq::B b; } } } using namespace ns1::qq; )cpp"}, {R"cpp( namespace ns1 { namespace qq { template struct Foo { template struct Bar {}; }; template using B = typename Foo::template Bar; } namespace ns2 { void baz(); } } using namespace ns1::qq; void ns1::ns2::b^az() { B b; })cpp", R"cpp( namespace ns1 { namespace qq { template struct Foo { template struct Bar {}; }; template using B = typename Foo::template Bar; } namespace ns2 { void baz(){ qq::B b; } } } using namespace ns1::qq; )cpp"}, }; for (const auto &Case : Cases) EXPECT_EQ(apply(Case.Test), Case.Expected) << Case.Test; } TEST_F(DefineInlineTest, QualifyWithUsingDirectives) { llvm::StringRef Test = R"cpp( namespace a { void bar(); namespace b { struct Foo{}; void aux(); } namespace c { void cux(); } } using namespace a; using X = b::Foo; void foo(); using namespace b; using namespace c; void ^foo() { cux(); bar(); X x; aux(); using namespace c; // FIXME: The last reference to cux() in body of foo should not be // qualified, since there is a using directive inside the function body. cux(); })cpp"; llvm::StringRef Expected = R"cpp( namespace a { void bar(); namespace b { struct Foo{}; void aux(); } namespace c { void cux(); } } using namespace a; using X = b::Foo; void foo(){ c::cux(); bar(); X x; b::aux(); using namespace c; // FIXME: The last reference to cux() in body of foo should not be // qualified, since there is a using directive inside the function body. c::cux(); } using namespace b; using namespace c; )cpp"; EXPECT_EQ(apply(Test), Expected) << Test; } TEST_F(DefineInlineTest, AddInline) { ExtraArgs.push_back("-fno-delayed-template-parsing"); llvm::StringMap EditedFiles; ExtraFiles["a.h"] = "void foo();"; apply(R"cpp(#include "a.h" void fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "inline void foo(){}"))); // Check we put inline before cv-qualifiers. ExtraFiles["a.h"] = "const int foo();"; apply(R"cpp(#include "a.h" const int fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "inline const int foo(){}"))); // No double inline. ExtraFiles["a.h"] = "inline void foo();"; apply(R"cpp(#include "a.h" inline void fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "inline void foo(){}"))); // Constexprs don't need "inline". ExtraFiles["a.h"] = "constexpr void foo();"; apply(R"cpp(#include "a.h" constexpr void fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "constexpr void foo(){}"))); // Class members don't need "inline". ExtraFiles["a.h"] = "struct Foo { void foo(); };"; apply(R"cpp(#include "a.h" void Foo::fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "struct Foo { void foo(){} };"))); // Function template doesn't need to be "inline"d. ExtraFiles["a.h"] = "template void foo();"; apply(R"cpp(#include "a.h" template void fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents( testPath("a.h"), "template void foo(){}"))); // Specializations needs to be marked "inline". ExtraFiles["a.h"] = R"cpp( template void foo(); template <> void foo();)cpp"; apply(R"cpp(#include "a.h" template <> void fo^o() {})cpp", &EditedFiles); EXPECT_THAT(EditedFiles, testing::ElementsAre(FileWithContents(testPath("a.h"), R"cpp( template void foo(); template <> inline void foo(){})cpp"))); } } // namespace } // namespace clangd } // namespace clang