diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -193,6 +193,8 @@ llvm::DenseMap CacheFEToURI; llvm::StringMap CachePathToURI; llvm::DenseMap CacheFIDToInclude; + llvm::StringMap CachePathToFrameworkSpelling; + llvm::StringMap CacheFrameworkToUmbrellaInclude; public: HeaderFileURICache(Preprocessor *&PP, const SourceManager &SM, @@ -249,6 +251,86 @@ return R.first->second; } + struct FrameworkInfo { + // Path to the Headers dir e.g. /Frameworks/Foundation.framework/Headers/ + llvm::StringRef HeadersDir; + // Subpath relative to the Headers dir, e.g. NSObject.h + llvm::StringRef HeaderSubpath; + + bool valid() { + return !HeadersDir.empty() && !HeaderSubpath.empty(); + } + }; + + FrameworkInfo getHeaderFrameworkInfo(llvm::StringRef Path) { + using namespace llvm::sys; + path::reverse_iterator I = path::rbegin(Path); + path::reverse_iterator Prev = I; + path::reverse_iterator E = path::rend(Path); + while (I != E) { + if (*I == "Headers") { + auto PrefixLen = Prev - E; + return {Path.substr(0, PrefixLen), Path.substr(PrefixLen)}; + } + // We don't currently support handling the private headers dir, just + // return early. + if (*I == "PrivateHeaders") { + return {StringRef(), StringRef()}; + } + Prev = I; + ++I; + } + // Unexpected, must not be a framework header. + return {StringRef(), StringRef()}; + } + + llvm::StringRef getFrameworkIncludeSpelling(llvm::StringRef Path, + const FileEntry *FE, + llvm::StringRef Framework, + HeaderSearch &HS) { + auto SpellingR = CachePathToFrameworkSpelling.try_emplace(Path); + if (!SpellingR.second) + return SpellingR.first->second; + + auto R = CacheFrameworkToUmbrellaInclude.try_emplace(Framework); + if (!R.second && !R.first->second.empty()) { + // Framework has known umbrella spelling, cache it for this header as well. + SpellingR.first->second = R.first->second; + return R.first->second; + } + FrameworkInfo Info = getHeaderFrameworkInfo(Path); + if (!Info.valid()) { + SpellingR.first->second = ""; + R.first->second = ""; + return R.first->second; + } + bool IsSystem = isSystem(HS.getFileDirFlavor(FE)); + + SmallString<256> UmbrellaPath(Info.HeadersDir); + llvm::sys::path::append(UmbrellaPath, Framework + ".h"); + + llvm::vfs::Status Status; + auto StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); + if (StatErr) { + // Umbrella header does not exist, cache the failure. + R.first->second = ""; + if (IsSystem) { + SpellingR.first->second = llvm::formatv("<{0}/{1}>", Framework, Info.HeaderSubpath).str(); + } else { + SpellingR.first->second = llvm::formatv("\"{0}/{1}\"", Framework, Info.HeaderSubpath).str(); + } + return SpellingR.first->second; + } else { + if (IsSystem) { + R.first->second = llvm::formatv("<{0}/{0}.h>", Framework); + } else { + R.first->second = llvm::formatv("\"{0}/{0}.h\"", Framework); + } + SpellingR.first->second = R.first->second; + return R.first->second; + } + } + llvm::StringRef getIncludeHeaderUncached(FileID FID) { const FileEntry *FE = SM.getFileEntryForID(FID); if (!FE || FE->getName().empty()) @@ -265,6 +347,19 @@ return toURI(Canonical); } } + // Special case framework headers since they have special framework-style + // include spelling. We also check if an umbrella header with the same name + // exists, and if so, use that instead. + auto &HS = PP->getHeaderSearchInfo(); + if (const auto *HFI = HS.getExistingFileInfo(FE, /*WantExternal*/false)) { + auto Framework = HFI->Framework; + if (!Framework.empty()) { + auto Spelling = getFrameworkIncludeSpelling(Filename, FE, Framework, HS); + if (!Spelling.empty()) { + return Spelling; + } + } + } if (!isSelfContainedHeader(FE, FID, PP->getSourceManager(), PP->getHeaderSearchInfo())) { // A .inc or .def file is often included into a real header to define diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -663,6 +663,102 @@ QName("Cat::meow"), QName("Cat::pur"))); } +TEST_F(SymbolCollectorTest, ObjCFrameworkIncludeHeader) { + CollectorOpts.CollectIncludePath = true; + auto FrameworksPath = testPath("Frameworks/"); + auto FrameworkHeaderPath = testPath("Frameworks/Foundation.framework/Headers/NSObject.h"); + const std::string FrameworkHeader = R"( + __attribute((objc_root_class)) + @interface NSObject + @end + )"; + InMemoryFileSystem->addFile(FrameworkHeaderPath, 0, + llvm::MemoryBuffer::getMemBuffer(FrameworkHeader)); + + const std::string Header = R"( + #import + + @interface Container : NSObject + @end + )"; + const std::string Main = ""; + TestFileName = testPath("test.m"); + runSymbolCollector(Header, Main, {"-F", FrameworksPath, "-xobjective-c++"}); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(QName("NSObject"), IncludeHeader("\"Foundation/NSObject.h\"")), + AllOf(QName("Container")))); +} + +TEST_F(SymbolCollectorTest, ObjCUmbrellaFrameworkIncludeHeader) { + CollectorOpts.CollectIncludePath = true; + auto FrameworksPath = testPath("Frameworks/"); + auto UmbrellaHeaderPath = testPath("Frameworks/Foundation.framework/Headers/Foundation.h"); + const std::string UmbrellaHeader = R"( + #import + )"; + auto FrameworkHeaderPath = testPath("Frameworks/Foundation.framework/Headers/NSObject.h"); + const std::string FrameworkHeader = R"( + __attribute((objc_root_class)) + @interface NSObject + @end + )"; + InMemoryFileSystem->addFile(UmbrellaHeaderPath, 0, + llvm::MemoryBuffer::getMemBuffer(UmbrellaHeader)); + InMemoryFileSystem->addFile(FrameworkHeaderPath, 0, + llvm::MemoryBuffer::getMemBuffer(FrameworkHeader)); + + const std::string Header = R"( + #import + + @interface Container : NSObject + @end + )"; + const std::string Main = ""; + TestFileName = testPath("test.m"); + runSymbolCollector(Header, Main, {"-F", FrameworksPath, "-xobjective-c++"}); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(QName("NSObject"), IncludeHeader("\"Foundation/Foundation.h\"")), + AllOf(QName("Container")))); +} + +TEST_F(SymbolCollectorTest, ObjCSystemUmbrellaFrameworkIncludeHeader) { + CollectorOpts.CollectIncludePath = true; + auto FrameworksPath = testPath("Frameworks/"); + auto UmbrellaHeaderPath = testPath("Frameworks/Foundation.framework/Headers/Foundation.h"); + const std::string UmbrellaHeader = R"( + #import + )"; + auto FrameworkHeaderPath = testPath("Frameworks/Foundation.framework/Headers/NSObject.h"); + const std::string FrameworkHeader = R"( + __attribute((objc_root_class)) + @interface NSObject + @end + )"; + InMemoryFileSystem->addFile(UmbrellaHeaderPath, 0, + llvm::MemoryBuffer::getMemBuffer(UmbrellaHeader)); + InMemoryFileSystem->addFile(FrameworkHeaderPath, 0, + llvm::MemoryBuffer::getMemBuffer(FrameworkHeader)); + + const std::string Header = R"( + #import + + @interface Container : NSObject + @end + )"; + const std::string Main = ""; + TestFileName = testPath("test.m"); + runSymbolCollector(Header, Main, {"-iframeworkwithsysroot", FrameworksPath, "-xobjective-c++"}); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(QName("NSObject"), IncludeHeader("")), + AllOf(QName("Container")))); +} + TEST_F(SymbolCollectorTest, Locations) { Annotations Header(R"cpp( // Declared in header, defined in main. diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -1049,6 +1049,11 @@ getUniqueFrameworkName(StringRef(Filename.begin(), SlashPos)); if (CurDir->isIndexHeaderMap()) HFI.IndexHeaderMapHeader = 1; + } else if (CurDir->isFramework()) { + size_t SlashPos = Filename.find('/'); + if (SlashPos != StringRef::npos) + HFI.Framework = + getUniqueFrameworkName(StringRef(Filename.begin(), SlashPos)); } if (checkMSVCHeaderSearch(Diags, MSFE ? &MSFE->getFileEntry() : nullptr,