[clang-tools-extra] [clangd] Turn `Path` and `PathRef` into classes (PR #136439)

via cfe-commits cfe-commits at lists.llvm.org
Sat Apr 19 09:59:12 PDT 2025


https://github.com/Nerixyz created https://github.com/llvm/llvm-project/pull/136439

This is the initial step towards https://github.com/clangd/clangd/issues/108.
It introduces the `Path` and `PathRef` classes as wrappers for `std::string` and `llvm::StringRef` respectively (`Path` and `PathRef` were aliases to `std::string` and `llvm::StringRef` before). The classes compare and hash case-insensitive based on the platform (based on `CLANGD_PATH_CASE_INSENSITIVE` - set on Windows and macOS). They can be implicitly constructed from strings but can't be implicitly converted back. Both require `.raw()` to convert them to the underlying types. One can go from a `PathRef` to `Path` using `.owned()` (that naming is very Rust-y, feel free to suggest something better).

Because of disallowing implicit conversions back to strings, this PR is quite large. My main goal with this is to only introduce the types, but not to refactor code to use them (i.e. it should be functionally the same). Thus, it shouldn't fix the issue yet, but make it possible to adopt the wrappers in multiple parts in parallel. As a first step, by getting rid of as many `.raw()` calls as possible. I left some todos where I turned a `Path(Ref)` into a `string(ref)`, because these locations aren't visible when X-Ref searching.

There are many string maps that are actually path maps and would need their keys to be compared and hashed case-insensitively. This isn't possible with a `StringMap`. I'm not that involved with clangd/LLVM, so I'm not sure what the ideal map type would be.

>From fdab9b9566c54a8555e17998b2fd92938d570d02 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Sat, 19 Apr 2025 18:30:38 +0200
Subject: [PATCH] [clangd] Turn `Path` and `PathRef` into classes

---
 clang-tools-extra/clangd/ASTSignals.cpp       |   2 +-
 clang-tools-extra/clangd/ClangdLSPServer.cpp  |   6 +-
 clang-tools-extra/clangd/ClangdServer.cpp     |  72 ++++---
 clang-tools-extra/clangd/CodeComplete.cpp     |  32 +--
 clang-tools-extra/clangd/ConfigCompile.cpp    |   5 +-
 clang-tools-extra/clangd/ConfigProvider.cpp   |   9 +-
 clang-tools-extra/clangd/DraftStore.cpp       |   6 +-
 clang-tools-extra/clangd/FS.cpp               |   6 -
 clang-tools-extra/clangd/FS.h                 |   8 -
 .../clangd/GlobalCompilationDatabase.cpp      |  50 ++---
 .../clangd/HeaderSourceSwitch.cpp             |  14 +-
 clang-tools-extra/clangd/Headers.cpp          |   6 +-
 clang-tools-extra/clangd/Hover.cpp            |   2 +-
 clang-tools-extra/clangd/IncludeCleaner.cpp   |  16 +-
 clang-tools-extra/clangd/ModulesBuilder.cpp   |  14 +-
 clang-tools-extra/clangd/ParsedAST.cpp        |   5 +-
 clang-tools-extra/clangd/Preamble.cpp         |   8 +-
 clang-tools-extra/clangd/Protocol.cpp         |  10 +-
 clang-tools-extra/clangd/Protocol.h           |   4 +-
 .../clangd/ScanningProjectModules.cpp         |   8 +-
 clang-tools-extra/clangd/TUScheduler.cpp      |  60 +++---
 clang-tools-extra/clangd/TidyProvider.cpp     |  14 +-
 clang-tools-extra/clangd/XRefs.cpp            |  29 +--
 clang-tools-extra/clangd/index/Background.cpp |  14 +-
 clang-tools-extra/clangd/index/Background.h   |   1 +
 .../clangd/index/BackgroundIndexLoader.cpp    |  16 +-
 .../clangd/index/BackgroundIndexLoader.h      |   2 +-
 .../clangd/index/BackgroundIndexStorage.cpp   |   4 +-
 clang-tools-extra/clangd/index/FileIndex.cpp  |   4 +-
 clang-tools-extra/clangd/refactor/Rename.cpp  |   5 +-
 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  |  17 +-
 .../clangd/support/FileCache.cpp              |   6 +-
 clang-tools-extra/clangd/support/Path.cpp     | 146 +++++++++++--
 clang-tools-extra/clangd/support/Path.h       | 202 ++++++++++++++++--
 .../clangd/support/ThreadsafeFS.cpp           |   2 +-
 clang-tools-extra/clangd/tool/ClangdMain.cpp  |  16 +-
 .../clangd/unittests/ASTTests.cpp             |   2 +-
 .../clangd/unittests/BackgroundIndexTests.cpp |  43 ++--
 .../clangd/unittests/ClangdTests.cpp          |  16 +-
 .../clangd/unittests/CodeCompleteTests.cpp    |   2 +-
 .../GlobalCompilationDatabaseTests.cpp        |  10 +-
 .../unittests/HeaderSourceSwitchTests.cpp     |   7 +-
 .../clangd/unittests/HeadersTests.cpp         |   2 +-
 .../clangd/unittests/PreambleTests.cpp        |  10 +-
 .../clangd/unittests/TUSchedulerTests.cpp     |   6 +-
 clang-tools-extra/clangd/unittests/TestFS.cpp |  21 +-
 .../clangd/unittests/support/PathTests.cpp    |  20 +-
 50 files changed, 613 insertions(+), 357 deletions(-)

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/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 1e981825c7c15..35ada432cd5dd 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -926,11 +926,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
@@ -1816,7 +1816,7 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
   // Cache DiagRefMap
   {
     std::lock_guard<std::mutex> Lock(DiagRefMutex);
-    DiagRefMap[File] = LocalDiagMap;
+    DiagRefMap[File.raw()] = LocalDiagMap;
   }
 
   // Send a notification to the LSP client.
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 52844129834c3..8ae5aa79674f2 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -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() + Version,
                       std::move(Task));
     } else
       Task();
@@ -262,7 +262,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());
   }
@@ -316,14 +316,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);
@@ -340,7 +340,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;
@@ -408,8 +408,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());
   };
 }
 
@@ -426,7 +426,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());
@@ -442,7 +442,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()};
@@ -473,7 +474,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
@@ -495,7 +497,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)
@@ -540,12 +542,13 @@ void ClangdServer::formatFile(PathRef File, std::optional<Range> Rng,
   }
 
   // Call clang-format.
-  auto Action = [File = File.str(), Code = std::move(*Code),
+  auto Action = [File = File.owned(), Code = std::move(*Code),
                  Ranges = std::vector<tooling::Range>{RequestedRange},
                  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());
@@ -553,9 +556,9 @@ void ClangdServer::formatFile(PathRef File, std::optional<Range> Rng,
     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,
@@ -568,24 +571,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)
@@ -594,7 +597,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
@@ -610,7 +613,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.
@@ -618,13 +621,13 @@ 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)
@@ -756,7 +759,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)
@@ -782,7 +785,7 @@ 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));
       }
@@ -817,7 +820,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());
@@ -840,12 +843,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));
   };
 
@@ -855,7 +858,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());
@@ -894,7 +898,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());
@@ -976,7 +980,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/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 0eb196fbad46a..ebbcea5f67bbf 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1384,14 +1384,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.owned().raw();
   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
@@ -1649,7 +1649,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 NextToken = findTokenAfterCompletionPoint(
@@ -1660,7 +1660,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,
@@ -1741,12 +1741,12 @@ class CodeCompleteFlow {
     ReplacedRange.start.character -= HeuristicPrefix.Name.size();
 
     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);
@@ -1917,7 +1917,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.owned().raw());
     if (PreferredType)
       Req.PreferredTypes.push_back(std::string(PreferredType->raw()));
     vlog("Code complete: fuzzyFind({0:2})", toJSON(Req));
@@ -1972,8 +1972,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();
@@ -2140,8 +2141,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);
     }
@@ -2214,7 +2216,7 @@ CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset,
   semaCodeComplete(
       std::make_unique<ParamNameCollector>(Options, ParamNames), Options,
       {FileName, Offset, *Preamble,
-       PreamblePatch::createFullPatch(FileName, ParseInput, *Preamble),
+       PreamblePatch::createFullPatch(FileName.raw(), ParseInput, *Preamble),
        ParseInput});
   if (ParamNames.empty())
     return CodeCompleteResult();
@@ -2288,7 +2290,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});
 }
 
@@ -2312,7 +2314,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 13c2405e76df7..a2487c957a230 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -411,8 +411,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..de84b693f449b 100644
--- a/clang-tools-extra/clangd/DraftStore.cpp
+++ b/clang-tools-extra/clangd/DraftStore.cpp
@@ -19,7 +19,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;
 
@@ -76,7 +76,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,7 +86,7 @@ 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 {
diff --git a/clang-tools-extra/clangd/FS.cpp b/clang-tools-extra/clangd/FS.cpp
index 5729b9341d9d4..cdf5fc6aceae0 100644
--- a/clang-tools-extra/clangd/FS.cpp
+++ b/clang-tools-extra/clangd/FS.cpp
@@ -113,11 +113,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..4f9845bdf9111 100644
--- a/clang-tools-extra/clangd/FS.h
+++ b/clang-tools-extra/clangd/FS.h
@@ -9,7 +9,6 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FS_H
 
-#include "support/Path.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/Support/VirtualFileSystem.h"
@@ -68,13 +67,6 @@ class PreambleFileStatusCache {
   llvm::StringMap<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/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index 7c0eb9651feaa..8743344845125 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -47,8 +47,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())
     ;
 }
 
@@ -60,12 +60,12 @@ 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 = File.extension();
   if (FileExtension.empty() || FileExtension == ".h")
     Argv.push_back("-xobjective-c++-header");
-  Argv.push_back(std::string(File));
-  tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
-                              llvm::sys::path::filename(File), std::move(Argv),
+  Argv.push_back(File.owned().raw());
+  tooling::CompileCommand Cmd(File.parentPath().raw(), File.filename(),
+                              std::move(Argv),
                               /*Output=*/"");
   Cmd.Heuristic = "clangd fallback";
   return Cmd;
@@ -265,7 +265,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(
@@ -374,7 +374,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());
 
@@ -388,10 +388,10 @@ DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
   FoldedDirs.reserve(Dirs.size());
   for (const auto &Dir : Dirs) {
 #ifndef NDEBUG
-    if (!llvm::sys::path::is_absolute(Dir))
+    if (!PathRef(Dir).isAbsolute())
       elog("Trying to cache CDB for relative {0}");
 #endif
-    FoldedDirs.push_back(maybeCaseFoldPath(Dir));
+    FoldedDirs.push_back(PathRef(Dir).caseFolded().raw());
   }
 
   std::vector<DirectoryCache *> Ret;
@@ -406,15 +406,15 @@ DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
 std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
 DirectoryBasedGlobalCompilationDatabase::lookupCDB(
     CDBLookupRequest Request) const {
-  assert(llvm::sys::path::is_absolute(Request.FileName) &&
-         "path must be absolute");
+  assert(Request.FileName.isAbsolute() && "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:
@@ -427,9 +427,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;
       });
     }
@@ -581,8 +581,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;
@@ -677,7 +677,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
@@ -768,7 +768,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;
   }
@@ -793,7 +793,7 @@ OverlayCDB::getCompileCommand(PathRef File) const {
   if (!Cmd)
     return std::nullopt;
   if (Mangler)
-    Mangler(*Cmd, File);
+    Mangler(*Cmd, File.raw());
   return Cmd;
 }
 
@@ -803,7 +803,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;
 }
 
@@ -812,7 +812,7 @@ 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);
+  std::string CanonPath = File.removeDots().raw();
   {
     std::unique_lock<std::mutex> Lock(Mutex);
     if (Cmd) {
@@ -835,7 +835,7 @@ OverlayCDB::getProjectModules(PathRef File) const {
   auto MDB = DelegatingCDB::getProjectModules(File);
   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/HeaderSourceSwitch.cpp b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
index d54c3668570eb..6fedc9c75e9fa 100644
--- a/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
+++ b/clang-tools-extra/clangd/HeaderSourceSwitch.cpp
@@ -26,14 +26,14 @@ std::optional<Path> getCorrespondingHeaderOrSource(
                                         ".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.
-  bool IsSource = llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
+  bool IsSource = llvm::any_of(SourceExtensions, [&PathExt](llvm::StringRef SourceExt) {
     return SourceExt.equals_insensitive(PathExt);
   });
 
-  bool IsHeader = llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
+  bool IsHeader = llvm::any_of(HeaderExtensions, [&PathExt](llvm::StringRef HeaderExt) {
     return HeaderExt.equals_insensitive(PathExt);
   });
 
@@ -50,7 +50,7 @@ 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) {
@@ -81,8 +81,8 @@ std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
   }
   llvm::StringMap<int> Candidates; // Target path => score.
   auto AwardTarget = [&](const char *TargetURI) {
-    if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
-      if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
+    if (auto TargetPath = URI::resolve(TargetURI, OriginalFile.raw())) {
+      if (PathRef(*TargetPath) != OriginalFile) // exclude the original file.
         ++Candidates[*TargetPath];
     } else {
       elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
@@ -94,7 +94,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.
-  bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
+  bool IsHeader = isHeaderFile(OriginalFile.raw(), AST.getLangOpts());
   Index->lookup(Request, [&](const Symbol &Sym) {
     if (IsHeader)
       AwardTarget(Sym.Definition.FileURI);
diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp
index 87fd261b906e6..58bf77a1c9285 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
@@ -278,7 +278,7 @@ bool IncludeInserter::shouldInsertInclude(
   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>
@@ -348,7 +348,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 3ab3d89030520..c5957afbdc4de 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -1302,7 +1302,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();
     // FIXME: We don't have a fitting value for Kind.
     HI.Definition =
         URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp
index e34706172f0bf..c37adc1f58593 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;
     }
   }
@@ -122,9 +122,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 =
@@ -157,7 +157,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();
     D.InsideMainFile = true;
     // We avoid the "warning" severity here in favor of LSP's "information".
     //
@@ -194,7 +194,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 =
@@ -204,7 +204,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();
     D.InsideMainFile = true;
     D.Severity = DiagnosticsEngine::Warning;
     D.Tags.push_back(Unnecessary);
@@ -343,7 +343,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}: {1}",
            Inc.Resolved, FE.takeError());
@@ -363,7 +363,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 c1878f91b5e16..72d5f6568ce51 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.cpp
+++ b/clang-tools-extra/clangd/ModulesBuilder.cpp
@@ -36,10 +36,10 @@ namespace {
 // TODO: Move these module fils out of the temporary directory if the module
 // files are persistent.
 llvm::SmallString<256> getUniqueModuleFilesPath(PathRef MainFile) {
-  llvm::SmallString<128> HashedPrefix = llvm::sys::path::filename(MainFile);
+  llvm::SmallString<128> HashedPrefix = MainFile.filename();
   // There might be multiple files with the same name in a project. So appending
   // the hash value of the full path to make sure they won't conflict.
-  HashedPrefix += std::to_string(llvm::hash_value(MainFile));
+  HashedPrefix += std::to_string(hash_value(MainFile));
 
   llvm::SmallString<256> ResultPattern;
 
@@ -64,7 +64,7 @@ llvm::SmallString<256> getUniqueModuleFilesPath(PathRef MainFile) {
 // Get a unique 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()) {
@@ -97,7 +97,7 @@ class FailedPrerequisiteModules : public PrerequisiteModules {
 
 struct ModuleFile {
   ModuleFile(StringRef ModuleName, PathRef ModuleFilePath)
-      : ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.str()) {}
+      : ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.raw()) {}
 
   ModuleFile() = delete;
 
@@ -216,7 +216,7 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,
   // listener.
   Reader.setListener(nullptr);
 
-  if (Reader.ReadAST(ModuleFilePath, serialization::MK_MainFile,
+  if (Reader.ReadAST(ModuleFilePath.raw(), serialization::MK_MainFile,
                      SourceLocation(),
                      ASTReader::ARR_None) != ASTReader::Success)
     return false;
@@ -308,7 +308,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);
@@ -370,7 +370,7 @@ class ModuleNameToSourceCache {
 
   void addEntry(llvm::StringRef ModuleName, PathRef Source) {
     std::lock_guard<std::mutex> Lock(CacheMutex);
-    ModuleNameToSourceCache[ModuleName] = Source.str();
+    ModuleNameToSourceCache[ModuleName] = Source.raw();
   }
 
   void eraseEntry(llvm::StringRef ModuleName) {
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index 3f63daaf400db..2874b2c245bbd 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -196,8 +196,9 @@ class ReplayPreamble : private PPCallbacks {
   void replay() {
     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 6fab3e2191426..1e2fcccf78c71 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -595,11 +595,11 @@ 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, 0);
 
   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) {
@@ -650,7 +650,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);
@@ -743,7 +743,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, 0);
   auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
   return compileCommandsAreEqual(Inputs.CompileCommand,
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 05c8041df7de7..dc220daba1c82 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 c7ef1a13e6e39..e89880527ca1c 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"
@@ -94,8 +95,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);
diff --git a/clang-tools-extra/clangd/ScanningProjectModules.cpp b/clang-tools-extra/clangd/ScanningProjectModules.cpp
index e561513fe687f..b509d7cee838d 100644
--- a/clang-tools-extra/clangd/ScanningProjectModules.cpp
+++ b/clang-tools-extra/clangd/ScanningProjectModules.cpp
@@ -92,7 +92,7 @@ class ModuleDependencyScanner {
 std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
 ModuleDependencyScanner::scan(PathRef FilePath,
                               const ProjectModules::CommandMangler &Mangler) {
-  auto Candidates = CDB->getCompileCommands(FilePath);
+  auto Candidates = CDB->getCompileCommands(FilePath.raw());
   if (Candidates.empty())
     return std::nullopt;
 
@@ -106,7 +106,7 @@ ModuleDependencyScanner::scan(PathRef FilePath,
 
   using namespace clang::tooling::dependencies;
 
-  llvm::SmallString<128> FilePathDir(FilePath);
+  llvm::SmallString<128> FilePathDir(FilePath.raw());
   llvm::sys::path::remove_filename(FilePathDir);
   DependencyScanningTool ScanningTool(Service, TFS.view(FilePathDir));
 
@@ -122,7 +122,7 @@ ModuleDependencyScanner::scan(PathRef FilePath,
   ModuleDependencyInfo Result;
 
   if (ScanningResult->Provides) {
-    ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath;
+    ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath.raw();
     Result.ModuleName = ScanningResult->Provides->ModuleName;
   }
 
@@ -195,7 +195,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).owned().raw();
   }
 
   std::string getModuleNameForSource(PathRef File) override {
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 035e5e63d8fbb..c7e76d9d70e96 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -310,7 +310,7 @@ class TUScheduler::HeaderIncluderCache {
   // 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);
     Association *&First = It.first->second;
     if (First)
       invalidate(First);
@@ -323,7 +323,7 @@ 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];
+    Association *&First = MainToFirst[MainFile.raw()];
     if (First) {
       invalidate(First);
       First = nullptr;
@@ -335,7 +335,7 @@ class TUScheduler::HeaderIncluderCache {
   /// 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();
+    return HeaderToMain.lookup(Header.raw()).MainFile.str();
   }
 
   size_t getUsedBytes() const {
@@ -483,7 +483,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()) {
@@ -822,9 +822,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 +841,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 +882,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 +997,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;
@@ -1211,8 +1213,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.
@@ -1324,7 +1327,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();
 }
 
@@ -1352,7 +1355,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));
@@ -1640,7 +1643,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();
     };
   }
@@ -1673,7 +1676,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) {
@@ -1692,12 +1695,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);
@@ -1744,13 +1747,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);
 }
@@ -1758,18 +1761,18 @@ 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);
@@ -1781,11 +1784,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.owned().raw()),
                Action = std::move(Action), this]() mutable {
     clang::noteBottomOfStack();
     ThreadCrashReporter ScopedReporter([&Name, &Contents, &Command]() {
@@ -1811,8 +1814,7 @@ void TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
     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 {
diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp
index 1d79a7a7399ec..d2be8fa03b057 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,21 +99,21 @@ class DotClangTidyTree {
 
   void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) {
     namespace path = llvm::sys::path;
-    assert(path::is_absolute(AbsPath));
+    assert(AbsPath.isAbsolute());
 
     // Compute absolute paths to all ancestors (substrings of P.Path).
     // Ensure cache entries for each ancestor exist in the map.
     llvm::SmallVector<DotClangTidyCache *> Caches;
     {
       std::lock_guard<std::mutex> Lock(Mu);
-      for (auto Ancestor = absoluteParent(AbsPath); !Ancestor.empty();
-           Ancestor = absoluteParent(Ancestor)) {
-        auto It = Cache.find(Ancestor);
+      for (auto Ancestor = AbsPath.absoluteParent(); !Ancestor.empty();
+           Ancestor = Ancestor.absoluteParent()) {
+        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);
       }
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 8b9fffa3f64cd..af63e15bcea14 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -226,7 +226,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;
@@ -514,7 +514,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.
@@ -776,7 +776,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)};
@@ -1320,7 +1320,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 {
@@ -1538,8 +1538,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};
@@ -1590,7 +1591,7 @@ 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))
@@ -1668,9 +1669,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));
   }
@@ -1789,7 +1790,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;
@@ -2204,12 +2205,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();
@@ -2289,7 +2290,7 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
           !cast<VarDecl>(Decl)->isLocalVarDecl()) &&
         Decl->getKind() != Decl::Kind::Field)
       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 496d1455def4b..fad6e3cbe3e46 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());
@@ -224,14 +224,14 @@ void BackgroundIndex::update(
     // 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);
+      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.
@@ -386,12 +386,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);
     }
@@ -417,7 +417,9 @@ BackgroundIndex::loadProject(std::vector<std::string> MainFiles) {
     TUsToIndex.insert(TUForFile);
   }
 
-  return {TUsToIndex.begin(), TUsToIndex.end()};
+  auto ToRaw = [](PathRef R) { return R.raw(); };
+  return {llvm::map_iterator(TUsToIndex.begin(), ToRaw),
+          llvm::map_iterator(TUsToIndex.end(), ToRaw)};
 }
 
 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..1babc3f85edbb 100644
--- a/clang-tools-extra/clangd/index/Background.h
+++ b/clang-tools-extra/clangd/index/Background.h
@@ -211,6 +211,7 @@ class BackgroundIndex : public SwapIndex {
   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..4abe594e50e6f 100644
--- a/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp
+++ b/clang-tools-extra/clangd/index/BackgroundIndexLoader.cpp
@@ -48,17 +48,17 @@ class BackgroundIndexLoader {
 
 std::pair<const LoadedShard &, std::vector<Path>>
 BackgroundIndexLoader::loadShard(PathRef StartSourceFile, PathRef DependentTU) {
-  auto It = LoadedShards.try_emplace(StartSourceFile);
+  auto It = LoadedShards.try_emplace(StartSourceFile.raw());
   LoadedShard &LS = It.first->getValue();
   std::vector<Path> Edges = {};
   // Return the cached shard.
   if (!It.second)
     return {LS, Edges};
 
-  LS.AbsolutePath = StartSourceFile.str();
-  LS.DependentTU = std::string(DependentTU);
+  LS.AbsolutePath = StartSourceFile.owned();
+  LS.DependentTU = DependentTU.owned();
   BackgroundIndexStorage *Storage = IndexStorageFactory(LS.AbsolutePath);
-  auto Shard = Storage->loadShard(StartSourceFile);
+  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;
@@ -91,7 +91,7 @@ void BackgroundIndexLoader::load(PathRef MainFile) {
   llvm::StringSet<> InQueue;
   // Following containers points to strings inside InQueue.
   std::queue<PathRef> ToVisit;
-  InQueue.insert(MainFile);
+  InQueue.insert(MainFile.raw());
   ToVisit.push(MainFile);
 
   while (!ToVisit.empty()) {
@@ -100,7 +100,7 @@ void BackgroundIndexLoader::load(PathRef MainFile) {
 
     auto ShardAndEdges = loadShard(SourceFile, MainFile);
     for (PathRef Edge : ShardAndEdges.second) {
-      auto It = InQueue.insert(Edge);
+      auto It = InQueue.insert(Edge.raw());
       if (It.second)
         ToVisit.push(It.first->getKey());
     }
@@ -117,7 +117,7 @@ std::vector<LoadedShard> BackgroundIndexLoader::takeResult() && {
 } // 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 d887b09482a95..5c077c4ed9471 100644
--- a/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp
+++ b/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp
@@ -109,7 +109,7 @@ 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");
@@ -126,7 +126,7 @@ 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;
diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp
index aa573e312a756..7714fdbdebe65 100644
--- a/clang-tools-extra/clangd/index/FileIndex.cpp
+++ b/clang-tools-extra/clangd/index/FileIndex.cpp
@@ -182,7 +182,7 @@ FileShardedIndex::FileShardedIndex(IndexFileIn Input)
 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);
@@ -470,7 +470,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/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp
index 26059167208aa..436b6be40dbf6 100644
--- a/clang-tools-extra/clangd/refactor/Rename.cpp
+++ b/clang-tools-extra/clangd/refactor/Rename.cpp
@@ -784,7 +784,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());
@@ -891,7 +892,7 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
     if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
       return;
     if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
-      if (!pathEqual(*RefFilePath, MainFile))
+      if (PathRef(*RefFilePath) != MainFile)
         AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
     }
   });
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 d6556bba14725..52e8c9315de73 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 e4eef228b6b99..6e87cc3ecfab1 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -428,7 +428,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 in-line function definition.
@@ -489,12 +489,12 @@ class DefineOutline : public Tweak {
 
   Expected<Effect> apply(const Selection &Sel) override {
     const SourceManager &SM = Sel.AST->getSourceManager();
-    auto CCFile = SameFile ? Sel.AST->tuPath().str()
-                           : getSourceFile(Sel.AST->tuPath(), Sel);
+    auto CCFile = SameFile ? 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)
@@ -507,13 +507,14 @@ class DefineOutline : public Tweak {
     auto FuncDef = getFunctionSourceCode(
         Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
         Sel.AST->getHeuristicResolver(),
-        SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
+        SameFile &&
+            isHeaderFile(Sel.AST->tuPath().raw(), Sel.AST->getLangOpts()));
     if (!FuncDef)
       return FuncDef.takeError();
 
-    SourceManagerForFile SMFF(*CCFile, Contents);
+    SourceManagerForFile SMFF(CCFile->raw(), Contents);
     const tooling::Replacement InsertFunctionDef(
-        *CCFile, InsertionPoint->Offset, 0, *FuncDef);
+        CCFile->raw(), InsertionPoint->Offset, 0, *FuncDef);
     auto Effect = Effect::mainFileEdit(
         SMFF.get(), tooling::Replacements(InsertFunctionDef));
     if (!Effect)
@@ -536,7 +537,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);
diff --git a/clang-tools-extra/clangd/support/FileCache.cpp b/clang-tools-extra/clangd/support/FileCache.cpp
index cc59c648b062a..9c55c0499f427 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)
-    : Path(Path), ValidTime(std::chrono::steady_clock::time_point::min()),
+FileCache::FileCache(PathRef Path)
+    : Path(Path.raw()), ValidTime(std::chrono::steady_clock::time_point::min()),
       ModifiedTime(), Size(CacheDiskMismatch) {
-  assert(llvm::sys::path::is_absolute(Path));
+  assert(Path.isAbsolute());
 }
 
 void FileCache::read(
diff --git a/clang-tools-extra/clangd/support/Path.cpp b/clang-tools-extra/clangd/support/Path.cpp
index 1dd107cc6abef..9e9a1c308e36c 100644
--- a/clang-tools-extra/clangd/support/Path.cpp
+++ b/clang-tools-extra/clangd/support/Path.cpp
@@ -7,47 +7,151 @@
 //===----------------------------------------------------------------------===//
 
 #include "support/Path.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
+
+#include <ostream>
+
 namespace clang {
 namespace clangd {
 
-#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));
+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));
+PathRef PathRef::parentPath(Style Style) const {
+  return llvm::sys::path::parent_path(Data, Style);
+}
+
+bool PathRef::startsWith(PathRef Path, Style Style) const {
+  assert(isAbsolute() && Path.isAbsolute()); // XXX: this doesn't pass the Style
+
   // 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();
+  PathRef Ancestor = withoutTrailingSeparator(Style);
   // Ensure Path starts with Ancestor.
-  if (!pathEqual(Ancestor, Path.take_front(Ancestor.size())))
+  if (Ancestor != PathRef(Path.raw().take_front(Ancestor.size())))
     return false;
-  Path = Path.drop_front(Ancestor.size());
+  Path = Path.Data.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);
+  return Path.empty() ||
+         llvm::sys::path::is_separator(Path.raw().front(), Style);
+}
+
+llvm::StringRef PathRef::filename(Style Style) const {
+  return llvm::sys::path::filename(raw(), Style);
+}
+
+llvm::StringRef PathRef::extension(Style Style) const {
+  return llvm::sys::path::extension(raw(), Style);
+}
+
+PathRef PathRef::stem(Style Style) const {
+  return llvm::sys::path::stem(raw(), Style);
+}
+
+PathRef PathRef::withoutTrailingSeparator(Style Style) const {
+  if (llvm::sys::path::is_separator(Data.back(), Style))
+    return Data.drop_back();
+  return Data;
+}
+
+Path PathRef::removeDots() const {
+  llvm::SmallString<128> CanonPath(Data);
+  llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
+  return CanonPath.str().str();
+}
+
+Path PathRef::caseFolded() const {
+#ifdef CLANGD_PATH_CASE_INSENSITIVE
+  return Data.lower();
+#else
+  return Data.str();
+#endif
+}
+
+bool PathRef::isAbsolute(Style Style) const {
+  return llvm::sys::path::is_absolute(Data, Style);
+}
+
+bool PathRef::isRelative(Style Style) const {
+  return llvm::sys::path::is_relative(Data, Style);
+}
+
+bool PathRef::exists() const { return llvm::sys::fs::exists(Data); }
+
+std::ostream &operator<<(std::ostream &OS, PathRef Path) {
+  OS << std::string_view(Path.raw());
+  return OS;
+}
+
+bool operator==(PathRef LHS, PathRef RHS) {
+#ifdef CLANGD_PATH_CASE_INSENSITIVE
+  return LHS.raw().equals_insensitive(RHS.raw());
+#else
+  return LHS.raw() == RHS.raw();
+#endif
+}
+
+llvm::hash_code hash_value(PathRef P) {
+#ifdef CLANGD_PATH_CASE_INSENSITIVE
+  return hash_combine_range(llvm::map_iterator(P.raw().begin(), llvm::toLower),
+                            llvm::map_iterator(P.raw().end(), llvm::toLower));
+#else
+  return hash_value(P.raw());
+#endif
 }
+
 } // namespace clangd
 } // namespace clang
+
+namespace llvm {
+
+unsigned DenseMapInfo<clang::clangd::PathRef, void>::getHashValue(
+    clang::clangd::PathRef Val) {
+  assert(Val.Data.data() != getEmptyKey().Data.data() &&
+         "Cannot hash the empty key!");
+  assert(Val.Data.data() != getTombstoneKey().Data.data() &&
+         "Cannot hash the tombstone key!");
+  return (unsigned)(hash_value(Val));
+}
+
+namespace cl {
+
+void parser<clang::clangd::Path>::printOptionDiff(const Option &O,
+                                                  clang::clangd::PathRef V,
+                                                  const OptVal &Default,
+                                                  size_t GlobalWidth) const {
+  constexpr static const size_t MaxOptWidth = 8;
+
+  printOptionName(O, GlobalWidth);
+  outs() << "= " << V.raw();
+  size_t NumSpaces = MaxOptWidth > V.size() ? MaxOptWidth - V.size() : 0;
+  outs().indent(NumSpaces) << " (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..81d8183a728f0 100644
--- a/clang-tools-extra/clangd/support/Path.h
+++ b/clang-tools-extra/clangd/support/Path.h
@@ -9,8 +9,15 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_PATH_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_PATH_H
 
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/SmallString.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 <string>
 
 /// Whether current platform treats paths case insensitively.
@@ -21,30 +28,179 @@
 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;
+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);
+  }
+};
+
+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 int 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;
+
+  /// Checks if \p this 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 this and \p Path must be absolute.
+  [[nodiscard]] bool startsWith(PathRef Path,
+                                Style Style = Style::native) const;
+
+  [[nodiscard]] llvm::StringRef filename(Style Style = Style::native) const;
+
+  [[nodiscard]] llvm::StringRef extension(Style Style = Style::native) const;
+
+  [[nodiscard]] PathRef stem(Style Style = Style::native) const;
+
+  /// 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.
+  [[nodiscard]] Path removeDots() const;
+
+  [[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;
+
+  [[nodiscard]] size_t size() const { return Data.size(); }
+
+  [[nodiscard]] bool empty() const { return Data.empty(); }
+
+  [[nodiscard]] bool isAbsolute(Style Style = Style::native) const;
+  [[nodiscard]] bool isRelative(Style Style = Style::native) const;
+
+  [[nodiscard]] bool exists() const;
+
+private:
+  llvm::StringRef Data;
+
+  friend llvm::DenseMapInfo<clang::clangd::PathRef, void>;
+};
+
+inline Path::Path(PathRef Ref) : Data(Ref.raw().str()) {}
+
+// for gtest
+std::ostream &operator<<(std::ostream &OS, PathRef Path);
+
+inline Path::operator PathRef() const { return PathRef(Data); }
+inline PathRef Path::ref() const { return PathRef(Data); }
+
+bool operator==(PathRef LHS, PathRef RHS);
+inline bool operator!=(PathRef LHS, PathRef RHS) { return !(LHS == RHS); }
+
+inline bool operator==(Path LHS, Path RHS) {
+  return clang::clangd::PathRef(LHS) == clang::clangd::PathRef(RHS);
+}
+inline bool operator!=(Path LHS, Path RHS) { return !(LHS == RHS); }
+
+[[nodiscard]] llvm::hash_code hash_value(PathRef P);
+[[nodiscard]] inline llvm::hash_code hash_value(const Path &P) {
+  return hash_value(PathRef(P));
+}
+
+inline llvm::json::Value toJson(PathRef Path) { return Path.raw(); }
+
 } // namespace clangd
 } // namespace clang
 
+namespace llvm {
+
+template <> struct format_provider<clang::clangd::PathRef> {
+  static void format(const clang::clangd::PathRef &V, llvm::raw_ostream &Stream,
+                     StringRef Style) {
+    format_provider<llvm::StringRef>::format(V.raw(), Stream, Style);
+  }
+};
+
+template <> struct format_provider<clang::clangd::Path> {
+  static void format(const clang::clangd::Path &V, llvm::raw_ostream &Stream,
+                     StringRef Style) {
+    format_provider<clang::clangd::PathRef>::format(V, Stream, Style);
+  }
+};
+
+template <> struct DenseMapInfo<clang::clangd::PathRef, void> {
+  static inline clang::clangd::PathRef getEmptyKey() {
+    return DenseMapInfo<llvm::StringRef>::getEmptyKey();
+  }
+
+  static inline clang::clangd::PathRef getTombstoneKey() {
+    return DenseMapInfo<llvm::StringRef>::getTombstoneKey();
+  }
+
+  static unsigned getHashValue(clang::clangd::PathRef Val);
+
+  static bool isEqual(clang::clangd::PathRef LHS, clang::clangd::PathRef RHS) {
+    if (RHS.Data.data() == getEmptyKey().Data.data())
+      return LHS.Data.data() == getEmptyKey().Data.data();
+    if (RHS.Data.data() == getTombstoneKey().Data.data())
+      return LHS.Data.data() == getTombstoneKey().Data.data();
+    return LHS == RHS;
+  }
+};
+
+namespace cl {
+
+template <>
+struct parser<clang::clangd::Path> : public basic_parser<clang::clangd::Path> {
+public:
+  parser(Option &O) : basic_parser(O) {}
+
+  bool parse(cl::Option &O, StringRef ArgName, StringRef ArgValue,
+             clang::clangd::Path &Val) {
+    Val = ArgValue.str();
+    return true;
+  }
+
+  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/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 4bd256d6be22b..26b34e8dee3fc 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -673,12 +673,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 "
@@ -695,7 +695,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
@@ -826,7 +826,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();
@@ -911,7 +911,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.BuildDynamicSymbolIndex = true;
   std::vector<std::unique_ptr<SymbolIndex>> IdxStack;
 #if CLANGD_ENABLE_REMOTE
@@ -1013,9 +1013,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 d0bc3c4d7db98..3eee66c02b851 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -622,7 +622,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 ada14c9939318..2d26e1ac5656c 100644
--- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
@@ -98,7 +98,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;
@@ -132,11 +132,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 +148,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 +190,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");
@@ -246,8 +244,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");
@@ -286,7 +283,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());
@@ -296,7 +293,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());
@@ -355,7 +352,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());
@@ -405,7 +402,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());
@@ -420,7 +417,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());
@@ -438,7 +435,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());
@@ -479,7 +476,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());
@@ -495,7 +492,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());
@@ -511,7 +508,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());
@@ -529,7 +526,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());
 
@@ -560,7 +557,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;
@@ -624,7 +621,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;
@@ -652,7 +649,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.
@@ -901,7 +898,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 643b8e9f12d75..fce04fe711680 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.
@@ -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,11 +1117,11 @@ 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 = File.filename();
       std::vector<std::string> CommandLine = {"clangd", "-ffreestanding",
-                                              std::string(File)};
-      return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
-                                      FileName, std::move(CommandLine), "")};
+                                              File.owned().raw()};
+      return {tooling::CompileCommand(File.parentPath().raw(), FileName,
+                                      std::move(CommandLine), "")};
     }
 
     std::vector<std::string> ExtraClangFlags;
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 718bee2e40b11..c197c288a0f3d 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/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
index c9e01e52dac1f..1fc72fb36a9ee 100644
--- a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
+++ b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
@@ -55,23 +55,23 @@ TEST(GlobalCompilationDatabaseTest, FallbackCommand) {
                                            testPath("foo/bar")));
 }
 
-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 {
+    getCompileCommand(PathRef File) const override {
       if (File == 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");
     }
 
diff --git a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
index e600207de458a..e729dea4cacc0 100644
--- a/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HeaderSourceSwitchTests.cpp
@@ -339,13 +339,8 @@ TEST(HeaderSourceSwitchTest, CaseSensitivity) {
   //   match what we've seen through index.
   // - 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))));
-#else
-  EXPECT_THAT(getCorrespondingHeaderOrSource(HeaderAbsPath, AST, Index.get()),
-              llvm::ValueIs(testing::StrCaseEq(testPath(TU.HeaderFilename))));
-#endif
+              llvm::ValueIs(Path(testPath(TU.Filename))));
 }
 
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/HeadersTests.cpp b/clang-tools-extra/clangd/unittests/HeadersTests.cpp
index 751383e3b4650..fd62c72b9340b 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/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
index 16a2f9448b1ec..330bed8d08a5c 100644
--- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
@@ -883,14 +883,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/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
index 43f38e39c8952..27c39955035b4 100644
--- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
@@ -79,7 +79,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 +155,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 {
@@ -1569,7 +1569,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.owned().raw());
     }
   };
 
diff --git a/clang-tools-extra/clangd/unittests/TestFS.cpp b/clang-tools-extra/clangd/unittests/TestFS.cpp
index bb309609eda20..816d5e364d99b 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.owned().raw());
   } 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() {
@@ -91,9 +90,9 @@ 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(File.isRelative() && "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);
@@ -110,7 +109,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("/"))
diff --git a/clang-tools-extra/clangd/unittests/support/PathTests.cpp b/clang-tools-extra/clangd/unittests/support/PathTests.cpp
index 599c76926d30d..98f3d6a2cc6d7 100644
--- a/clang-tools-extra/clangd/unittests/support/PathTests.cpp
+++ b/clang-tools-extra/clangd/unittests/support/PathTests.cpp
@@ -15,21 +15,21 @@ 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
 }
 } // namespace



More information about the cfe-commits mailing list