diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -122,13 +122,33 @@ void add(const Decl *D) { if (!D || !isNew(D->getCanonicalDecl())) return; - // If we see a declaration in the mainfile, skip all non-definition decls. - // We only do this for classes because forward declarations are common and - // we don't want to include every header that forward-declares the symbol - // because they're often unrelated. + // Handle RecordDecls separately: classes and structs are often + // forward-declared and we don't want to include every header that + // forward-declares the symbol because they're often unrelated. Also, if + // definition is available, we can safely skip forward declarations because + // they have no effect. The only exception is symbols that require their + // original declaration because of the scope specifier, e.g. nested + // classes: + // + // Foo.h: + // class Foo { class Bar; } + // + // Foo.cpp + // class Foo::Bar {}; + // + // Here, Foo::Bar definition will require the original declaration inside + // Foo, but it will be available because we will still mark Foo definition + // as used. if (const auto *RD = llvm::dyn_cast(D)) { - if (SM.isInMainFile(RD->getMostRecentDecl()->getBeginLoc())) { - if (const auto *Definition = RD->getMostRecentDecl()->getDefinition()) + const auto *Definition = RD->getDefinition(); + if (Definition) { + Result.insert(Definition->getLocation()); + return; + } + // If we see a declaration in the mainfile, skip all non-definition + // decls. + if (SM.isInMainFile(RD->getMostRecentDecl()->getLocation())) { + if (Definition) Result.insert(Definition->getLocation()); return; } diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -39,10 +39,10 @@ "class ^X;", "X *y;", }, - // FIXME(kirillbobyrev): When definition is available, we don't need to - // mark forward declarations as used. + // When definition is available, we don't need to mark forward + // declarations as used. { - "class ^X {}; class ^X;", + "class ^X {}; class X;", "X *y;", }, // We already have forward declaration in the main file, the other @@ -51,13 +51,20 @@ "class ^X {}; class X;", "class X; X *y;", }, + // Nested class definition can occur outside of the parent class + // definition. Bar declaration should be visible to its definition but + // it will always be because we will mark Foo definition as used. + { + "class ^Foo { class Bar; };", + "class Foo::Bar {};", + }, // TypedefType and UsingDecls { "using ^Integer = int;", "Integer x;", }, { - "namespace ns { struct ^X; struct ^X {}; }", + "namespace ns { struct X; struct ^X {}; }", "using ns::X;", }, { @@ -88,8 +95,8 @@ }, // Redecls { - "class ^X; class ^X{}; class ^X;", - "X *y;", + "void ^foo(); void ^foo() {} void ^foo();", + "void bar() { foo(); }", }, // Constructor {