[clang-tools-extra] [llvm] [clangd] Normalize path identity across file-tracking boundaries (PR #221921)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 8 01:45:58 PDT 2026
https://github.com/Daedie-git created https://github.com/llvm/llvm-project/pull/221921
## Summary
Normalize file identity across clangd's document tracking, scheduling, caches, edits, and indexing. Absolute Windows drive-letter and separator aliases refer to the same file, while filename case is preserved so case-sensitive directories do not lose distinct files.
- Introduce explicit `Path`, `PathRef`, and `PathMap` boundaries while preserving original spellings.
- Provide path-aware dirty-header snapshots with stable file IDs and correct working-directory resolution.
- Keep custom URI keys opaque and avoid allocations for common index-coverage lookups.
- Add regression coverage and update CMake/GN integration and cache memory accounting.
## Validation
Windows Release build with assertions: `check-clangd` — **1,509 passed, 22 unsupported**.
Linux/macOS and GN builds have not been run locally.
>From 03958e19887df137b66574a2b8b068a0bf4e0dd9 Mon Sep 17 00:00:00 2001
From: Bjorn Schobben <bjorn.schobben at aimsport.com>
Date: Tue, 8 Sep 2026 10:43:16 +0200
Subject: [PATCH] [clangd] Normalize path identity across file-tracking
boundaries
Unify absolute Windows drive-letter and separator aliases while preserving filename case. Introduce explicit Path, PathRef, and PathMap types so document state, scheduling, caches, edits, and indexing use consistent keys without discarding original spellings.
Provide path-aware dirty-header snapshots with stable file IDs and native working-directory resolution. Keep custom URI keys opaque, use borrowed keys for index coverage queries, and normalize background task and shard identities conservatively.
Add regression coverage for aliases, case-distinct files, URI schemes, snapshot lifetimes, working-directory changes, and cache accounting. Update both CMake and GN integration.
Tested on Windows in Release with assertions: check-clangd reports 1509 passed and 22 unsupported.
---
clang-tools-extra/clangd/ASTSignals.cpp | 2 +-
clang-tools-extra/clangd/CMakeLists.txt | 1 +
clang-tools-extra/clangd/ClangdLSPServer.cpp | 25 +-
clang-tools-extra/clangd/ClangdLSPServer.h | 5 +-
clang-tools-extra/clangd/ClangdServer.cpp | 81 ++--
clang-tools-extra/clangd/ClangdServer.h | 2 +-
clang-tools-extra/clangd/CodeComplete.cpp | 34 +-
clang-tools-extra/clangd/ConfigCompile.cpp | 9 +-
clang-tools-extra/clangd/ConfigProvider.cpp | 9 +-
clang-tools-extra/clangd/DraftStore.cpp | 279 ++++++++++++--
clang-tools-extra/clangd/DraftStore.h | 6 +-
clang-tools-extra/clangd/FS.cpp | 34 +-
clang-tools-extra/clangd/FS.h | 11 +-
clang-tools-extra/clangd/FileDistance.cpp | 4 +
.../clangd/GlobalCompilationDatabase.cpp | 59 +--
.../clangd/GlobalCompilationDatabase.h | 2 +-
.../clangd/HeaderSourceSwitch.cpp | 24 +-
clang-tools-extra/clangd/Headers.cpp | 9 +-
clang-tools-extra/clangd/Hover.cpp | 2 +-
clang-tools-extra/clangd/IncludeCleaner.cpp | 16 +-
clang-tools-extra/clangd/ModulesBuilder.cpp | 59 ++-
clang-tools-extra/clangd/ParsedAST.cpp | 5 +-
clang-tools-extra/clangd/Preamble.cpp | 8 +-
clang-tools-extra/clangd/ProjectModules.cpp | 28 +-
clang-tools-extra/clangd/Protocol.cpp | 10 +-
clang-tools-extra/clangd/Protocol.h | 8 +-
clang-tools-extra/clangd/SourceCode.h | 5 +-
.../clangd/SystemIncludeExtractor.cpp | 3 +-
clang-tools-extra/clangd/TUScheduler.cpp | 148 ++++----
clang-tools-extra/clangd/TUScheduler.h | 2 +-
clang-tools-extra/clangd/TidyProvider.cpp | 14 +-
clang-tools-extra/clangd/URI.h | 2 +
clang-tools-extra/clangd/XRefs.cpp | 32 +-
clang-tools-extra/clangd/index/Background.cpp | 54 +--
clang-tools-extra/clangd/index/Background.h | 5 +-
.../clangd/index/BackgroundIndexLoader.cpp | 37 +-
.../clangd/index/BackgroundIndexLoader.h | 2 +-
.../clangd/index/BackgroundIndexStorage.cpp | 16 +-
clang-tools-extra/clangd/index/FileIndex.cpp | 95 +++--
clang-tools-extra/clangd/index/FileIndex.h | 21 +-
clang-tools-extra/clangd/index/MemIndex.cpp | 9 +-
clang-tools-extra/clangd/index/MemIndex.h | 11 +-
.../clangd/index/PathIdentity.cpp | 94 +++++
clang-tools-extra/clangd/index/PathIdentity.h | 93 +++++
clang-tools-extra/clangd/index/dex/Dex.cpp | 7 +-
clang-tools-extra/clangd/index/dex/Dex.h | 13 +-
clang-tools-extra/clangd/refactor/Rename.cpp | 21 +-
clang-tools-extra/clangd/refactor/Tweak.cpp | 2 +-
clang-tools-extra/clangd/refactor/Tweak.h | 3 +-
.../clangd/refactor/tweaks/DefineInline.cpp | 5 +-
.../clangd/refactor/tweaks/DefineOutline.cpp | 31 +-
.../clangd/support/FileCache.cpp | 10 +-
clang-tools-extra/clangd/support/FileCache.h | 2 +-
clang-tools-extra/clangd/support/Path.cpp | 259 +++++++++++--
clang-tools-extra/clangd/support/Path.h | 353 ++++++++++++++++--
.../clangd/support/ThreadsafeFS.cpp | 2 +-
.../clangd/test/memory_tree.test | 1 -
clang-tools-extra/clangd/tool/ClangdMain.cpp | 16 +-
.../clangd/unittests/ASTTests.cpp | 2 +-
.../clangd/unittests/BackgroundIndexTests.cpp | 107 ++++--
.../clangd/unittests/ClangdTests.cpp | 16 +-
.../clangd/unittests/CodeCompleteTests.cpp | 2 +-
.../clangd/unittests/CompileCommandsTests.cpp | 15 +
.../clangd/unittests/ConfigCompileTests.cpp | 15 +
.../clangd/unittests/DexTests.cpp | 14 +
.../clangd/unittests/DraftStoreTests.cpp | 346 +++++++++++++++++
.../clangd/unittests/FSTests.cpp | 39 ++
.../clangd/unittests/FileIndexTests.cpp | 166 ++++++++
.../GlobalCompilationDatabaseTests.cpp | 25 +-
.../unittests/HeaderSourceSwitchTests.cpp | 14 +-
.../clangd/unittests/HeadersTests.cpp | 2 +-
.../clangd/unittests/IndexTests.cpp | 42 +++
.../clangd/unittests/PreambleTests.cpp | 10 +-
.../unittests/PrerequisiteModulesTest.cpp | 6 +-
.../clangd/unittests/RenameTests.cpp | 23 +-
.../clangd/unittests/TUSchedulerTests.cpp | 110 +++++-
clang-tools-extra/clangd/unittests/TestFS.cpp | 26 +-
.../clangd/unittests/URITests.cpp | 94 +++++
.../clangd/unittests/support/PathTests.cpp | 144 ++++++-
.../clangd/unittests/tweaks/TweakTesting.cpp | 6 +-
.../clang-tools-extra/clangd/BUILD.gn | 1 +
81 files changed, 2698 insertions(+), 627 deletions(-)
create mode 100644 clang-tools-extra/clangd/index/PathIdentity.cpp
create mode 100644 clang-tools-extra/clangd/index/PathIdentity.h
diff --git a/clang-tools-extra/clangd/ASTSignals.cpp b/clang-tools-extra/clangd/ASTSignals.cpp
index cffadb091d557..21647994d259e 100644
--- a/clang-tools-extra/clangd/ASTSignals.cpp
+++ b/clang-tools-extra/clangd/ASTSignals.cpp
@@ -19,7 +19,7 @@ ASTSignals ASTSignals::derive(const ParsedAST &AST) {
trace::Span Span("ASTSignals::derive");
ASTSignals Signals;
Signals.InsertionDirective = preferredIncludeDirective(
- AST.tuPath(), AST.getLangOpts(),
+ AST.tuPath().raw(), AST.getLangOpts(),
AST.getIncludeStructure().MainFileIncludes, AST.getLocalTopLevelDecls());
const SourceManager &SM = AST.getSourceManager();
findExplicitReferences(
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 151f4ee028b87..33562a74f212c 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -128,6 +128,7 @@ add_clang_library(clangDaemon STATIC
index/IndexAction.cpp
index/MemIndex.cpp
index/Merge.cpp
+ index/PathIdentity.cpp
index/ProjectAware.cpp
index/Ref.cpp
index/Relation.cpp
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 43e8b35e45c89..b919293bff69c 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -176,13 +176,13 @@ llvm::Error validateEdits(const ClangdServer &Server, const FileEdits &FE) {
size_t InvalidFileCount = 0;
llvm::StringRef LastInvalidFile;
for (const auto &It : FE) {
- if (auto Draft = Server.getDraft(It.first())) {
+ if (auto Draft = Server.getDraft(It.first)) {
// If the file is open in user's editor, make sure the version we
// saw and current version are compatible as this is the text that
// will be replaced by editors.
if (!It.second.canApplyTo(*Draft)) {
++InvalidFileCount;
- LastInvalidFile = It.first();
+ LastInvalidFile = It.first.raw();
}
}
}
@@ -831,7 +831,7 @@ void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args,
// FIXME: use documentChanges when SupportDocumentChanges is true.
WE.changes.emplace();
for (const auto &It : R->ApplyEdits) {
- (*WE.changes)[URI::createFile(It.first()).toString()] =
+ (*WE.changes)[URI::createFile(It.first.raw()).toString()] =
It.second.asTextEdits();
}
// ApplyEdit will take care of calling Reply().
@@ -920,7 +920,7 @@ void ClangdLSPServer::onRename(const RenameParams &Params,
Result.changes.emplace();
for (const auto &Rep : R->GlobalChanges) {
(*Result
- .changes)[URI::createFile(Rep.first()).toString()] =
+ .changes)[URI::createFile(Rep.first.raw()).toString()] =
Rep.second.asTextEdits();
}
Reply(Result);
@@ -934,11 +934,11 @@ void ClangdLSPServer::onDocumentDidClose(
{
std::lock_guard<std::mutex> Lock(DiagRefMutex);
- DiagRefMap.erase(File);
+ DiagRefMap.erase(File.raw());
}
{
std::lock_guard<std::mutex> HLock(SemanticTokensMutex);
- LastSemanticTokens.erase(File);
+ LastSemanticTokens.erase(File.raw());
}
// clangd will not send updates for this file anymore, so we empty out the
// list of diagnostics shown on the client (e.g. in the "Problems" pane of
@@ -1202,10 +1202,10 @@ static Location *getToggle(const TextDocumentPositionParams &Point,
// Toggle only makes sense with two distinct locations.
if (!Sym.Definition || *Sym.Definition == Sym.PreferredDeclaration)
return nullptr;
- if (Sym.Definition->uri.file() == Point.textDocument.uri.file() &&
+ if (Sym.Definition->uri == Point.textDocument.uri &&
Sym.Definition->range.contains(Point.position))
return &Sym.PreferredDeclaration;
- if (Sym.PreferredDeclaration.uri.file() == Point.textDocument.uri.file() &&
+ if (Sym.PreferredDeclaration.uri == Point.textDocument.uri &&
Sym.PreferredDeclaration.range.contains(Point.position))
return &*Sym.Definition;
return nullptr;
@@ -1444,19 +1444,20 @@ void ClangdLSPServer::onCallHierarchyOutgoingCalls(
void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
- llvm::StringSet<> ModifiedFiles;
+ PathSet ModifiedFiles;
for (auto &[File, Command] : Settings.compilationDatabaseChanges) {
auto Cmd =
tooling::CompileCommand(std::move(Command.workingDirectory), File,
std::move(Command.compilationCommand),
/*Output=*/"");
if (CDB->setCompileCommand(File, std::move(Cmd))) {
- ModifiedFiles.insert(File);
+ ModifiedFiles.insert(Path(File));
}
}
- Server->reparseOpenFilesIfNeeded(
- [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; });
+ Server->reparseOpenFilesIfNeeded([&](llvm::StringRef File) {
+ return ModifiedFiles.find_as(PathRef(File)) != ModifiedFiles.end();
+ });
}
void ClangdLSPServer::maybeExportMemoryProfile() {
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index bd9c5e6bc6954..08eb505b4c15e 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -262,12 +262,11 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
typedef std::map<DiagKey, ClangdServer::DiagRef>
DiagnosticToDiagRefMap;
/// Caches the mapping LSP and clangd-naive diagnostics per file.
- llvm::StringMap<DiagnosticToDiagRefMap>
- DiagRefMap;
+ PathMap<DiagnosticToDiagRefMap> DiagRefMap;
// Last semantic-tokens response, for incremental requests.
std::mutex SemanticTokensMutex;
- llvm::StringMap<SemanticTokens> LastSemanticTokens;
+ PathMap<SemanticTokens> LastSemanticTokens;
// Most code should not deal with Transport, callMethod, notify directly.
// Use LSPBinder to handle incoming and outgoing calls.
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 37eb82116f3a9..4feb55a6cc23b 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -62,8 +62,8 @@ namespace clangd {
namespace {
// Tracks number of times a tweak has been offered.
-static constexpr trace::Metric TweakAvailable(
- "tweak_available", trace::Metric::Counter, "tweak_id");
+static constexpr trace::Metric
+ TweakAvailable("tweak_available", trace::Metric::Counter, "tweak_id");
// Update the FileIndex with new ASTs and plumb the diagnostics responses.
struct UpdateIndexCallbacks : public ParsingCallbacks {
@@ -88,15 +88,15 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
indexStdlib(CI, std::move(*Loc));
// FIndex outlives the UpdateIndexCallbacks.
- auto Task = [FIndex(FIndex), Path(Path.str()), Version(Version.str()),
+ auto Task = [FIndex(FIndex), Path(Path.owned()), Version(Version.str()),
ASTCtx(std::move(ASTCtx)), PI(std::move(PI))]() mutable {
trace::Span Tracer("PreambleIndexing");
- FIndex->updatePreamble(Path, Version, ASTCtx.getASTContext(),
+ FIndex->updatePreamble(Path.raw(), Version, ASTCtx.getASTContext(),
ASTCtx.getPreprocessor(), *PI);
};
if (Tasks) {
- Tasks->runAsync("Preamble indexing for:" + Path + Version,
+ Tasks->runAsync("Preamble indexing for:" + Path.raw().str() + Version,
std::move(Task));
} else
Task();
@@ -264,7 +264,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
BackgroundIdx = std::make_unique<BackgroundIndex>(
TFS, CDB,
BackgroundIndexStorage::createDiskBackedStorageFactory(
- [&CDB](llvm::StringRef File) { return CDB.getProjectInfo(File); }),
+ [&CDB](PathRef File) { return CDB.getProjectInfo(File); }),
std::move(BGOpts));
AddIndex(BackgroundIdx.get());
}
@@ -319,14 +319,14 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);
// If we loaded Foo.h, we want to make sure Foo.cpp is indexed.
if (NewFile && BackgroundIdx)
- BackgroundIdx->boostRelated(File);
+ BackgroundIdx->boostRelated(File.raw());
}
void ClangdServer::reparseOpenFilesIfNeeded(
llvm::function_ref<bool(llvm::StringRef File)> Filter) {
// Reparse only opened files that were modified.
for (const Path &FilePath : DraftMgr.getActiveFiles())
- if (Filter(FilePath))
+ if (Filter(FilePath.raw()))
if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
addDocument(FilePath, *Draft->Contents, Draft->Version,
WantDiagnostics::Auto);
@@ -343,7 +343,7 @@ std::function<Context(PathRef)>
ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,
Callbacks *Publish) {
if (!Provider)
- return [](llvm::StringRef) { return Context::current().clone(); };
+ return [](PathRef) { return Context::current().clone(); };
struct Impl {
const config::Provider *Provider;
@@ -411,8 +411,8 @@ ClangdServer::createConfiguredContextProvider(const config::Provider *Provider,
};
// Copyable wrapper.
- return [I(std::make_shared<Impl>(Provider, Publish))](llvm::StringRef Path) {
- return (*I)(Path);
+ return [I(std::make_shared<Impl>(Provider, Publish))](PathRef Path) {
+ return (*I)(Path.raw());
};
}
@@ -429,7 +429,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
if (!CodeCompleteOpts.Index) // Respect overridden index.
CodeCompleteOpts.Index = Index;
- auto Task = [Pos, CodeCompleteOpts, File = File.str(), CB = std::move(CB),
+ auto Task = [Pos, CodeCompleteOpts, File = File.owned(), CB = std::move(CB),
this](llvm::Expected<InputsAndPreamble> IP) mutable {
if (!IP)
return CB(IP.takeError());
@@ -445,7 +445,8 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
SpecFuzzyFind.emplace();
{
std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);
- SpecFuzzyFind->CachedReq = CachedCompletionFuzzyFindRequestByFile[File];
+ SpecFuzzyFind->CachedReq =
+ CachedCompletionFuzzyFindRequestByFile[File.raw()];
}
}
ParseInputs ParseInput{IP->Command, &getHeaderFS(), IP->Contents.str()};
@@ -479,7 +480,8 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
return;
if (SpecFuzzyFind->NewReq) {
std::lock_guard<std::mutex> Lock(CachedCompletionFuzzyFindRequestMutex);
- CachedCompletionFuzzyFindRequestByFile[File] = *SpecFuzzyFind->NewReq;
+ CachedCompletionFuzzyFindRequestByFile[File.raw()] =
+ *SpecFuzzyFind->NewReq;
}
// Explicitly block until async task completes, this is fine as we've
// already provided reply to the client and running as a preamble task
@@ -501,7 +503,7 @@ void ClangdServer::signatureHelp(PathRef File, Position Pos,
MarkupKind DocumentationFormat,
Callback<SignatureHelp> CB) {
- auto Action = [Pos, File = File.str(), CB = std::move(CB),
+ auto Action = [Pos, File = File.owned(), CB = std::move(CB),
DocumentationFormat,
this](llvm::Expected<InputsAndPreamble> IP) mutable {
if (!IP)
@@ -549,12 +551,13 @@ void ClangdServer::formatFile(PathRef File, const std::vector<Range> &Rngs,
}
// Call clang-format.
- auto Action = [File = File.str(), Code = std::move(*Code),
+ auto Action = [File = File.owned(), Code = std::move(*Code),
Ranges = std::move(RequestedRanges), CB = std::move(CB),
this]() mutable {
- format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS, true);
+ format::FormatStyle Style =
+ getFormatStyleForFile(File.raw(), Code, TFS, true);
tooling::Replacements IncludeReplaces =
- format::sortIncludes(Style, Code, Ranges, File);
+ format::sortIncludes(Style, Code, Ranges, File.raw());
auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
if (!Changed)
return CB(Changed.takeError());
@@ -562,9 +565,9 @@ void ClangdServer::formatFile(PathRef File, const std::vector<Range> &Rngs,
CB(IncludeReplaces.merge(format::reformat(
Style, *Changed,
tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
- File)));
+ File.raw())));
};
- WorkScheduler->runQuick("Format", File, std::move(Action));
+ WorkScheduler->runQuick("Format", File.raw(), std::move(Action));
}
void ClangdServer::formatOnType(PathRef File, Position Pos,
@@ -577,24 +580,24 @@ void ClangdServer::formatOnType(PathRef File, Position Pos,
llvm::Expected<size_t> CursorPos = positionToOffset(*Code, Pos);
if (!CursorPos)
return CB(CursorPos.takeError());
- auto Action = [File = File.str(), Code = std::move(*Code),
+ auto Action = [File = File.owned(), Code = std::move(*Code),
TriggerText = TriggerText.str(), CursorPos = *CursorPos,
CB = std::move(CB), this]() mutable {
- auto Style = getFormatStyleForFile(File, Code, TFS, false);
+ auto Style = getFormatStyleForFile(File.raw(), Code, TFS, false);
std::vector<TextEdit> Result;
for (const tooling::Replacement &R :
formatIncremental(Code, CursorPos, TriggerText, Style))
Result.push_back(replacementToEdit(Code, R));
return CB(Result);
};
- WorkScheduler->runQuick("FormatOnType", File, std::move(Action));
+ WorkScheduler->runQuick("FormatOnType", File.raw(), std::move(Action));
}
void ClangdServer::prepareRename(PathRef File, Position Pos,
std::optional<std::string> NewName,
const RenameOptions &RenameOpts,
Callback<RenameResult> CB) {
- auto Action = [Pos, File = File.str(), CB = std::move(CB),
+ auto Action = [Pos, File = File.owned(), CB = std::move(CB),
NewName = std::move(NewName),
RenameOpts](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
@@ -603,7 +606,7 @@ void ClangdServer::prepareRename(PathRef File, Position Pos,
// only need main-file references
auto Results =
clangd::rename({Pos, NewName.value_or("__clangd_rename_placeholder"),
- InpAST->AST, File, /*FS=*/nullptr,
+ InpAST->AST, File.raw(), /*FS=*/nullptr,
/*Index=*/nullptr, RenameOpts});
if (!Results) {
// LSP says to return null on failure, but that will result in a generic
@@ -619,7 +622,7 @@ void ClangdServer::prepareRename(PathRef File, Position Pos,
void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
const RenameOptions &Opts,
Callback<RenameResult> CB) {
- auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts,
+ auto Action = [File = File.owned(), NewName = NewName.str(), Pos, Opts,
CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
// Tracks number of files edited per invocation.
@@ -627,18 +630,17 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
trace::Metric::Distribution);
if (!InpAST)
return CB(InpAST.takeError());
- auto R = clangd::rename({Pos, NewName, InpAST->AST, File,
+ auto R = clangd::rename({Pos, NewName, InpAST->AST, File.raw(),
DirtyFS->view(std::nullopt), Index, Opts});
if (!R)
return CB(R.takeError());
if (Opts.WantFormat) {
- auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
+ auto Style = getFormatStyleForFile(File.raw(), InpAST->Inputs.Contents,
*InpAST->Inputs.TFS, false);
llvm::Error Err = llvm::Error::success();
for (auto &E : R->GlobalChanges)
- Err =
- llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));
+ Err = llvm::joinErrors(reformatEdit(E.second, Style), std::move(Err));
if (Err)
return CB(std::move(Err));
@@ -765,7 +767,7 @@ void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
static constexpr trace::Metric TweakFailed(
"tweak_failed", trace::Metric::Counter, "tweak_id");
TweakAttempt.record(1, TweakID);
- auto Action = [File = File.str(), Sel, TweakID = TweakID.str(),
+ auto Action = [File = File.owned(), Sel, TweakID = TweakID.str(),
CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
@@ -791,9 +793,9 @@ void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
for (auto &It : (*Effect)->ApplyEdits) {
Edit &E = It.second;
format::FormatStyle Style =
- getFormatStyleForFile(File, E.InitialCode, TFS, false);
+ getFormatStyleForFile(File.raw(), E.InitialCode, TFS, false);
if (llvm::Error Err = reformatEdit(E, Style))
- elog("Failed to format {0}: {1}", It.first(), std::move(Err));
+ elog("Failed to format {0}: {1}", It.first, std::move(Err));
}
} else {
TweakFailed.record(1, TweakID);
@@ -826,7 +828,7 @@ void ClangdServer::switchSourceHeader(
if (auto CorrespondingFile =
getCorrespondingHeaderOrSource(Path, TFS.view(std::nullopt)))
return CB(std::move(CorrespondingFile));
- auto Action = [Path = Path.str(), CB = std::move(CB),
+ auto Action = [Path = Path.owned(), CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
@@ -849,12 +851,12 @@ void ClangdServer::findDocumentHighlights(
void ClangdServer::findHover(PathRef File, Position Pos,
Callback<std::optional<HoverInfo>> CB) {
- auto Action = [File = File.str(), Pos, CB = std::move(CB),
+ auto Action = [File = File.owned(), Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
format::FormatStyle Style = getFormatStyleForFile(
- File, InpAST->Inputs.Contents, *InpAST->Inputs.TFS, false);
+ File.raw(), InpAST->Inputs.Contents, *InpAST->Inputs.TFS, false);
CB(clangd::getHover(InpAST->AST, Pos, std::move(Style), Index));
};
@@ -864,7 +866,8 @@ void ClangdServer::findHover(PathRef File, Position Pos,
void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction,
Callback<std::vector<TypeHierarchyItem>> CB) {
- auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),
+ auto Action = [File = File.owned(), Pos, Resolve, Direction,
+ CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
@@ -903,7 +906,7 @@ void ClangdServer::resolveTypeHierarchy(
void ClangdServer::prepareCallHierarchy(
PathRef File, Position Pos, Callback<std::vector<CallHierarchyItem>> CB) {
- auto Action = [File = File.str(), Pos,
+ auto Action = [File = File.owned(), Pos,
CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
@@ -985,7 +988,7 @@ void ClangdServer::foldingRanges(llvm::StringRef File,
WorkScheduler->runQuick("FoldingRanges", File, std::move(Action));
}
-void ClangdServer::findType(llvm::StringRef File, Position Pos,
+void ClangdServer::findType(PathRef File, Position Pos,
Callback<std::vector<LocatedSymbol>> CB) {
auto Action = [Pos, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 264ab7437c248..eeb2c764e396f 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -520,7 +520,7 @@ class ClangdServer {
bool PublishInactiveRegions = false;
// GUARDED_BY(CachedCompletionFuzzyFindRequestMutex)
- llvm::StringMap<std::optional<FuzzyFindRequest>>
+ PathMap<std::optional<FuzzyFindRequest>>
CachedCompletionFuzzyFindRequestByFile;
mutable std::mutex CachedCompletionFuzzyFindRequestMutex;
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 80091d3a48b33..2d79feea5fa7d 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1399,14 +1399,14 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
CI->getLangOpts().DelayedTemplateParsing = false;
// Setup code completion.
FrontendOpts.CodeCompleteOpts = Options;
- FrontendOpts.CodeCompletionAt.FileName = std::string(Input.FileName);
+ FrontendOpts.CodeCompletionAt.FileName = Input.FileName.raw().str();
std::tie(FrontendOpts.CodeCompletionAt.Line,
FrontendOpts.CodeCompletionAt.Column) =
offsetToClangLineColumn(Input.ParseInput.Contents, Input.Offset);
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
llvm::MemoryBuffer::getMemBuffer(Input.ParseInput.Contents,
- Input.FileName);
+ Input.FileName.raw());
// The diagnostic options must be set before creating a CompilerInstance.
CI->getDiagnosticOpts().IgnoreWarnings = true;
// We reuse the preamble whether it's valid or not. This is a
@@ -1628,7 +1628,7 @@ class CodeCompleteFlow {
assert(Recorder && "Recorder is not set");
CCContextKind = Recorder->CCContext.getKind();
IsUsingDeclaration = Recorder->CCContext.isUsingDeclaration();
- auto Style = getFormatStyleForFile(SemaCCInput.FileName,
+ auto Style = getFormatStyleForFile(SemaCCInput.FileName.raw(),
SemaCCInput.ParseInput.Contents,
*SemaCCInput.ParseInput.TFS, false);
const auto &SM = Recorder->CCSema->getSourceManager();
@@ -1648,7 +1648,7 @@ class CodeCompleteFlow {
// If preprocessor was run, inclusions from preprocessor callback should
// already be added to Includes.
Inserter.emplace(
- SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, Style,
+ SemaCCInput.FileName.raw(), SemaCCInput.ParseInput.Contents, Style,
SemaCCInput.ParseInput.CompileCommand.Directory,
&Recorder->CCSema->getPreprocessor().getHeaderSearchInfo(),
Config::current().Style.QuotedHeaders,
@@ -1739,12 +1739,12 @@ class CodeCompleteFlow {
}
llvm::StringMap<SourceParams> ProxSources;
- ProxSources[FileName].Cost = 0;
+ ProxSources[FileName.raw()].Cost = 0;
FileProximity.emplace(ProxSources);
- auto Style = getFormatStyleForFile(FileName, Content, TFS, false);
+ auto Style = getFormatStyleForFile(FileName.raw(), Content, TFS, false);
// This will only insert verbatim headers.
- Inserter.emplace(FileName, Content, Style,
+ Inserter.emplace(FileName.raw(), Content, Style,
/*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr,
Config::current().Style.QuotedHeaders,
Config::current().Style.AngledHeaders);
@@ -1942,7 +1942,7 @@ class CodeCompleteFlow {
Req.Scopes = QueryScopes;
Req.AnyScope = AllScopes;
// FIXME: we should send multiple weighted paths here.
- Req.ProximityPaths.push_back(std::string(FileName));
+ Req.ProximityPaths.push_back(FileName.raw().str());
if (PreferredType)
Req.PreferredTypes.push_back(std::string(PreferredType->raw()));
vlog("Code complete: fuzzyFind({0:2})", toJSON(Req));
@@ -1997,8 +1997,9 @@ class CodeCompleteFlow {
assert(IdentifierResult);
C.Name = IdentifierResult->Name;
}
- if (auto OverloadSet = C.overloadSet(
- Opts, FileName, Inserter ? &*Inserter : nullptr, CCContextKind)) {
+ if (auto OverloadSet =
+ C.overloadSet(Opts, FileName.raw(),
+ Inserter ? &*Inserter : nullptr, CCContextKind)) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
if (Ret.second)
Bundles.emplace_back();
@@ -2180,8 +2181,9 @@ class CodeCompleteFlow {
: nullptr;
if (!Builder)
Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr,
- Item, SemaCCS, AccessibleScopes, *Inserter, FileName,
- CCContextKind, Opts, IsUsingDeclaration, NextTokenKind);
+ Item, SemaCCS, AccessibleScopes, *Inserter,
+ FileName.raw(), CCContextKind, Opts, IsUsingDeclaration,
+ NextTokenKind);
else
Builder->add(Item, SemaCCS, CCContextKind);
}
@@ -2244,7 +2246,7 @@ maybeFunctionArgumentCommentEnd(const PathRef FileName, const unsigned Offset,
if (Offset > Content.size())
return std::nullopt;
- SourceManagerForFile FileSM(FileName, Content);
+ SourceManagerForFile FileSM(FileName.raw(), Content);
const SourceManager &SM = FileSM.get();
const SourceLocation Cursor = SM.getComposedLoc(SM.getMainFileID(), Offset);
const SourceLocation EndOfSuffix =
@@ -2293,7 +2295,7 @@ codeCompleteComment(PathRef FileName, const unsigned CursorOffset,
semaCodeComplete(
std::make_unique<ParamNameCollector>(Options, ParamNames), Options,
{FileName, OutsideStartOffset, *Preamble,
- PreamblePatch::createFullPatch(FileName, ParseInput, *Preamble),
+ PreamblePatch::createFullPatch(FileName.raw(), ParseInput, *Preamble),
ParseInput},
/*Includes=*/nullptr, std::move(CI));
if (ParamNames.empty())
@@ -2379,7 +2381,7 @@ CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
: std::move(Flow).run({FileName, *Offset, *Preamble,
/*PreamblePatch=*/
PreamblePatch::createMacroPatch(
- FileName, ParseInput, *Preamble),
+ FileName.raw(), ParseInput, *Preamble),
ParseInput});
}
@@ -2403,7 +2405,7 @@ SignatureHelp signatureHelp(PathRef FileName, Position Pos,
ParseInput.Index, Result),
Options,
{FileName, *Offset, Preamble,
- PreamblePatch::createFullPatch(FileName, ParseInput, Preamble),
+ PreamblePatch::createFullPatch(FileName.raw(), ParseInput, Preamble),
ParseInput});
return Result;
}
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index 2b41949d6d05c..201fc05bd053d 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -59,8 +59,10 @@ llvm::StringRef configRelative(llvm::StringRef Path,
llvm::StringRef FragmentDir) {
if (FragmentDir.empty())
return Path;
- if (!Path.consume_front(FragmentDir))
+ if (Path.size() < FragmentDir.size() ||
+ !pathEquals(Path.take_front(FragmentDir.size()), FragmentDir))
return llvm::StringRef();
+ Path = Path.drop_front(FragmentDir.size());
return Path.empty() ? "." : Path;
}
@@ -412,8 +414,9 @@ struct FragmentCompiler {
C.Index.External = Spec;
return;
}
- if (P.Path.empty() || !pathStartsWith(Spec.MountPoint, P.Path,
- llvm::sys::path::Style::posix))
+ if (P.Path.empty() ||
+ !PathRef(Spec.MountPoint)
+ .startsWith(P.Path, llvm::sys::path::Style::posix))
return;
C.Index.External = Spec;
// Disable background indexing for the files under the mountpoint.
diff --git a/clang-tools-extra/clangd/ConfigProvider.cpp b/clang-tools-extra/clangd/ConfigProvider.cpp
index ac437ee8b6eb1..24dacabd82b2a 100644
--- a/clang-tools-extra/clangd/ConfigProvider.cpp
+++ b/clang-tools-extra/clangd/ConfigProvider.cpp
@@ -43,7 +43,8 @@ class FileConfigCache : public FileCache {
[&](std::optional<llvm::StringRef> Data) {
CachedValue.clear();
if (Data)
- for (auto &Fragment : Fragment::parseYAML(*Data, path(), DC)) {
+ for (auto &Fragment :
+ Fragment::parseYAML(*Data, path().raw(), DC)) {
Fragment.Source.Directory = Directory;
Fragment.Source.Trusted = Trusted;
CachedValue.push_back(std::move(Fragment).compile(DC));
@@ -103,9 +104,9 @@ Provider::fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,
// Compute absolute paths to all ancestors (substrings of P.Path).
llvm::SmallVector<llvm::StringRef, 8> Ancestors;
- for (auto Ancestor = absoluteParent(P.Path); !Ancestor.empty();
- Ancestor = absoluteParent(Ancestor)) {
- Ancestors.emplace_back(Ancestor);
+ for (auto Ancestor = PathRef(P.Path).absoluteParent(); !Ancestor.empty();
+ Ancestor = Ancestor.absoluteParent()) {
+ Ancestors.emplace_back(Ancestor.raw());
}
// Ensure corresponding cache entries exist in the map.
llvm::SmallVector<FileConfigCache *, 8> Caches;
diff --git a/clang-tools-extra/clangd/DraftStore.cpp b/clang-tools-extra/clangd/DraftStore.cpp
index 66e45b0c04ce3..6cce775973e15 100644
--- a/clang-tools-extra/clangd/DraftStore.cpp
+++ b/clang-tools-extra/clangd/DraftStore.cpp
@@ -8,10 +8,16 @@
#include "DraftStore.h"
#include "support/Logger.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/VirtualFileSystem.h"
+#include <chrono>
#include <memory>
#include <optional>
+#include <system_error>
namespace clang {
namespace clangd {
@@ -19,7 +25,7 @@ namespace clangd {
std::optional<DraftStore::Draft> DraftStore::getDraft(PathRef File) const {
std::lock_guard<std::mutex> Lock(Mutex);
- auto It = Drafts.find(File);
+ auto It = Drafts.find(File.raw());
if (It == Drafts.end())
return std::nullopt;
@@ -30,8 +36,8 @@ std::vector<Path> DraftStore::getActiveFiles() const {
std::lock_guard<std::mutex> Lock(Mutex);
std::vector<Path> ResultVector;
- for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++)
- ResultVector.push_back(std::string(DraftIt->getKey()));
+ for (const auto &Draft : Drafts)
+ ResultVector.push_back(Draft.first);
return ResultVector;
}
@@ -76,7 +82,7 @@ std::string DraftStore::addDraft(PathRef File, llvm::StringRef Version,
llvm::StringRef Contents) {
std::lock_guard<std::mutex> Lock(Mutex);
- auto &D = Drafts[File];
+ auto &D = Drafts[File.raw()];
updateVersion(D.D, Version);
std::time(&D.MTime);
D.D.Contents = std::make_shared<std::string>(Contents);
@@ -86,42 +92,269 @@ std::string DraftStore::addDraft(PathRef File, llvm::StringRef Version,
void DraftStore::removeDraft(PathRef File) {
std::lock_guard<std::mutex> Lock(Mutex);
- Drafts.erase(File);
+ Drafts.erase(File.raw());
}
namespace {
+using PathStyle = llvm::sys::path::Style;
+
+bool isWindowsPath(llvm::StringRef Path) {
+ return Path.size() >= 2 && llvm::isAlpha(Path[0]) && Path[1] == ':';
+}
+
+PathStyle pathStyle(llvm::StringRef Path) {
+ return isWindowsPath(Path) ? PathStyle::windows : PathStyle::native;
+}
-/// A read only MemoryBuffer shares ownership of a ref counted string. The
-/// shared string object must not be modified while an owned by this buffer.
+/// A read-only MemoryBuffer that keeps the draft contents alive.
class SharedStringBuffer : public llvm::MemoryBuffer {
- const std::shared_ptr<const std::string> BufferContents;
- const std::string Name;
+ std::shared_ptr<const std::string> Contents;
+ std::string Name;
public:
+ SharedStringBuffer(std::shared_ptr<const std::string> Contents,
+ llvm::StringRef Name)
+ : Contents(std::move(Contents)), Name(Name) {
+ assert(this->Contents && "draft contents must be present");
+ init(this->Contents->c_str(),
+ this->Contents->c_str() + this->Contents->size(),
+ /*RequiresNullTerminator=*/true);
+ }
+
BufferKind getBufferKind() const override {
return MemoryBuffer::MemoryBuffer_Malloc;
}
- StringRef getBufferIdentifier() const override { return Name; }
+ llvm::StringRef getBufferIdentifier() const override { return Name; }
+};
+
+struct DraftNode {
+ struct Child {
+ std::string Name;
+ llvm::sys::fs::file_type Type;
+ };
+
+ enum Kind { File, Directory } K = Directory;
+ llvm::sys::fs::UniqueID ID;
+ std::shared_ptr<const std::string> Contents;
+ std::time_t MTime = 0;
+ llvm::SmallVector<Child, 2> Children;
+
+ explicit DraftNode(
+ llvm::sys::fs::UniqueID ID = llvm::vfs::getNextVirtualUniqueID())
+ : ID(ID) {}
+};
+
+class DraftFile : public llvm::vfs::File {
+ llvm::vfs::Status Stat;
+ std::shared_ptr<const std::string> Contents;
+
+public:
+ DraftFile(llvm::vfs::Status S, std::shared_ptr<const std::string> C)
+ : Stat(std::move(S)), Contents(std::move(C)) {}
+
+ llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; }
+
+ llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+ getBuffer(const Twine &Name, int64_t /*FileSize*/,
+ bool RequiresNullTerminator, bool /*IsVolatile*/) override {
+ (void)RequiresNullTerminator;
+ return std::make_unique<SharedStringBuffer>(Contents, Name.str());
+ }
+
+ std::error_code close() override { return {}; }
+};
+
+class DraftDirIterator : public llvm::vfs::detail::DirIterImpl {
+ llvm::ArrayRef<DraftNode::Child> Children;
+ std::string RequestedDir;
+ PathStyle Style;
+ size_t Index = 0;
+
+ void setCurrentEntry() {
+ if (Index == Children.size()) {
+ CurrentEntry = {};
+ return;
+ }
+ llvm::SmallString<256> Path(RequestedDir);
+ llvm::sys::path::append(Path, Style, Children[Index].Name);
+ CurrentEntry =
+ llvm::vfs::directory_entry(Path.str().str(), Children[Index].Type);
+ }
+
+public:
+ DraftDirIterator(llvm::ArrayRef<DraftNode::Child> Children,
+ std::string RequestedDir, PathStyle Style)
+ : Children(Children), RequestedDir(std::move(RequestedDir)),
+ Style(Style) {
+ setCurrentEntry();
+ }
+
+ std::error_code increment() override {
+ ++Index;
+ setCurrentEntry();
+ return {};
+ }
+};
+
+/// Overlay whose lookups use Path identity (drive letter, slashes) rather than
+/// dumping first-inserted spellings into a case-sensitive InMemoryFileSystem.
+class DraftsFileSystem : public llvm::vfs::FileSystem {
+ using NodeMap = PathMap<DraftNode>;
+ NodeMap Nodes;
+ std::string CWD;
+
+ static llvm::vfs::Status makeStatus(llvm::StringRef Requested,
+ const DraftNode &N) {
+ const bool IsFile = N.K == DraftNode::File;
+ return llvm::vfs::Status(Requested, N.ID,
+ std::chrono::system_clock::from_time_t(N.MTime),
+ /*User=*/0,
+ /*Group=*/0, IsFile ? N.Contents->size() : 0,
+ IsFile ? llvm::sys::fs::file_type::regular_file
+ : llvm::sys::fs::file_type::directory_file,
+ llvm::sys::fs::all_all);
+ }
+
+ PathRef resolve(const llvm::Twine &Requested,
+ llvm::SmallString<256> &Storage) const {
+ Requested.toVector(Storage);
+ PathStyle Style = pathStyle(Storage);
+ if (llvm::sys::path::is_relative(Storage, Style) && !CWD.empty()) {
+ llvm::sys::path::make_absolute(CWD, Storage);
+ Style = pathStyle(Storage);
+ }
+ llvm::sys::path::remove_dots(Storage, /*remove_dot_dot=*/true, Style);
+ return Storage;
+ }
+
+ NodeMap::const_iterator lookup(const llvm::Twine &Path,
+ llvm::SmallString<256> &Storage) const {
+ return Nodes.find(resolve(Path, Storage));
+ }
+
+public:
+ explicit DraftsFileSystem(PathMap<DraftNode> Nodes)
+ : Nodes(std::move(Nodes)) {}
+
+ llvm::ErrorOr<llvm::vfs::Status> status(const llvm::Twine &Path) override {
+ llvm::SmallString<256> Storage;
+ auto It = lookup(Path, Storage);
+ if (It != Nodes.end())
+ return makeStatus(Path.str(), It->second);
+ return llvm::errc::no_such_file_or_directory;
+ }
+
+ llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
+ openFileForRead(const llvm::Twine &Path) override {
+ llvm::SmallString<256> Storage;
+ auto It = lookup(Path, Storage);
+ if (It == Nodes.end())
+ return llvm::errc::no_such_file_or_directory;
+ if (It->second.K != DraftNode::File)
+ return llvm::errc::invalid_argument;
+ return std::unique_ptr<llvm::vfs::File>(std::make_unique<DraftFile>(
+ makeStatus(Path.str(), It->second), It->second.Contents));
+ }
+
+ llvm::vfs::directory_iterator dir_begin(const llvm::Twine &Path,
+ std::error_code &EC) override {
+ llvm::SmallString<256> Storage;
+ auto It = lookup(Path, Storage);
+ if (It == Nodes.end()) {
+ EC = llvm::errc::no_such_file_or_directory;
+ return {};
+ }
+ if (It->second.K != DraftNode::Directory) {
+ EC = llvm::errc::not_a_directory;
+ return {};
+ }
+ EC = {};
+ return llvm::vfs::directory_iterator(std::make_shared<DraftDirIterator>(
+ It->second.Children, Path.str(), pathStyle(Storage)));
+ }
+
+ std::error_code setCurrentWorkingDirectory(const llvm::Twine &Path) override {
+ llvm::SmallString<256> Storage;
+ CWD = resolve(Path, Storage).raw().str();
+ return {};
+ }
+
+ llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+ if (CWD.empty())
+ return llvm::errc::no_such_file_or_directory;
+ return CWD;
+ }
- SharedStringBuffer(std::shared_ptr<const std::string> Data, StringRef Name)
- : BufferContents(std::move(Data)), Name(Name) {
- assert(BufferContents && "Can't create from empty shared_ptr");
- MemoryBuffer::init(BufferContents->c_str(),
- BufferContents->c_str() + BufferContents->size(),
- /*RequiresNullTerminator=*/true);
+ std::error_code getRealPath(const llvm::Twine &Path,
+ llvm::SmallVectorImpl<char> &Output) override {
+ llvm::SmallString<256> Storage;
+ auto It = lookup(Path, Storage);
+ if (It == Nodes.end())
+ return llvm::errc::no_such_file_or_directory;
+ Output.clear();
+ Output.append(It->first.raw().begin(), It->first.raw().end());
+ return {};
+ }
+
+ std::error_code isLocal(const llvm::Twine &Path, bool &Result) override {
+ llvm::SmallString<256> Storage;
+ if (lookup(Path, Storage) == Nodes.end())
+ return llvm::errc::no_such_file_or_directory;
+ Result = false;
+ return {};
}
};
} // namespace
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> DraftStore::asVFS() const {
- auto MemFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
- std::lock_guard<std::mutex> Guard(Mutex);
- for (const auto &Draft : Drafts)
- MemFS->addFile(Draft.getKey(), Draft.getValue().MTime,
- std::make_unique<SharedStringBuffer>(
- Draft.getValue().D.Contents, Draft.getKey()));
- return MemFS;
+ PathMap<DraftNode> Snapshot;
+ {
+ std::lock_guard<std::mutex> Guard(Mutex);
+ for (const auto &Draft : Drafts) {
+ llvm::SmallString<256> Canonical(Draft.first.raw());
+ PathStyle Style = pathStyle(Canonical);
+ llvm::sys::path::remove_dots(Canonical, /*remove_dot_dot=*/true, Style);
+ auto FileIt =
+ Snapshot.try_emplace(PathRef(Canonical), Draft.second.ID).first;
+ FileIt->second.K = DraftNode::File;
+ FileIt->second.ID = Draft.second.ID;
+ FileIt->second.Contents = Draft.second.D.Contents;
+ FileIt->second.MTime = Draft.second.MTime;
+
+ PathRef Child = Canonical;
+ while (!llvm::sys::path::relative_path(Child.raw(), Style).empty()) {
+ llvm::StringRef Parent =
+ llvm::sys::path::parent_path(Child.raw(), Style);
+ if (Parent.empty() || Parent == Child.raw())
+ break;
+ Snapshot.try_emplace(PathRef(Parent));
+ Child = Parent;
+ }
+ }
+ }
+
+ // Populate directory adjacency only after all nodes have been inserted, so
+ // DenseMap rehashing cannot invalidate any node references.
+ for (const auto &Entry : Snapshot) {
+ PathStyle Style = pathStyle(Entry.first.raw());
+ if (llvm::sys::path::relative_path(Entry.first.raw(), Style).empty())
+ continue;
+ llvm::StringRef Parent =
+ llvm::sys::path::parent_path(Entry.first.raw(), Style);
+ if (Parent.empty())
+ continue;
+ auto ParentIt = Snapshot.find(Parent);
+ if (ParentIt == Snapshot.end() ||
+ ParentIt->second.K != DraftNode::Directory)
+ continue;
+ ParentIt->second.Children.push_back(
+ {llvm::sys::path::filename(Entry.first.raw(), Style).str(),
+ Entry.second.K == DraftNode::File
+ ? llvm::sys::fs::file_type::regular_file
+ : llvm::sys::fs::file_type::directory_file});
+ }
+ return llvm::makeIntrusiveRefCnt<DraftsFileSystem>(std::move(Snapshot));
}
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/DraftStore.h b/clang-tools-extra/clangd/DraftStore.h
index 0d5204215f8c9..ae045535e8c10 100644
--- a/clang-tools-extra/clangd/DraftStore.h
+++ b/clang-tools-extra/clangd/DraftStore.h
@@ -11,7 +11,6 @@
#include "support/Path.h"
#include "clang/Basic/LLVM.h"
-#include "llvm/ADT/StringMap.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <mutex>
#include <optional>
@@ -54,9 +53,12 @@ class DraftStore {
struct DraftAndTime {
Draft D;
std::time_t MTime;
+ // Keep file identity across snapshots, including content updates. A new
+ // draft gets a fresh ID, so cached statuses cannot alias unrelated files.
+ llvm::sys::fs::UniqueID ID = llvm::vfs::getNextVirtualUniqueID();
};
mutable std::mutex Mutex;
- llvm::StringMap<DraftAndTime> Drafts;
+ PathMap<DraftAndTime> Drafts;
};
} // namespace clangd
diff --git a/clang-tools-extra/clangd/FS.cpp b/clang-tools-extra/clangd/FS.cpp
index 5729b9341d9d4..be6c775e933e7 100644
--- a/clang-tools-extra/clangd/FS.cpp
+++ b/clang-tools-extra/clangd/FS.cpp
@@ -8,6 +8,7 @@
#include "FS.h"
#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <optional>
@@ -16,11 +17,11 @@
namespace clang {
namespace clangd {
-PreambleFileStatusCache::PreambleFileStatusCache(llvm::StringRef MainFilePath){
- assert(llvm::sys::path::is_absolute(MainFilePath));
- llvm::SmallString<256> MainFileCanonical(MainFilePath);
- llvm::sys::path::remove_dots(MainFileCanonical, /*remove_dot_dot=*/true);
- this->MainFilePath = std::string(MainFileCanonical);
+PreambleFileStatusCache::PreambleFileStatusCache(llvm::StringRef MainFilePath) {
+ assert(llvm::sys::path::is_absolute(MainFilePath) ||
+ (MainFilePath.size() >= 2 && llvm::isAlpha(MainFilePath[0]) &&
+ MainFilePath[1] == ':'));
+ this->MainFilePath = PathRef(MainFilePath).removeDots();
}
void PreambleFileStatusCache::update(const llvm::vfs::FileSystem &FS,
@@ -28,27 +29,28 @@ void PreambleFileStatusCache::update(const llvm::vfs::FileSystem &FS,
llvm::StringRef File) {
// Canonicalize path for later lookup, which is usually by absolute path.
llvm::SmallString<32> PathStore(File);
- if (FS.makeAbsolute(PathStore))
+ // Preserve absolute Windows paths on POSIX hosts, but still resolve
+ // drive-relative paths such as C:header.h against the current directory.
+ if (!llvm::sys::path::is_absolute(File, llvm::sys::path::Style::windows) &&
+ FS.makeAbsolute(PathStore))
return;
- llvm::sys::path::remove_dots(PathStore, /*remove_dot_dot=*/true);
+ Path Canonical = PathRef(PathStore).removeDots();
// Do not cache status for the main file.
- if (PathStore == MainFilePath)
+ if (Canonical == MainFilePath)
return;
// Stores the latest status in cache as it can change in a preamble build.
- StatCache.insert({PathStore, std::move(S)});
+ StatCache[Canonical] = std::move(S);
}
std::optional<llvm::vfs::Status>
PreambleFileStatusCache::lookup(llvm::StringRef File) const {
// Canonicalize to match the cached form.
// Lookup tends to be first by absolute path, so no need to make absolute.
- llvm::SmallString<256> PathLookup(File);
- llvm::sys::path::remove_dots(PathLookup, /*remove_dot_dot=*/true);
-
+ Path PathLookup = PathRef(File).removeDots();
auto I = StatCache.find(PathLookup);
if (I != StatCache.end())
// Returned Status name should always match the requested File.
- return llvm::vfs::Status::copyWithNewName(I->getValue(), File);
+ return llvm::vfs::Status::copyWithNewName(I->second, File);
return std::nullopt;
}
@@ -113,11 +115,5 @@ PreambleFileStatusCache::getConsumingFS(
return llvm::IntrusiveRefCntPtr<CacheVFS>(new CacheVFS(std::move(FS), *this));
}
-Path removeDots(PathRef File) {
- llvm::SmallString<128> CanonPath(File);
- llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
- return CanonPath.str().str();
-}
-
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/FS.h b/clang-tools-extra/clangd/FS.h
index 827b465aed983..b111ed0267c73 100644
--- a/clang-tools-extra/clangd/FS.h
+++ b/clang-tools-extra/clangd/FS.h
@@ -64,17 +64,10 @@ class PreambleFileStatusCache {
getConsumingFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) const;
private:
- std::string MainFilePath;
- llvm::StringMap<llvm::vfs::Status> StatCache;
+ Path MainFilePath;
+ PathMap<llvm::vfs::Status> StatCache;
};
-/// Returns a version of \p File that doesn't contain dots and dot dots.
-/// e.g /a/b/../c -> /a/c
-/// /a/b/./c -> /a/b/c
-/// FIXME: We should avoid encountering such paths in clangd internals by
-/// filtering everything we get over LSP, CDB, etc.
-Path removeDots(PathRef File);
-
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/FileDistance.cpp b/clang-tools-extra/clangd/FileDistance.cpp
index d587c26a82145..b52c65234087a 100644
--- a/clang-tools-extra/clangd/FileDistance.cpp
+++ b/clang-tools-extra/clangd/FileDistance.cpp
@@ -51,6 +51,10 @@ static llvm::SmallString<128> canonicalize(llvm::StringRef Path) {
native(Result, llvm::sys::path::Style::posix);
if (Result.empty() || Result.front() != '/')
Result.insert(Result.begin(), '/');
+ // C:\foo\bar --> /c:/foo/bar (drive letter is never case-sensitive).
+ if (Result.size() >= 3 && Result[0] == '/' && Result[2] == ':' &&
+ llvm::isAlpha(Result[1]))
+ Result[1] = llvm::toLower(Result[1]);
return Result;
}
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index adb771ecbbaad..dcbad108fb0c8 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -46,8 +46,8 @@ namespace {
// deepest directory and going up to root. Stops whenever action succeeds.
void actOnAllParentDirectories(PathRef FileName,
llvm::function_ref<bool(PathRef)> Action) {
- for (auto Path = absoluteParent(FileName); !Path.empty() && !Action(Path);
- Path = absoluteParent(Path))
+ for (auto Path = FileName.absoluteParent(); !Path.empty() && !Action(Path);
+ Path = Path.absoluteParent())
;
}
@@ -59,15 +59,15 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
// Clang treats .h files as C by default and files without extension as linker
// input, resulting in unhelpful diagnostics.
// Parsing as Objective C++ is friendly to more cases.
- auto FileExtension = llvm::sys::path::extension(File);
+ auto FileExtension = llvm::sys::path::extension(File.raw());
if (FileExtension.empty() || FileExtension == ".h")
Argv.push_back("-xobjective-c++-header");
- Argv.push_back(std::string(File));
- tooling::CompileCommand Cmd(FallbackWorkingDirectory
- ? *FallbackWorkingDirectory
- : llvm::sys::path::parent_path(File),
- llvm::sys::path::filename(File), std::move(Argv),
- /*Output=*/"");
+ Argv.push_back(File.raw().str());
+ tooling::CompileCommand Cmd(
+ FallbackWorkingDirectory ? *FallbackWorkingDirectory
+ : llvm::sys::path::parent_path(File.raw()),
+ llvm::sys::path::filename(File.raw()), std::move(Argv),
+ /*Output=*/"");
Cmd.Heuristic = "clangd fallback";
return Cmd;
}
@@ -266,7 +266,7 @@ parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
static std::unique_ptr<tooling::CompilationDatabase>
parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error) {
return tooling::FixedCompilationDatabase::loadFromBuffer(
- llvm::sys::path::parent_path(Path), Data, Error);
+ Path.parentPath().raw(), Data, Error);
}
bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
@@ -376,7 +376,7 @@ DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
return std::nullopt;
}
- auto Candidates = Res->CDB->getCompileCommands(File);
+ auto Candidates = Res->CDB->getCompileCommands(File.raw());
if (!Candidates.empty())
return std::move(Candidates.front());
@@ -393,7 +393,7 @@ DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
if (!llvm::sys::path::is_absolute(Dir))
elog("Trying to cache CDB for relative {0}");
#endif
- FoldedDirs.push_back(maybeCaseFoldPath(Dir));
+ FoldedDirs.push_back(maybeCaseFoldPath(Dir).raw());
}
std::vector<DirectoryCache *> Ret;
@@ -408,15 +408,16 @@ DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
DirectoryBasedGlobalCompilationDatabase::lookupCDB(
CDBLookupRequest Request) const {
- assert(llvm::sys::path::is_absolute(Request.FileName) &&
+ assert(llvm::sys::path::is_absolute(Request.FileName.raw()) &&
"path must be absolute");
std::string Storage;
std::vector<llvm::StringRef> SearchDirs;
if (Opts.CompileCommandsDir) // FIXME: unify this case with config.
- SearchDirs = {*Opts.CompileCommandsDir};
+ SearchDirs = {Opts.CompileCommandsDir->raw()};
else {
- WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
+ WithContext WithProvidedContext(
+ Opts.ContextProvider(Request.FileName.raw()));
const auto &Spec = Config::current().CompileFlags.CDBSearch;
switch (Spec.Policy) {
case Config::CDBSearchSpec::NoCDBSearch:
@@ -429,9 +430,9 @@ DirectoryBasedGlobalCompilationDatabase::lookupCDB(
// Traverse the canonical version to prevent false positives. i.e.:
// src/build/../a.cc can detect a CDB in /src/build if not
// canonicalized.
- Storage = removeDots(Request.FileName);
- actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
- SearchDirs.push_back(Dir);
+ Storage = Request.FileName.removeDots().raw();
+ actOnAllParentDirectories(Storage, [&](PathRef Dir) {
+ SearchDirs.push_back(Dir.raw());
return false;
});
}
@@ -598,8 +599,8 @@ class DirectoryBasedGlobalCompilationDatabase::BroadcastThread::Filter {
DirInfo *addParents(llvm::StringRef FilePath) {
DirInfo *Leaf = nullptr;
DirInfo *Child = nullptr;
- actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
- auto &Info = Dirs[Dir];
+ actOnAllParentDirectories(FilePath, [&](PathRef Dir) {
+ auto &Info = Dirs[Dir.raw()];
// If this is the first iteration, then this node is the overall result.
if (!Leaf)
Leaf = &Info;
@@ -694,7 +695,7 @@ class DirectoryBasedGlobalCompilationDatabase::BroadcastThread::Filter {
std::vector<SearchPath> SearchPaths(AllFiles.size());
for (unsigned I = 0; I < AllFiles.size(); ++I) {
if (Parent.Opts.CompileCommandsDir) { // FIXME: unify with config
- SearchPaths[I].setPointer(&Dirs[*Parent.Opts.CompileCommandsDir]);
+ SearchPaths[I].setPointer(&Dirs[Parent.Opts.CompileCommandsDir->raw()]);
continue;
}
if (ExitEarly()) // loading config may be slow
@@ -786,7 +787,7 @@ OverlayCDB::getCompileCommand(PathRef File) const {
std::optional<tooling::CompileCommand> Cmd;
{
std::lock_guard<std::mutex> Lock(Mutex);
- auto It = Commands.find(removeDots(File));
+ auto It = Commands.find(File.removeDots().raw());
if (It != Commands.end())
Cmd = It->second;
}
@@ -811,7 +812,7 @@ OverlayCDB::getCompileCommand(PathRef File) const {
if (!Cmd)
return std::nullopt;
if (Mangler)
- Mangler(*Cmd, File);
+ Mangler(*Cmd, File.raw());
return Cmd;
}
@@ -821,7 +822,7 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
FallbackFlags.end());
if (Mangler)
- Mangler(Cmd, File);
+ Mangler(Cmd, File.raw());
return Cmd;
}
@@ -830,21 +831,21 @@ bool OverlayCDB::setCompileCommand(PathRef File,
// We store a canonical version internally to prevent mismatches between set
// and get compile commands. Also it assures clients listening to broadcasts
// doesn't receive different names for the same file.
- std::string CanonPath = removeDots(File);
+ Path CanonPath = File.removeDots();
{
std::unique_lock<std::mutex> Lock(Mutex);
if (Cmd) {
if (auto [It, Inserted] =
- Commands.try_emplace(CanonPath, std::move(*Cmd));
+ Commands.try_emplace(CanonPath.raw(), std::move(*Cmd));
!Inserted) {
if (It->second == *Cmd)
return false;
It->second = *Cmd;
}
} else
- Commands.erase(CanonPath);
+ Commands.erase(CanonPath.raw());
}
- OnCommandChanged.broadcast({CanonPath});
+ OnCommandChanged.broadcast({CanonPath.raw()});
return true;
}
@@ -857,7 +858,7 @@ OverlayCDB::getProjectModules(PathRef File) const {
}
MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
PathRef CommandPath) {
- Mangler(Command, CommandPath);
+ Mangler(Command, CommandPath.raw());
});
return MDB;
}
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 415c7f50f8606..38000fc74b555 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -230,7 +230,7 @@ class OverlayCDB : public DelegatingCDB {
private:
mutable std::mutex Mutex;
- llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
+ PathMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
CommandMangler Mangler;
std::vector<std::string> FallbackFlags;
};
diff --git a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index 0bce98b0dacfd..885f7b1615485 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -26,16 +26,16 @@ std::optional<Path> getCorrespondingHeaderOrSource(
".hpp", ".hh", ".hxx", ".h++", ".h", ".inc",
".cppm", ".ccm", ".cxxm", ".c++m", ".ixx"};
- llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
+ llvm::StringRef PathExt = OriginalFile.extension();
// Lookup in a list of known extensions.
const bool IsSource =
- llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
+ llvm::any_of(SourceExtensions, [&PathExt](StringRef SourceExt) {
return SourceExt.equals_insensitive(PathExt);
});
const bool IsHeader =
- llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
+ llvm::any_of(HeaderExtensions, [&PathExt](StringRef HeaderExt) {
return HeaderExt.equals_insensitive(PathExt);
});
@@ -52,18 +52,18 @@ std::optional<Path> getCorrespondingHeaderOrSource(
NewExts = SourceExtensions;
// Storage for the new path.
- llvm::SmallString<128> NewPath = OriginalFile;
+ llvm::SmallString<128> NewPath = OriginalFile.raw();
// Loop through switched extension candidates.
for (llvm::StringRef NewExt : NewExts) {
llvm::sys::path::replace_extension(NewPath, NewExt);
if (VFS->exists(NewPath))
- return Path(NewPath);
+ return Path(NewPath.str());
// Also check NewExt in upper-case, just in case.
llvm::sys::path::replace_extension(NewPath, NewExt.upper());
if (VFS->exists(NewPath))
- return Path(NewPath);
+ return Path(NewPath.str());
}
return std::nullopt;
}
@@ -81,11 +81,11 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
if (auto ID = getSymbolID(D))
Request.IDs.insert(ID);
}
- llvm::StringMap<int> Candidates; // Target path => score.
+ PathMap<int> Candidates; // Target path => score.
auto AwardTarget = [&](const char *TargetURI) {
- if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
+ if (auto TargetPath = URI::resolve(TargetURI, OriginalFile.raw())) {
if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
- ++Candidates[*TargetPath];
+ ++Candidates[PathRef(*TargetPath)];
} else {
elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
}
@@ -96,7 +96,7 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
//
// For each symbol in the original file, we get its target location (decl or
// def) from the index, then award that target file.
- const bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
+ const bool IsHeader = isHeaderFile(OriginalFile.raw(), AST.getLangOpts());
Index->lookup(Request, [&](const Symbol &Sym) {
if (IsHeader)
AwardTarget(Sym.Definition.FileURI);
@@ -115,12 +115,12 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
if (It->second > Best->second)
Best = It;
- else if (It->second == Best->second && It->first() < Best->first())
+ else if (It->second == Best->second && It->first.raw() < Best->first.raw())
// Select the first one in the lexical order if we have multiple
// candidates.
Best = It;
}
- return Path(Best->first());
+ return Best->first;
}
std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp
index b9d67cc6a1602..21d6b1b53cf0b 100644
--- a/clang-tools-extra/clangd/Headers.cpp
+++ b/clang-tools-extra/clangd/Headers.cpp
@@ -263,7 +263,7 @@ IncludeStructure::mainFileIncludesWithSpelling(llvm::StringRef Spelling) const {
void IncludeInserter::addExisting(const Inclusion &Inc) {
IncludedHeaders.insert(Inc.Written);
if (!Inc.Resolved.empty())
- IncludedHeaders.insert(Inc.Resolved);
+ IncludedHeaders.insert(Inc.Resolved.raw());
}
/// FIXME(ioeric): we might not want to insert an absolute include path if the
@@ -273,12 +273,13 @@ bool IncludeInserter::shouldInsertInclude(
assert(InsertedHeader.valid());
if (!HeaderSearchInfo && !InsertedHeader.Verbatim)
return false;
- if (FileName == DeclaringHeader || FileName == InsertedHeader.File)
+ if (PathRef(FileName) == DeclaringHeader ||
+ PathRef(FileName) == PathRef(InsertedHeader.File))
return false;
auto Included = [&](llvm::StringRef Header) {
return IncludedHeaders.contains(Header);
};
- return !Included(DeclaringHeader) && !Included(InsertedHeader.File);
+ return !Included(DeclaringHeader.raw()) && !Included(InsertedHeader.File);
}
std::optional<std::string>
@@ -349,7 +350,7 @@ IncludeInserter::insert(llvm::StringRef VerbatimHeader,
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Inclusion &Inc) {
return OS << Inc.Written << " = "
- << (!Inc.Resolved.empty() ? Inc.Resolved : "[unresolved]")
+ << (!Inc.Resolved.empty() ? Inc.Resolved.raw() : "[unresolved]")
<< " at line" << Inc.HashLine;
}
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index a2f8b6418833d..eb85d73c5c608 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -1330,7 +1330,7 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
continue;
HoverCountMetric.record(1, "include");
HoverInfo HI;
- HI.Name = std::string(llvm::sys::path::filename(Inc.Resolved));
+ HI.Name = Inc.Resolved.ref().filename().str();
HI.Definition =
URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
HI.DefinitionLanguage = "";
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
index 92637178d4158..a5f709a6b8d3b 100644
--- a/clang-tools-extra/clangd/IncludeCleaner.cpp
+++ b/clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -100,7 +100,7 @@ bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
// Since most private -> public mappings happen in a verbatim way, we
// check textually here. This might go wrong in presence of symlinks or
// header mappings. But that's not different than rest of the places.
- if (AST.tuPath().ends_with(PHeader))
+ if (AST.tuPath().raw().ends_with(PHeader))
return false;
}
}
@@ -124,9 +124,9 @@ std::vector<Diag> generateMissingIncludeDiagnostics(
const SourceManager &SM = AST.getSourceManager();
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
- auto FileStyle = getFormatStyleForFile(AST.tuPath(), Code, TFS, false);
+ auto FileStyle = getFormatStyleForFile(AST.tuPath().raw(), Code, TFS, false);
- tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code,
+ tooling::HeaderIncludes HeaderIncludes(AST.tuPath().raw(), Code,
FileStyle.IncludeStyle);
for (const auto &SymbolWithMissingInclude : MissingIncludes) {
llvm::StringRef ResolvedPath =
@@ -175,7 +175,7 @@ std::vector<Diag> generateMissingIncludeDiagnostics(
SymbolWithMissingInclude.Symbol.name());
D.Name = "missing-includes";
D.Source = Diag::DiagSource::Clangd;
- D.File = AST.tuPath();
+ D.File = AST.tuPath().raw().str();
D.InsideMainFile = true;
// We avoid the "warning" severity here in favor of LSP's "information".
//
@@ -212,7 +212,7 @@ std::vector<Diag> generateUnusedIncludeDiagnostics(
llvm::StringRef Code, HeaderFilter IgnoreHeaders) {
std::vector<Diag> Result;
for (const auto *Inc : UnusedIncludes) {
- if (isIgnored(Inc->Resolved, IgnoreHeaders))
+ if (isIgnored(Inc->Resolved.raw(), IgnoreHeaders))
continue;
Diag &D = Result.emplace_back();
D.Message =
@@ -222,7 +222,7 @@ std::vector<Diag> generateUnusedIncludeDiagnostics(
llvm::sys::path::Style::posix));
D.Name = "unused-includes";
D.Source = Diag::DiagSource::Clangd;
- D.File = FileName;
+ D.File = FileName.raw().str();
D.InsideMainFile = true;
D.Severity = DiagnosticsEngine::Warning;
D.Tags.push_back(Unnecessary);
@@ -361,7 +361,7 @@ include_cleaner::Includes convertIncludes(const ParsedAST &AST) {
TransformedInc.Angled = WrittenRef.starts_with("<");
// Inc.Resolved is canonicalized with clangd::getCanonicalPath(),
// which is based on FileManager::getCanonicalName(ParentDir).
- auto FE = SM.getFileManager().getFileRef(Inc.Resolved);
+ auto FE = SM.getFileManager().getFileRef(Inc.Resolved.raw());
if (!FE) {
elog("IncludeCleaner: Failed to get an entry for resolved path '{0}' "
"from include {1} : {2}",
@@ -382,7 +382,7 @@ computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes) {
const auto &SM = AST.getSourceManager();
include_cleaner::Includes ConvertedIncludes = convertIncludes(AST);
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
- auto PreamblePatch = PreamblePatch::getPatchEntry(AST.tuPath(), SM);
+ auto PreamblePatch = PreamblePatch::getPatchEntry(AST.tuPath().raw(), SM);
std::vector<include_cleaner::SymbolReference> Macros =
collectMacroReferences(AST);
diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp
index 6e47122bdd82b..7dcaa04b83c8b 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.cpp
+++ b/clang-tools-extra/clangd/ModulesBuilder.cpp
@@ -69,9 +69,9 @@ std::string hashStringForCache(llvm::StringRef Content) {
}
std::string normalizePathForCache(PathRef Path) {
- llvm::SmallString<256> Normalized(Path);
+ llvm::SmallString<256> Normalized(Path.raw());
llvm::sys::path::remove_dots(Normalized, /*remove_dot_dot=*/true);
- return maybeCaseFoldPath(Normalized);
+ return maybeCaseFoldPath(Normalized).raw();
}
/// Returns the root directory used for persistent module cache storage.
@@ -115,7 +115,7 @@ std::string getModuleUnitSourcePathHash(PathRef ModuleUnitFileName) {
}
std::string getModuleUnitSourceDirectoryName(PathRef ModuleUnitFileName) {
- std::string Result = llvm::sys::path::filename(ModuleUnitFileName).str();
+ std::string Result = ModuleUnitFileName.filename().str();
Result.push_back('-');
Result.append(getModuleUnitSourcePathHash(ModuleUnitFileName));
return Result;
@@ -167,7 +167,7 @@ getModuleSourceHashLockPath(PathRef ModuleUnitFileName,
/// Returns a unique temporary path used to stage a BMI before atomically
/// publishing it to the stable cache path.
llvm::SmallString<256> getTemporaryModuleFilePath(PathRef ModuleFilePath) {
- llvm::SmallString<256> ResultPattern(ModuleFilePath);
+ llvm::SmallString<256> ResultPattern(ModuleFilePath.raw());
ResultPattern.append(".tmp-%%-%%-%%-%%-%%-%%");
llvm::SmallString<256> Result;
llvm::sys::fs::createUniquePath(ResultPattern, Result,
@@ -197,21 +197,20 @@ std::string getModuleFileVersionTimestamp() {
llvm::SmallString<256>
getCopyOnReadModuleFilePath(PathRef PublishedModuleFile) {
- llvm::SmallString<256> Result(PublishedModuleFile);
+ llvm::SmallString<256> Result(PublishedModuleFile.raw());
llvm::sys::path::remove_filename(Result);
llvm::sys::path::append(
- Result,
- llvm::formatv("{0}-{1}{2}", llvm::sys::path::stem(PublishedModuleFile),
- getModuleFileVersionTimestamp(),
- llvm::sys::path::extension(PublishedModuleFile))
- .str());
+ Result, llvm::formatv("{0}-{1}{2}", PublishedModuleFile.stem().raw(),
+ getModuleFileVersionTimestamp(),
+ PublishedModuleFile.extension())
+ .str());
return Result;
}
/// Ensures the lock anchor file exists before LockFileManager tries to acquire
/// ownership, creating parent directories as needed.
llvm::Error ensureLockAnchorFileExists(PathRef LockPath) {
- llvm::SmallString<256> LockParent(LockPath);
+ llvm::SmallString<256> LockParent(LockPath.raw());
llvm::sys::path::remove_filename(LockParent);
if (std::error_code EC = llvm::sys::fs::create_directories(LockParent))
return llvm::createStringError(llvm::formatv(
@@ -219,7 +218,7 @@ llvm::Error ensureLockAnchorFileExists(PathRef LockPath) {
int FD = -1;
if (std::error_code EC = llvm::sys::fs::openFileForWrite(
- LockPath, FD, llvm::sys::fs::CD_OpenAlways))
+ LockPath.raw(), FD, llvm::sys::fs::CD_OpenAlways))
return llvm::createStringError(llvm::formatv(
"Failed to open lock file anchor {0}: {1}", LockPath, EC.message()));
llvm::sys::Process::SafelyCloseFileDescriptor(FD);
@@ -290,7 +289,7 @@ class ScopedModuleSourceLock {
// Get the stable published module file path under \param ModuleFilesPrefix.
std::string getModuleFilePath(llvm::StringRef ModuleName,
PathRef ModuleFilesPrefix) {
- llvm::SmallString<256> ModuleFilePath(ModuleFilesPrefix);
+ llvm::SmallString<256> ModuleFilePath(ModuleFilesPrefix.raw());
auto [PrimaryModuleName, PartitionName] = ModuleName.split(':');
llvm::sys::path::append(ModuleFilePath, PrimaryModuleName);
if (!PartitionName.empty()) {
@@ -331,7 +330,7 @@ class FailedPrerequisiteModules : public PrerequisiteModules {
class ModuleFile {
protected:
ModuleFile(StringRef ModuleName, PathRef ModuleFilePath)
- : ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.str()) {}
+ : ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.raw()) {}
public:
ModuleFile() = delete;
@@ -573,7 +572,7 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,
// without treating it as a hard error.
// ReadAST will validate all input files internally and return OutOfDate
// if any file is modified.
- return Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath),
+ return Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath.raw()),
serialization::MK_MainFile, SourceLocation(),
ASTReader::ARR_OutOfDate) == ASTReader::Success;
}
@@ -598,7 +597,7 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
const ReusablePrerequisiteModules &BuiltModuleFiles,
bool &PublishedExistingModuleFile) {
PublishedExistingModuleFile = false;
- llvm::SmallString<256> ModuleFilesPrefix(ModuleFilePath);
+ llvm::SmallString<256> ModuleFilesPrefix(ModuleFilePath.raw());
llvm::sys::path::remove_filename(ModuleFilesPrefix);
if (std::error_code EC = llvm::sys::fs::create_directories(ModuleFilesPrefix))
return llvm::createStringError(
@@ -675,9 +674,9 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
ModuleUnitFileName));
}
- if (std::error_code EC =
- llvm::sys::fs::rename(TemporaryModuleFilePath, ModuleFilePath)) {
- if (!llvm::sys::fs::exists(ModuleFilePath))
+ if (std::error_code EC = llvm::sys::fs::rename(TemporaryModuleFilePath,
+ ModuleFilePath.raw())) {
+ if (!llvm::sys::fs::exists(ModuleFilePath.raw()))
return llvm::createStringError(
llvm::formatv("Failed to publish module file {0}: {1}",
ModuleFilePath, EC.message()));
@@ -698,8 +697,8 @@ copyModuleFileForRead(llvm::StringRef ModuleName,
PathRef PublishedModuleFilePath) {
llvm::SmallString<256> VersionedModuleFilePath =
getCopyOnReadModuleFilePath(PublishedModuleFilePath);
- if (std::error_code EC = llvm::sys::fs::copy_file(PublishedModuleFilePath,
- VersionedModuleFilePath))
+ if (std::error_code EC = llvm::sys::fs::copy_file(
+ PublishedModuleFilePath.raw(), VersionedModuleFilePath))
return llvm::createStringError(llvm::formatv(
"Failed to copy module file {0} to {1}: {2}", PublishedModuleFilePath,
VersionedModuleFilePath, EC.message()));
@@ -712,7 +711,7 @@ bool ReusablePrerequisiteModules::canReuse(
if (RequiredModules.empty())
return true;
- llvm::SmallVector<llvm::StringRef> BMIPaths;
+ llvm::SmallVector<PathRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF->getModuleFilePath());
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
@@ -769,7 +768,7 @@ class ModuleFileCache {
CommandHash.size() + 2);
Key.append(ModuleName);
Key.push_back('\0');
- Key.append(maybeCaseFoldPath(ModuleUnitSource));
+ Key.append(maybeCaseFoldPath(ModuleUnitSource).raw());
Key.push_back('\0');
Key.append(CommandHash);
return Key;
@@ -816,7 +815,7 @@ class ModuleNameToSourceCache {
void addUniqueEntry(llvm::StringRef ModuleName, PathRef Source) {
std::lock_guard<std::mutex> Lock(CacheMutex);
- ModuleNameToUniqueSourceCache[ModuleName] = Source.str();
+ ModuleNameToUniqueSourceCache[ModuleName] = Source.raw();
}
void eraseUniqueEntry(llvm::StringRef ModuleName) {
@@ -830,7 +829,7 @@ class ModuleNameToSourceCache {
auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName);
if (Outer == ModuleNameToMultipleSourceCache.end())
return "";
- auto Inner = Outer->second.find(maybeCaseFoldPath(RequiredSrcFile));
+ auto Inner = Outer->second.find(maybeCaseFoldPath(RequiredSrcFile).raw());
if (Inner == Outer->second.end())
return "";
return Inner->second;
@@ -840,8 +839,8 @@ class ModuleNameToSourceCache {
PathRef Source) {
std::lock_guard<std::mutex> Lock(CacheMutex);
ModuleNameToMultipleSourceCache[ModuleName]
- [maybeCaseFoldPath(RequiredSrcFile)] =
- Source.str();
+ [maybeCaseFoldPath(RequiredSrcFile).raw()] =
+ Source.raw();
}
void eraseMultipleEntry(llvm::StringRef ModuleName, PathRef RequiredSrcFile) {
@@ -849,7 +848,7 @@ class ModuleNameToSourceCache {
auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName);
if (Outer == ModuleNameToMultipleSourceCache.end())
return;
- Outer->second.erase(maybeCaseFoldPath(RequiredSrcFile));
+ Outer->second.erase(maybeCaseFoldPath(RequiredSrcFile).raw());
if (Outer->second.empty())
ModuleNameToMultipleSourceCache.erase(Outer);
}
@@ -971,7 +970,7 @@ llvm::SmallVector<std::string> getAllRequiredModules(PathRef RequiredSource,
std::vector<std::string> collectModuleFiles(PathRef CacheRoot) {
std::vector<std::string> Result;
std::error_code EC;
- for (llvm::sys::fs::recursive_directory_iterator It(CacheRoot, EC), End;
+ for (llvm::sys::fs::recursive_directory_iterator It(CacheRoot.raw(), EC), End;
It != End && !EC; It.increment(EC)) {
if (llvm::sys::path::extension(It->path()) != ".pcm")
continue;
@@ -1061,7 +1060,7 @@ void ModulesBuilder::ModulesBuilderImpl::
return;
}
- llvm::SmallString<256> CacheRoot(ProjectRoot);
+ llvm::SmallString<256> CacheRoot(ProjectRoot.raw());
llvm::sys::path::append(CacheRoot, ".cache", "clangd", "modules");
log("Running GC pass for clangd built module files under {0} with age "
"threshold {1} seconds (adjust with --modules-builder-versioned-gc-"
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index df56420cd7f24..e41b7586fbcaa 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -216,8 +216,9 @@ class ReplayPreamble : private PPCallbacks {
}
for (const auto &Inc : Includes) {
OptionalFileEntryRef File;
- if (Inc.Resolved != "")
- File = expectedToOptional(SM.getFileManager().getFileRef(Inc.Resolved));
+ if (!Inc.Resolved.empty())
+ File = expectedToOptional(
+ SM.getFileManager().getFileRef(Inc.Resolved.ref().raw()));
// Re-lex the #include directive to find its interesting parts.
auto HashLoc = SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset);
diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp
index 31f141f62eb7f..5d0edf3437b90 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -577,12 +577,12 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
// Note that we don't need to copy the input contents, preamble can live
// without those.
auto ContentsBuffer =
- llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
+ llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName.raw());
auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
Inputs.Opts.SkipPreambleBuild);
trace::Span Tracer("BuildPreamble");
- SPAN_ATTACH(Tracer, "File", FileName);
+ SPAN_ATTACH(Tracer, "File", FileName.raw());
std::vector<std::unique_ptr<FeatureModule::ASTListener>> ASTListeners;
if (Inputs.FeatureModules) {
for (auto &M : *Inputs.FeatureModules) {
@@ -628,7 +628,7 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
for (const auto &L : ASTListeners)
L->beforeExecute(CI);
});
- llvm::SmallString<32> AbsFileName(FileName);
+ llvm::SmallString<32> AbsFileName(FileName.raw());
VFS->makeAbsolute(AbsFileName);
auto StatCache = std::make_shared<PreambleFileStatusCache>(AbsFileName);
auto StatCacheFS = StatCache->getProducingFS(VFS);
@@ -724,7 +724,7 @@ bool isPreambleCompatible(const PreambleData &Preamble,
const ParseInputs &Inputs, PathRef FileName,
const CompilerInvocation &CI) {
auto ContentsBuffer =
- llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
+ llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName.raw());
auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
Inputs.Opts.SkipPreambleBuild);
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
diff --git a/clang-tools-extra/clangd/ProjectModules.cpp b/clang-tools-extra/clangd/ProjectModules.cpp
index 4ea38f4177fe9..c85aad35d86e0 100644
--- a/clang-tools-extra/clangd/ProjectModules.cpp
+++ b/clang-tools-extra/clangd/ProjectModules.cpp
@@ -24,7 +24,7 @@ namespace clang::clangd {
namespace {
llvm::SmallString<128> normalizePath(PathRef Path) {
- llvm::SmallString<128> Result(Path);
+ llvm::SmallString<128> Result(Path.raw());
llvm::sys::path::remove_dots(Result, /*remove_dot_dot=*/true);
llvm::sys::path::native(Result, llvm::sys::path::Style::posix);
return Result;
@@ -35,11 +35,11 @@ std::string normalizePath(PathRef Path, PathRef WorkingDir) {
return {};
llvm::SmallString<128> Result;
- if (llvm::sys::path::is_absolute(Path) || WorkingDir.empty())
- Result = Path;
+ if (llvm::sys::path::is_absolute(Path.raw()) || WorkingDir.empty())
+ Result = Path.raw();
else {
- Result = WorkingDir;
- llvm::sys::path::append(Result, Path);
+ Result = WorkingDir.raw();
+ llvm::sys::path::append(Result, Path.raw());
}
return normalizePath(Result).str().str();
@@ -115,7 +115,7 @@ std::optional<tooling::CompileCommand>
getCompileCommandForFile(const clang::tooling::CompilationDatabase &CDB,
PathRef FilePath,
const ProjectModules::CommandMangler &Mangler) {
- auto Candidates = CDB.getCompileCommands(FilePath);
+ auto Candidates = CDB.getCompileCommands(FilePath.raw());
if (Candidates.empty())
return std::nullopt;
@@ -246,7 +246,7 @@ ModuleDependencyScanner::scan(PathRef FilePath,
Result.ModuleName = ScanningResult->Provides->ModuleName;
auto [Iter, Inserted] = ModuleNameToSource.try_emplace(
- ScanningResult->Provides->ModuleName, FilePath);
+ ScanningResult->Provides->ModuleName, FilePath.raw().str());
if (!Inserted &&
!pathEqual(normalizePath(Iter->second), normalizePath(FilePath))) {
@@ -326,7 +326,7 @@ class ScanningAllProjectModules : public ProjectModules {
std::string getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSourceFile) override {
Scanner.globalScan(Mangler);
- return Scanner.getSourceForModuleName(ModuleName).str();
+ return Scanner.getSourceForModuleName(ModuleName).raw().str();
}
std::string getModuleNameForSource(PathRef File) override {
@@ -396,7 +396,7 @@ class CompileCommandsProjectModules : public ProjectModules {
std::string getModuleNameForSource(PathRef File) override {
indexProducerCommands();
auto It = SourceToModuleName.find(
- maybeCaseFoldPath(normalizePath(File, /*WorkingDir=*/{})));
+ maybeCaseFoldPath(normalizePath(File, /*WorkingDir=*/{})).raw());
if (It == SourceToModuleName.end() || It->second.Ambiguous)
return {};
return It->second.Name;
@@ -422,7 +422,7 @@ class CompileCommandsProjectModules : public ProjectModules {
return {};
indexProducerCommands();
- auto SourceIt = PCMToSource.find(maybeCaseFoldPath(It->second));
+ auto SourceIt = PCMToSource.find(maybeCaseFoldPath(It->second).raw());
if (SourceIt == PCMToSource.end())
return {};
@@ -467,7 +467,7 @@ class CompileCommandsProjectModules : public ProjectModules {
continue;
if (Parsed->OutputModuleFile)
- PCMToSource[maybeCaseFoldPath(*Parsed->OutputModuleFile)] =
+ PCMToSource[maybeCaseFoldPath(*Parsed->OutputModuleFile).raw()] =
Parsed->SourceFile;
ParsedCommands.push_back(std::move(*Parsed));
@@ -476,14 +476,14 @@ class CompileCommandsProjectModules : public ProjectModules {
for (const auto &Parsed : ParsedCommands) {
for (const auto &Required : Parsed.RequiredModuleFiles) {
auto SourceIt =
- PCMToSource.find(maybeCaseFoldPath(Required.getValue()));
+ PCMToSource.find(maybeCaseFoldPath(Required.getValue()).raw());
if (SourceIt == PCMToSource.end())
continue;
ModuleNameToDistinctSources[Required.getKey()].insert(
- maybeCaseFoldPath(SourceIt->second));
+ maybeCaseFoldPath(SourceIt->second).raw());
auto &Recovered =
- SourceToModuleName[maybeCaseFoldPath(SourceIt->second)];
+ SourceToModuleName[maybeCaseFoldPath(SourceIt->second).raw()];
if (Recovered.Name.empty())
Recovered.Name = Required.getKey().str();
else if (Recovered.Name != Required.getKey()) {
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index c22ada45d44cf..5bc5ad79f8cf8 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -13,6 +13,7 @@
#include "Protocol.h"
#include "URI.h"
#include "support/Logger.h"
+#include "support/Path.h"
#include "clang/Basic/LLVM.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/StringExtras.h"
@@ -43,15 +44,14 @@ bool mapOptOrNull(const llvm::json::Value &Params, llvm::StringLiteral Prop,
char LSPError::ID;
-URIForFile URIForFile::canonicalize(llvm::StringRef AbsPath,
- llvm::StringRef TUPath) {
- assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative");
- auto Resolved = URI::resolvePath(AbsPath, TUPath);
+URIForFile URIForFile::canonicalize(PathRef AbsPath, PathRef TUPath) {
+ assert(AbsPath.isAbsolute() && "the path is relative");
+ auto Resolved = URI::resolvePath(AbsPath.raw(), TUPath.raw());
if (!Resolved) {
elog("URIForFile: failed to resolve path {0} with TU path {1}: "
"{2}.\nUsing unresolved path.",
AbsPath, TUPath, Resolved.takeError());
- return URIForFile(std::string(AbsPath));
+ return URIForFile(AbsPath.owned().raw());
}
return URIForFile(std::move(*Resolved));
}
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index e81603a7b1a35..3073fcce2dc44 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -26,6 +26,7 @@
#include "URI.h"
#include "index/SymbolID.h"
#include "support/MemoryTree.h"
+#include "support/Path.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/JSON.h"
@@ -95,8 +96,7 @@ struct URIForFile {
/// Files can be referred to by several paths (e.g. in the presence of links).
/// Which one we prefer may depend on where we're coming from. \p TUPath is a
/// hint, and should usually be the main entrypoint file we're processing.
- static URIForFile canonicalize(llvm::StringRef AbsPath,
- llvm::StringRef TUPath);
+ static URIForFile canonicalize(PathRef AbsPath, PathRef TUPath);
static llvm::Expected<URIForFile> fromURI(const URI &U,
llvm::StringRef HintPath);
@@ -108,7 +108,7 @@ struct URIForFile {
std::string uri() const { return URI::createFile(File).toString(); }
friend bool operator==(const URIForFile &LHS, const URIForFile &RHS) {
- return LHS.File == RHS.File;
+ return PathRef(LHS.File) == PathRef(RHS.File);
}
friend bool operator!=(const URIForFile &LHS, const URIForFile &RHS) {
@@ -116,7 +116,7 @@ struct URIForFile {
}
friend bool operator<(const URIForFile &LHS, const URIForFile &RHS) {
- return LHS.File < RHS.File;
+ return pathCompare(LHS.File, RHS.File) < 0;
}
private:
diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h
index 274099f1f4d33..db2a2a3077d79 100644
--- a/clang-tools-extra/clangd/SourceCode.h
+++ b/clang-tools-extra/clangd/SourceCode.h
@@ -15,6 +15,7 @@
#include "Protocol.h"
#include "support/Context.h"
+#include "support/Path.h"
#include "support/ThreadsafeFS.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/Diagnostic.h"
@@ -205,8 +206,8 @@ struct Edit {
bool canApplyTo(llvm::StringRef Code) const;
};
/// A mapping from absolute file path (the one used for accessing the underlying
-/// VFS) to edits.
-using FileEdits = llvm::StringMap<Edit>;
+/// VFS) to edits. Keys use Path identity so C: and c: are the same file.
+using FileEdits = PathMap<Edit>;
/// Formats the edits and code around it according to Style. Changes
/// Replacements to formatted ones if succeeds.
diff --git a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
index 22237863ff041..6cc1cebc5583c 100644
--- a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
+++ b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp
@@ -491,7 +491,8 @@ llvm::Regex convertGlobsToRegex(llvm::ArrayRef<std::string> Globs) {
for (llvm::StringRef Glob : Globs)
RegTexts.push_back(convertGlobToRegex(Glob));
- // Tempting to pass IgnoreCase, but we don't know the FS sensitivity.
+ // This authorizes execution, so host-wide case assumptions are not enough:
+ // macOS and Windows can both have case-sensitive directories or volumes.
llvm::Regex Reg(llvm::join(RegTexts, "|"));
assert(Reg.isValid(RegTexts.front()) &&
"Created an invalid regex from globs");
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 0661ecb58008e..eb88d255e2117 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -249,15 +249,20 @@ class TUScheduler::HeaderIncluderCache {
// We should be a little careful how we store the include graph of open
// files, as each can have a large number of transitive headers.
// This representation is O(unique transitive source files).
- llvm::BumpPtrAllocator Arena;
struct Association {
- llvm::StringRef MainFile;
+ // Owned: PathMap keys move on rehash, so this cannot be a view into a key.
+ Path MainFile;
// Circular-linked-list of associations with the same mainFile.
// Null indicates that the mainfile was removed.
- Association *Next;
+ Association *Next = nullptr;
};
- llvm::StringMap<Association, llvm::BumpPtrAllocator &> HeaderToMain;
- llvm::StringMap<Association *, llvm::BumpPtrAllocator &> MainToFirst;
+ // unique_ptr: PathMap is a DenseMap; values move on rehash. The circular
+ // list and MainToFirst store Association*, which must stay stable.
+ PathMap<std::unique_ptr<Association>> HeaderToMain;
+ PathMap<Association *> MainToFirst;
+ // Track owned allocations incrementally, rather than scanning all historical
+ // headers on every update. String storage is estimated using capacity.
+ size_t OwnedBytes = 0;
std::atomic<size_t> UsedBytes; // Updated after writes.
mutable std::mutex Mu;
@@ -271,15 +276,28 @@ class TUScheduler::HeaderIncluderCache {
}
// Create the circular list and return the head of it.
- Association *associate(llvm::StringRef MainFile,
+ Association *associate(PathRef MainFile,
llvm::ArrayRef<std::string> Headers) {
Association *First = nullptr, *Prev = nullptr;
for (const std::string &Header : Headers) {
- auto &Assoc = HeaderToMain[Header];
+ if (Header.empty())
+ continue;
+ auto It = HeaderToMain.find(Header);
+ if (It == HeaderToMain.end()) {
+ auto [NewIt, Inserted] = HeaderToMain.try_emplace(
+ PathRef(Header), std::make_unique<Association>());
+ (void)Inserted;
+ It = NewIt;
+ OwnedBytes += sizeof(Association) + It->first.raw().capacity() + 1 +
+ It->second->MainFile.raw().capacity() + 1;
+ }
+ Association &Assoc = *It->second;
if (Assoc.Next)
continue; // Already has a valid association.
- Assoc.MainFile = MainFile;
+ OwnedBytes -= Assoc.MainFile.raw().capacity() + 1;
+ Assoc.MainFile = Path(MainFile);
+ OwnedBytes += Assoc.MainFile.raw().capacity() + 1;
Assoc.Next = Prev;
Prev = &Assoc;
if (!First)
@@ -291,30 +309,25 @@ class TUScheduler::HeaderIncluderCache {
}
void updateMemoryUsage() {
- auto StringMapHeap = [](const auto &Map) {
- // StringMap stores the hashtable on the heap.
- // It contains pointers to the entries, and a hashcode for each.
- return Map.getNumBuckets() * (sizeof(void *) + sizeof(unsigned));
- };
- size_t Usage = Arena.getTotalMemory() + StringMapHeap(MainToFirst) +
- StringMapHeap(HeaderToMain) + sizeof(*this);
+ size_t Usage = HeaderToMain.getMemorySize() + MainToFirst.getMemorySize() +
+ sizeof(*this) + OwnedBytes;
UsedBytes.store(Usage, std::memory_order_release);
}
public:
- HeaderIncluderCache() : HeaderToMain(Arena), MainToFirst(Arena) {
- updateMemoryUsage();
- }
+ HeaderIncluderCache() { updateMemoryUsage(); }
// Associate each header with MainFile (unless already associated).
// Headers not in the list will have their associations removed.
void update(PathRef MainFile, llvm::ArrayRef<std::string> Headers) {
std::lock_guard<std::mutex> Lock(Mu);
- auto It = MainToFirst.try_emplace(MainFile, nullptr);
+ auto It = MainToFirst.try_emplace(MainFile.raw(), nullptr);
+ if (It.second)
+ OwnedBytes += It.first->first.raw().capacity() + 1;
Association *&First = It.first->second;
if (First)
invalidate(First);
- First = associate(It.first->first(), Headers);
+ First = associate(It.first->first, Headers);
updateMemoryUsage();
}
@@ -323,19 +336,25 @@ class TUScheduler::HeaderIncluderCache {
// will be eligible for association with other files that get update()d.
void remove(PathRef MainFile) {
std::lock_guard<std::mutex> Lock(Mu);
- Association *&First = MainToFirst[MainFile];
+ auto It = MainToFirst.find(MainFile);
+ if (It == MainToFirst.end())
+ return;
+ Association *&First = It->second;
if (First) {
invalidate(First);
First = nullptr;
}
- // MainToFirst entry should stay alive, as Associations might be pointing at
- // its key.
+ // Keep the MainToFirst entry so a later update() of the same main file
+ // reuses the slot. Associations no longer alias this key.
}
/// Get the mainfile associated with Header, or the empty string if none.
std::string get(PathRef Header) const {
std::lock_guard<std::mutex> Lock(Mu);
- return HeaderToMain.lookup(Header).MainFile.str();
+ auto It = HeaderToMain.find(Header);
+ if (It == HeaderToMain.end() || !It->second)
+ return std::string();
+ return It->second->MainFile.raw();
}
size_t getUsedBytes() const {
@@ -391,8 +410,7 @@ class PreambleThrottlerRequest {
PreambleThrottlerRequest(llvm::StringRef Filename,
PreambleThrottler *Throttler,
std::condition_variable &CV)
- : Throttler(Throttler),
- Satisfied(Throttler == nullptr) {
+ : Throttler(Throttler), Satisfied(Throttler == nullptr) {
// If there is no throttler, this dummy request is always satisfied.
if (!Throttler)
return;
@@ -483,7 +501,7 @@ class PreambleThread {
break;
{
- Throttle.emplace(FileName, Throttler, ReqCV);
+ Throttle.emplace(FileName.raw(), Throttler, ReqCV);
std::optional<trace::Span> Tracer;
// If acquire succeeded synchronously, avoid status jitter.
if (!Throttle->satisfied()) {
@@ -571,9 +589,9 @@ class PreambleThread {
void build(Request Req);
mutable std::mutex Mutex;
- bool Done = false; /* GUARDED_BY(Mutex) */
- std::optional<Request> NextReq; /* GUARDED_BY(Mutex) */
- std::optional<Request> CurrentReq; /* GUARDED_BY(Mutex) */
+ bool Done = false; /* GUARDED_BY(Mutex) */
+ std::optional<Request> NextReq; /* GUARDED_BY(Mutex) */
+ std::optional<Request> CurrentReq; /* GUARDED_BY(Mutex) */
// Signaled whenever a thread populates NextReq or worker thread builds a
// Preamble.
mutable std::condition_variable ReqCV; /* GUARDED_BY(Mutex) */
@@ -737,9 +755,9 @@ class ASTWorker {
llvm::SmallVector<DebouncePolicy::clock::duration>
RebuildTimes; /* GUARDED_BY(Mutex) */
/// Set to true to signal run() to finish processing.
- bool Done; /* GUARDED_BY(Mutex) */
- std::deque<Request> Requests; /* GUARDED_BY(Mutex) */
- std::optional<Request> CurrentRequest; /* GUARDED_BY(Mutex) */
+ bool Done; /* GUARDED_BY(Mutex) */
+ std::deque<Request> Requests; /* GUARDED_BY(Mutex) */
+ std::optional<Request> CurrentRequest; /* GUARDED_BY(Mutex) */
/// Signalled whenever a new request has been scheduled or processing of a
/// request has completed.
mutable std::condition_variable RequestsCV;
@@ -822,9 +840,9 @@ ASTWorker::create(PathRef FileName, const GlobalCompilationDatabase &CDB,
new ASTWorker(FileName, CDB, IdleASTs, HeaderIncluders, Barrier,
/*RunSync=*/!Tasks, Opts, Callbacks));
if (Tasks) {
- Tasks->runAsync("ASTWorker:" + llvm::sys::path::filename(FileName),
+ Tasks->runAsync("ASTWorker:" + FileName.filename(),
[Worker]() { Worker->run(); });
- Tasks->runAsync("PreambleWorker:" + llvm::sys::path::filename(FileName),
+ Tasks->runAsync("PreambleWorker:" + FileName.filename(),
[Worker]() { Worker->PreamblePeer.run(); });
}
@@ -841,8 +859,9 @@ ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
Barrier(Barrier), Done(false), Status(FileName, Callbacks),
- PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
- Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
+ PreamblePeer(FileName.raw(), Callbacks, Opts.StorePreamblesInMemory,
+ RunSync, Opts.PreambleThrottler, Status, HeaderIncluders,
+ *this) {
// Set a fallback command because compile command can be accessed before
// `Inputs` is initialized. Other fields are only used after initialization
// from client inputs.
@@ -881,7 +900,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
HeaderIncluders.remove(ProxyFile);
} else {
// We have a reliable command for an including file, use it.
- Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd), FileName);
+ Cmd = tooling::transferCompileCommand(std::move(*ProxyCmd),
+ FileName.raw());
}
}
}
@@ -995,9 +1015,9 @@ void ASTWorker::runWithAST(
// return a compatible preamble as ASTWorker::update blocks.
std::optional<ParsedAST> NewAST;
if (Invocation) {
- NewAST = ParsedAST::build(FileName, FileInputs, std::move(Invocation),
- CompilerInvocationDiagConsumer.take(),
- getPossiblyStalePreamble());
+ NewAST = ParsedAST::build(
+ FileName.raw(), FileInputs, std::move(Invocation),
+ CompilerInvocationDiagConsumer.take(), getPossiblyStalePreamble());
++ASTBuildCount;
}
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
@@ -1210,8 +1230,9 @@ void ASTWorker::generateDiagnostics(
IdleASTs.take(this, &ASTAccessForDiag);
if (!AST || !InputsAreLatest) {
auto RebuildStartTime = DebouncePolicy::clock::now();
- std::optional<ParsedAST> NewAST = ParsedAST::build(
- FileName, Inputs, std::move(Invocation), CIDiags, *LatestPreamble);
+ std::optional<ParsedAST> NewAST =
+ ParsedAST::build(FileName.raw(), Inputs, std::move(Invocation), CIDiags,
+ *LatestPreamble);
auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
++ASTBuildCount;
// Try to record the AST-build time, to inform future update debouncing.
@@ -1323,7 +1344,7 @@ void ASTWorker::runTask(llvm::StringRef Name, llvm::function_ref<void()> Task) {
crashDumpParseInputs(llvm::errs(), FileInputs);
});
trace::Span Tracer(Name);
- WithContext WithProvidedContext(ContextProvider(FileName));
+ WithContext WithProvidedContext(ContextProvider(FileName.raw()));
Task();
}
@@ -1351,7 +1372,7 @@ void ASTWorker::startTask(llvm::StringRef Name,
}
// Allow this request to be cancelled if invalidated.
- Context Ctx = Context::current().derive(FileBeingProcessed, FileName);
+ Context Ctx = Context::current().derive(FileBeingProcessed, FileName.raw());
Canceler Invalidate = nullptr;
if (Invalidation) {
WithContext WC(std::move(Ctx));
@@ -1639,7 +1660,7 @@ TUScheduler::TUScheduler(const GlobalCompilationDatabase &CDB,
HeaderIncluders(std::make_unique<HeaderIncluderCache>()) {
// Avoid null checks everywhere.
if (!Opts.ContextProvider) {
- this->Opts.ContextProvider = [](llvm::StringRef) {
+ this->Opts.ContextProvider = [](PathRef) {
return Context::current().clone();
};
}
@@ -1662,7 +1683,7 @@ TUScheduler::~TUScheduler() {
bool TUScheduler::blockUntilIdle(Deadline D) const {
for (auto &File : Files)
- if (!File.getValue()->Worker->blockUntilIdle(D))
+ if (!File.second->Worker->blockUntilIdle(D))
return false;
if (PreambleTasks)
if (!PreambleTasks->wait(D))
@@ -1672,7 +1693,7 @@ bool TUScheduler::blockUntilIdle(Deadline D) const {
bool TUScheduler::update(PathRef File, ParseInputs Inputs,
WantDiagnostics WantDiags) {
- std::unique_ptr<FileData> &FD = Files[File];
+ std::unique_ptr<FileData> &FD = Files[File.raw()];
bool NewFile = FD == nullptr;
bool ContentChanged = false;
if (!FD) {
@@ -1691,12 +1712,12 @@ bool TUScheduler::update(PathRef File, ParseInputs Inputs,
// There might be synthetic update requests, don't change the LastActiveFile
// in such cases.
if (ContentChanged)
- LastActiveFile = File.str();
+ LastActiveFile = File.owned().raw();
return NewFile;
}
void TUScheduler::remove(PathRef File) {
- bool Removed = Files.erase(File);
+ bool Removed = Files.erase(File.raw());
if (!Removed)
elog("Trying to remove file from TUScheduler that is not tracked: {0}",
File);
@@ -1743,13 +1764,13 @@ void TUScheduler::runWithAST(
llvm::StringRef Name, PathRef File,
llvm::unique_function<void(llvm::Expected<InputsAndAST>)> Action,
TUScheduler::ASTActionInvalidation Invalidation) {
- auto It = Files.find(File);
+ auto It = Files.find(File.raw());
if (It == Files.end()) {
Action(llvm::make_error<LSPError>(
"trying to get AST for non-added document", ErrorCode::InvalidParams));
return;
}
- LastActiveFile = File.str();
+ LastActiveFile = File.owned().raw();
It->second->Worker->runWithAST(Name, std::move(Action), Invalidation);
}
@@ -1757,22 +1778,22 @@ void TUScheduler::runWithAST(
void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
PreambleConsistency Consistency,
Callback<InputsAndPreamble> Action) {
- auto It = Files.find(File);
+ auto It = Files.find(File.raw());
if (It == Files.end()) {
Action(llvm::make_error<LSPError>(
"trying to get preamble for non-added document",
ErrorCode::InvalidParams));
return;
}
- LastActiveFile = File.str();
+ LastActiveFile = File.owned().raw();
if (!PreambleTasks) {
trace::Span Tracer(Name);
- SPAN_ATTACH(Tracer, "file", File);
+ SPAN_ATTACH(Tracer, "file", File.raw());
std::shared_ptr<const ASTSignals> Signals;
std::shared_ptr<const PreambleData> Preamble =
It->second->Worker->getPossiblyStalePreamble(&Signals);
- WithContext WithProvidedContext(Opts.ContextProvider(File));
+ WithContext WithProvidedContext(Opts.ContextProvider(File.raw()));
Action(InputsAndPreamble{It->second->Contents,
It->second->Worker->getCurrentCompileCommand(),
Preamble.get(), Signals.get()});
@@ -1780,11 +1801,11 @@ void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
}
std::shared_ptr<const ASTWorker> Worker = It->second->Worker.lock();
- auto Task = [Worker, Consistency, Name = Name.str(), File = File.str(),
+ auto Task = [Worker, Consistency, Name = Name.str(), File = File.owned(),
Contents = It->second->Contents,
Command = Worker->getCurrentCompileCommand(),
Ctx = Context::current().derive(FileBeingProcessed,
- std::string(File)),
+ File.raw().str()),
Action = std::move(Action), this]() mutable {
clang::noteBottomOfStack();
ThreadCrashReporter ScopedReporter([&Name, &Contents, &Command]() {
@@ -1806,18 +1827,17 @@ void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
WithContext Guard(std::move(Ctx));
trace::Span Tracer(Name);
SPAN_ATTACH(Tracer, "file", File);
- WithContext WithProvidedContext(Opts.ContextProvider(File));
+ WithContext WithProvidedContext(Opts.ContextProvider(File.raw()));
Action(InputsAndPreamble{Contents, Command, Preamble.get(), Signals.get()});
};
- PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File),
- std::move(Task));
+ PreambleTasks->runAsync("task:" + File.filename(), std::move(Task));
}
llvm::StringMap<TUScheduler::FileStats> TUScheduler::fileStats() const {
llvm::StringMap<TUScheduler::FileStats> Result;
for (const auto &PathAndFile : Files)
- Result.try_emplace(PathAndFile.first(),
+ Result.try_emplace(PathAndFile.first.raw(),
PathAndFile.second->Worker->stats());
return Result;
}
@@ -1827,7 +1847,7 @@ std::vector<Path> TUScheduler::getFilesWithCachedAST() const {
for (auto &&PathAndFile : Files) {
if (!PathAndFile.second->Worker->isASTCached())
continue;
- Result.push_back(std::string(PathAndFile.first()));
+ Result.push_back(PathAndFile.first);
}
return Result;
}
@@ -1867,8 +1887,8 @@ void TUScheduler::profile(MemoryTree &MT) const {
.addUsage(Opts.StorePreamblesInMemory ? Elem.second.UsedBytesPreamble
: 0);
MT.detail(Elem.first()).child("ast").addUsage(Elem.second.UsedBytesAST);
- MT.child("header_includer_cache").addUsage(HeaderIncluders->getUsedBytes());
}
+ MT.child("header_includer_cache").addUsage(HeaderIncluders->getUsedBytes());
}
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h
index d0da20310a8b2..2725a7bb4e78c 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -368,7 +368,7 @@ class TUScheduler {
std::unique_ptr<ParsingCallbacks> Callbacks; // not nullptr
Semaphore Barrier;
Semaphore QuickRunBarrier;
- llvm::StringMap<std::unique_ptr<FileData>> Files;
+ PathMap<std::unique_ptr<FileData>> Files;
std::unique_ptr<ASTCache> IdleASTs;
std::unique_ptr<HeaderIncluderCache> HeaderIncluders;
// std::nullopt when running tasks synchronously and non-std::nullopt when
diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp
index aae7d6b126c5a..c895e2d9a9ce5 100644
--- a/clang-tools-extra/clangd/TidyProvider.cpp
+++ b/clang-tools-extra/clangd/TidyProvider.cpp
@@ -64,7 +64,7 @@ class DotClangTidyCache : private FileCache {
}
};
if (auto Parsed = tidy::parseConfigurationWithDiags(
- llvm::MemoryBufferRef(*Data, path()), Diagnostics))
+ llvm::MemoryBufferRef(*Data, path().raw()), Diagnostics))
Value = std::make_shared<const tidy::ClangTidyOptions>(
std::move(*Parsed));
else
@@ -99,7 +99,7 @@ class DotClangTidyTree {
void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) {
namespace path = llvm::sys::path;
- assert(path::is_absolute(AbsPath));
+ assert(path::is_absolute(AbsPath.raw()));
// Compute absolute paths to all ancestors (substrings of P.Path).
// Ensure cache entries for each ancestor exist in the map.
@@ -108,12 +108,12 @@ class DotClangTidyTree {
std::lock_guard<std::mutex> Lock(Mu);
for (auto Ancestor = absoluteParent(AbsPath); !Ancestor.empty();
Ancestor = absoluteParent(Ancestor)) {
- auto It = Cache.find(Ancestor);
+ auto It = Cache.find(Ancestor.raw());
// Assemble the actual config file path only if needed.
if (It == Cache.end()) {
- llvm::SmallString<256> ConfigPath = Ancestor;
+ llvm::SmallString<256> ConfigPath(Ancestor.raw());
path::append(ConfigPath, RelPath);
- It = Cache.try_emplace(Ancestor, ConfigPath.str()).first;
+ It = Cache.try_emplace(Ancestor.raw(), ConfigPath.str()).first;
}
Caches.push_back(&It->second);
}
@@ -320,8 +320,8 @@ bool isRegisteredTidyCheck(llvm::StringRef Check) {
std::optional<bool> isFastTidyCheck(llvm::StringRef Check) {
static auto &Fast = *new llvm::StringMap<bool>{
-#define FAST(CHECK, TIME) {#CHECK,true},
-#define SLOW(CHECK, TIME) {#CHECK,false},
+#define FAST(CHECK, TIME) {#CHECK, true},
+#define SLOW(CHECK, TIME) {#CHECK, false},
#include "TidyFastChecks.inc"
};
if (auto It = Fast.find(Check); It != Fast.end())
diff --git a/clang-tools-extra/clangd/URI.h b/clang-tools-extra/clangd/URI.h
index d4629f17551cc..138580198c2c3 100644
--- a/clang-tools-extra/clangd/URI.h
+++ b/clang-tools-extra/clangd/URI.h
@@ -9,6 +9,8 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_URI_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_URI_H
+#include "support/Path.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Registry.h"
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 86528d806eab3..07bef4919237c 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -242,7 +242,7 @@ std::optional<LocatedSymbol> locateFileReferent(const Position &Pos,
for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) {
if (!Inc.Resolved.empty() && Inc.HashLine == Pos.line) {
LocatedSymbol File;
- File.Name = std::string(llvm::sys::path::filename(Inc.Resolved));
+ File.Name = Inc.Resolved.ref().filename().str();
File.PreferredDeclaration = {
URIForFile::canonicalize(Inc.Resolved, MainFilePath), Range{}};
File.Definition = File.PreferredDeclaration;
@@ -563,7 +563,7 @@ std::vector<LocatedSymbol> locateSymbolForType(const ParsedAST &AST,
const QualType &Type,
const SymbolIndex *Index) {
const auto &SM = AST.getSourceManager();
- auto MainFilePath = AST.tuPath();
+ auto MainFilePath = AST.tuPath().raw();
// FIXME: this sends unique_ptr<Foo> to unique_ptr<T>.
// Likely it would be better to send it to Foo (heuristically) or to both.
@@ -825,7 +825,7 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word,
std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
const SymbolIndex *Index) {
const auto &SM = AST.getSourceManager();
- auto MainFilePath = AST.tuPath();
+ auto MainFilePath = AST.tuPath().raw();
if (auto File = locateFileReferent(Pos, AST, MainFilePath))
return {std::move(*File)};
@@ -1420,7 +1420,7 @@ std::vector<LocatedSymbol> findImplementations(ParsedAST &AST, Position Pos,
QueryKind = RelationKind::BaseOf;
}
}
- return findImplementors(std::move(IDs), QueryKind, Index, AST.tuPath());
+ return findImplementors(std::move(IDs), QueryKind, Index, AST.tuPath().raw());
}
namespace {
@@ -1638,8 +1638,9 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
return;
}
const auto LSPLocDecl =
- toLSPLocation(Object.CanonicalDeclaration, MainFilePath);
- const auto LSPLocDef = toLSPLocation(Object.Definition, MainFilePath);
+ toLSPLocation(Object.CanonicalDeclaration, MainFilePath.raw());
+ const auto LSPLocDef =
+ toLSPLocation(Object.Definition, MainFilePath.raw());
if (LSPLocDecl && LSPLocDecl != LSPLocDef) {
ReferencesResult::Reference Result;
Result.Loc = {std::move(*LSPLocDecl), std::nullopt};
@@ -1690,10 +1691,11 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
LookupRequest ContainerLookup;
llvm::DenseMap<SymbolID, std::vector<size_t>> RefIndicesForContainer;
Results.HasMore |= Index->refs(Req, [&](const Ref &R) {
- auto LSPLoc = toLSPLocation(R.Location, MainFilePath);
+ auto LSPLoc = toLSPLocation(R.Location, MainFilePath.raw());
// Avoid indexed results for the main file - the AST is authoritative.
if (!LSPLoc ||
- (!AllowMainFileSymbols && LSPLoc->uri.file() == MainFilePath))
+ (!AllowMainFileSymbols &&
+ PathRef(LSPLoc->uri.file()) == PathRef(MainFilePath)))
return;
ReferencesResult::Reference Result;
Result.Loc = {std::move(*LSPLoc), std::nullopt};
@@ -1768,9 +1770,9 @@ std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos) {
}
if (const NamedDecl *Def = getDefinition(D))
NewSymbol.definitionRange = makeLocation(
- AST.getASTContext(), nameLocation(*Def, SM), MainFilePath);
- NewSymbol.declarationRange =
- makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath);
+ AST.getASTContext(), nameLocation(*Def, SM), MainFilePath.raw());
+ NewSymbol.declarationRange = makeLocation(
+ AST.getASTContext(), nameLocation(*D, SM), MainFilePath.raw());
Results.push_back(std::move(NewSymbol));
}
@@ -1890,7 +1892,7 @@ declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
template <typename HierarchyItem>
static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
PathRef TUPath) {
- auto Loc = symbolToLocation(S, TUPath);
+ auto Loc = symbolToLocation(S, TUPath.raw());
if (!Loc) {
elog("Failed to convert symbol to hierarchy item: {0}", Loc.takeError());
return std::nullopt;
@@ -2314,12 +2316,12 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
}
std::optional<TypeHierarchyItem> Result =
- declToTypeHierarchyItem(*CXXRD, AST.tuPath());
+ declToTypeHierarchyItem(*CXXRD, AST.tuPath().raw());
if (!Result)
continue;
RecursionProtectionSet RPSet;
- fillSuperTypes(*CXXRD, AST.tuPath(), *Result, RPSet);
+ fillSuperTypes(*CXXRD, AST.tuPath().raw(), *Result, RPSet);
if (WantChildren && ResolveLevels > 0) {
Result->children.emplace();
@@ -2399,7 +2401,7 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
Decl->getKind() != Decl::Kind::Field &&
Decl->getKind() != Decl::Kind::EnumConstant)
continue;
- if (auto CHI = declToCallHierarchyItem(*Decl, AST.tuPath()))
+ if (auto CHI = declToCallHierarchyItem(*Decl, AST.tuPath().raw()))
Result.emplace_back(std::move(*CHI));
}
return Result;
diff --git a/clang-tools-extra/clangd/index/Background.cpp b/clang-tools-extra/clangd/index/Background.cpp
index 17a8097394492..0655c3e3c43a9 100644
--- a/clang-tools-extra/clangd/index/Background.cpp
+++ b/clang-tools-extra/clangd/index/Background.cpp
@@ -78,7 +78,7 @@ llvm::SmallString<128> getAbsolutePath(const tooling::CompileCommand &Cmd) {
}
bool shardIsStale(const LoadedShard &LS, llvm::vfs::FileSystem *FS) {
- auto Buf = FS->getBufferForFile(LS.AbsolutePath);
+ auto Buf = FS->getBufferForFile(LS.AbsolutePath.raw());
if (!Buf) {
vlog("Background-index: Couldn't read {0} to validate stored index: {1}",
LS.AbsolutePath, Buf.getError().message());
@@ -157,7 +157,7 @@ static llvm::StringRef filenameWithoutExtension(llvm::StringRef Path) {
BackgroundQueue::Task BackgroundIndex::indexFileTask(std::string Path) {
std::string Tag = filenameWithoutExtension(Path).str();
- uint64_t Key = llvm::xxh3_64bits(Path);
+ uint64_t Key = llvm::xxh3_64bits(PathRef(Path).identityNormalized().raw());
BackgroundQueue::Task T([this, Path(std::move(Path))] {
std::optional<WithContext> WithProvidedContext;
if (ContextProvider)
@@ -183,12 +183,14 @@ void BackgroundIndex::boostRelated(llvm::StringRef Path) {
/// Given index results from a TU, only update symbols coming from files that
/// are different or missing from than \p ShardVersionsSnapshot. Also stores new
/// index information on IndexStorage.
-void BackgroundIndex::update(
- llvm::StringRef MainFile, IndexFileIn Index,
- const llvm::StringMap<ShardVersion> &ShardVersionsSnapshot,
- bool HadErrors) {
- // Keys are URIs.
- llvm::StringMap<std::pair<Path, FileDigest>> FilesToUpdate;
+void BackgroundIndex::update(llvm::StringRef MainFile, IndexFileIn Index,
+ const PathMap<ShardVersion> &ShardVersionsSnapshot,
+ bool HadErrors) {
+ struct FileToUpdate {
+ std::string URI;
+ FileDigest Digest;
+ };
+ PathMap<FileToUpdate> FilesToUpdate;
// Note that sources do not contain any information regarding missing headers,
// since we don't even know what absolute path they should fall in.
for (const auto &IndexIt : *Index.Sources) {
@@ -198,12 +200,14 @@ void BackgroundIndex::update(
elog("Failed to resolve URI: {0}", AbsPath.takeError());
continue;
}
- const auto DigestIt = ShardVersionsSnapshot.find(*AbsPath);
+ Path Identity(std::move(*AbsPath));
+ const auto DigestIt = ShardVersionsSnapshot.find(Identity);
// File has different contents, or indexing was successful this time.
if (DigestIt == ShardVersionsSnapshot.end() ||
- DigestIt->getValue().Digest != IGN.Digest ||
- (DigestIt->getValue().HadErrors && !HadErrors))
- FilesToUpdate[IGN.URI] = {std::move(*AbsPath), IGN.Digest};
+ DigestIt->second.Digest != IGN.Digest ||
+ (DigestIt->second.HadErrors && !HadErrors))
+ FilesToUpdate.try_emplace(std::move(Identity),
+ FileToUpdate{IGN.URI.str(), IGN.Digest});
}
// Shard slabs into files.
@@ -211,27 +215,27 @@ void BackgroundIndex::update(
// Build and store new slabs for each updated file.
for (const auto &FileIt : FilesToUpdate) {
- auto Uri = FileIt.first();
+ llvm::StringRef Uri = FileIt.second.URI;
auto IF = ShardedIndex.getShard(Uri);
assert(IF && "no shard for file in Index.Sources?");
- PathRef Path = FileIt.getValue().first;
+ PathRef Path = FileIt.first;
// Only store command line hash for main files of the TU, since our
// current model keeps only one version of a header file.
- if (Path != MainFile)
+ if (Path != PathRef(MainFile))
IF->Cmd.reset();
// We need to store shards before updating the index, since the latter
// consumes slabs.
// FIXME: Also skip serializing the shard if it is already up-to-date.
- if (auto Error = IndexStorageFactory(Path)->storeShard(Path, *IF))
+ if (auto Error = IndexStorageFactory(Path)->storeShard(Path.raw(), *IF))
elog("Failed to write background-index shard for file {0}: {1}", Path,
std::move(Error));
{
std::lock_guard<std::mutex> Lock(ShardVersionsMu);
- const auto &Hash = FileIt.getValue().second;
- auto DigestIt = ShardVersions.try_emplace(Path);
+ const auto &Hash = FileIt.second.Digest;
+ auto DigestIt = ShardVersions.try_emplace(Path.raw());
ShardVersion &SV = DigestIt.first->second;
// Skip if file is already up to date, unless previous index was broken
// and this one is not.
@@ -247,7 +251,7 @@ void BackgroundIndex::update(
Uri, std::make_unique<SymbolSlab>(std::move(*IF->Symbols)),
std::make_unique<RefSlab>(std::move(*IF->Refs)),
std::make_unique<RelationSlab>(std::move(*IF->Relations)),
- Path == MainFile);
+ Path == PathRef(MainFile));
}
}
}
@@ -264,7 +268,7 @@ llvm::Error BackgroundIndex::index(tooling::CompileCommand Cmd) {
auto Hash = digest(Buf->get()->getBuffer());
// Take a snapshot of the versions to avoid locking for each file in the TU.
- llvm::StringMap<ShardVersion> ShardVersionsSnapshot;
+ PathMap<ShardVersion> ShardVersionsSnapshot;
{
std::lock_guard<std::mutex> Lock(ShardVersionsMu);
ShardVersionsSnapshot = ShardVersions;
@@ -383,12 +387,12 @@ BackgroundIndex::loadProject(std::vector<std::string> MainFiles) {
LS.Shard->Relations
? std::make_unique<RelationSlab>(std::move(*LS.Shard->Relations))
: nullptr;
- ShardVersion &SV = ShardVersions[LS.AbsolutePath];
+ ShardVersion &SV = ShardVersions[LS.AbsolutePath.raw()];
SV.Digest = LS.Digest;
SV.HadErrors = LS.HadErrors;
++LoadedShards;
- IndexedSymbols.update(URI::create(LS.AbsolutePath).toString(),
+ IndexedSymbols.update(URI::create(LS.AbsolutePath.raw()).toString(),
std::move(SS), std::move(RS), std::move(RelS),
LS.CountReferences);
}
@@ -414,7 +418,11 @@ BackgroundIndex::loadProject(std::vector<std::string> MainFiles) {
TUsToIndex.insert(TUForFile);
}
- return {TUsToIndex.begin(), TUsToIndex.end()};
+ std::vector<std::string> TUs;
+ TUs.reserve(TUsToIndex.size());
+ for (PathRef P : TUsToIndex)
+ TUs.push_back(P.raw().str());
+ return TUs;
}
void BackgroundIndex::profile(MemoryTree &MT) const {
diff --git a/clang-tools-extra/clangd/index/Background.h b/clang-tools-extra/clangd/index/Background.h
index 448e911201575..a79b19304ef74 100644
--- a/clang-tools-extra/clangd/index/Background.h
+++ b/clang-tools-extra/clangd/index/Background.h
@@ -194,7 +194,7 @@ class BackgroundIndex : public SwapIndex {
/// different digests than \p ShardVersionsSnapshot. Also stores new index
/// information on IndexStorage.
void update(llvm::StringRef MainFile, IndexFileIn Index,
- const llvm::StringMap<ShardVersion> &ShardVersionsSnapshot,
+ const PathMap<ShardVersion> &ShardVersionsSnapshot,
bool HadErrors);
// configuration
@@ -207,10 +207,11 @@ class BackgroundIndex : public SwapIndex {
FileSymbols IndexedSymbols;
BackgroundIndexRebuilder Rebuilder;
- llvm::StringMap<ShardVersion> ShardVersions; // Key is absolute file path.
+ PathMap<ShardVersion> ShardVersions; // Key is absolute file path.
std::mutex ShardVersionsMu;
BackgroundIndexStorage::Factory IndexStorageFactory;
+ // XXX: `MainFiles` should be a vector of `Path`s
// Tries to load shards for the MainFiles and their dependencies.
std::vector<std::string> loadProject(std::vector<std::string> MainFiles);
diff --git a/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp b/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp
index c09a5c3a3aeb8..b228ab318cc92 100644
--- a/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp
+++ b/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp
@@ -11,8 +11,8 @@
#include "index/Background.h"
#include "support/Logger.h"
#include "support/Path.h"
-#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Path.h"
+#include <queue>
#include <string>
#include <utility>
#include <vector>
@@ -41,7 +41,7 @@ class BackgroundIndexLoader {
loadShard(PathRef StartSourceFile, PathRef DependentTU);
/// Cache for Storage lookups.
- llvm::StringMap<LoadedShard> LoadedShards;
+ PathMap<LoadedShard> LoadedShards;
BackgroundIndexStorage::Factory &IndexStorageFactory;
};
@@ -49,16 +49,16 @@ class BackgroundIndexLoader {
std::pair<const LoadedShard &, std::vector<Path>>
BackgroundIndexLoader::loadShard(PathRef StartSourceFile, PathRef DependentTU) {
auto It = LoadedShards.try_emplace(StartSourceFile);
- LoadedShard &LS = It.first->getValue();
+ LoadedShard &LS = It.first->second;
std::vector<Path> Edges = {};
// Return the cached shard.
if (!It.second)
return {LS, Edges};
- LS.AbsolutePath = StartSourceFile.str();
- LS.DependentTU = std::string(DependentTU);
- BackgroundIndexStorage *Storage = IndexStorageFactory(LS.AbsolutePath);
- auto Shard = Storage->loadShard(StartSourceFile);
+ LS.AbsolutePath = StartSourceFile.owned();
+ LS.DependentTU = DependentTU.owned();
+ BackgroundIndexStorage *Storage = IndexStorageFactory(LS.AbsolutePath.raw());
+ auto Shard = Storage->loadShard(StartSourceFile.raw());
if (!Shard || !Shard->Sources) {
vlog("Failed to load shard: {0}", StartSourceFile);
return {LS, Edges};
@@ -66,7 +66,7 @@ BackgroundIndexLoader::loadShard(PathRef StartSourceFile, PathRef DependentTU) {
LS.Shard = std::move(Shard);
for (const auto &It : *LS.Shard->Sources) {
- auto AbsPath = URI::resolve(It.getKey(), StartSourceFile);
+ auto AbsPath = URI::resolve(It.getKey(), StartSourceFile.raw());
if (!AbsPath) {
elog("Failed to resolve URI: {0}", AbsPath.takeError());
continue;
@@ -88,21 +88,22 @@ BackgroundIndexLoader::loadShard(PathRef StartSourceFile, PathRef DependentTU) {
}
void BackgroundIndexLoader::load(PathRef MainFile) {
- llvm::StringSet<> InQueue;
- // Following containers points to strings inside InQueue.
- std::queue<PathRef> ToVisit;
- InQueue.insert(MainFile);
- ToVisit.push(MainFile);
+ PathSet InQueue;
+ // DenseSet keys move when the table grows, so queue owned paths rather than
+ // references into the set.
+ std::queue<Path> ToVisit;
+ InQueue.insert(Path(MainFile));
+ ToVisit.push(Path(MainFile));
while (!ToVisit.empty()) {
- PathRef SourceFile = ToVisit.front();
+ Path SourceFile = std::move(ToVisit.front());
ToVisit.pop();
auto ShardAndEdges = loadShard(SourceFile, MainFile);
- for (PathRef Edge : ShardAndEdges.second) {
+ for (Path &Edge : ShardAndEdges.second) {
auto It = InQueue.insert(Edge);
if (It.second)
- ToVisit.push(It.first->getKey());
+ ToVisit.push(std::move(Edge));
}
}
}
@@ -111,13 +112,13 @@ std::vector<LoadedShard> BackgroundIndexLoader::takeResult() && {
std::vector<LoadedShard> Result;
Result.reserve(LoadedShards.size());
for (auto &It : LoadedShards)
- Result.push_back(std::move(It.getValue()));
+ Result.push_back(std::move(It.second));
return Result;
}
} // namespace
std::vector<LoadedShard>
-loadIndexShards(llvm::ArrayRef<Path> MainFiles,
+loadIndexShards(llvm::ArrayRef<std::string> MainFiles,
BackgroundIndexStorage::Factory &IndexStorageFactory,
const GlobalCompilationDatabase &CDB) {
BackgroundIndexLoader Loader(IndexStorageFactory);
diff --git a/clang-tools-extra/clangd/index/BackgroundIndexLoader.h b/clang-tools-extra/clangd/index/BackgroundIndexLoader.h
index 81033646b6a4e..bee5ce6c6974b 100644
--- a/clang-tools-extra/clangd/index/BackgroundIndexLoader.h
+++ b/clang-tools-extra/clangd/index/BackgroundIndexLoader.h
@@ -39,7 +39,7 @@ struct LoadedShard {
/// Loads all shards for the TU \p MainFile from \p Storage.
std::vector<LoadedShard>
-loadIndexShards(llvm::ArrayRef<Path> MainFiles,
+loadIndexShards(llvm::ArrayRef<std::string> MainFiles,
BackgroundIndexStorage::Factory &IndexStorageFactory,
const GlobalCompilationDatabase &CDB);
diff --git a/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp b/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp
index 470be79590863..21744cb2bfb33 100644
--- a/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp
+++ b/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp
@@ -27,9 +27,11 @@ namespace {
std::string getShardPathFromFilePath(llvm::StringRef ShardRoot,
llvm::StringRef FilePath) {
llvm::SmallString<128> ShardRootSS(ShardRoot);
- llvm::sys::path::append(ShardRootSS, llvm::sys::path::filename(FilePath) +
- "." + llvm::toHex(digest(FilePath)) +
- ".idx");
+ llvm::sys::path::append(
+ ShardRootSS,
+ llvm::sys::path::filename(FilePath) + "." +
+ llvm::toHex(digest(PathRef(FilePath).identityNormalized().raw())) +
+ ".idx");
return std::string(ShardRootSS);
}
@@ -118,12 +120,12 @@ class DiskBackedIndexStorageManager {
// Creates or fetches to storage from cache for the specified project.
BackgroundIndexStorage *operator()(PathRef File) {
std::lock_guard<std::mutex> Lock(*IndexStorageMapMu);
- llvm::SmallString<128> StorageDir(FallbackDir);
+ llvm::SmallString<128> StorageDir(FallbackDir.raw());
if (auto PI = GetProjectInfo(File)) {
StorageDir = PI->SourceRoot;
llvm::sys::path::append(StorageDir, ".cache", "clangd", "index");
}
- auto &IndexStorage = IndexStorageMap[StorageDir];
+ auto &IndexStorage = IndexStorageMap[PathRef(StorageDir)];
if (!IndexStorage)
IndexStorage = create(StorageDir);
return IndexStorage.get();
@@ -135,12 +137,12 @@ class DiskBackedIndexStorageManager {
elog("Tried to create storage for empty directory!");
return std::make_unique<NullStorage>();
}
- return std::make_unique<DiskBackedIndexStorage>(CDBDirectory);
+ return std::make_unique<DiskBackedIndexStorage>(CDBDirectory.raw());
}
Path FallbackDir;
- llvm::StringMap<std::unique_ptr<BackgroundIndexStorage>> IndexStorageMap;
+ PathMap<std::unique_ptr<BackgroundIndexStorage>> IndexStorageMap;
std::unique_ptr<std::mutex> IndexStorageMapMu;
std::function<std::optional<ProjectInfo>(PathRef)> GetProjectInfo;
diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp
index 2e005bfe3537e..43804c112194e 100644
--- a/clang-tools-extra/clangd/index/FileIndex.cpp
+++ b/clang-tools-extra/clangd/index/FileIndex.cpp
@@ -9,10 +9,12 @@
#include "FileIndex.h"
#include "CollectMacros.h"
#include "ParsedAST.h"
+#include "URI.h"
#include "clang-include-cleaner/Record.h"
#include "index/Index.h"
#include "index/MemIndex.h"
#include "index/Merge.h"
+#include "index/PathIdentity.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Serialization.h"
@@ -29,6 +31,7 @@
#include "clang/Index/IndexingOptions.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
@@ -128,18 +131,32 @@ FileShardedIndex::FileShardedIndex(IndexFileIn Input)
: Index(std::move(Input)) {
// Used to build RelationSlabs.
llvm::DenseMap<SymbolID, FileShard *> SymbolIDToFile;
+ auto ShardFor = [&](llvm::StringRef URI) -> FileShard * {
+ auto Identity = indexFileIdentity(URI);
+ if (!Identity)
+ return nullptr;
+ auto It = Shards.find(*Identity);
+ if (It == Shards.end()) {
+ auto Shard = std::make_unique<FileShard>();
+ Shard->URI = URI.str();
+ It = Shards.try_emplace(std::move(*Identity), std::move(Shard)).first;
+ }
+ return It->second.get();
+ };
// Attribute each Symbol to both their declaration and definition locations.
if (Index.Symbols) {
for (const auto &S : *Index.Symbols) {
- auto It = Shards.try_emplace(S.CanonicalDeclaration.FileURI);
- It.first->getValue().Symbols.insert(&S);
- SymbolIDToFile[S.ID] = &It.first->getValue();
+ FileShard *Declaration = ShardFor(S.CanonicalDeclaration.FileURI);
+ if (!Declaration)
+ continue;
+ Declaration->Symbols.insert(&S);
+ SymbolIDToFile[S.ID] = Declaration;
// Only bother if definition file is different than declaration file.
- if (S.Definition &&
- S.Definition.FileURI != S.CanonicalDeclaration.FileURI) {
- auto It = Shards.try_emplace(S.Definition.FileURI);
- It.first->getValue().Symbols.insert(&S);
+ if (S.Definition) {
+ FileShard *Definition = ShardFor(S.Definition.FileURI);
+ if (Definition && Definition != Declaration)
+ Definition->Symbols.insert(&S);
}
}
}
@@ -147,9 +164,10 @@ FileShardedIndex::FileShardedIndex(IndexFileIn Input)
if (Index.Refs) {
for (const auto &SymRefs : *Index.Refs) {
for (const auto &R : SymRefs.second) {
- const auto It = Shards.try_emplace(R.Location.FileURI);
- It.first->getValue().Refs.insert(&R);
- RefToSymID[&R] = SymRefs.first;
+ if (FileShard *Shard = ShardFor(R.Location.FileURI)) {
+ Shard->Refs.insert(&R);
+ RefToSymID[&R] = SymRefs.first;
+ }
}
}
}
@@ -174,45 +192,47 @@ FileShardedIndex::FileShardedIndex(IndexFileIn Input)
if (Index.Sources) {
const auto &FullGraph = *Index.Sources;
for (const auto &It : FullGraph) {
- auto ShardIt = Shards.try_emplace(It.first());
- ShardIt.first->getValue().IG = getSubGraph(It.first(), FullGraph);
+ if (FileShard *Shard = ShardFor(It.first()))
+ Shard->IG = getSubGraph(It.first(), FullGraph);
}
}
}
std::vector<llvm::StringRef> FileShardedIndex::getAllSources() const {
- // It should be enough to construct a vector with {Shards.keys().begin(),
- // Shards.keys().end()} but MSVC fails to compile that.
- std::vector<PathRef> Result;
+ std::vector<llvm::StringRef> Result;
Result.reserve(Shards.size());
- for (auto Key : Shards.keys())
- Result.push_back(Key);
+ for (const auto &Entry : Shards)
+ Result.push_back(Entry.second->URI);
return Result;
}
std::optional<IndexFileIn>
FileShardedIndex::getShard(llvm::StringRef Uri) const {
- auto It = Shards.find(Uri);
+ auto Identity = indexFileIdentity(Uri);
+ if (!Identity)
+ return std::nullopt;
+ auto It = Shards.find(*Identity);
if (It == Shards.end())
return std::nullopt;
+ const FileShard &Shard = *It->second;
IndexFileIn IF;
- IF.Sources = It->getValue().IG;
+ IF.Sources = Shard.IG;
IF.Cmd = Index.Cmd;
SymbolSlab::Builder SymB;
- for (const auto *S : It->getValue().Symbols)
+ for (const auto *S : Shard.Symbols)
SymB.insert(*S);
IF.Symbols = std::move(SymB).build();
RefSlab::Builder RefB;
- for (const auto *Ref : It->getValue().Refs) {
+ for (const auto *Ref : Shard.Refs) {
auto SID = RefToSymID.lookup(Ref);
RefB.insert(SID, *Ref);
}
IF.Refs = std::move(RefB).build();
RelationSlab::Builder RelB;
- for (const auto *Rel : It->getValue().Relations) {
+ for (const auto *Rel : Shard.Relations) {
RelB.insert(*Rel);
}
IF.Relations = std::move(RelB).build();
@@ -249,24 +269,27 @@ void FileSymbols::update(llvm::StringRef Key,
std::unique_ptr<RefSlab> Refs,
std::unique_ptr<RelationSlab> Relations,
bool CountReferences) {
+ auto Id = indexFileIdentity(Key);
+ if (!Id)
+ return;
std::lock_guard<std::mutex> Lock(Mutex);
++Version;
if (!Symbols)
- SymbolsSnapshot.erase(Key);
+ SymbolsSnapshot.erase(*Id);
else
- SymbolsSnapshot[Key] = std::move(Symbols);
+ SymbolsSnapshot[*Id] = std::move(Symbols);
if (!Refs) {
- RefsSnapshot.erase(Key);
+ RefsSnapshot.erase(*Id);
} else {
RefSlabAndCountReferences Item;
Item.CountReferences = CountReferences;
Item.Slab = std::move(Refs);
- RefsSnapshot[Key] = std::move(Item);
+ RefsSnapshot[*Id] = std::move(Item);
}
if (!Relations)
- RelationsSnapshot.erase(Key);
+ RelationsSnapshot.erase(*Id);
else
- RelationsSnapshot[Key] = std::move(Relations);
+ RelationsSnapshot[*Id] = std::move(Relations);
}
std::unique_ptr<SymbolIndex>
@@ -275,22 +298,22 @@ FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle,
std::vector<std::shared_ptr<SymbolSlab>> SymbolSlabs;
std::vector<std::shared_ptr<RefSlab>> RefSlabs;
std::vector<std::shared_ptr<RelationSlab>> RelationSlabs;
- llvm::StringSet<> Files;
+ IndexFileSet Files;
std::vector<RefSlab *> MainFileRefs;
{
std::lock_guard<std::mutex> Lock(Mutex);
for (const auto &FileAndSymbols : SymbolsSnapshot) {
SymbolSlabs.push_back(FileAndSymbols.second);
- Files.insert(FileAndSymbols.first());
+ Files.insert(FileAndSymbols.first);
}
for (const auto &FileAndRefs : RefsSnapshot) {
RefSlabs.push_back(FileAndRefs.second.Slab);
- Files.insert(FileAndRefs.first());
+ Files.insert(FileAndRefs.first);
if (FileAndRefs.second.CountReferences)
MainFileRefs.push_back(RefSlabs.back().get());
}
for (const auto &FileAndRelations : RelationsSnapshot) {
- Files.insert(FileAndRelations.first());
+ Files.insert(FileAndRelations.first);
RelationSlabs.push_back(FileAndRelations.second);
}
@@ -404,17 +427,17 @@ FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle,
void FileSymbols::profile(MemoryTree &MT) const {
std::lock_guard<std::mutex> Lock(Mutex);
for (const auto &SymSlab : SymbolsSnapshot) {
- MT.detail(SymSlab.first())
+ MT.detail(SymSlab.first.raw())
.child("symbols")
.addUsage(SymSlab.second->bytes());
}
for (const auto &RefSlab : RefsSnapshot) {
- MT.detail(RefSlab.first())
+ MT.detail(RefSlab.first.raw())
.child("references")
.addUsage(RefSlab.second.Slab->bytes());
}
for (const auto &RelSlab : RelationsSnapshot) {
- MT.detail(RelSlab.first())
+ MT.detail(RelSlab.first.raw())
.child("relations")
.addUsage(RelSlab.second->bytes());
}
@@ -471,7 +494,7 @@ void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version,
void FileIndex::updateMain(PathRef Path, ParsedAST &AST) {
auto Contents = indexMainDecls(AST);
MainFileSymbols.update(
- URI::create(Path).toString(),
+ URI::create(Path.raw()).toString(),
std::make_unique<SymbolSlab>(std::move(std::get<0>(Contents))),
std::make_unique<RefSlab>(std::move(std::get<1>(Contents))),
std::make_unique<RelationSlab>(std::move(std::get<2>(Contents))),
diff --git a/clang-tools-extra/clangd/index/FileIndex.h b/clang-tools-extra/clangd/index/FileIndex.h
index 86af5ee3723f6..d233dab0ef4d4 100644
--- a/clang-tools-extra/clangd/index/FileIndex.h
+++ b/clang-tools-extra/clangd/index/FileIndex.h
@@ -19,6 +19,7 @@
#include "clang-include-cleaner/Record.h"
#include "index/Index.h"
#include "index/Merge.h"
+#include "index/PathIdentity.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Serialization.h"
@@ -100,9 +101,9 @@ class FileSymbols {
mutable std::mutex Mutex;
size_t Version = 0;
- llvm::StringMap<std::shared_ptr<SymbolSlab>> SymbolsSnapshot;
- llvm::StringMap<RefSlabAndCountReferences> RefsSnapshot;
- llvm::StringMap<std::shared_ptr<RelationSlab>> RelationsSnapshot;
+ IndexFileMap<std::shared_ptr<SymbolSlab>> SymbolsSnapshot;
+ IndexFileMap<RefSlabAndCountReferences> RefsSnapshot;
+ IndexFileMap<std::shared_ptr<RelationSlab>> RelationsSnapshot;
};
/// This manages symbols from files and an in-memory index on all symbols.
@@ -170,11 +171,11 @@ SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
/// Takes slabs coming from a TU (multiple files) and shards them per
/// declaration location.
struct FileShardedIndex {
- /// \p HintPath is used to convert file URIs stored in symbols into absolute
- /// paths.
+ /// File URIs use conservative filesystem-path identity. Other URI schemes
+ /// remain opaque, case-sensitive keys.
explicit FileShardedIndex(IndexFileIn Input);
- /// Returns uris for all files that has a shard.
+ /// Returns the first-observed URI spelling for each file with a shard.
std::vector<llvm::StringRef> getAllSources() const;
/// Generates index shard for the \p Uri. Note that this function results in
@@ -186,6 +187,8 @@ struct FileShardedIndex {
private:
// Contains all the information that belongs to a single file.
struct FileShard {
+ // First URI spelling observed for this index key.
+ std::string URI;
// Either declared or defined in the file.
llvm::DenseSet<const Symbol *> Symbols;
// Reference occurs in the file.
@@ -198,8 +201,10 @@ struct FileShardedIndex {
// Keeps all the information alive.
const IndexFileIn Index;
- // Mapping from URIs to slab information.
- llvm::StringMap<FileShard> Shards;
+ // Mapping from path or opaque URI identity to slab information. FileShard is
+ // separately allocated because SymbolIDToFile retains pointers while this
+ // DenseMap grows.
+ IndexFileMap<std::unique_ptr<FileShard>> Shards;
// Used to build RefSlabs.
llvm::DenseMap<const Ref *, SymbolID> RefToSymID;
};
diff --git a/clang-tools-extra/clangd/index/MemIndex.cpp b/clang-tools-extra/clangd/index/MemIndex.cpp
index feac1cf4fb7a7..fd00320e36563 100644
--- a/clang-tools-extra/clangd/index/MemIndex.cpp
+++ b/clang-tools-extra/clangd/index/MemIndex.cpp
@@ -10,6 +10,7 @@
#include "FuzzyMatch.h"
#include "Quality.h"
#include "index/Index.h"
+#include "index/PathIdentity.h"
#include "support/Trace.h"
namespace clang {
@@ -21,7 +22,7 @@ std::unique_ptr<SymbolIndex> MemIndex::build(SymbolSlab Slab, RefSlab Refs,
const auto BackingDataSize = Slab.bytes() + Refs.bytes();
auto Data = std::make_pair(std::move(Slab), std::move(Refs));
return std::make_unique<MemIndex>(Data.first, Data.second, Relations,
- std::move(Data), BackingDataSize);
+ std::move(Data), BackingDataSize);
}
bool MemIndex::fuzzyFind(
@@ -149,7 +150,11 @@ void MemIndex::reverseRelations(
llvm::unique_function<IndexContents(llvm::StringRef) const>
MemIndex::indexedFiles() const {
return [this](llvm::StringRef FileURI) {
- return Files.contains(FileURI) ? IdxContents : IndexContents::None;
+ llvm::SmallString<256> Storage;
+ auto Identity = indexFileIdentity(FileURI, Storage);
+ return Identity && Files.find_as(*Identity) != Files.end()
+ ? IdxContents
+ : IndexContents::None;
};
}
diff --git a/clang-tools-extra/clangd/index/MemIndex.h b/clang-tools-extra/clangd/index/MemIndex.h
index 8ece9994872a9..058cdaba1f9b4 100644
--- a/clang-tools-extra/clangd/index/MemIndex.h
+++ b/clang-tools-extra/clangd/index/MemIndex.h
@@ -10,8 +10,10 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_MEMINDEX_H
#include "index/Index.h"
+#include "index/PathIdentity.h"
#include "index/Relation.h"
-#include "llvm/ADT/StringSet.h"
+#include "support/Path.h"
+#include "llvm/ADT/DenseSet.h"
#include <mutex>
namespace clang {
@@ -61,7 +63,9 @@ class MemIndex : public SymbolIndex {
std::forward<RefRange>(Refs),
std::forward<RelationRange>(Relations),
std::forward<Payload>(BackingData), BackingDataSize) {
- this->Files = std::forward<FileRange>(Files);
+ for (const auto &F : Files)
+ if (auto Identity = indexFileIdentityFrom(F))
+ this->Files.insert(std::move(*Identity));
this->IdxContents = IdxContents;
}
@@ -110,7 +114,8 @@ class MemIndex : public SymbolIndex {
llvm::DenseMap<std::pair<SymbolID, uint8_t>, std::vector<SymbolID>>
ReverseRelations;
// Set of files which were used during this index build.
- llvm::StringSet<> Files;
+ // Keys are Path identity (drive letter / slashes), from URI or path.
+ IndexFileSet Files;
// Contents of the index (symbols, references, etc.)
IndexContents IdxContents = IndexContents::None;
std::shared_ptr<void> KeepAlive; // poor man's move-only std::any
diff --git a/clang-tools-extra/clangd/index/PathIdentity.cpp b/clang-tools-extra/clangd/index/PathIdentity.cpp
new file mode 100644
index 0000000000000..38b67bda91d62
--- /dev/null
+++ b/clang-tools-extra/clangd/index/PathIdentity.cpp
@@ -0,0 +1,94 @@
+//===--- PathIdentity.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 "index/PathIdentity.h"
+#include "URI.h"
+#include "support/Logger.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Path.h"
+#include <utility>
+
+namespace clang {
+namespace clangd {
+namespace {
+
+bool hasWindowsDrive(llvm::StringRef S) {
+ return S.size() >= 2 && llvm::isAlpha(S[0]) && S[1] == ':';
+}
+
+bool hasURIScheme(llvm::StringRef S) {
+ if (S.empty() || !llvm::isAlpha(S.front()))
+ return false;
+ size_t Colon = S.find(':');
+ if (Colon == llvm::StringRef::npos)
+ return false;
+ return llvm::all_of(S.take_front(Colon).drop_front(), [](char C) {
+ return llvm::isAlnum(C) || C == '+' || C == '-' || C == '.';
+ });
+}
+
+} // namespace
+
+std::optional<IndexFileKeyRef>
+indexFileIdentity(llvm::StringRef URIOrPath,
+ llvm::SmallVectorImpl<char> &Storage) {
+ // A drive path has URI-like syntax ("c:"), so recognize it first. Inputs
+ // without a valid URI scheme are unambiguously filesystem paths, including
+ // relative and UNC paths.
+ if (hasWindowsDrive(URIOrPath) || !hasURIScheme(URIOrPath))
+ return IndexFileKeyRef{URIOrPath, IndexFileKeyRef::FilePath};
+
+ if (!URIOrPath.starts_with("file:"))
+ return IndexFileKeyRef{URIOrPath, IndexFileKeyRef::OpaqueURI};
+
+ // Mirror the file scheme's authority/body handling without constructing a
+ // URI or an owned path on every index coverage query. Escapes use the parser.
+ if (!URIOrPath.contains('%')) {
+ llvm::StringRef Body = URIOrPath.drop_front(5);
+ bool HasAuthority = false;
+ if (Body.starts_with("//")) {
+ auto AuthorityAndBody = Body.drop_front(2);
+ HasAuthority = !AuthorityAndBody.starts_with("/");
+ if (!HasAuthority)
+ Body = AuthorityAndBody;
+ else if (!AuthorityAndBody.contains('/'))
+ Body = {}; // Missing absolute body: let the resolver diagnose it.
+ }
+ if (Body.starts_with("/")) {
+ if (!HasAuthority && hasWindowsDrive(Body.drop_front()))
+ Body = Body.drop_front();
+ return IndexFileKeyRef{Body, IndexFileKeyRef::FilePath};
+ }
+ }
+
+ auto Parsed = URI::parse(URIOrPath);
+ if (!Parsed) {
+ elog("Invalid index file URI {0}: {1}", URIOrPath, Parsed.takeError());
+ return std::nullopt;
+ }
+ auto Abs = URI::resolve(*Parsed, /*HintPath=*/"");
+ if (!Abs) {
+ elog("Failed to resolve index file URI {0}: {1}", URIOrPath,
+ Abs.takeError());
+ return std::nullopt;
+ }
+ Storage.assign(Abs->begin(), Abs->end());
+ return IndexFileKeyRef{llvm::StringRef(Storage.data(), Storage.size()),
+ IndexFileKeyRef::FilePath};
+}
+
+std::optional<IndexFileKey> indexFileIdentity(llvm::StringRef URIOrPath) {
+ llvm::SmallString<256> Storage;
+ if (auto Ref = indexFileIdentity(URIOrPath, Storage))
+ return IndexFileKey(*Ref);
+ return std::nullopt;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/index/PathIdentity.h b/clang-tools-extra/clangd/index/PathIdentity.h
new file mode 100644
index 0000000000000..21279e77d02a3
--- /dev/null
+++ b/clang-tools-extra/clangd/index/PathIdentity.h
@@ -0,0 +1,93 @@
+//===--- PathIdentity.h - File identity for index keys ---------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_PATHIDENTITY_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_PATHIDENTITY_H
+
+#include "support/Path.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include <optional>
+
+namespace clang {
+namespace clangd {
+
+/// Filesystem paths use Path identity. Other URI schemes are opaque and
+/// case-sensitive: resolving them can require a workspace hint we don't have.
+struct IndexFileKeyRef {
+ llvm::StringRef Value;
+ enum Kind { FilePath, OpaqueURI } K = FilePath;
+
+ friend bool operator==(IndexFileKeyRef L, IndexFileKeyRef R) {
+ return L.K == R.K && (L.K == FilePath ? pathEquals(L.Value, R.Value)
+ : L.Value == R.Value);
+ }
+};
+
+class IndexFileKey {
+public:
+ explicit IndexFileKey(IndexFileKeyRef Ref)
+ : Value(Ref.Value.str()), K(Ref.K) {}
+ operator IndexFileKeyRef() const { return {Value, K}; }
+ llvm::StringRef raw() const { return Value; }
+ friend bool operator==(const IndexFileKey &L, const IndexFileKey &R) {
+ return IndexFileKeyRef(L) == IndexFileKeyRef(R);
+ }
+ friend bool operator!=(const IndexFileKey &L, const IndexFileKey &R) {
+ return !(L == R);
+ }
+
+private:
+ std::string Value;
+ IndexFileKeyRef::Kind K;
+};
+
+struct IndexFileKeyInfo {
+ static unsigned getHashValue(IndexFileKeyRef Key) {
+ return Key.K == IndexFileKeyRef::FilePath
+ ? pathHash(Key.Value)
+ : llvm::DenseMapInfo<llvm::StringRef>::getHashValue(Key.Value);
+ }
+ static bool isEqual(IndexFileKeyRef L, IndexFileKeyRef R) { return L == R; }
+};
+
+template <typename T>
+using IndexFileMap = llvm::DenseMap<IndexFileKey, T, IndexFileKeyInfo>;
+using IndexFileSet = llvm::DenseSet<IndexFileKey, IndexFileKeyInfo>;
+
+/// Invalid file URIs are diagnosed and rejected, never treated as paths.
+std::optional<IndexFileKey> indexFileIdentity(llvm::StringRef URIOrPath);
+/// Borrowed lookup key, backed by URIOrPath or Storage. Common unescaped file
+/// URIs and opaque URIs need no allocation or scheme resolution.
+std::optional<IndexFileKeyRef>
+indexFileIdentity(llvm::StringRef URIOrPath,
+ llvm::SmallVectorImpl<char> &Storage);
+
+inline std::optional<IndexFileKey> indexFileIdentityFrom(llvm::StringRef S) {
+ return indexFileIdentity(S);
+}
+inline std::optional<IndexFileKey> indexFileIdentityFrom(PathRef P) {
+ return IndexFileKey({P.raw(), IndexFileKeyRef::FilePath});
+}
+inline std::optional<IndexFileKey> indexFileIdentityFrom(const Path &P) {
+ return indexFileIdentityFrom(P.ref());
+}
+inline std::optional<IndexFileKey>
+indexFileIdentityFrom(const IndexFileKey &K) {
+ return K;
+}
+template <typename Val>
+inline std::optional<IndexFileKey>
+indexFileIdentityFrom(const llvm::StringMapEntry<Val> &E) {
+ return indexFileIdentity(E.getKey());
+}
+
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clang-tools-extra/clangd/index/dex/Dex.cpp b/clang-tools-extra/clangd/index/dex/Dex.cpp
index 179d8c4da0b3e..a0acffb35d6b1 100644
--- a/clang-tools-extra/clangd/index/dex/Dex.cpp
+++ b/clang-tools-extra/clangd/index/dex/Dex.cpp
@@ -12,6 +12,7 @@
#include "Quality.h"
#include "URI.h"
#include "index/Index.h"
+#include "index/PathIdentity.h"
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "index/dex/Trigram.h"
@@ -404,7 +405,11 @@ void Dex::reverseRelations(
llvm::unique_function<IndexContents(llvm::StringRef) const>
Dex::indexedFiles() const {
return [this](llvm::StringRef FileURI) {
- return Files.contains(FileURI) ? IdxContents : IndexContents::None;
+ llvm::SmallString<256> Storage;
+ auto Identity = indexFileIdentity(FileURI, Storage);
+ return Identity && Files.find_as(*Identity) != Files.end()
+ ? IdxContents
+ : IndexContents::None;
};
}
diff --git a/clang-tools-extra/clangd/index/dex/Dex.h b/clang-tools-extra/clangd/index/dex/Dex.h
index 1ea7d8c06c67c..6f06b34ed4be5 100644
--- a/clang-tools-extra/clangd/index/dex/Dex.h
+++ b/clang-tools-extra/clangd/index/dex/Dex.h
@@ -20,12 +20,14 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_DEX_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_DEX_H
-#include "index/dex/Iterator.h"
#include "index/Index.h"
+#include "index/PathIdentity.h"
#include "index/Relation.h"
+#include "index/dex/Iterator.h"
#include "index/dex/PostingList.h"
#include "index/dex/Token.h"
-#include "llvm/ADT/StringSet.h"
+#include "support/Path.h"
+#include "llvm/ADT/DenseSet.h"
namespace clang {
namespace clangd {
@@ -76,7 +78,9 @@ class Dex : public SymbolIndex {
std::forward<RelationsRange>(Relations),
std::forward<Payload>(BackingData), BackingDataSize,
SupportContainedRefs) {
- this->Files = std::forward<FileRange>(Files);
+ for (const auto &F : Files)
+ if (auto Identity = indexFileIdentityFrom(F))
+ this->Files.insert(std::move(*Identity));
this->IdxContents = IdxContents;
}
@@ -158,7 +162,8 @@ class Dex : public SymbolIndex {
ReverseRelations;
std::shared_ptr<void> KeepAlive; // poor man's move-only std::any
// Set of files which were used during this index build.
- llvm::StringSet<> Files;
+ // Keys are Path identity (drive letter / slashes), from URI or path.
+ IndexFileSet Files;
// Contents of the index (symbols, references, etc.)
// This is only populated if `Files` is, which applies to some but not all
// consumers of this class.
diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp
index c56375b1a98d3..52c72bc204333 100644
--- a/clang-tools-extra/clangd/refactor/Rename.cpp
+++ b/clang-tools-extra/clangd/refactor/Rename.cpp
@@ -783,7 +783,8 @@ renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
auto FilePath = AST.tuPath();
auto RenameRanges = collectRenameIdentifierRanges(
RenameSymbolName(MD->getDeclName()), Code, LangOpts);
- auto RenameEdit = buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
+ auto RenameEdit =
+ buildRenameEdit(FilePath.raw(), Code, RenameRanges, NewNames);
if (!RenameEdit)
return error("failed to rename in file {0}: {1}", FilePath,
RenameEdit.takeError());
@@ -870,7 +871,7 @@ void insertTransitiveOverrides(SymbolID Base, llvm::DenseSet<SymbolID> &IDs,
// Return all rename occurrences (using the index) outside of the main file,
// grouped by the absolute file path.
-llvm::Expected<llvm::StringMap<std::vector<Range>>>
+llvm::Expected<PathMap<std::vector<Range>>>
findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
llvm::StringRef MainFile, const SymbolIndex &Index,
size_t MaxLimitFiles) {
@@ -883,14 +884,14 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index);
// Absolute file path => rename occurrences in that file.
- llvm::StringMap<std::vector<Range>> AffectedFiles;
+ PathMap<std::vector<Range>> AffectedFiles;
bool HasMore = Index.refs(RQuest, [&](const Ref &R) {
if (AffectedFiles.size() >= MaxLimitFiles)
return;
if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
return;
if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
- if (!pathEqual(*RefFilePath, MainFile))
+ if (PathRef(*RefFilePath) != PathRef(MainFile))
AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
}
});
@@ -903,11 +904,11 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
RenameDecl.getQualifiedNameAsString());
// Sort and deduplicate the results, in case that index returns duplications.
for (auto &FileAndOccurrences : AffectedFiles) {
- auto &Ranges = FileAndOccurrences.getValue();
+ auto &Ranges = FileAndOccurrences.second;
llvm::sort(Ranges);
Ranges.erase(llvm::unique(Ranges), Ranges.end());
- SPAN_ATTACH(Tracer, FileAndOccurrences.first(),
+ SPAN_ATTACH(Tracer, FileAndOccurrences.first.raw(),
static_cast<int64_t>(Ranges.size()));
}
return AffectedFiles;
@@ -936,7 +937,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
return AffectedFiles.takeError();
FileEdits Results;
for (auto &FileAndOccurrences : *AffectedFiles) {
- llvm::StringRef FilePath = FileAndOccurrences.first();
+ llvm::StringRef FilePath = FileAndOccurrences.first.raw();
auto ExpBuffer = FS.getBufferForFile(FilePath);
if (!ExpBuffer) {
@@ -966,7 +967,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
return error("failed to rename in file {0}: {1}", FilePath,
RenameEdit.takeError());
if (!RenameEdit->Replacements.empty())
- Results.insert({FilePath, std::move(*RenameEdit)});
+ Results.try_emplace(FilePath, std::move(*RenameEdit));
}
return Results;
}
@@ -1173,8 +1174,8 @@ llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
// return the main file edit if this is a within-file rename or the symbol
// being renamed is function local.
if (RenameDecl.getParentFunctionOrMethod()) {
- Result.GlobalChanges = FileEdits(
- {std::make_pair(RInputs.MainFilePath, std::move(MainFileEdits))});
+ Result.GlobalChanges.try_emplace(RInputs.MainFilePath,
+ std::move(MainFileEdits));
return Result;
}
diff --git a/clang-tools-extra/clangd/refactor/Tweak.cpp b/clang-tools-extra/clangd/refactor/Tweak.cpp
index 840843d1bfc4b..683b8c5710448 100644
--- a/clang-tools-extra/clangd/refactor/Tweak.cpp
+++ b/clang-tools-extra/clangd/refactor/Tweak.cpp
@@ -100,7 +100,7 @@ prepareTweak(StringRef ID, const Tweak::Selection &S,
return error("tweak ID {0} is invalid", ID);
}
-llvm::Expected<std::pair<Path, Edit>>
+llvm::Expected<std::pair<std::string, Edit>>
Tweak::Effect::fileEdit(const SourceManager &SM, FileID FID,
tooling::Replacements Replacements) {
Edit Ed(SM.getBufferData(FID), std::move(Replacements));
diff --git a/clang-tools-extra/clangd/refactor/Tweak.h b/clang-tools-extra/clangd/refactor/Tweak.h
index 257f44a285f88..4e8269138eda9 100644
--- a/clang-tools-extra/clangd/refactor/Tweak.h
+++ b/clang-tools-extra/clangd/refactor/Tweak.h
@@ -85,10 +85,11 @@ class Tweak {
return E;
}
+ /// XXX: This should return Path instead of std::string
/// Path is the absolute, symlink-resolved path for the file pointed by FID
/// in SM. Edit is generated from Replacements.
/// Fails if cannot figure out absolute path for FID.
- static llvm::Expected<std::pair<Path, Edit>>
+ static llvm::Expected<std::pair<std::string, Edit>>
fileEdit(const SourceManager &SM, FileID FID,
tooling::Replacements Replacements);
diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
index c9704492bf1cd..7ae311f076f34 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp
@@ -474,7 +474,7 @@ class DefineInline : public Tweak {
const tooling::Replacement DeleteFuncBody(SM, DefRange->getBegin(),
SourceLen, "");
- llvm::SmallVector<std::pair<std::string, Edit>> Edits;
+ llvm::SmallVector<std::pair<Path, Edit>> Edits;
// Edit for Target.
auto FE = Effect::fileEdit(SM, SM.getFileID(*Semicolon),
std::move(TargetFileReplacements));
@@ -499,7 +499,8 @@ class DefineInline : public Tweak {
Effect E;
for (auto &Pair : Edits)
- E.ApplyEdits.try_emplace(std::move(Pair.first), std::move(Pair.second));
+ E.ApplyEdits.try_emplace(std::move(Pair.first).raw(),
+ std::move(Pair.second));
return E;
}
diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
index dcc417b5f7d8c..ad20992480582 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -475,7 +475,7 @@ class DefineOutline : public Tweak {
}
bool prepare(const Selection &Sel) override {
- SameFile = !isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts());
+ SameFile = !isHeaderFile(Sel.AST->tuPath().raw(), Sel.AST->getLangOpts());
Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
// Bail out if the selection is not a function declaration.
@@ -561,22 +561,22 @@ class DefineOutline : public Tweak {
std::optional<Path> CCFile;
auto Anchor = getDefinitionOfAdjacentDecl(Sel);
if (Anchor) {
- CCFile = Anchor->Loc.uri.file();
+ CCFile = Path(Anchor->Loc.uri.file().str());
} else {
- CCFile = SameFile ? Sel.AST->tuPath().str()
- : getSourceFile(Sel.AST->tuPath(), Sel);
+ CCFile = SameFile ? std::optional<Path>(Sel.AST->tuPath().owned())
+ : getSourceFile(Sel.AST->tuPath().raw(), Sel);
}
if (!CCFile)
return error("Couldn't find a suitable implementation file.");
assert(Sel.FS && "FS Must be set in apply");
- auto Buffer = Sel.FS->getBufferForFile(*CCFile);
+ auto Buffer = Sel.FS->getBufferForFile(CCFile->raw());
// FIXME: Maybe we should consider creating the implementation file if it
// doesn't exist?
if (!Buffer)
return llvm::errorCodeToError(Buffer.getError());
auto Contents = Buffer->get()->getBuffer();
- SourceManagerForFile SMFF(*CCFile, Contents);
+ SourceManagerForFile SMFF(CCFile->raw(), Contents);
std::optional<Position> InsertionPos;
if (Anchor) {
@@ -622,14 +622,16 @@ class DefineOutline : public Tweak {
assert(Offset);
assert(EnclosingNamespace);
- auto FuncDef = getFunctionSourceCode(
- Source, EnclosingNamespace, Sel.AST->getTokens(),
- Sel.AST->getHeuristicResolver(),
- SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
+ auto FuncDef =
+ getFunctionSourceCode(Source, EnclosingNamespace, Sel.AST->getTokens(),
+ Sel.AST->getHeuristicResolver(),
+ SameFile && isHeaderFile(Sel.AST->tuPath().raw(),
+ Sel.AST->getLangOpts()));
if (!FuncDef)
return FuncDef.takeError();
- const tooling::Replacement InsertFunctionDef(*CCFile, *Offset, 0, *FuncDef);
+ const tooling::Replacement InsertFunctionDef(CCFile->raw(), *Offset, 0,
+ *FuncDef);
auto Effect = Effect::mainFileEdit(
SMFF.get(), tooling::Replacements(InsertFunctionDef));
if (!Effect)
@@ -657,7 +659,7 @@ class DefineOutline : public Tweak {
}
if (SameFile) {
- tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements;
+ tooling::Replacements &R = Effect->ApplyEdits[CCFile->raw()].Replacements;
R = R.merge(HeaderUpdates);
} else {
auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates);
@@ -674,7 +676,7 @@ class DefineOutline : public Tweak {
if (!Sel.Index)
return {};
std::optional<Location> Anchor;
- std::string TuURI = URI::createFile(Sel.AST->tuPath()).toString();
+ std::string TuURI = URI::createFile(Sel.AST->tuPath().raw()).toString();
auto CheckCandidate = [&](Decl *Candidate) {
assert(Candidate != Source);
if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
@@ -684,7 +686,8 @@ class DefineOutline : public Tweak {
std::optional<Location> CandidateLoc;
Sel.Index->lookup({{getSymbolID(Candidate)}}, [&](const Symbol &S) {
if (S.Definition) {
- if (auto Loc = indexToLSPLocation(S.Definition, Sel.AST->tuPath()))
+ if (auto Loc =
+ indexToLSPLocation(S.Definition, Sel.AST->tuPath().raw()))
CandidateLoc = *Loc;
else
log("getDefinitionOfAdjacentDecl: {0}", Loc.takeError());
diff --git a/clang-tools-extra/clangd/support/FileCache.cpp b/clang-tools-extra/clangd/support/FileCache.cpp
index 25de5a77e5bec..4ad9175ef4164 100644
--- a/clang-tools-extra/clangd/support/FileCache.cpp
+++ b/clang-tools-extra/clangd/support/FileCache.cpp
@@ -22,10 +22,10 @@ static constexpr uint64_t CacheDiskMismatch =
// The cached value reflects that the file doesn't exist.
static constexpr uint64_t FileNotFound = CacheDiskMismatch - 1;
-FileCache::FileCache(llvm::StringRef Path)
+FileCache::FileCache(PathRef Path)
: Path(Path), ValidTime(std::chrono::steady_clock::time_point::min()),
ModifiedTime(), Size(CacheDiskMismatch) {
- assert(llvm::sys::path::is_absolute(Path));
+ assert(llvm::sys::path::is_absolute(Path.raw()));
}
void FileCache::read(
@@ -47,9 +47,9 @@ void FileCache::read(
[&] { ValidTime = std::chrono::steady_clock::now(); });
// stat is cheaper than opening the file. It's usually unchanged.
- assert(llvm::sys::path::is_absolute(Path));
+ assert(llvm::sys::path::is_absolute(Path.raw()));
auto FS = TFS.view(/*CWD=*/std::nullopt);
- auto Stat = FS->status(Path);
+ auto Stat = FS->status(Path.raw());
if (!Stat || !Stat->isRegularFile()) {
if (Size != FileNotFound) // Allow "not found" value to be cached.
Parse(std::nullopt);
@@ -66,7 +66,7 @@ void FileCache::read(
Size = Stat->getSize();
ModifiedTime = Stat->getLastModificationTime();
// Now read the file from disk.
- if (auto Buf = FS->getBufferForFile(Path)) {
+ if (auto Buf = FS->getBufferForFile(Path.raw())) {
Parse(Buf->get()->getBuffer());
// Result is cacheable if the actual read size matches the new cache key.
// (We can't update the cache key, because we don't know the new mtime).
diff --git a/clang-tools-extra/clangd/support/FileCache.h b/clang-tools-extra/clangd/support/FileCache.h
index edb3498528322..37404575aa319 100644
--- a/clang-tools-extra/clangd/support/FileCache.h
+++ b/clang-tools-extra/clangd/support/FileCache.h
@@ -63,7 +63,7 @@ class FileCache {
PathRef path() const { return Path; }
private:
- std::string Path;
+ Path Path;
// Members are mutable so read() can present a const interface.
// (It is threadsafe and approximates read-through to TFS).
mutable std::mutex Mu;
diff --git a/clang-tools-extra/clangd/support/Path.cpp b/clang-tools-extra/clangd/support/Path.cpp
index 1dd107cc6abef..8e4dd5e068d8b 100644
--- a/clang-tools-extra/clangd/support/Path.cpp
+++ b/clang-tools-extra/clangd/support/Path.cpp
@@ -1,4 +1,4 @@
-//===--- Path.cpp -------------------------------------------*- C++-*------===//
+//===--- Path.cpp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -7,47 +7,252 @@
//===----------------------------------------------------------------------===//
#include "support/Path.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
+#include <ostream>
+#include <string_view>
+
namespace clang {
namespace clangd {
+namespace {
+
+bool hasWindowsDrive(llvm::StringRef P) {
+ return P.size() >= 2 && llvm::isAlpha(P[0]) && P[1] == ':';
+}
+
+llvm::StringRef dropWindowsDrive(llvm::StringRef P) {
+ return hasWindowsDrive(P) ? P.drop_front(2) : P;
+}
+
+bool isPathSep(char C) { return C == '/' || C == '\\'; }
+
+bool isAbsoluteWindowsDrivePath(llvm::StringRef P) {
+ return P.size() >= 3 && hasWindowsDrive(P) && isPathSep(P[2]);
+}
+bool isWindowsUNCPath(llvm::StringRef P) {
+ return P.size() >= 2 && isPathSep(P[0]) && isPathSep(P[1]);
+}
+
+bool usesWindowsSeparators(llvm::StringRef P) {
+#ifdef _WIN32
+ return true;
+#else
+ return isAbsoluteWindowsDrivePath(P) || isWindowsUNCPath(P);
+#endif
+}
+
+llvm::sys::path::Style pathStyle(llvm::StringRef P) {
+ return usesWindowsSeparators(P) ? llvm::sys::path::Style::windows
+ : llvm::sys::path::Style::native;
+}
+
+// Compare the tail of two Windows paths. Drive letter is already stripped.
+// '/' and '\\' are the same separator. Full case folding is opt-in.
+bool pathRestEqual(llvm::StringRef LHS, llvm::StringRef RHS,
+ bool NormalizeSeparators, bool IgnoreCase) {
+ if (LHS.size() != RHS.size())
+ return false;
+ for (size_t I = 0; I < LHS.size(); ++I) {
+ unsigned char A = LHS[I], B = RHS[I];
+ if (NormalizeSeparators && isPathSep(A) && isPathSep(B))
+ continue;
+ if (IgnoreCase) {
+ A = llvm::toLower(A);
+ B = llvm::toLower(B);
+ }
+ if (A != B)
+ return false;
+ }
+ return true;
+}
+
+llvm::StringRef slashNormalized(llvm::StringRef P,
+ llvm::SmallVectorImpl<char> &Buf) {
+ if (!P.contains('\\'))
+ return P;
+ Buf.assign(P.begin(), P.end());
+ for (char &C : Buf)
+ if (C == '\\')
+ C = '/';
+ return llvm::StringRef(Buf.data(), Buf.size());
+}
+
+bool pathEqualsImpl(llvm::StringRef LHS, llvm::StringRef RHS, bool IgnoreCase) {
+ if (LHS == RHS)
+ return true;
+ const bool NormalizeSeparators =
+ usesWindowsSeparators(LHS) && usesWindowsSeparators(RHS);
+ const bool BothDrivePaths =
+ isAbsoluteWindowsDrivePath(LHS) && isAbsoluteWindowsDrivePath(RHS);
+ if (BothDrivePaths) {
+ if (llvm::toLower(LHS[0]) != llvm::toLower(RHS[0]))
+ return false;
+ LHS = dropWindowsDrive(LHS);
+ RHS = dropWindowsDrive(RHS);
+ }
+ if (NormalizeSeparators)
+ return pathRestEqual(LHS, RHS, /*NormalizeSeparators=*/true, IgnoreCase);
+ return IgnoreCase ? LHS.equals_insensitive(RHS) : LHS == RHS;
+}
+
+Path normalizedIdentity(llvm::StringRef Data, bool FoldCase) {
+ std::string Result = FoldCase ? Data.lower() : Data.str();
+ if (isAbsoluteWindowsDrivePath(Data))
+ Result[0] = llvm::toLower(Result[0]);
+ if (usesWindowsSeparators(Data)) {
+ for (char &C : Result)
+ if (C == '\\')
+ C = '/';
+ }
+ return Path(std::move(Result));
+}
+
+} // namespace
+
+bool pathEquals(llvm::StringRef LHS, llvm::StringRef RHS) {
+ return pathEqualsImpl(LHS, RHS, /*IgnoreCase=*/false);
+}
+
+bool pathEqual(PathRef LHS, PathRef RHS) {
#ifdef CLANGD_PATH_CASE_INSENSITIVE
-std::string maybeCaseFoldPath(PathRef Path) { return Path.lower(); }
-bool pathEqual(PathRef A, PathRef B) { return A.equals_insensitive(B); }
-#else // NOT CLANGD_PATH_CASE_INSENSITIVE
-std::string maybeCaseFoldPath(PathRef Path) { return Path.str(); }
-bool pathEqual(PathRef A, PathRef B) { return A == B; }
-#endif // CLANGD_PATH_CASE_INSENSITIVE
-
-PathRef absoluteParent(PathRef Path) {
- assert(llvm::sys::path::is_absolute(Path));
+ return pathEqualsImpl(LHS.raw(), RHS.raw(), /*IgnoreCase=*/true);
+#else
+ return LHS == RHS;
+#endif
+}
+
+unsigned pathHash(llvm::StringRef P) {
+ llvm::SmallString<256> Norm;
+ const bool HasWindowsDrive = isAbsoluteWindowsDrivePath(P);
+ const bool NormalizeSeparators = usesWindowsSeparators(P);
+ unsigned char Drive = 0;
+ if (HasWindowsDrive) {
+ Drive = llvm::toLower(P[0]);
+ P = dropWindowsDrive(P);
+ }
+ if (NormalizeSeparators)
+ P = slashNormalized(P, Norm);
+ unsigned Result = static_cast<unsigned>(llvm::xxh3_64bits(P));
+ // The normalized tail is the expensive part. Mix the drive into its hash
+ // directly rather than running another general-purpose hash-combine step.
+ if (HasWindowsDrive)
+ Result ^= static_cast<unsigned>(Drive) * 0x9e3779b9U;
+ return Result;
+}
+
+int pathCompare(llvm::StringRef LHS, llvm::StringRef RHS) {
+ const bool LDrive = isAbsoluteWindowsDrivePath(LHS);
+ const bool RDrive = isAbsoluteWindowsDrivePath(RHS);
+ const bool LSeparators = usesWindowsSeparators(LHS);
+ const bool RSeparators = usesWindowsSeparators(RHS);
+ for (size_t I = 0, E = std::min(LHS.size(), RHS.size()); I != E; ++I) {
+ unsigned char L = LHS[I], R = RHS[I];
+ if (I == 0) {
+ if (LDrive)
+ L = llvm::toLower(L);
+ if (RDrive)
+ R = llvm::toLower(R);
+ }
+ if (LSeparators && L == '\\')
+ L = '/';
+ if (RSeparators && R == '\\')
+ R = '/';
+ if (L != R)
+ return int(L) - int(R);
+ }
+ return LHS.size() < RHS.size() ? -1 : LHS.size() > RHS.size() ? 1 : 0;
+}
+
+PathRef PathRef::absoluteParent() const {
+ assert(llvm::sys::path::is_absolute(Data));
#if defined(_WIN32)
// llvm::sys says "C:\" is absolute, and its parent is "C:" which is relative.
// This unhelpful behavior seems to have been inherited from boost.
- if (llvm::sys::path::relative_path(Path).empty()) {
+ if (llvm::sys::path::relative_path(Data).empty())
return PathRef();
- }
#endif
- PathRef Result = llvm::sys::path::parent_path(Path);
+ llvm::StringRef Result = llvm::sys::path::parent_path(Data);
assert(Result.empty() || llvm::sys::path::is_absolute(Result));
return Result;
}
-bool pathStartsWith(PathRef Ancestor, PathRef Path,
- llvm::sys::path::Style Style) {
- assert(llvm::sys::path::is_absolute(Ancestor) &&
- llvm::sys::path::is_absolute(Path));
- // If ancestor ends with a separator drop that, so that we can match /foo/ as
- // a parent of /foo.
- if (llvm::sys::path::is_separator(Ancestor.back(), Style))
- Ancestor = Ancestor.drop_back();
- // Ensure Path starts with Ancestor.
- if (!pathEqual(Ancestor, Path.take_front(Ancestor.size())))
+bool PathRef::startsWith(PathRef Other, Style Style) const {
+ // Style describes separators, not necessarily the host path's root syntax.
+ // Config paths on Windows use POSIX separators but still have drive roots.
+ assert((isAbsolute() || isAbsolute(Style)) &&
+ (Other.isAbsolute() || Other.isAbsolute(Style)));
+ PathRef Ancestor = withoutTrailingSeparator(Style);
+ // Keep the root separator when comparing drive roots: C: alone is relative
+ // and does not have case-insensitive drive-letter identity on POSIX hosts.
+ if (Ancestor.size() == 2 && isAbsoluteWindowsDrivePath(Data))
+ return pathEqual(Data, Other.raw().take_front(3));
+ if (Ancestor.size() > Other.size())
return false;
- Path = Path.drop_front(Ancestor.size());
- // Then make sure either two paths are equal or Path has a separator
- // afterwards.
- return Path.empty() || llvm::sys::path::is_separator(Path.front(), Style);
+ if (!pathEqual(Ancestor.raw(), Other.raw().take_front(Ancestor.size())))
+ return false;
+ llvm::StringRef Rest = Other.raw().drop_front(Ancestor.size());
+ // Windows paths treat both slashes as separators even when Style is native
+ // on a POSIX host (Linux tests of C: vs c:).
+ return Rest.empty() || llvm::sys::path::is_separator(Rest.front(), Style) ||
+ (usesWindowsSeparators(Other.raw()) && isPathSep(Rest.front()));
+}
+
+Path PathRef::removeDots() const {
+ llvm::SmallString<128> CanonPath(Data);
+ llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true,
+ pathStyle(Data));
+ return Path(CanonPath.str());
+}
+
+Path PathRef::identityNormalized() const {
+ return normalizedIdentity(Data, /*FoldCase=*/false);
}
+
+Path PathRef::caseFolded() const {
+#ifdef CLANGD_PATH_CASE_INSENSITIVE
+ return normalizedIdentity(Data, /*FoldCase=*/true);
+#else
+ return identityNormalized();
+#endif
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, PathRef P) {
+ return OS << P.raw();
+}
+
+std::ostream &operator<<(std::ostream &OS, PathRef P) {
+ return OS << std::string_view(P.raw().data(), P.raw().size());
+}
+
+bool PathRef::exists() const { return llvm::sys::fs::exists(Data); }
+
} // namespace clangd
} // namespace clang
+
+namespace llvm {
+namespace cl {
+
+void parser<clang::clangd::Path>::printOptionDiff(const Option &O,
+ clang::clangd::PathRef V,
+ const OptVal &Default,
+ size_t GlobalWidth) const {
+ constexpr size_t MaxOptWidth = 8;
+ printOptionName(O, GlobalWidth);
+ outs() << "= " << V.raw();
+ outs().indent(MaxOptWidth > V.size() ? MaxOptWidth - V.size() : 0)
+ << " (default: ";
+ if (Default.hasValue())
+ outs() << Default.getValue().raw();
+ else
+ outs() << "*no default*";
+ outs() << ")\n";
+}
+
+void parser<clang::clangd::Path>::anchor() {}
+
+} // namespace cl
+} // namespace llvm
diff --git a/clang-tools-extra/clangd/support/Path.h b/clang-tools-extra/clangd/support/Path.h
index ff45a436e5f08..bc2a8efe0d770 100644
--- a/clang-tools-extra/clangd/support/Path.h
+++ b/clang-tools-extra/clangd/support/Path.h
@@ -1,19 +1,46 @@
-//===--- Path.h - Helper typedefs --------------------------------*- C++-*-===//
+//===--- Path.h - Path identity for clangd -----------------------*- 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
//
//===----------------------------------------------------------------------===//
+//
+// Identity (equality, hashing, map keys) preserves filename case: even Windows
+// and macOS can have case-sensitive directories. Only absolute Windows drive
+// letters are folded, and Windows path separators are interchangeable. This
+// handles the common CMake vs LSP mismatch (C: vs c:) without merging distinct
+// files. Operations that explicitly require legacy host-default case folding
+// use pathEqual() or maybeCaseFoldPath() instead.
+//
+// Path/PathRef deliberately do not convert to StringRef implicitly. Callers
+// must use raw() when crossing into string-based APIs, making the loss of path
+// identity visible at the call site.
+//
+//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_PATH_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_PATH_H
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FormatProviders.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <iosfwd>
#include <string>
+#include <type_traits>
+#include <utility>
-/// Whether current platform treats paths case insensitively.
+/// Whether legacy comparisons assume case-insensitive paths on this platform.
#if defined(_WIN32) || defined(__APPLE__)
#define CLANGD_PATH_CASE_INSENSITIVE
#endif
@@ -21,30 +48,306 @@
namespace clang {
namespace clangd {
-/// A typedef to represent a file path. Used solely for more descriptive
-/// signatures.
-using Path = std::string;
-/// A typedef to represent a ref to file path. Used solely for more descriptive
-/// signatures.
-using PathRef = llvm::StringRef;
-
-// For platforms where paths are case-insensitive (but case-preserving),
-// we need to do case-insensitive comparisons and use lowercase keys.
-// FIXME: Make Path a real class with desired semantics instead.
-std::string maybeCaseFoldPath(PathRef Path);
-bool pathEqual(PathRef, PathRef);
-
-/// Checks if \p Ancestor is a proper ancestor of \p Path. This is just a
-/// smarter lexical prefix match, e.g: foo/bar/baz doesn't start with foo/./bar.
-/// Both \p Ancestor and \p Path must be absolute.
-bool pathStartsWith(
- PathRef Ancestor, PathRef Path,
- llvm::sys::path::Style Style = llvm::sys::path::Style::native);
-
-/// Variant of parent_path that operates only on absolute paths.
-/// Unlike parent_path doesn't consider C: a parent of C:\.
-PathRef absoluteParent(PathRef Path);
+class PathRef;
+
+/// Owned filesystem path with conservative lexical identity.
+class Path {
+public:
+ Path() = default;
+ Path(std::string Data) : Data(std::move(Data)) {}
+ Path(const char *Data) : Data(Data) {}
+ explicit Path(PathRef Ref);
+
+ operator PathRef() const;
+ PathRef ref() const;
+
+ [[nodiscard]] const std::string &raw() const & { return Data; }
+ [[nodiscard]] std::string &&raw() && { return std::move(Data); }
+
+ [[nodiscard]] size_t size() const { return Data.size(); }
+ [[nodiscard]] bool empty() const { return Data.empty(); }
+
+private:
+ std::string Data;
+
+ friend llvm::json::Value toJSON(const Path &Path) { return Path.Data; }
+ friend bool fromJSON(const llvm::json::Value &Value, Path &Path,
+ llvm::json::Path Cursor) {
+ return fromJSON(Value, Path.Data, Cursor);
+ }
+ friend struct llvm::DenseMapInfo<Path, void>;
+};
+
+/// Non-owning filesystem path with the same identity as Path.
+class LLVM_GSL_POINTER PathRef {
+public:
+ using Style = llvm::sys::path::Style;
+
+ PathRef() = default;
+ PathRef(llvm::StringRef Ref) : Data(Ref) {}
+ PathRef(const std::string &Str) : Data(Str) {}
+ PathRef(const char *Str) : Data(Str) {}
+ template <unsigned N> PathRef(const llvm::SmallString<N> &Str) : Data(Str) {}
+
+ /// Variant of parent_path that operates only on absolute paths.
+ /// Unlike parent_path doesn't consider C: a parent of C:\.
+ [[nodiscard]] PathRef absoluteParent() const;
+
+ [[nodiscard]] PathRef parentPath(Style Style = Style::native) const {
+ return llvm::sys::path::parent_path(Data, Style);
+ }
+
+ /// True if this is a proper ancestor of \p Other, or the same path.
+ /// Lexical only: foo/bar/baz does not start with foo/./bar.
+ /// Both paths must be absolute.
+ [[nodiscard]] bool startsWith(PathRef Other,
+ Style Style = Style::native) const;
+
+ [[nodiscard]] llvm::StringRef filename(Style Style = Style::native) const {
+ return llvm::sys::path::filename(Data, Style);
+ }
+ [[nodiscard]] llvm::StringRef extension(Style Style = Style::native) const {
+ return llvm::sys::path::extension(Data, Style);
+ }
+ [[nodiscard]] PathRef stem(Style Style = Style::native) const {
+ return llvm::sys::path::stem(Data, Style);
+ }
+
+ [[nodiscard]] Path removeDots() const;
+ /// Canonical spelling for lexical identity, not a filesystem realpath.
+ [[nodiscard]] Path identityNormalized() const;
+ /// Explicit legacy host-default case folding, not the identity of Path.
+ [[nodiscard]] Path caseFolded() const;
+ [[nodiscard]] Path owned() const { return Path(*this); }
+ [[nodiscard]] llvm::StringRef raw() const { return Data; }
+
+ [[nodiscard]] PathRef
+ withoutTrailingSeparator(Style Style = Style::native) const {
+ if (!Data.empty() && llvm::sys::path::is_separator(Data.back(), Style))
+ return Data.drop_back();
+ return Data;
+ }
+
+ [[nodiscard]] size_t size() const { return Data.size(); }
+ [[nodiscard]] bool empty() const { return Data.empty(); }
+ [[nodiscard]] bool isAbsolute(Style Style = Style::native) const {
+ return llvm::sys::path::is_absolute(Data, Style);
+ }
+ [[nodiscard]] bool isRelative(Style Style = Style::native) const {
+ return llvm::sys::path::is_relative(Data, Style);
+ }
+ [[nodiscard]] bool exists() const;
+
+private:
+ llvm::StringRef Data;
+
+ friend struct llvm::DenseMapInfo<PathRef, void>;
+};
+
+inline Path::Path(PathRef Ref) : Data(Ref.raw().str()) {}
+inline Path::operator PathRef() const { return PathRef(Data); }
+inline PathRef Path::ref() const { return PathRef(Data); }
+
+// For gtest diagnostics.
+std::ostream &operator<<(std::ostream &OS, PathRef Path);
+
+/// Conservative lexical path identity (see Path.cpp).
+bool pathEquals(llvm::StringRef LHS, llvm::StringRef RHS);
+unsigned pathHash(llvm::StringRef P);
+/// Orders normalized identities without allocating temporary strings.
+int pathCompare(llvm::StringRef LHS, llvm::StringRef RHS);
+
+inline bool operator==(PathRef LHS, PathRef RHS) {
+ return pathEquals(LHS.raw(), RHS.raw());
+}
+inline bool operator!=(PathRef LHS, PathRef RHS) { return !(LHS == RHS); }
+inline bool operator==(const Path &LHS, const Path &RHS) {
+ return PathRef(LHS) == PathRef(RHS);
+}
+inline bool operator!=(const Path &LHS, const Path &RHS) {
+ return !(LHS == RHS);
+}
+
+inline llvm::hash_code hash_value(PathRef P) { return pathHash(P.raw()); }
+inline llvm::hash_code hash_value(const Path &P) { return hash_value(P.ref()); }
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, PathRef P);
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Path &P) {
+ return OS << P.ref();
+}
+
+inline llvm::json::Value toJSON(PathRef P) { return P.raw(); }
+
+// Explicit legacy comparisons retain their host-default case folding.
+inline Path maybeCaseFoldPath(PathRef P) { return P.caseFolded(); }
+bool pathEqual(PathRef A, PathRef B);
+inline bool
+pathStartsWith(PathRef Ancestor, PathRef Path,
+ llvm::sys::path::Style Style = llvm::sys::path::Style::native) {
+ return Ancestor.startsWith(Path, Style);
+}
+inline PathRef absoluteParent(PathRef P) { return P.absoluteParent(); }
+
+/// Map keyed by Path. Lookups normalize drive letters and Windows separators,
+/// preserving filename case and the first-inserted spelling.
+template <typename ValueT> class PathMap {
+ llvm::DenseMap<Path, ValueT> Impl;
+
+public:
+ using MapType = llvm::DenseMap<Path, ValueT>;
+ using iterator = typename MapType::iterator;
+ using const_iterator = typename MapType::const_iterator;
+ using value_type = typename MapType::value_type;
+ using mapped_type = ValueT;
+ using key_type = Path;
+
+ PathMap() = default;
+ PathMap(std::initializer_list<std::pair<Path, ValueT>> Init) {
+ for (auto &E : Init)
+ try_emplace(std::move(E.first), std::move(E.second));
+ }
+
+ iterator begin() { return Impl.begin(); }
+ const_iterator begin() const { return Impl.begin(); }
+ iterator end() { return Impl.end(); }
+ const_iterator end() const { return Impl.end(); }
+
+ bool empty() const { return Impl.empty(); }
+ size_t size() const { return Impl.size(); }
+ void clear() { Impl.clear(); }
+
+ iterator find(PathRef Key) { return Impl.find_as(Key); }
+ const_iterator find(PathRef Key) const { return Impl.find_as(Key); }
+
+ bool contains(PathRef Key) const { return find(Key) != end(); }
+
+ ValueT lookup(PathRef Key) const {
+ auto It = find(Key);
+ return It == end() ? ValueT() : It->second;
+ }
+
+ ValueT &operator[](PathRef Key) {
+ if (auto It = find(Key); It != end())
+ return It->second;
+ return Impl[Path(Key)];
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(PathRef Key, Args &&...Rest) {
+ if (auto It = find(Key); It != end())
+ return {It, false};
+ return Impl.try_emplace(Path(Key), std::forward<Args>(Rest)...);
+ }
+
+ // When the caller already owns the key, DenseMap can both check and insert
+ // it with one hash, and only copies/moves the string when insertion occurs.
+ template <
+ typename KeyT, typename... Args,
+ std::enable_if_t<
+ std::is_same_v<std::remove_cv_t<std::remove_reference_t<KeyT>>, Path>,
+ int> = 0>
+ std::pair<iterator, bool> try_emplace(KeyT &&Key, Args &&...Rest) {
+ return Impl.try_emplace(std::forward<KeyT>(Key),
+ std::forward<Args>(Rest)...);
+ }
+
+ std::pair<iterator, bool> insert(const std::pair<Path, ValueT> &KV) {
+ return try_emplace(KV.first, KV.second);
+ }
+ std::pair<iterator, bool> insert(std::pair<Path, ValueT> &&KV) {
+ return try_emplace(std::move(KV.first), std::move(KV.second));
+ }
+
+ bool erase(PathRef Key) {
+ auto It = find(Key);
+ if (It == end())
+ return false;
+ Impl.erase(It);
+ return true;
+ }
+ iterator erase(iterator It) { return Impl.erase(It); }
+
+ size_t getMemorySize() const { return Impl.getMemorySize(); }
+
+ llvm::SmallVector<PathRef, 8> keys() const {
+ llvm::SmallVector<PathRef, 8> Result;
+ Result.reserve(Impl.size());
+ for (const auto &E : Impl)
+ Result.push_back(E.first);
+ return Result;
+ }
+};
+
+using PathSet = llvm::DenseSet<Path>;
+
} // namespace clangd
} // namespace clang
+namespace llvm {
+
+template <> struct format_provider<clang::clangd::PathRef> {
+ static void format(const clang::clangd::PathRef &V, raw_ostream &Stream,
+ StringRef Style) {
+ format_provider<StringRef>::format(V.raw(), Stream, Style);
+ }
+};
+
+template <> struct format_provider<clang::clangd::Path> {
+ static void format(const clang::clangd::Path &V, raw_ostream &Stream,
+ StringRef Style) {
+ format_provider<clang::clangd::PathRef>::format(V, Stream, Style);
+ }
+};
+
+template <> struct DenseMapInfo<clang::clangd::PathRef, void> {
+ static unsigned getHashValue(clang::clangd::PathRef Val) {
+ return (unsigned)hash_value(Val);
+ }
+ static bool isEqual(clang::clangd::PathRef LHS, clang::clangd::PathRef RHS) {
+ return LHS == RHS;
+ }
+};
+
+template <> struct DenseMapInfo<clang::clangd::Path, void> {
+ static unsigned getHashValue(const clang::clangd::Path &Val) {
+ return (unsigned)hash_value(Val);
+ }
+ static unsigned getHashValue(clang::clangd::PathRef Val) {
+ return (unsigned)hash_value(Val);
+ }
+ static bool isEqual(const clang::clangd::Path &LHS,
+ const clang::clangd::Path &RHS) {
+ return LHS == RHS;
+ }
+ static bool isEqual(clang::clangd::PathRef LHS,
+ const clang::clangd::Path &RHS) {
+ return LHS == clang::clangd::PathRef(RHS);
+ }
+};
+
+namespace cl {
+
+template <>
+struct parser<clang::clangd::Path> : public basic_parser<clang::clangd::Path> {
+public:
+ parser(Option &O) : basic_parser(O) {}
+
+ bool parse(Option &, StringRef, StringRef ArgValue,
+ clang::clangd::Path &Val) {
+ Val = ArgValue.str();
+ return false;
+ }
+
+ StringRef getValueName() const override { return "path"; }
+
+ void printOptionDiff(const Option &O, clang::clangd::PathRef V,
+ const OptVal &Default, size_t GlobalWidth) const;
+
+ void anchor() override;
+};
+
+} // namespace cl
+
+} // namespace llvm
+
#endif
diff --git a/clang-tools-extra/clangd/support/ThreadsafeFS.cpp b/clang-tools-extra/clangd/support/ThreadsafeFS.cpp
index 7398e4258527b..8c78ad0fe088e 100644
--- a/clang-tools-extra/clangd/support/ThreadsafeFS.cpp
+++ b/clang-tools-extra/clangd/support/ThreadsafeFS.cpp
@@ -74,7 +74,7 @@ class VolatileFileSystem : public llvm::vfs::ProxyFileSystem {
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>
ThreadsafeFS::view(PathRef CWD) const {
auto FS = view(std::nullopt);
- if (auto EC = FS->setCurrentWorkingDirectory(CWD))
+ if (auto EC = FS->setCurrentWorkingDirectory(CWD.raw()))
elog("VFS: failed to set CWD to {0}: {1}", CWD, EC.message());
return FS;
}
diff --git a/clang-tools-extra/clangd/test/memory_tree.test b/clang-tools-extra/clangd/test/memory_tree.test
index 207f5abd55e12..d9e44583f6612 100644
--- a/clang-tools-extra/clangd/test/memory_tree.test
+++ b/clang-tools-extra/clangd/test/memory_tree.test
@@ -82,4 +82,3 @@
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
-
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 13fe4d3911731..1bd6f5570c8a6 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -693,12 +693,12 @@ class FlagsConfigProvider : public config::Provider {
// If --compile-commands-dir arg was invoked, check value and override
// default path.
if (!CompileCommandsDir.empty()) {
- if (llvm::sys::fs::exists(CompileCommandsDir)) {
+ if (CompileCommandsDir.ref().exists()) {
// We support passing both relative and absolute paths to the
// --compile-commands-dir argument, but we assume the path is absolute
// in the rest of clangd so we make sure the path is absolute before
// continuing.
- llvm::SmallString<128> Path(CompileCommandsDir);
+ llvm::SmallString<128> Path(CompileCommandsDir.raw());
if (std::error_code EC = llvm::sys::fs::make_absolute(Path)) {
elog("Error while converting the relative path specified by "
"--compile-commands-dir to an absolute path: {0}. The argument "
@@ -715,7 +715,7 @@ class FlagsConfigProvider : public config::Provider {
if (!IndexFile.empty()) {
Config::ExternalIndexSpec Spec;
Spec.Kind = Spec.File;
- Spec.Location = IndexFile;
+ Spec.Location = IndexFile.raw();
IndexSpec = std::move(Spec);
}
#if CLANGD_ENABLE_REMOTE
@@ -841,7 +841,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
std::optional<llvm::raw_fd_ostream> InputMirrorStream;
if (!InputMirrorFile.empty()) {
std::error_code EC;
- InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC,
+ InputMirrorStream.emplace(InputMirrorFile.raw(), /*ref*/ EC,
llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write);
if (EC) {
InputMirrorStream.reset();
@@ -946,7 +946,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
break;
}
if (!ResourceDir.empty())
- Opts.ResourceDir = ResourceDir;
+ Opts.ResourceDir = ResourceDir.raw();
Opts.StrongWorkspaceMode = StrongWorkspaceMode;
Opts.BuildDynamicSymbolIndex = true;
#if CLANGD_ENABLE_REMOTE
@@ -1049,9 +1049,9 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
if (CheckFile.getNumOccurrences()) {
llvm::SmallString<256> Path;
- if (auto Error =
- llvm::sys::fs::real_path(CheckFile, Path, /*expand_tilde=*/true)) {
- elog("Failed to resolve path {0}: {1}", CheckFile, Error.message());
+ if (auto Error = llvm::sys::fs::real_path(CheckFile.raw(), Path,
+ /*expand_tilde=*/true)) {
+ elog("Failed to resolve path {0}: {1}", CheckFile.raw(), Error.message());
return 1;
}
log("Entering check mode (no LSP server)");
diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp
index 91ae727d8c944..55e9e2c361b85 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -624,7 +624,7 @@ TEST(ClangdAST, HasReservedName) {
TEST(ClangdAST, PreferredIncludeDirective) {
auto ComputePreferredDirective = [](TestTU &TU) {
auto AST = TU.build();
- return preferredIncludeDirective(AST.tuPath(), AST.getLangOpts(),
+ return preferredIncludeDirective(AST.tuPath().raw(), AST.getLangOpts(),
AST.getIncludeStructure().MainFileIncludes,
AST.getLocalTopLevelDecls());
};
diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
index 0eb4acf0469b7..5ee094c0a4eac 100644
--- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
@@ -12,6 +12,7 @@
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ScopedPrinter.h"
+#include "llvm/Testing/Support/SupportHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <deque>
@@ -98,7 +99,7 @@ TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) {
size_t CacheHits = 0;
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
tooling::CompileCommand Cmd;
@@ -110,6 +111,65 @@ TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) {
ASSERT_TRUE(Idx.blockUntilIdleForTest());
}
+TEST_F(BackgroundIndexTest, CaseDistinctTranslationUnits) {
+ MockFS FS;
+ auto Upper = testPath("root/Foo.cc");
+ auto Lower = testPath("root/foo.cc");
+ FS.Files[Upper] = "void upperSymbol() {}";
+ FS.Files[Lower] = "void lowerSymbol() {}";
+ llvm::StringMap<std::string> Storage;
+ size_t CacheHits = 0;
+ MemoryShardStorage MSS(Storage, CacheHits);
+ OverlayCDB CDB(/*Base=*/nullptr);
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, {});
+ for (const auto &File : {Upper, Lower}) {
+ tooling::CompileCommand Cmd;
+ Cmd.Directory = testPath("root");
+ Cmd.Filename = File;
+ Cmd.CommandLine = {"clang++", File};
+ CDB.setCompileCommand(File, Cmd);
+ ASSERT_TRUE(Idx.blockUntilIdleForTest());
+ }
+ EXPECT_THAT(runFuzzyFind(Idx, ""),
+ UnorderedElementsAre(named("upperSymbol"), named("lowerSymbol")));
+ EXPECT_TRUE(Storage.contains(Upper));
+ EXPECT_TRUE(Storage.contains(Lower));
+}
+
+TEST(BackgroundIndexStorageTest, CaseDistinctShardIdentifiers) {
+ llvm::unittest::TempDir Dir("clangd-case-distinct-shards", true);
+ ASSERT_FALSE(Dir.path().empty());
+ auto Factory = BackgroundIndexStorage::createDiskBackedStorageFactory(
+ [&](PathRef) -> std::optional<ProjectInfo> {
+ return ProjectInfo{Dir.path().str()};
+ });
+ // Keep the basename identical so this also tests case-sensitive disks.
+ auto Upper = testPath("Project/header.h");
+ auto Lower = testPath("project/header.h");
+ auto *Storage = Factory(Upper);
+ tooling::CompileCommand UpperCmd, LowerCmd;
+ UpperCmd.Directory = LowerCmd.Directory = testRoot();
+ UpperCmd.CommandLine = {"clang++", "-DUPPER"};
+ LowerCmd.CommandLine = {"clang++", "-DLOWER"};
+ SymbolSlab Symbols;
+ IndexFileOut UpperShard, LowerShard;
+ UpperShard.Symbols = LowerShard.Symbols = &Symbols;
+ UpperShard.Cmd = &UpperCmd;
+ LowerShard.Cmd = &LowerCmd;
+ auto UpperError = Storage->storeShard(Upper, UpperShard);
+ ASSERT_FALSE(bool(UpperError)) << llvm::toString(std::move(UpperError));
+ auto LowerError = Storage->storeShard(Lower, LowerShard);
+ ASSERT_FALSE(bool(LowerError)) << llvm::toString(std::move(LowerError));
+ auto LoadedUpper = Storage->loadShard(Upper);
+ auto LoadedLower = Storage->loadShard(Lower);
+ ASSERT_TRUE(LoadedUpper);
+ ASSERT_TRUE(LoadedLower);
+ ASSERT_TRUE(LoadedUpper->Cmd);
+ ASSERT_TRUE(LoadedLower->Cmd);
+ EXPECT_EQ(LoadedUpper->Cmd->CommandLine, UpperCmd.CommandLine);
+ EXPECT_EQ(LoadedLower->Cmd->CommandLine, LowerCmd.CommandLine);
+}
+
TEST_F(BackgroundIndexTest, Config) {
MockFS FS;
// Set up two identical TUs, foo and bar.
@@ -132,11 +192,11 @@ TEST_F(BackgroundIndexTest, Config) {
BackgroundIndex::Options Opts;
Opts.ContextProvider = [](PathRef P) {
Config C;
- if (P.ends_with("foo.cpp"))
+ if (P.raw().ends_with("foo.cpp"))
C.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) {
Argv = tooling::getInsertArgumentAdjuster("-Done=two")(Argv, "");
});
- if (P.ends_with("baz.cpp"))
+ if (P.raw().ends_with("baz.cpp"))
C.Index.Background = Config::BackgroundPolicy::Skip;
return Context::current().derive(Config::Key, std::move(C));
};
@@ -148,8 +208,7 @@ TEST_F(BackgroundIndexTest, Config) {
OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{},
CommandMangler::forTests());
- BackgroundIndex Idx(
- FS, CDB, [&](llvm::StringRef) { return &MSS; }, std::move(Opts));
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, std::move(Opts));
// Index the two files.
for (auto &Cmd : Cmds) {
std::string FullPath = testPath(Cmd.Filename);
@@ -191,8 +250,7 @@ TEST_F(BackgroundIndexTest, IndexTwoFiles) {
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
BackgroundIndex::Options Opts;
- BackgroundIndex Idx(
- FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts);
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, Opts);
tooling::CompileCommand Cmd;
Cmd.Filename = testPath("root/A.cc");
@@ -258,7 +316,7 @@ TEST_F(BackgroundIndexTest, ConstructorForwarding) {
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
BackgroundIndex::Options Opts;
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts);
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, Opts);
FS.Files[testPath("root/header.hpp")] = Header.code();
FS.Files[testPath("root/test.cpp")] = Main.code();
@@ -318,7 +376,7 @@ TEST_F(BackgroundIndexTest, ConstructorForwardingMultiFile) {
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
BackgroundIndex::Options Opts;
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts);
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, Opts);
FS.Files[testPath("root/header.hpp")] = Header.code();
FS.Files[testPath("root/first.cpp")] = First.code();
@@ -366,8 +424,7 @@ TEST_F(BackgroundIndexTest, MainFileRefs) {
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
BackgroundIndex::Options Opts;
- BackgroundIndex Idx(
- FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts);
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; }, Opts);
tooling::CompileCommand Cmd;
Cmd.Filename = testPath("root/A.cc");
@@ -406,7 +463,7 @@ TEST_F(BackgroundIndexTest, ShardStorageTest) {
// Check nothing is loaded from Storage, but A.cc and A.h has been stored.
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -416,7 +473,7 @@ TEST_F(BackgroundIndexTest, ShardStorageTest) {
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -475,7 +532,7 @@ TEST_F(BackgroundIndexTest, DirectIncludesTest) {
Cmd.CommandLine = {"clang++", testPath("root/A.cc")};
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -525,7 +582,7 @@ TEST_F(BackgroundIndexTest, ShardStorageLoad) {
// Check nothing is loaded from Storage, but A.cc and A.h has been stored.
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -540,7 +597,7 @@ TEST_F(BackgroundIndexTest, ShardStorageLoad) {
)cpp";
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -558,7 +615,7 @@ TEST_F(BackgroundIndexTest, ShardStorageLoad) {
{
CacheHits = 0;
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -599,7 +656,7 @@ TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) {
// Check that A.cc, A.h and B.h has been stored.
{
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -615,7 +672,7 @@ TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) {
{
CacheHits = 0;
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -631,7 +688,7 @@ TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) {
{
CacheHits = 0;
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
CDB.setCompileCommand(testPath("root/A.cc"), Cmd);
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -649,7 +706,7 @@ TEST_F(BackgroundIndexTest, NoDotsInAbsPath) {
size_t CacheHits = 0;
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
ASSERT_TRUE(Idx.blockUntilIdleForTest());
@@ -680,7 +737,7 @@ TEST_F(BackgroundIndexTest, UncompilableFiles) {
size_t CacheHits = 0;
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
tooling::CompileCommand Cmd;
@@ -744,7 +801,7 @@ TEST_F(BackgroundIndexTest, CmdLineHash) {
size_t CacheHits = 0;
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
tooling::CompileCommand Cmd;
@@ -772,7 +829,7 @@ TEST_F(BackgroundIndexTest, Reindex) {
size_t CacheHits = 0;
MemoryShardStorage MSS(Storage, CacheHits);
OverlayCDB CDB(/*Base=*/nullptr);
- BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; },
+ BackgroundIndex Idx(FS, CDB, [&](PathRef) { return &MSS; },
/*Opts=*/{});
// Index a file.
@@ -1021,7 +1078,7 @@ TEST(BackgroundQueueTest, Progress) {
TEST(BackgroundIndex, Profile) {
MockFS FS;
MockCompilationDatabase CDB;
- BackgroundIndex Idx(FS, CDB, [](llvm::StringRef) { return nullptr; },
+ BackgroundIndex Idx(FS, CDB, [](PathRef) { return nullptr; },
/*Opts=*/{});
llvm::BumpPtrAllocator Alloc;
diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
index 9ea7c3e02411d..ca76d5aaf5c2b 100644
--- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
@@ -102,7 +102,7 @@ class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
- LastDiagsHadError[File] = HadError;
+ LastDiagsHadError[File.raw()] = HadError;
}
/// Exposes all files consumed by onDiagnosticsReady in an unspecified order.
@@ -570,7 +570,7 @@ int hello;
}
MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "") {
- return arg.first() == Name &&
+ return PathRef(arg.first()) == PathRef(Name) &&
(arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) ==
UsesMemory &&
std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
@@ -591,8 +591,8 @@ struct Something {
)cpp";
Path BarCpp = testPath("bar.cpp");
- FS.Files[FooCpp] = "";
- FS.Files[BarCpp] = "";
+ FS.Files[FooCpp.raw()] = "";
+ FS.Files[BarCpp.raw()] = "";
EXPECT_THAT(Server.fileStats(), IsEmpty());
@@ -691,7 +691,7 @@ int d;
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
llvm::ArrayRef<Diag> Diagnostics) override {
- StringRef FileIndexStr = llvm::sys::path::stem(File);
+ StringRef FileIndexStr = File.stem().raw();
ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
unsigned long FileIndex = std::stoul(FileIndexStr.str());
@@ -1117,10 +1117,10 @@ TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) {
// FIXME: make this timeout and fail instead of waiting forever in case
// something goes wrong.
CanReturnCommand.wait();
- auto FileName = llvm::sys::path::filename(File);
+ auto FileName = llvm::sys::path::filename(File.raw());
std::vector<std::string> CommandLine = {"clangd", "-ffreestanding",
- std::string(File)};
- return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
+ File.raw().str()};
+ return {tooling::CompileCommand(llvm::sys::path::parent_path(File.raw()),
FileName, std::move(CommandLine), "")};
}
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 4c1cab7b11e60..bc0e931a01c3e 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -147,7 +147,7 @@ CodeCompleteResult completions(llvm::StringRef Text,
Annotations Test(Text);
auto TU = TestTU::withCode(Test.code());
// To make sure our tests for completiopns inside templates work on Windows.
- TU.Filename = FilePath.str();
+ TU.Filename = FilePath.owned().raw();
return completions(TU, Test.point(), std::move(IndexSymbols),
std::move(Opts));
}
diff --git a/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp
index 228a4da6969ce..37ca3cc07d906 100644
--- a/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp
@@ -8,8 +8,10 @@
#include "CompileCommands.h"
#include "Config.h"
+#include "GlobalCompilationDatabase.h"
#include "TestFS.h"
#include "support/Context.h"
+#include "support/Logger.h"
#include "clang/Testing/CommandLineArgs.h"
#include "clang/Tooling/ArgumentsAdjusters.h"
@@ -43,6 +45,19 @@ using ::testing::Not;
// Make use of all features and assert the exact command we get out.
// Other tests just verify presence/absence of certain args.
+TEST(CommandMangler, QueryDriverAllowlistIsCaseSensitive) {
+ std::string Logs;
+ llvm::raw_string_ostream OS(Logs);
+ StreamLogger Logger(OS, Logger::Verbose);
+ LoggingSession Session(Logger);
+ auto Extract = getSystemIncludeExtractor({testPath("SDK/clang")});
+ tooling::CompileCommand Cmd;
+ Cmd.Directory = testRoot();
+ Cmd.CommandLine = {testPath("sdk/clang"), "-xc++", "foo.cc"};
+ Extract(Cmd, testPath("foo.cc"));
+ EXPECT_THAT(Logs, HasSubstr("not allowed driver"));
+}
+
TEST(CommandMangler, Everything) {
llvm::InitializeAllTargetInfos(); // As in ClangdMain
std::string Target = getAnyTargetForTesting();
diff --git a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
index 5fecf32f8e5a5..1a3430d17f530 100644
--- a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
@@ -188,6 +188,21 @@ TEST_F(ConfigCompileTests, Index) {
"Invalid Background value 'Foo'. Valid values are Build, Skip.")));
}
+TEST_F(ConfigCompileTests, ConfigRelativeDriveLetter) {
+ Frag = {};
+ Frag.Source.Directory = "C:/proj";
+ Frag.If.PathMatch.emplace_back("foo\\.cpp");
+ Parm.Path = "c:/proj/foo.cpp";
+ EXPECT_TRUE(compileAndApply());
+ ASSERT_THAT(Diags.Diagnostics, IsEmpty());
+
+ Frag = {};
+ Frag.Source.Directory = "C:/proj";
+ Frag.If.PathMatch.emplace_back("foo\\.cpp");
+ Parm.Path = "c:/other/foo.cpp";
+ EXPECT_FALSE(compileAndApply());
+}
+
TEST_F(ConfigCompileTests, PathSpecMatch) {
auto BarPath = llvm::sys::path::convert_to_slash(testPath("foo/bar.h"));
Parm.Path = BarPath;
diff --git a/clang-tools-extra/clangd/unittests/DexTests.cpp b/clang-tools-extra/clangd/unittests/DexTests.cpp
index ca8b81b5cb3c0..8249fe2501556 100644
--- a/clang-tools-extra/clangd/unittests/DexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DexTests.cpp
@@ -14,6 +14,7 @@
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "index/dex/Trigram.h"
+#include "llvm/ADT/StringSet.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -778,6 +779,19 @@ TEST(DexIndex, IndexedFiles) {
EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None);
}
+TEST(DexIndex, IndexedFilesDriveLetter) {
+ SymbolSlab Symbols;
+ RefSlab Refs;
+ auto Size = Symbols.bytes() + Refs.bytes();
+ auto Data = std::make_pair(std::move(Symbols), std::move(Refs));
+ llvm::StringSet<> Files = {"file:///c:/proj/foo.cpp"};
+ Dex I(std::move(Data.first), std::move(Data.second), RelationSlab(),
+ std::move(Files), IndexContents::All, std::move(Data), Size, true);
+ auto ContainsFile = I.indexedFiles();
+ EXPECT_EQ(ContainsFile("file:///C:/proj/foo.cpp"), IndexContents::All);
+ EXPECT_EQ(ContainsFile("C:\\proj\\foo.cpp"), IndexContents::All);
+}
+
TEST(DexTest, PreferredTypesBoosting) {
auto Sym1 = symbol("t1");
Sym1.Type = "T1";
diff --git a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp
index 9d202e40113fe..cfbd6a0277f28 100644
--- a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp
@@ -7,6 +7,12 @@
//===----------------------------------------------------------------------===//
#include "DraftStore.h"
+#include "FS.h"
+#include "TestFS.h"
+#include "clang/Basic/FileManager.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -14,6 +20,23 @@ namespace clang {
namespace clangd {
namespace {
+using ::testing::UnorderedElementsAre;
+
+std::vector<std::string> directoryEntries(llvm::vfs::FileSystem &FS,
+ llvm::StringRef Directory) {
+ std::vector<std::string> Result;
+ std::error_code EC;
+ for (auto It = FS.dir_begin(Directory, EC),
+ End = llvm::vfs::directory_iterator();
+ !EC && It != End; It.increment(EC)) {
+ auto Style = It->path().contains('\\') ? llvm::sys::path::Style::windows
+ : llvm::sys::path::Style::native;
+ Result.push_back(llvm::sys::path::filename(It->path(), Style).str());
+ }
+ EXPECT_FALSE(EC) << EC.message();
+ return Result;
+}
+
TEST(DraftStore, Versions) {
DraftStore DS;
Path File = "foo.cpp";
@@ -36,6 +59,329 @@ TEST(DraftStore, Versions) {
EXPECT_EQ("y", *DS.getDraft(File)->Contents);
}
+TEST(DraftStore, DriveLetterIdentity) {
+ DraftStore DS;
+ EXPECT_EQ("1", DS.addDraft("C:/proj/a.cpp", "1", "int x;"));
+ auto Draft = DS.getDraft("c:/proj/a.cpp");
+ ASSERT_TRUE(Draft);
+ EXPECT_EQ("int x;", *Draft->Contents);
+ EXPECT_EQ("1", Draft->Version);
+ DS.removeDraft("c:/proj/a.cpp");
+ EXPECT_FALSE(DS.getDraft("C:/proj/a.cpp"));
+}
+
+TEST(DraftStore, DistinctFilenameCase) {
+ DraftStore DS;
+ DS.addDraft("C:/proj/Foo.h", "1", "upper");
+ DS.addDraft("C:/proj/foo.h", "2", "lower");
+ EXPECT_EQ(DS.getActiveFiles().size(), 2u);
+ ASSERT_TRUE(DS.getDraft("c:/proj/Foo.h"));
+ ASSERT_TRUE(DS.getDraft("c:/proj/foo.h"));
+ EXPECT_EQ(*DS.getDraft("c:/proj/Foo.h")->Contents, "upper");
+ EXPECT_EQ(*DS.getDraft("c:/proj/foo.h")->Contents, "lower");
+
+ auto FS = DS.asVFS();
+ auto Upper = FS->getBufferForFile("c:/proj/Foo.h");
+ auto Lower = FS->getBufferForFile("c:/proj/foo.h");
+ ASSERT_TRUE(Upper);
+ ASSERT_TRUE(Lower);
+ EXPECT_EQ((*Upper)->getBuffer(), "upper");
+ EXPECT_EQ((*Lower)->getBuffer(), "lower");
+ DS.removeDraft("c:/proj/foo.h");
+ EXPECT_TRUE(DS.getDraft("C:/proj/Foo.h"));
+ EXPECT_FALSE(DS.getDraft("C:/proj/foo.h"));
+}
+
+TEST(DraftStore, AsVFSDriveLetterAndSlash) {
+ DraftStore DS;
+ DS.addDraft("c:/proj/a.cpp", "1", "int unsaved;");
+ auto FS = DS.asVFS();
+
+ for (const char *P : {"C:/proj/a.cpp", "c:/proj/a.cpp", "C:\\proj\\a.cpp",
+ "c:\\proj\\a.cpp"}) {
+ auto S = FS->status(P);
+ ASSERT_TRUE(S) << P;
+ EXPECT_TRUE(S->isRegularFile());
+ auto Buf = FS->getBufferForFile(P);
+ ASSERT_TRUE(Buf) << P;
+ EXPECT_EQ((*Buf)->getBuffer(), "int unsaved;");
+ }
+ EXPECT_FALSE(FS->status("C:/proj/missing.cpp"));
+ EXPECT_FALSE(FS->status("D:/proj/a.cpp"));
+}
+
+TEST(DraftStore, VFSBufferOwnsContents) {
+ std::unique_ptr<llvm::MemoryBuffer> Buffer;
+ {
+ DraftStore DS;
+ DS.addDraft("/proj/a.cpp", "1", "old contents");
+ auto FS = DS.asVFS();
+ auto Result = FS->getBufferForFile("/proj/a.cpp");
+ ASSERT_TRUE(Result);
+ Buffer = std::move(*Result);
+ }
+ EXPECT_EQ(Buffer->getBuffer(), "old contents");
+}
+
+TEST(DraftStore, VFSDirectoriesAndMetadata) {
+ DraftStore DS;
+ DS.addDraft("c:/proj/src/a.cpp", "1", "int unsaved;");
+ auto FS = DS.asVFS();
+
+ auto Dir = FS->status("C:\\proj\\src");
+ ASSERT_TRUE(Dir);
+ EXPECT_TRUE(Dir->isDirectory());
+ EXPECT_THAT(directoryEntries(*FS, "C:\\proj\\src"),
+ UnorderedElementsAre("a.cpp"));
+
+ llvm::SmallString<64> RealPath;
+ EXPECT_FALSE(FS->getRealPath("C:\\proj\\src\\a.cpp", RealPath));
+ EXPECT_EQ(llvm::sys::path::convert_to_slash(RealPath,
+ llvm::sys::path::Style::windows),
+ "c:/proj/src/a.cpp");
+ bool IsLocal = true;
+ EXPECT_FALSE(FS->isLocal("C:/proj/src/a.cpp", IsLocal));
+ EXPECT_FALSE(IsLocal);
+}
+
+TEST(DraftStore, VFSUniqueIDs) {
+ DraftStore DS;
+ DS.addDraft("C:/proj/a.cpp", "1", "a");
+ DS.addDraft("C:/proj/b.cpp", "1", "b");
+ auto FS = DS.asVFS();
+
+ auto A = FS->status("C:/proj/a.cpp");
+ auto Alias = FS->status("c:\\proj\\a.cpp");
+ auto B = FS->status("C:/proj/b.cpp");
+ auto Dir = FS->status("C:/proj");
+ ASSERT_TRUE(A);
+ ASSERT_TRUE(Alias);
+ ASSERT_TRUE(B);
+ ASSERT_TRUE(Dir);
+ EXPECT_EQ(A->getUniqueID(), Alias->getUniqueID());
+ EXPECT_NE(A->getUniqueID(), B->getUniqueID());
+ EXPECT_NE(A->getUniqueID(), Dir->getUniqueID());
+ EXPECT_NE(B->getUniqueID(), Dir->getUniqueID());
+}
+
+TEST(DraftStore, VFSUniqueIDsAcrossSnapshots) {
+ DraftStore DS;
+ auto Original = testPath("original.h");
+ DS.addDraft(Original, "1", "struct Original {};");
+ auto Before = DS.asVFS();
+ auto Status = Before->status(Original);
+ ASSERT_TRUE(Status);
+ PreambleFileStatusCache Cache(testPath("main.cc"));
+ Cache.update(*Before, *Status, Original);
+
+ // A preamble's cached status can outlive the draft snapshot it came from.
+ for (int I = 0; I != 16; ++I) {
+ auto Added = testPath("new" + std::to_string(I) + ".h");
+ DS.addDraft(Added, "1", "struct Added {};");
+ auto After = DS.asVFS();
+ auto AddedStatus = After->status(Added);
+ ASSERT_TRUE(AddedStatus);
+ EXPECT_NE(Status->getUniqueID(), AddedStatus->getUniqueID());
+ FileManager FM({}, Cache.getConsumingFS(After));
+ auto A = FM.getOptionalFileRef(Original);
+ auto B = FM.getOptionalFileRef(Added, /*OpenFile=*/true);
+ ASSERT_TRUE(A);
+ ASSERT_TRUE(B);
+ EXPECT_NE(&A->getFileEntry(), &B->getFileEntry());
+ DS.removeDraft(Added);
+ }
+}
+
+TEST(DraftStore, VFSStableIDsWithPreambleCache) {
+ DraftStore DS;
+ auto Original = testPath("original.h");
+ auto Alias = testPath("./original.h");
+ DS.addDraft(Original, "1", "#pragma once\nstruct Original {};");
+ auto Before = DS.asVFS();
+ auto Status = Before->status(Original);
+ ASSERT_TRUE(Status);
+ PreambleFileStatusCache Cache(testPath("main.cc"));
+ Cache.update(*Before, *Status, Original);
+
+ for (bool AddOtherDraft : {false, true}) {
+ SCOPED_TRACE(AddOtherDraft);
+ if (AddOtherDraft)
+ DS.addDraft(testPath("other.h"), "1", "struct Other {};");
+ auto After = DS.asVFS();
+ auto CurrentStatus = After->status(Original);
+ ASSERT_TRUE(CurrentStatus);
+ EXPECT_EQ(Status->getUniqueID(), CurrentStatus->getUniqueID());
+
+ auto FS = Cache.getConsumingFS(After);
+ auto CachedStatus = FS->status(Original);
+ auto Opened = FS->openFileForRead(Alias);
+ ASSERT_TRUE(CachedStatus);
+ ASSERT_TRUE(Opened);
+ auto OpenStatus = (*Opened)->status();
+ ASSERT_TRUE(OpenStatus);
+ EXPECT_EQ(CachedStatus->getUniqueID(), OpenStatus->getUniqueID());
+
+ // ASTReader uses cached status, while a later include may open an alias.
+ FileManager FM({}, FS);
+ auto A = FM.getOptionalFileRef(Original);
+ auto B = FM.getOptionalFileRef(Alias, /*OpenFile=*/true);
+ ASSERT_TRUE(A);
+ ASSERT_TRUE(B);
+ EXPECT_EQ(&A->getFileEntry(), &B->getFileEntry());
+ }
+}
+
+TEST(DraftStore, VFSIdentityLifetime) {
+ DraftStore DS;
+ auto File = testPath("header.h");
+ DS.addDraft(File, "1", "old");
+ auto Before = DS.asVFS();
+ auto Original = Before->status(File);
+ ASSERT_TRUE(Original);
+
+ DS.addDraft(File, "2", "new");
+ auto After = DS.asVFS();
+ auto Updated = After->status(File);
+ ASSERT_TRUE(Updated);
+ EXPECT_EQ(Original->getUniqueID(), Updated->getUniqueID());
+ auto OldBuffer = Before->getBufferForFile(File);
+ auto NewBuffer = After->getBufferForFile(File);
+ ASSERT_TRUE(OldBuffer);
+ ASSERT_TRUE(NewBuffer);
+ EXPECT_EQ((*OldBuffer)->getBuffer(), "old");
+ EXPECT_EQ((*NewBuffer)->getBuffer(), "new");
+
+ DS.removeDraft(File);
+ DS.addDraft(File, "3", "reopened");
+ auto Reopened = DS.asVFS()->status(File);
+ ASSERT_TRUE(Reopened);
+ EXPECT_NE(Original->getUniqueID(), Reopened->getUniqueID());
+}
+
+TEST(DraftStore, VFSDottedPaths) {
+ for (const char *Name : {"C:/proj/./dirty.h", "C:/proj/sub/../dirty.h",
+ "C:\\proj\\sub\\..\\dirty.h"}) {
+ SCOPED_TRACE(Name);
+ DraftStore DS;
+ DS.addDraft(Name, "1", "unsaved");
+ auto FS = DS.asVFS();
+ for (const char *Alias : {Name, "C:/proj/dirty.h", "c:\\proj\\dirty.h"}) {
+ auto Buffer = FS->getBufferForFile(Alias);
+ ASSERT_TRUE(Buffer) << Alias;
+ EXPECT_EQ((*Buffer)->getBuffer(), "unsaved");
+ }
+ EXPECT_THAT(directoryEntries(*FS, "C:/proj"),
+ UnorderedElementsAre("dirty.h"));
+ }
+}
+
+TEST(DraftStore, VFSRelativeWorkingDirectory) {
+ for (const auto &Root : {testPath("proj"), std::string("C:/proj")}) {
+ SCOPED_TRACE(Root);
+ DraftStore DS;
+ DS.addDraft(Root + "/src/header.h", "1", "unsaved");
+ auto FS = DS.asVFS();
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory(Root));
+
+ for (const char *Next : {"src", ".", "", "../src", "nested/.."}) {
+ SCOPED_TRACE(Next);
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory(Next));
+ auto CWD = FS->getCurrentWorkingDirectory();
+ ASSERT_TRUE(CWD);
+ EXPECT_EQ(PathRef(*CWD), PathRef(Root + "/src"));
+ EXPECT_TRUE(FS->status("header.h"));
+ auto Buffer = FS->getBufferForFile("header.h");
+ ASSERT_TRUE(Buffer);
+ EXPECT_EQ((*Buffer)->getBuffer(), "unsaved");
+ EXPECT_THAT(directoryEntries(*FS, "."), UnorderedElementsAre("header.h"));
+ }
+
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory(".."));
+ auto CWD = FS->getCurrentWorkingDirectory();
+ ASSERT_TRUE(CWD);
+ EXPECT_EQ(PathRef(*CWD), PathRef(Root));
+ EXPECT_TRUE(FS->status("src/header.h"));
+
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory(Root + "/src/../src"));
+ CWD = FS->getCurrentWorkingDirectory();
+ ASSERT_TRUE(CWD);
+ EXPECT_EQ(PathRef(*CWD), PathRef(Root + "/src"));
+ EXPECT_TRUE(FS->status("header.h"));
+ }
+}
+
+TEST(DraftStore, VFSRelativeWorkingDirectoryPreservesOverlayPrecedence) {
+ auto Header = testPath("proj/src/header.h");
+ auto Base = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(
+ Base->addFile(Header, 0, llvm::MemoryBuffer::getMemBuffer("stale disk")));
+ DraftStore DS;
+ DS.addDraft(Header, "1", "unsaved");
+ auto Overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(Base);
+ Overlay->pushOverlay(DS.asVFS());
+
+ ASSERT_FALSE(Overlay->setCurrentWorkingDirectory(testPath("proj")));
+ for (const char *Next : {"src", ".", "../src"}) {
+ SCOPED_TRACE(Next);
+ ASSERT_FALSE(Overlay->setCurrentWorkingDirectory(Next));
+ auto Buffer = Overlay->getBufferForFile("header.h");
+ ASSERT_TRUE(Buffer);
+ EXPECT_EQ((*Buffer)->getBuffer(), "unsaved");
+ }
+}
+
+#ifdef _WIN32
+TEST(DraftStore, VFSWindowsRootedAndDriveRelativePaths) {
+ DraftStore DS;
+ DS.addDraft("C:/proj/header.h", "1", "unsaved");
+ auto Base = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(Base->addFile("C:/proj/header.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("stale disk")));
+ auto Overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(Base);
+ auto DraftFS = DS.asVFS();
+ Overlay->pushOverlay(DraftFS);
+ ASSERT_FALSE(Overlay->setCurrentWorkingDirectory("C:/proj"));
+
+ llvm::vfs::FileSystem *Systems[] = {DraftFS.get(), Overlay.get()};
+ for (auto *FS : Systems) {
+ for (const char *Path : {"header.h", "C:/proj/header.h", "/proj/header.h",
+ "\\proj\\header.h", "C:header.h", "c:header.h"}) {
+ SCOPED_TRACE(Path);
+ EXPECT_TRUE(FS->status(Path));
+ auto Buffer = FS->getBufferForFile(Path);
+ ASSERT_TRUE(Buffer);
+ EXPECT_EQ((*Buffer)->getBuffer(), "unsaved");
+ }
+ }
+
+ for (const char *Path : {"/proj", "\\proj", "C:", "c:"}) {
+ SCOPED_TRACE(Path);
+ ASSERT_FALSE(Overlay->setCurrentWorkingDirectory(Path));
+ auto Buffer = Overlay->getBufferForFile("header.h");
+ ASSERT_TRUE(Buffer);
+ EXPECT_EQ((*Buffer)->getBuffer(), "unsaved");
+ }
+}
+#endif
+
+TEST(DraftStore, VFSDirectoryIterationComposesWithBase) {
+ auto Base = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(Base->addFile("/proj/on-disk.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("disk")));
+ ASSERT_TRUE(Base->addFile("/other/only-base.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("disk")));
+
+ DraftStore DS;
+ DS.addDraft("/proj/dirty.h", "1", "dirty");
+ auto Overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(Base);
+ Overlay->pushOverlay(DS.asVFS());
+
+ EXPECT_THAT(directoryEntries(*Overlay, "/proj"),
+ UnorderedElementsAre("dirty.h", "on-disk.h"));
+ EXPECT_THAT(directoryEntries(*Overlay, "/other"),
+ UnorderedElementsAre("only-base.h"));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/FSTests.cpp b/clang-tools-extra/clangd/unittests/FSTests.cpp
index 0b2bc688335d7..b47f9b340a925 100644
--- a/clang-tools-extra/clangd/unittests/FSTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FSTests.cpp
@@ -15,6 +15,45 @@ namespace clang {
namespace clangd {
namespace {
+TEST(FSTests, PreambleStatusCacheDriveLetter) {
+ PreambleFileStatusCache StatCache("C:/proj/main.cpp");
+ llvm::vfs::Status S("fake", llvm::sys::fs::UniqueID(1, 2),
+ std::chrono::system_clock::now(), 0, 0, 8,
+ llvm::sys::fs::file_type::regular_file,
+ llvm::sys::fs::all_all);
+ llvm::StringMap<std::string> Files;
+ auto FS = buildTestFS(Files);
+ StatCache.update(*FS, S, "C:/proj/header.h");
+ EXPECT_TRUE(StatCache.lookup("c:/proj/header.h"));
+ EXPECT_TRUE(StatCache.lookup("c:\\proj\\header.h"));
+ EXPECT_FALSE(StatCache.lookup("C:/proj/main.cpp"));
+}
+
+#ifdef _WIN32
+TEST(FSTests, PreambleStatusCacheDriveRelativePath) {
+ auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(FS->addFile("C:/one/header.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("one")));
+ ASSERT_TRUE(FS->addFile("C:/two/header.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("two longer")));
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory("C:/one"));
+ PreambleFileStatusCache Cache("C:/one/main.cc");
+ auto Original = Cache.getProducingFS(FS)->status("C:header.h");
+ ASSERT_TRUE(Original);
+ EXPECT_TRUE(Cache.lookup("C:/one/header.h"));
+ EXPECT_FALSE(Cache.lookup("C:header.h"));
+
+ ASSERT_FALSE(FS->setCurrentWorkingDirectory("C:/two"));
+ auto Cached = Cache.getConsumingFS(FS)->status("C:header.h");
+ auto Actual = FS->status("C:header.h");
+ ASSERT_TRUE(Cached);
+ ASSERT_TRUE(Actual);
+ EXPECT_EQ(Cached->getUniqueID(), Actual->getUniqueID());
+ EXPECT_EQ(Cached->getSize(), Actual->getSize());
+ EXPECT_NE(Cached->getUniqueID(), Original->getUniqueID());
+}
+#endif
+
TEST(FSTests, PreambleStatusCache) {
llvm::StringMap<std::string> Files;
Files["x"] = "";
diff --git a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
index a92142fbcd7c4..8ae5a9e840661 100644
--- a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
@@ -103,6 +103,91 @@ std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) {
return std::make_unique<RelationSlab>(std::move(RelBuilder).build());
}
+class HintRequiredScheme : public URIScheme {
+public:
+ llvm::Expected<std::string>
+ getAbsolutePath(llvm::StringRef, llvm::StringRef Body,
+ llvm::StringRef Hint) const override {
+ if (Hint.empty())
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "workspace hint required");
+ return testPath(Body.drop_front());
+ }
+ llvm::Expected<URI> uriFromAbsolutePath(llvm::StringRef) const override {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "not used to create URIs");
+ }
+};
+static URISchemeRegistry::Add<HintRequiredScheme>
+ HintRequired("hint-required", "test workspace-relative index keys");
+
+TEST(FileSymbolsTest, HintDependentURIKeys) {
+ FileSymbols FS(IndexContents::All, true);
+ constexpr llvm::StringLiteral Upper = "hint-required:///A.h";
+ constexpr llvm::StringLiteral Lower = "hint-required:///a.h";
+ auto Resolved = URI::resolve(Upper, testPath("main.cc"));
+ ASSERT_TRUE(bool(Resolved));
+ FS.update(Upper, numSlab(1, 1), nullptr, nullptr, false);
+ FS.update(Lower, numSlab(2, 2), nullptr, nullptr, false);
+ for (auto Type : {IndexType::Light, IndexType::Heavy}) {
+ auto Index = FS.buildIndex(Type);
+ EXPECT_THAT(runFuzzyFind(*Index, ""),
+ UnorderedElementsAre(qName("1"), qName("2")));
+ auto Contains = Index->indexedFiles();
+ EXPECT_EQ(Contains(Upper), IndexContents::All);
+ EXPECT_EQ(Contains(Lower), IndexContents::All);
+ EXPECT_EQ(Contains("hint-required:///missing.h"), IndexContents::None);
+ }
+ FS.update(Upper, nullptr, nullptr, nullptr, false);
+ auto Index = FS.buildIndex(IndexType::Light);
+ EXPECT_THAT(runFuzzyFind(*Index, ""), ElementsAre(qName("2")));
+ EXPECT_EQ(Index->indexedFiles()(Upper), IndexContents::None);
+ EXPECT_EQ(Index->indexedFiles()(Lower), IndexContents::All);
+}
+
+TEST(FileSymbolsTest, DriveLetterURIKeys) {
+ FileSymbols FS(IndexContents::All, true);
+ auto SlabC = numSlab(1, 1);
+ FS.update("file:///C:/proj/a.cpp", std::move(SlabC), nullptr, nullptr, false);
+ auto Slabc = numSlab(2, 2);
+ FS.update("file:///c:/proj/a.cpp", std::move(Slabc), nullptr, nullptr, false);
+
+ auto Index = FS.buildIndex(IndexType::Light);
+ EXPECT_THAT(runFuzzyFind(*Index, ""), UnorderedElementsAre(qName("2")))
+ << "second update must replace, not accumulate";
+ auto Contains = Index->indexedFiles();
+ EXPECT_EQ(Contains("file:///C:/proj/a.cpp"), IndexContents::All);
+ EXPECT_EQ(Contains("file:///c:/proj/a.cpp"), IndexContents::All);
+ EXPECT_EQ(Contains("C:/proj/a.cpp"), IndexContents::All);
+ EXPECT_EQ(Contains("c:\\proj\\a.cpp"), IndexContents::All);
+ EXPECT_EQ(Contains("file:///D:/proj/a.cpp"), IndexContents::None);
+}
+
+TEST(FileSymbolsTest, CaseDistinctFileURIKeys) {
+ constexpr llvm::StringLiteral Upper = "file:///C:/proj/Foo.h";
+ constexpr llvm::StringLiteral Lower = "file:///C:/proj/foo.h";
+ FileSymbols FS(IndexContents::All, true);
+ FS.update(Upper, numSlab(1, 1), nullptr, nullptr, false);
+ FS.update(Lower, numSlab(2, 2), nullptr, nullptr, false);
+ for (auto Type : {IndexType::Light, IndexType::Heavy}) {
+ auto Index = FS.buildIndex(Type);
+ EXPECT_THAT(runFuzzyFind(*Index, ""),
+ UnorderedElementsAre(qName("1"), qName("2")));
+ auto Contains = Index->indexedFiles();
+ EXPECT_EQ(Contains(Upper), IndexContents::All);
+ EXPECT_EQ(Contains(Lower), IndexContents::All);
+ EXPECT_EQ(Contains("file:///c:/proj/Foo.h"), IndexContents::All);
+ EXPECT_EQ(Contains("file:///C:/proj/FOO.h"), IndexContents::None);
+ }
+ FS.update(Upper, nullptr, nullptr, nullptr, false);
+ for (auto Type : {IndexType::Light, IndexType::Heavy}) {
+ auto Index = FS.buildIndex(Type);
+ EXPECT_THAT(runFuzzyFind(*Index, ""), ElementsAre(qName("2")));
+ EXPECT_EQ(Index->indexedFiles()(Upper), IndexContents::None);
+ EXPECT_EQ(Index->indexedFiles()(Lower), IndexContents::All);
+ }
+}
+
TEST(FileSymbolsTest, UpdateAndGet) {
FileSymbols FS(IndexContents::All, true);
EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty());
@@ -713,6 +798,87 @@ TEST(FileShardedIndexTest, Sharding) {
}
}
+TEST(FileShardedIndexTest, DriveLetterURIIdentity) {
+ constexpr llvm::StringLiteral Upper = "file:///C:/proj/a.h";
+ constexpr llvm::StringLiteral Lower = "file:///c:/proj/a.h";
+
+ auto Sym1 = symbol("1");
+ Sym1.CanonicalDeclaration.FileURI = Upper.data();
+ auto Sym2 = symbol("2");
+ Sym2.CanonicalDeclaration.FileURI = Lower.data();
+
+ IndexFileIn IF;
+ SymbolSlab::Builder Symbols;
+ Symbols.insert(Sym1);
+ Symbols.insert(Sym2);
+ IF.Symbols.emplace(std::move(Symbols).build());
+
+ FileShardedIndex ShardedIndex(std::move(IF));
+ EXPECT_THAT(ShardedIndex.getAllSources(), ElementsAre(Upper));
+
+ auto Shard = ShardedIndex.getShard(Lower);
+ ASSERT_TRUE(Shard);
+ EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName("1"), qName("2")));
+}
+
+TEST(FileShardedIndexTest, HintDependentURIIdentity) {
+ constexpr llvm::StringLiteral Upper = "hint-required:///A.h";
+ constexpr llvm::StringLiteral Lower = "hint-required:///a.h";
+ auto Sym1 = symbol("1");
+ Sym1.CanonicalDeclaration.FileURI = Upper.data();
+ auto Sym2 = symbol("2");
+ Sym2.CanonicalDeclaration.FileURI = Lower.data();
+ IndexFileIn IF;
+ SymbolSlab::Builder Symbols;
+ Symbols.insert(Sym1);
+ Symbols.insert(Sym2);
+ IF.Symbols.emplace(std::move(Symbols).build());
+ IF.Sources.emplace();
+ (*IF.Sources)[Upper].URI = Upper.data();
+ (*IF.Sources)[Lower].URI = Lower.data();
+
+ FileShardedIndex Sharded(std::move(IF));
+ EXPECT_THAT(Sharded.getAllSources(), UnorderedElementsAre(Upper, Lower));
+ auto A = Sharded.getShard(Upper);
+ auto B = Sharded.getShard(Lower);
+ ASSERT_TRUE(A);
+ ASSERT_TRUE(B);
+ EXPECT_THAT(*A->Symbols, ElementsAre(qName("1")));
+ EXPECT_THAT(*B->Symbols, ElementsAre(qName("2")));
+ EXPECT_TRUE(A->Sources->contains(Upper));
+ EXPECT_TRUE(B->Sources->contains(Lower));
+}
+
+TEST(FileShardedIndexTest, CaseDistinctFileURIIdentity) {
+ constexpr llvm::StringLiteral Upper = "file:///C:/proj/Foo.h";
+ constexpr llvm::StringLiteral Lower = "file:///C:/proj/foo.h";
+ auto Sym1 = symbol("1");
+ Sym1.CanonicalDeclaration.FileURI = Upper.data();
+ auto Sym2 = symbol("2");
+ Sym2.CanonicalDeclaration.FileURI = Lower.data();
+ SymbolSlab::Builder Symbols;
+ Symbols.insert(Sym1);
+ Symbols.insert(Sym2);
+ IndexFileIn IF;
+ IF.Symbols.emplace(std::move(Symbols).build());
+ IF.Sources.emplace();
+ (*IF.Sources)[Upper].URI = Upper;
+ (*IF.Sources)[Lower].URI = Lower;
+
+ FileShardedIndex Sharded(std::move(IF));
+ EXPECT_THAT(Sharded.getAllSources(), UnorderedElementsAre(Upper, Lower));
+ auto A = Sharded.getShard("file:///c:/proj/Foo.h");
+ auto B = Sharded.getShard(Lower);
+ ASSERT_TRUE(A);
+ ASSERT_TRUE(B);
+ EXPECT_THAT(*A->Symbols, ElementsAre(qName("1")));
+ EXPECT_THAT(*B->Symbols, ElementsAre(qName("2")));
+ EXPECT_TRUE(A->Sources->contains(Upper));
+ EXPECT_FALSE(A->Sources->contains(Lower));
+ EXPECT_TRUE(B->Sources->contains(Lower));
+ EXPECT_FALSE(B->Sources->contains(Upper));
+}
+
TEST(FileIndexTest, Profile) {
FileIndex FI(true);
diff --git a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
index 39ab1446e980b..c08ca4e4c7b84 100644
--- a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
+++ b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
@@ -69,23 +69,23 @@ TEST(GlobalCompilationDatabaseTest, FallbackWorkingDirectory) {
EXPECT_EQ(Cmd.Output, "");
}
-static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) {
+static tooling::CompileCommand cmd(PathRef File, llvm::StringRef Arg) {
return tooling::CompileCommand(
- testRoot(), File, {"clang", std::string(Arg), std::string(File)}, "");
+ testRoot(), File.owned().raw(),
+ {"clang", std::string(Arg), File.owned().raw()}, "");
}
class OverlayCDBTest : public ::testing::Test {
class BaseCDB : public GlobalCompilationDatabase {
public:
std::optional<tooling::CompileCommand>
- getCompileCommand(llvm::StringRef File) const override {
- if (File == testPath("foo.cc"))
+ getCompileCommand(PathRef File) const override {
+ if (File == PathRef(testPath("foo.cc")))
return cmd(File, "-DA=1");
return std::nullopt;
}
- tooling::CompileCommand
- getFallbackCommand(llvm::StringRef File) const override {
+ tooling::CompileCommand getFallbackCommand(PathRef File) const override {
return cmd(File, "-DA=2");
}
@@ -99,6 +99,19 @@ class OverlayCDBTest : public ::testing::Test {
std::unique_ptr<GlobalCompilationDatabase> Base;
};
+TEST_F(OverlayCDBTest, DriveLetterIdentity) {
+ OverlayCDB CDB(nullptr);
+ auto Override = cmd("C:/proj/a.cpp", "-DUPPER");
+ EXPECT_TRUE(CDB.setCompileCommand("C:/proj/a.cpp", Override));
+ auto Got = CDB.getCompileCommand("c:/proj/a.cpp");
+ ASSERT_TRUE(Got);
+ EXPECT_THAT(Got->CommandLine, Contains("-DUPPER"));
+ EXPECT_THAT(CDB.getCompileCommand("c:\\proj\\a.cpp")->CommandLine,
+ Contains("-DUPPER"));
+ // Second set with the other spelling is a no-op (same command).
+ EXPECT_FALSE(CDB.setCompileCommand("c:/proj/a.cpp", Override));
+}
+
TEST_F(OverlayCDBTest, GetCompileCommand) {
OverlayCDB CDB(Base.get());
EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
diff --git a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
index 782e3aacba538..5de796883d7ed 100644
--- a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
@@ -357,11 +357,17 @@ TEST(HeaderSourceSwitchTest, CaseSensitivity) {
// - source on case insensitive file systems, as the HeaderAbsPath would match
// the filename in index.
#ifdef CLANGD_PATH_CASE_INSENSITIVE
- EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),
- llvm::ValueIs(testing::StrCaseEq(testPath(TU.Filename))));
+ {
+ auto Got = getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get());
+ ASSERT_TRUE(Got);
+ EXPECT_THAT(Got->raw(), testing::StrCaseEq(testPath(TU.Filename)));
+ }
#else
- EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),
- llvm::ValueIs(testing::StrCaseEq(testPath(TU.HeaderFilename))));
+ {
+ auto Got = getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get());
+ ASSERT_TRUE(Got);
+ EXPECT_THAT(Got->raw(), testing::StrCaseEq(testPath(TU.HeaderFilename)));
+ }
#endif
}
diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp
index 440582e14239a..1e685090ab59f 100644
--- a/clang-tools-extra/clangd/unittests/HeadersTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeadersTests.cpp
@@ -111,7 +111,7 @@ class HeadersTest : public ::testing::Test {
QuotedHeaders, AngledHeaders);
for (const auto &Inc : Inclusions)
Inserter.addExisting(Inc);
- auto Inserted = ToHeaderFile(Preferred);
+ auto Inserted = ToHeaderFile(Preferred.raw());
if (!Inserter.shouldInsertInclude(Original, Inserted))
return "";
auto Path = Inserter.calculateIncludePath(Inserted, MainFile);
diff --git a/clang-tools-extra/clangd/unittests/IndexTests.cpp b/clang-tools-extra/clangd/unittests/IndexTests.cpp
index a66680d39c87d..cb3afe13d909b 100644
--- a/clang-tools-extra/clangd/unittests/IndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/IndexTests.cpp
@@ -16,6 +16,7 @@
#include "index/Merge.h"
#include "index/Symbol.h"
#include "clang/Index/IndexSymbol.h"
+#include "llvm/ADT/StringSet.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <utility>
@@ -239,6 +240,21 @@ TEST(MemIndexTest, IndexedFiles) {
EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None);
}
+TEST(MemIndexTest, IndexedFilesDriveLetter) {
+ SymbolSlab Symbols;
+ RefSlab Refs;
+ auto Size = Symbols.bytes() + Refs.bytes();
+ auto Data = std::make_pair(std::move(Symbols), std::move(Refs));
+ llvm::StringSet<> Files = {"file:///c:/proj/foo.cpp"};
+ MemIndex I(std::move(Data.first), std::move(Data.second), RelationSlab(),
+ std::move(Files), IndexContents::All, std::move(Data), Size);
+ auto ContainsFile = I.indexedFiles();
+ EXPECT_EQ(ContainsFile("file:///C:/proj/foo.cpp"), IndexContents::All);
+ EXPECT_EQ(ContainsFile("file:///c:/proj/foo.cpp"), IndexContents::All);
+ EXPECT_EQ(ContainsFile("C:/proj/foo.cpp"), IndexContents::All);
+ EXPECT_EQ(ContainsFile("file:///D:/proj/foo.cpp"), IndexContents::None);
+}
+
TEST(MemIndexTest, TemplateSpecialization) {
SymbolSlab::Builder B;
@@ -541,6 +557,32 @@ TEST(MergeIndexTest, IndexedFiles) {
EXPECT_EQ(ContainsFile("unittest:///foobar.cc"), IndexContents::None);
}
+TEST(MergeIndexTest, IndexedFilesDriveLetter) {
+ SymbolSlab DynSymbols;
+ RefSlab DynRefs;
+ auto DynSize = DynSymbols.bytes() + DynRefs.bytes();
+ auto DynData = std::make_pair(std::move(DynSymbols), std::move(DynRefs));
+ llvm::StringSet<> DynFiles = {"file:///c:/proj/foo.cpp"};
+ MemIndex DynIndex(std::move(DynData.first), std::move(DynData.second),
+ RelationSlab(), std::move(DynFiles), IndexContents::Symbols,
+ std::move(DynData), DynSize);
+ SymbolSlab StaticSymbols;
+ RefSlab StaticRefs;
+ auto StaticData =
+ std::make_pair(std::move(StaticSymbols), std::move(StaticRefs));
+ llvm::StringSet<> StaticFiles = {"file:///C:/proj/foo.cpp",
+ "file:///C:/proj/bar.cpp"};
+ MemIndex StaticIndex(
+ std::move(StaticData.first), std::move(StaticData.second), RelationSlab(),
+ std::move(StaticFiles), IndexContents::References, std::move(StaticData),
+ StaticSymbols.bytes() + StaticRefs.bytes());
+ MergedIndex Merge(&DynIndex, &StaticIndex);
+ auto ContainsFile = Merge.indexedFiles();
+ EXPECT_EQ(ContainsFile("file:///C:/proj/foo.cpp"),
+ IndexContents::Symbols | IndexContents::References);
+ EXPECT_EQ(ContainsFile("file:///c:/proj/bar.cpp"), IndexContents::References);
+}
+
TEST(MergeIndexTest, NonDocumentation) {
using index::SymbolKind;
Symbol L, R;
diff --git a/clang-tools-extra/clangd/unittests/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
index a8d11bf681891..71098008320c4 100644
--- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
@@ -876,14 +876,14 @@ TEST(PreamblePatch, PatchFileEntry) {
#define FOO)cpp");
{
auto AST = createPatchedAST(Code.code(), Code.code());
- EXPECT_EQ(
- PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()),
- nullptr);
+ EXPECT_EQ(PreamblePatch::getPatchEntry(AST->tuPath().raw(),
+ AST->getSourceManager()),
+ nullptr);
}
{
auto AST = createPatchedAST(Code.code(), NewCode.code());
- auto FE =
- PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager());
+ auto FE = PreamblePatch::getPatchEntry(AST->tuPath().raw(),
+ AST->getSourceManager());
ASSERT_NE(FE, std::nullopt);
EXPECT_THAT(FE->getName().str(),
testing::EndsWith(PreamblePatch::HeaderName.str()));
diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index f10f3e4976cf9..5714e17411ea0 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -97,14 +97,14 @@ class PerFileModulesCompilationDatabase : public GlobalCompilationDatabase {
CommandLine.insert(CommandLine.end(), ExtraFlags.begin(), ExtraFlags.end());
CommandLine.push_back(std::string(AbsPath));
- Commands[maybeCaseFoldPath(AbsPath)] = tooling::CompileCommand(
+ Commands[maybeCaseFoldPath(AbsPath).raw()] = tooling::CompileCommand(
Directory, std::string(AbsPath), std::move(CommandLine), "");
Files.push_back(std::string(AbsPath));
}
std::optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override {
- auto It = Commands.find(maybeCaseFoldPath(File));
+ auto It = Commands.find(maybeCaseFoldPath(File).raw());
if (It == Commands.end())
return std::nullopt;
tooling::CompileCommand Cmd = It->second;
@@ -164,7 +164,7 @@ class ModuleUnitRootCompilationDatabase
std::optional<ProjectInfo> getProjectInfo(PathRef File) const override {
// Treat each module-unit directory as its own project root so tests can
// verify that the persistent cache follows the providing module unit.
- llvm::SmallString<256> Root(File);
+ llvm::SmallString<256> Root(File.raw());
llvm::sys::path::remove_filename(Root);
return ProjectInfo{std::string(Root)};
}
diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp
index 42279b51230e7..a9e78719f8fa5 100644
--- a/clang-tools-extra/clangd/unittests/RenameTests.cpp
+++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp
@@ -82,9 +82,9 @@ applyEdits(FileEdits FE) {
std::vector<std::pair<std::string, std::string>> Results;
for (auto &It : FE)
Results.emplace_back(
- It.first().str(),
+ It.first.raw(),
llvm::cantFail(tooling::applyAllReplacements(
- It.getValue().InitialCode, It.getValue().Replacements)));
+ It.second.InitialCode, It.second.Replacements)));
return Results;
}
@@ -1459,6 +1459,21 @@ TEST(RenameTest, IndexMergeMainFile) {
EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(),
ElementsAre(newText("xPrime")));
#endif
+
+ // Drive-letter case must not produce two edits for one file.
+ // https://github.com/clangd/clangd/issues/108
+#ifdef _WIN32
+ std::string DriveMain = testPath("main.cc");
+ ASSERT_GE(DriveMain.size(), 2u);
+ ASSERT_EQ(DriveMain[1], ':');
+ DriveMain[0] = llvm::isLower(DriveMain[0]) ? llvm::toUpper(DriveMain[0])
+ : llvm::toLower(DriveMain[0]);
+ TU.Filename = "main.cc";
+ // Keep TestTU's filenames relative; vary the LSP-side absolute spelling.
+ Main = DriveMain;
+ Results = Rename(TU.index().get());
+ EXPECT_EQ(Results.GlobalChanges.size(), 1u);
+#endif
}
TEST(RenameTest, MainFileReferencesOnly) {
@@ -2199,7 +2214,9 @@ TEST(CrossFileRenameTests, BuildRenameEdits) {
Edit =
buildRenameEdit(FilePath, T.code(), symbolRanges(T.ranges()), NewNames);
ASSERT_TRUE(bool(Edit)) << Edit.takeError();
- EXPECT_EQ(applyEdits(FileEdits{{T.code(), std::move(*Edit)}}).front().second,
+ FileEdits Edits;
+ Edits.try_emplace(Path(T.code().str()), std::move(*Edit));
+ EXPECT_EQ(applyEdits(std::move(Edits)).front().second,
expectedResult(T, NewNames[0]));
}
diff --git a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
index c6862b5eba6fa..c240d743be5dd 100644
--- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
@@ -21,6 +21,7 @@
#include "clang-include-cleaner/Record.h"
#include "support/Cancellation.h"
#include "support/Context.h"
+#include "support/MemoryTree.h"
#include "support/Path.h"
#include "support/TestTracer.h"
#include "support/Threading.h"
@@ -79,7 +80,7 @@ MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
// Simple ContextProvider to verify the provider is invoked & contexts are used.
static Key<std::string> BoundPath;
Context bindPath(PathRef F) {
- return Context::current().derive(BoundPath, F.str());
+ return Context::current().derive(BoundPath, F.owned().raw());
}
llvm::StringRef boundPath() {
const std::string *V = Context::current().get(BoundPath);
@@ -155,7 +156,7 @@ class TUSchedulerTests : public ::testing::Test {
void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs,
WantDiagnostics WD,
llvm::unique_function<void(std::vector<Diag>)> CB) {
- Path OrigFile = File.str();
+ Path OrigFile = File.owned();
WithContextValue Ctx(DiagsCallbackKey,
[OrigFile, CB = std::move(CB)](
PathRef File, std::vector<Diag> Diags) mutable {
@@ -445,9 +446,8 @@ TEST_F(TUSchedulerTests, InvalidationUnchanged) {
std::atomic<int> Actions(0);
Notification Start;
- updateWithDiags(S, Path, "a", WantDiagnostics::Yes, [&](std::vector<Diag>) {
- Start.wait();
- });
+ updateWithDiags(S, Path, "a", WantDiagnostics::Yes,
+ [&](std::vector<Diag>) { Start.wait(); });
S.runWithAST(
"invalidatable", Path,
[&](llvm::Expected<InputsAndAST> AST) {
@@ -1319,6 +1319,45 @@ TEST_F(TUSchedulerTests, PublishWithStalePreamble) {
EXPECT_THAT(Collector.diagVersions().back(), Pair("3", "3"));
}
+TEST_F(TUSchedulerTests, IncluderCacheMemoryUsage) {
+ CDB.ExtraClangFlags = {"-xc++"};
+ TUScheduler S(CDB, optsForTest());
+ auto Main = testPath(std::string(180, 'm') + ".cc");
+ std::string Contents;
+ size_t OwnedPathBytes = Main.size();
+ for (int I = 0; I != 16; ++I) {
+ auto Header = std::string(180, 'h') + std::to_string(I) + ".h";
+ FS.Files[testPath(Header)] = "";
+ Contents += "#include \"" + Header + "\"\n";
+ OwnedPathBytes += testPath(Header).size() + Main.size();
+ }
+ auto Usage = [&] {
+ MemoryTree MT;
+ S.profile(MT);
+ return MT.child("header_includer_cache").total();
+ };
+ S.update(Main, getInputs(Main, Contents), WantDiagnostics::Yes);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ auto FirstUsage = Usage();
+ EXPECT_GE(FirstUsage, OwnedPathBytes);
+
+ S.update(Main, getInputs(Main, "#define AGAIN\n" + Contents),
+ WantDiagnostics::Yes);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ EXPECT_EQ(Usage(), FirstUsage) << "reassociation must not double-count";
+
+ auto Other = testPath("second.cc");
+ S.update(Other, getInputs(Other, Contents), WantDiagnostics::Yes);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ auto TwoFilesUsage = Usage();
+ EXPECT_LT(TwoFilesUsage, 2 * FirstUsage)
+ << "the shared cache must only be counted once";
+ S.remove(Other);
+ S.remove(Main);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ EXPECT_EQ(Usage(), TwoFilesUsage) << "retained cache storage is still owned";
+}
+
// If a header file is missing from the CDB (or inferred using heuristics), and
// it's included by another open file, then we parse it using that files flags.
TEST_F(TUSchedulerTests, IncluderCache) {
@@ -1383,6 +1422,12 @@ TEST_F(TUSchedulerTests, IncluderCache) {
EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
EXPECT_THAT(GetFlags(NoCmd), Contains("-DMAIN"))
<< "Included from main file, has no own command";
+#ifdef _WIN32
+ std::string LowercaseDriveNoCmd = NoCmd;
+ LowercaseDriveNoCmd[0] = llvm::toLower(LowercaseDriveNoCmd[0]);
+ EXPECT_THAT(GetFlags(LowercaseDriveNoCmd), Contains("-DMAIN"))
+ << "CDB/include spelling and LSP spelling differ in drive-letter case";
+#endif
EXPECT_THAT(GetFlags(Unreliable), Contains("-DMAIN"))
<< "Included from main file, own command is heuristic";
EXPECT_THAT(GetFlags(OK), Not(Contains("-DMAIN")))
@@ -1438,6 +1483,56 @@ TEST_F(TUSchedulerTests, IncluderCache) {
<< "association invalidated and then claimed by main3";
}
+// HeaderIncluderCache stores Association by unique_ptr so a DenseMap rehash
+// (dozens of unique headers) cannot dangle the circular list.
+TEST_F(TUSchedulerTests, IncluderCacheManyHeaders) {
+ static std::string Main = testPath("bulk_main.cpp");
+ struct ManyCDB : public GlobalCompilationDatabase {
+ std::optional<tooling::CompileCommand>
+ getCompileCommand(PathRef File) const override {
+ if (File == Main) {
+ auto Cmd = getFallbackCommand(File);
+ Cmd.Heuristic.clear();
+ Cmd.CommandLine.push_back("-DMAIN");
+ return Cmd;
+ }
+ return std::nullopt;
+ }
+ } ManyCDB;
+ TUScheduler S(ManyCDB, optsForTest());
+
+ std::string Includes;
+ std::vector<std::string> Headers;
+ Headers.reserve(64);
+ for (int I = 0; I < 64; ++I) {
+ std::string H = testPath("bulk" + std::to_string(I) + ".h");
+ Headers.push_back(H);
+ FS.Files[H] = ";";
+ Includes += "#include \"bulk" + std::to_string(I) + ".h\"\n";
+ }
+
+ auto GetFlags = [&](PathRef Header) {
+ S.update(Header, getInputs(Header, ";"), WantDiagnostics::Yes);
+ EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ Notification CmdDone;
+ tooling::CompileCommand Cmd;
+ S.runWithPreamble("GetFlags", Header, TUScheduler::StaleOrAbsent,
+ [&](llvm::Expected<InputsAndPreamble> Inputs) {
+ ASSERT_FALSE(!Inputs) << Inputs.takeError();
+ Cmd = std::move(Inputs->Command);
+ CmdDone.notify();
+ });
+ CmdDone.wait();
+ EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ return Cmd.CommandLine;
+ };
+
+ S.update(Main, getInputs(Main, Includes), WantDiagnostics::Yes);
+ EXPECT_TRUE(S.blockUntilIdle(timeoutSeconds(60)));
+ EXPECT_THAT(GetFlags(Headers.front()), Contains("-DMAIN"));
+ EXPECT_THAT(GetFlags(Headers.back()), Contains("-DMAIN"));
+}
+
TEST_F(TUSchedulerTests, PreservesLastActiveFile) {
for (bool Sync : {false, true}) {
auto Opts = optsForTest();
@@ -1569,7 +1664,7 @@ TEST_F(TUSchedulerTests, PreambleThrottle) {
// Deliberately no synchronization.
// The PreambleThrottler should serialize these calls, if not then tsan
// will find a bug here.
- Filenames.emplace_back(Path);
+ Filenames.emplace_back(Path.raw());
}
};
@@ -1593,8 +1688,7 @@ TEST_F(TUSchedulerTests, PreambleThrottle) {
// The throttler saw all files, and we built them.
EXPECT_THAT(Throttler.Acquires,
testing::UnorderedElementsAreArray(Filenames));
- EXPECT_THAT(BuiltFilenames,
- testing::UnorderedElementsAreArray(Filenames));
+ EXPECT_THAT(BuiltFilenames, testing::UnorderedElementsAreArray(Filenames));
// We built the files in reverse order that the throttler saw them.
EXPECT_THAT(BuiltFilenames,
testing::ElementsAreArray(Throttler.Acquires.rbegin(),
diff --git a/clang-tools-extra/clangd/unittests/TestFS.cpp b/clang-tools-extra/clangd/unittests/TestFS.cpp
index 85407200833a0..fdeb8404678ae 100644
--- a/clang-tools-extra/clangd/unittests/TestFS.cpp
+++ b/clang-tools-extra/clangd/unittests/TestFS.cpp
@@ -21,8 +21,8 @@ namespace {
// Tries to strip \p Prefix from beginning of \p Path. Returns true on success.
// If \p Prefix doesn't match, leaves \p Path untouched and returns false.
-bool pathConsumeFront(PathRef &Path, PathRef Prefix) {
- if (!pathStartsWith(Prefix, Path))
+bool pathConsumeFront(llvm::StringRef &Path, PathRef Prefix) {
+ if (!Prefix.startsWith(Path))
return false;
Path = Path.drop_front(Prefix.size());
return true;
@@ -61,14 +61,14 @@ MockCompilationDatabase::getCompileCommand(PathRef File) const {
if (ExtraClangFlags.empty())
return std::nullopt;
- auto FileName = llvm::sys::path::filename(File);
+ auto FileName = File.filename();
// Build the compile command.
auto CommandLine = ExtraClangFlags;
CommandLine.insert(CommandLine.begin(), "clang");
if (RelPathPrefix.empty()) {
// Use the absolute path in the compile command.
- CommandLine.push_back(std::string(File));
+ CommandLine.push_back(File.raw().str());
} else {
// Build a relative path using RelPathPrefix.
llvm::SmallString<32> RelativeFilePath(RelPathPrefix);
@@ -76,10 +76,9 @@ MockCompilationDatabase::getCompileCommand(PathRef File) const {
CommandLine.push_back(std::string(RelativeFilePath.str()));
}
- return {tooling::CompileCommand(Directory != llvm::StringRef()
- ? Directory
- : llvm::sys::path::parent_path(File),
- FileName, std::move(CommandLine), "")};
+ return {tooling::CompileCommand(
+ Directory != llvm::StringRef() ? Directory : File.parentPath().raw(),
+ FileName, std::move(CommandLine), "")};
}
const char *testRoot() {
@@ -92,9 +91,10 @@ const char *testRoot() {
}
std::string testPath(PathRef File, llvm::sys::path::Style Style) {
- assert(llvm::sys::path::is_relative(File) && "FileName should be relative");
+ assert(llvm::sys::path::is_relative(File.raw()) &&
+ "FileName should be relative");
- llvm::SmallString<32> NativeFile = File;
+ llvm::SmallString<32> NativeFile = File.raw();
llvm::sys::path::native(NativeFile, Style);
llvm::SmallString<32> Path;
llvm::sys::path::append(Path, Style, testRoot(), NativeFile);
@@ -111,7 +111,7 @@ class TestScheme : public URIScheme {
llvm::Expected<std::string>
getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
llvm::StringRef HintPath) const override {
- if (!HintPath.empty() && !pathStartsWith(testRoot(), HintPath))
+ if (!HintPath.empty() && !PathRef(testRoot()).startsWith(HintPath))
return error("Hint path is not empty and doesn't start with {0}: {1}",
testRoot(), HintPath);
if (!Body.consume_front("/"))
@@ -123,8 +123,10 @@ class TestScheme : public URIScheme {
llvm::Expected<URI>
uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
- if (!pathConsumeFront(AbsolutePath, testRoot()))
+ llvm::StringRef Rest = AbsolutePath;
+ if (!pathConsumeFront(Rest, testRoot()))
return error("{0} does not start with {1}", AbsolutePath, testRoot());
+ AbsolutePath = Rest;
return URI(Scheme, /*Authority=*/"",
llvm::sys::path::convert_to_slash(AbsolutePath));
diff --git a/clang-tools-extra/clangd/unittests/URITests.cpp b/clang-tools-extra/clangd/unittests/URITests.cpp
index fae7de9783164..87ed016782749 100644
--- a/clang-tools-extra/clangd/unittests/URITests.cpp
+++ b/clang-tools-extra/clangd/unittests/URITests.cpp
@@ -7,8 +7,10 @@
//===----------------------------------------------------------------------===//
#include "Matchers.h"
+#include "Protocol.h"
#include "TestFS.h"
#include "URI.h"
+#include "index/PathIdentity.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -149,6 +151,98 @@ TEST(URITest, Resolve) {
testPath("a"));
}
+TEST(URITest, IndexFileIdentityDriveLetter) {
+ EXPECT_EQ(indexFileIdentity("file:///C:/proj/a.cpp"),
+ indexFileIdentity("file:///c:/proj/a.cpp"));
+ EXPECT_EQ(indexFileIdentity("file:///C:/proj/a.cpp"),
+ indexFileIdentity("C:/proj/a.cpp"));
+ EXPECT_EQ(indexFileIdentity("file:///C:/proj/a.cpp"),
+ indexFileIdentity("c:\\proj\\a.cpp"));
+ EXPECT_NE(indexFileIdentity("file:///C:/proj/a.cpp"),
+ indexFileIdentity("file:///D:/proj/a.cpp"));
+}
+
+TEST(URITest, IndexFileIdentityPreservesCase) {
+ EXPECT_NE(indexFileIdentity("file:///C:/proj/Foo.h"),
+ indexFileIdentity("file:///C:/proj/foo.h"));
+ EXPECT_NE(indexFileIdentity("file:///proj/Foo.h"),
+ indexFileIdentity("file:///proj/foo.h"));
+ EXPECT_EQ(indexFileIdentity("file:///C:/proj/%46oo.h"),
+ indexFileIdentity("c:\\proj\\Foo.h"));
+}
+
+TEST(URITest, IndexFileIdentityRejectsInvalidURI) {
+ EXPECT_FALSE(indexFileIdentity("file:relative/a.cpp"));
+ EXPECT_FALSE(indexFileIdentity("file://server"));
+
+ // Strings without URI syntax remain valid path keys.
+ EXPECT_EQ(indexFileIdentity("relative/a.cpp"),
+ indexFileIdentityFrom(Path("relative/a.cpp")));
+#ifndef CLANGD_PATH_CASE_INSENSITIVE
+ EXPECT_NE(indexFileIdentity("C:notes"), indexFileIdentity("c:notes"));
+#endif
+}
+
+TEST(URITest, IndexFileIdentityOpaqueURI) {
+ auto Key = indexFileIdentity("unknown-scheme:///proj/a.cpp");
+ ASSERT_TRUE(Key);
+ EXPECT_EQ(Key->raw(), "unknown-scheme:///proj/a.cpp");
+ EXPECT_NE(Key, indexFileIdentity("unknown-scheme:///proj/A.cpp"));
+ EXPECT_NE(Key, indexFileIdentityFrom(Path(Key->raw().str())));
+}
+
+TEST(URITest, IndexFileIdentityBorrowedLookup) {
+ for (const char *U :
+ {"file:/a/b", "file:///a/b", "file://server/share/a",
+ "file:////server/share/a", "file:///C:/proj/a.cpp",
+ "file:///c%3A/proj/a%20b.cpp", "file:///", "file:/C:"}) {
+ SCOPED_TRACE(U);
+ auto Resolved = resolveOrDie(parseOrDie(U));
+ auto Owned = indexFileIdentityFrom(Path(Resolved));
+ llvm::SmallString<256> Storage;
+ auto Borrowed = indexFileIdentity(U, Storage);
+ ASSERT_TRUE(Borrowed);
+ ASSERT_TRUE(Owned);
+ EXPECT_TRUE(IndexFileKeyInfo::isEqual(*Borrowed, *Owned));
+ EXPECT_EQ(IndexFileKeyInfo::getHashValue(*Borrowed),
+ IndexFileKeyInfo::getHashValue(*Owned));
+ IndexFileSet Files;
+ Files.insert(*Owned);
+ EXPECT_NE(Files.find_as(*Borrowed), Files.end());
+ if (!llvm::StringRef(U).contains('%')) {
+ EXPECT_TRUE(Storage.empty());
+ EXPECT_GE(Borrowed->Value.data(), U);
+ EXPECT_LE(Borrowed->Value.end(), U + strlen(U));
+ }
+ }
+}
+
+TEST(URITest, URIForFileDriveLetter) {
+ auto Upper = URIForFile::fromURI(parseOrDie("file:///C:/proj/a.cpp"), "");
+ auto Lower = URIForFile::fromURI(parseOrDie("file:///c:/proj/a.cpp"), "");
+ ASSERT_TRUE(bool(Upper)) << Upper.takeError();
+ ASSERT_TRUE(bool(Lower)) << Lower.takeError();
+ EXPECT_EQ(*Upper, *Lower);
+ EXPECT_FALSE(*Lower < *Upper);
+ EXPECT_FALSE(*Upper < *Lower);
+}
+
+TEST(URITest, URIForFilePreservesCase) {
+ auto Upper = URIForFile::fromURI(parseOrDie("file:///C:/proj/Foo.h"), "");
+ auto Lower = URIForFile::fromURI(parseOrDie("file:///C:/proj/foo.h"), "");
+ auto Alias = URIForFile::fromURI(parseOrDie("file:///c:/proj/Foo.h"), "");
+ ASSERT_TRUE(bool(Upper)) << Upper.takeError();
+ ASSERT_TRUE(bool(Lower)) << Lower.takeError();
+ ASSERT_TRUE(bool(Alias)) << Alias.takeError();
+ EXPECT_NE(*Upper, *Lower);
+ EXPECT_EQ(*Upper, *Alias);
+ EXPECT_TRUE(*Upper < *Lower);
+ EXPECT_TRUE(*Alias < *Lower);
+ EXPECT_FALSE(*Lower < *Upper);
+ EXPECT_FALSE(*Upper < *Alias);
+ EXPECT_FALSE(*Alias < *Upper);
+}
+
TEST(URITest, ResolveUNC) {
#ifdef _WIN32
// Expected path style depends on LLVM_WINDOWS_PREFER_FORWARD_SLASH.
diff --git a/clang-tools-extra/clangd/unittests/support/PathTests.cpp b/clang-tools-extra/clangd/unittests/support/PathTests.cpp
index 599c76926d30d..6a04d37149804 100644
--- a/clang-tools-extra/clangd/unittests/support/PathTests.cpp
+++ b/clang-tools-extra/clangd/unittests/support/PathTests.cpp
@@ -14,24 +14,148 @@
namespace clang {
namespace clangd {
namespace {
+
TEST(PathTests, IsAncestor) {
- EXPECT_TRUE(pathStartsWith(testPath("foo"), testPath("foo")));
- EXPECT_TRUE(pathStartsWith(testPath("foo/"), testPath("foo")));
+ EXPECT_TRUE(PathRef(testPath("foo")).startsWith(testPath("foo")));
+ EXPECT_TRUE(PathRef(testPath("foo/")).startsWith(testPath("foo")));
- EXPECT_FALSE(pathStartsWith(testPath("foo"), testPath("fooz")));
- EXPECT_FALSE(pathStartsWith(testPath("foo/"), testPath("fooz")));
+ EXPECT_FALSE(PathRef(testPath("foo")).startsWith(testPath("fooz")));
+ EXPECT_FALSE(PathRef(testPath("foo/")).startsWith(testPath("fooz")));
- EXPECT_TRUE(pathStartsWith(testPath("foo"), testPath("foo/bar")));
- EXPECT_TRUE(pathStartsWith(testPath("foo/"), testPath("foo/bar")));
+ EXPECT_TRUE(PathRef(testPath("foo")).startsWith(testPath("foo/bar")));
+ EXPECT_TRUE(PathRef(testPath("foo/")).startsWith(testPath("foo/bar")));
#ifdef CLANGD_PATH_CASE_INSENSITIVE
- EXPECT_TRUE(pathStartsWith(testPath("fOo"), testPath("foo/bar")));
- EXPECT_TRUE(pathStartsWith(testPath("foo"), testPath("fOo/bar")));
+ EXPECT_TRUE(PathRef(testPath("fOo")).startsWith(testPath("foo/bar")));
+ EXPECT_TRUE(PathRef(testPath("foo")).startsWith(testPath("fOo/bar")));
#else
- EXPECT_FALSE(pathStartsWith(testPath("fOo"), testPath("foo/bar")));
- EXPECT_FALSE(pathStartsWith(testPath("foo"), testPath("fOo/bar")));
+ EXPECT_FALSE(PathRef(testPath("fOo")).startsWith(testPath("foo/bar")));
+ EXPECT_FALSE(PathRef(testPath("foo")).startsWith(testPath("fOo/bar")));
+#endif
+}
+
+TEST(PathTests, PosixSeparatorsWithNativeRoots) {
+ const auto Posix = llvm::sys::path::Style::posix;
+ auto Parent = testPath("proj", Posix);
+ EXPECT_TRUE(PathRef(Parent).startsWith(testPath("proj/a.cpp", Posix), Posix));
+ EXPECT_FALSE(
+ PathRef(Parent).startsWith(testPath("project/a.cpp", Posix), Posix));
+}
+
+TEST(PathTests, DriveLetterIdentity) {
+ Path Upper("C:/Users/src/foo.cpp");
+ Path Lower("c:/Users/src/foo.cpp");
+ EXPECT_EQ(PathRef(Upper), PathRef(Lower));
+ EXPECT_TRUE(pathEqual(Upper, Lower));
+ EXPECT_EQ(pathHash(Upper.raw()), pathHash(Lower.raw()));
+ EXPECT_NE(PathRef("C:/Users/src/foo.cpp"), PathRef("D:/Users/src/foo.cpp"));
+
+#ifndef CLANGD_PATH_CASE_INSENSITIVE
+ // Only the drive letter is folded on case-sensitive hosts.
+ EXPECT_NE(PathRef("C:/Users/src/Foo.cpp"), PathRef("c:/Users/src/foo.cpp"));
#endif
}
+
+TEST(PathTests, WindowsSlashIdentity) {
+ EXPECT_EQ(PathRef("C:/proj/a.cpp"), PathRef("C:\\proj\\a.cpp"));
+ EXPECT_EQ(PathRef("C:/proj/a.cpp"), PathRef("c:\\proj\\a.cpp"));
+ EXPECT_EQ(pathHash("C:/proj/a.cpp"), pathHash("c:\\proj\\a.cpp"));
+ EXPECT_EQ(PathRef("C:/proj/a.cpp").caseFolded().raw(), "c:/proj/a.cpp");
+ EXPECT_EQ(PathRef("C:\\proj\\a.cpp").caseFolded().raw(), "c:/proj/a.cpp");
+}
+
+TEST(PathTests, WindowsPathClassification) {
+#ifndef CLANGD_PATH_CASE_INSENSITIVE
+ // A drive-relative POSIX filename is not an absolute Windows drive path.
+ EXPECT_NE(PathRef("C:notes"), PathRef("c:notes"));
+#endif
+
+ EXPECT_EQ(PathRef("//server/share/a.cpp"),
+ PathRef("\\\\server\\share\\a.cpp"));
+ EXPECT_EQ(pathHash("//server/share/a.cpp"),
+ pathHash("\\\\server\\share\\a.cpp"));
+}
+
+TEST(PathTests, RemoveDotsUsesPathStyle) {
+ EXPECT_EQ(PathRef("C:\\proj\\src\\..\\a.cpp").removeDots(),
+ Path("C:\\proj\\a.cpp"));
+ EXPECT_EQ(PathRef("\\\\server\\share\\src\\..\\a.cpp").removeDots(),
+ Path("\\\\server\\share\\a.cpp"));
+}
+
+TEST(PathTests, DriveLetterStartsWith) {
+ const auto Win = llvm::sys::path::Style::windows;
+ EXPECT_TRUE(pathStartsWith("C:/", "c:/proj/a.cpp", Win));
+ EXPECT_TRUE(pathStartsWith("C:\\", "c:/proj/a.cpp", Win));
+ EXPECT_TRUE(pathStartsWith("c:/", "C:\\", Win));
+ EXPECT_FALSE(pathStartsWith("C:/", "d:/proj/a.cpp", Win));
+ EXPECT_TRUE(
+ pathStartsWith(PathRef("C:/proj"), PathRef("c:/proj/src/a.cpp"), Win));
+ EXPECT_TRUE(pathStartsWith(PathRef("c:/proj/"), PathRef("C:/proj"), Win));
+ EXPECT_TRUE(
+ pathStartsWith(PathRef("C:/proj"), PathRef("c:\\proj\\src\\a.cpp"), Win));
+ EXPECT_FALSE(
+ pathStartsWith(PathRef("C:/proj"), PathRef("c:/other/a.cpp"), Win));
+}
+
+TEST(PathTests, PathMapDriveLetter) {
+ PathMap<int> M;
+ M[PathRef("C:/proj/a.cpp")] = 1;
+ auto It = M.find(PathRef("c:/proj/a.cpp"));
+ ASSERT_NE(It, M.end());
+ EXPECT_EQ(It->second, 1);
+ // First-inserted spelling is preserved.
+ EXPECT_EQ(It->first.raw(), "C:/proj/a.cpp");
+ EXPECT_TRUE(M.contains(PathRef("c:/proj/a.cpp")));
+ EXPECT_EQ(M[PathRef("c:/proj/a.cpp")], 1);
+ EXPECT_EQ(M.size(), 1u);
+
+ auto [InsertedIt, Inserted] = M.try_emplace(PathRef("c:/proj/a.cpp"), 2);
+ EXPECT_FALSE(Inserted);
+ EXPECT_EQ(InsertedIt->second, 1);
+
+ EXPECT_TRUE(M.erase(PathRef("c:/proj/a.cpp")));
+ EXPECT_TRUE(M.empty());
+}
+
+TEST(PathTests, PathMapPreservesFilenameCase) {
+ PathMap<int> M;
+ M[PathRef("C:/Proj/A.cpp")] = 7;
+ M[PathRef("C:/Proj/a.cpp")] = 8;
+ EXPECT_EQ(M.size(), 2u);
+ EXPECT_EQ(M.lookup(PathRef("c:/Proj/A.cpp")), 7);
+ EXPECT_EQ(M.lookup(PathRef("c:/Proj/a.cpp")), 8);
+ EXPECT_FALSE(M.contains(PathRef("C:/proj/A.cpp")));
+}
+
+TEST(PathTests, IdentityNormalizationAndOrdering) {
+ llvm::StringRef Paths[] = {"C:/Proj/A.cpp",
+ "c:\\Proj\\A.cpp",
+ "c:/Proj/a.cpp",
+ "C:notes",
+ "c:notes",
+ "//server/share/a",
+ "\\\\server\\share\\a",
+ "relative/file",
+ "relative\\file",
+ "/",
+ ""};
+ for (auto L : Paths) {
+ for (auto R : Paths) {
+ SCOPED_TRACE(L.str() + " vs " + R.str());
+ auto LNorm = PathRef(L).identityNormalized();
+ auto RNorm = PathRef(R).identityNormalized();
+ EXPECT_EQ(pathEquals(L, R), LNorm.raw() == RNorm.raw());
+ EXPECT_EQ(pathCompare(L, R) == 0, pathEquals(L, R));
+ EXPECT_EQ(pathCompare(L, R) < 0, LNorm.raw() < RNorm.raw());
+ if (pathEquals(L, R))
+ EXPECT_EQ(pathHash(L), pathHash(R));
+ }
+ }
+ EXPECT_EQ(PathRef("C:\\Proj\\A.cpp").identityNormalized().raw(),
+ "c:/Proj/A.cpp");
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp
index c26fc21d7a01c..9f18a91288cf3 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp
@@ -113,14 +113,14 @@ std::string TweakTest::apply(llvm::StringRef MarkedCode,
if (!NewText)
return "bad edits: " + llvm::toString(NewText.takeError());
llvm::StringRef Unwrapped = unwrap(Context, *NewText);
- if (It.first() == testPath(TU.Filename))
+ if (It.first == testPath(TU.Filename))
EditedMainFile = std::string(Unwrapped);
else {
if (!EditedFiles)
ADD_FAILURE() << "There were changes to additional files, but client "
"provided a nullptr for EditedFiles.";
else
- EditedFiles->insert_or_assign(It.first(), Unwrapped.str());
+ EditedFiles->insert_or_assign(It.first.raw(), Unwrapped.str());
}
}
return EditedMainFile;
@@ -190,7 +190,7 @@ TweakWorkspaceTest::apply(StringRef InvocationFile,
auto NewText = It.second.apply();
if (!NewText)
return TweakResult{"bad edits: " + llvm::toString(NewText.takeError())};
- Retval.EditedFiles.insert_or_assign(It.first(), *NewText);
+ Retval.EditedFiles.insert_or_assign(It.first.raw(), *NewText);
}
return Retval;
}
diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
index f30aac6add176..440a329f3ac2d 100644
--- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/BUILD.gn
@@ -142,6 +142,7 @@ static_library("clangd") {
"index/IndexAction.cpp",
"index/MemIndex.cpp",
"index/Merge.cpp",
+ "index/PathIdentity.cpp",
"index/ProjectAware.cpp",
"index/Ref.cpp",
"index/Relation.cpp",
More information about the cfe-commits
mailing list