[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-14 Thread Phabricator via Phabricator via cfe-commits
This revision was automatically updated to reflect the committed changes.
Closed by commit rL310819: [clangd] Check if CompileCommand has changed on 
forceReparse. (authored by ibiryukov).

Repository:
  rL LLVM

https://reviews.llvm.org/D36398

Files:
  clang-tools-extra/trunk/clangd/ClangdServer.cpp
  clang-tools-extra/trunk/clangd/ClangdServer.h
  clang-tools-extra/trunk/clangd/ClangdUnitStore.cpp
  clang-tools-extra/trunk/clangd/ClangdUnitStore.h
  clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp

Index: clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
===
--- clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
+++ clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
@@ -501,6 +501,53 @@
 }
 #endif // LLVM_ON_UNIX
 
+TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
+  MockFSProvider FS;
+  ErrorCheckingDiagConsumer DiagConsumer;
+  MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
+  ClangdServer Server(CDB, DiagConsumer, FS,
+  /*RunSynchronously=*/true);
+  // No need to sync reparses, because RunSynchronously is set
+  // to true.
+
+  auto FooCpp = getVirtualTestFilePath("foo.cpp");
+  const auto SourceContents1 = R"cpp(
+template 
+struct foo { T x; };
+)cpp";
+  const auto SourceContents2 = R"cpp(
+template 
+struct bar { T x; };
+)cpp";
+
+  FS.Files[FooCpp] = "";
+  FS.ExpectedFile = FooCpp;
+
+  // First parse files in C mode and check they produce errors.
+  CDB.ExtraClangFlags = {"-xc"};
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+
+  // Now switch to C++ mode.
+  CDB.ExtraClangFlags = {"-xc++"};
+  // Currently, addDocument never checks if CompileCommand has changed, so we
+  // expect to see the errors.
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  // But forceReparse should reparse the file with proper flags.
+  Server.forceReparse(FooCpp);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+  // Subsequent addDocument calls should finish without errors too.
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+}
+
 class ClangdCompletionTest : public ClangdVFSTest {
 protected:
   bool ContainsItem(std::vector const , StringRef Name) {
Index: clang-tools-extra/trunk/clangd/ClangdServer.h
===
--- clang-tools-extra/trunk/clangd/ClangdServer.h
+++ clang-tools-extra/trunk/clangd/ClangdServer.h
@@ -192,10 +192,13 @@
   std::future addDocument(PathRef File, StringRef Contents);
   /// Remove \p File from list of tracked files, schedule a request to free
   /// resources associated with it.
-  /// \return A future that will become ready the file is removed and all
-  /// associated reosources are freed.
+  /// \return A future that will become ready when the file is removed and all
+  /// associated resources are freed.
   std::future removeDocument(PathRef File);
   /// Force \p File to be reparsed using the latest contents.
+  /// Will also check if CompileCommand, provided by GlobalCompilationDatabase
+  /// for \p File has changed. If it has, will remove currently stored Preamble
+  /// and AST and rebuild them from scratch.
   std::future forceReparse(PathRef File);
 
   /// Run code completion for \p File at \p Pos. If \p OverridenContents is not
Index: clang-tools-extra/trunk/clangd/ClangdServer.cpp
===
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp
@@ -154,9 +154,20 @@
 }
 
 std::future ClangdServer::forceReparse(PathRef File) {
-  // The addDocument schedules the reparse even if the contents of the file
-  // never changed, so we just call it here.
-  return addDocument(File, getDocument(File));
+  auto FileContents = DraftMgr.getDraft(File);
+  assert(FileContents.Draft &&
+ "forceReparse() was called for non-added document");
+
+  auto TaggedFS = FSProvider.getTaggedFileSystem(File);
+  auto Recreated = Units.recreateFileIfCompileCommandChanged(
+  File, ResourceDir, CDB, PCHs, TaggedFS.Value);
+
+  // Note that std::future from this cleanup action is ignored.
+  scheduleCancelRebuild(std::move(Recreated.RemovedFile));
+  // Schedule a reparse.
+  return scheduleReparseAndDiags(File, std::move(FileContents),
+ std::move(Recreated.FileInCollection),
+ std::move(TaggedFS));
 }
 
 Tagged
Index: clang-tools-extra/trunk/clangd/ClangdUnitStore.h

[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-11 Thread Manuel Klimek via Phabricator via cfe-commits
klimek accepted this revision.
klimek added a comment.
This revision is now accepted and ready to land.

lg




Comment at: unittests/clangd/ClangdTests.cpp:509-510
+  /*RunSynchronously=*/true);
+  // No need to sync reparses, because RunSynchronously is set
+  // to true.
+

Nit: Comment fits in one line.


https://reviews.llvm.org/D36398



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-08 Thread Ilya Biryukov via Phabricator via cfe-commits
ilya-biryukov marked an inline comment as done.
ilya-biryukov added a comment.

In https://reviews.llvm.org/D36398#834904, @klimek wrote:

> Also missing tests :)


Done :-)




Comment at: clangd/ClangdUnitStore.h:45-48
+  struct RecreateResult {
+std::shared_ptr FileInCollection;
+std::shared_ptr RemovedFile;
+  };

klimek wrote:
> ilya-biryukov wrote:
> > klimek wrote:
> > > Not obvious to me what things in there mean.
> > Added a comment. Hopefully makes sense now.
> Better, thanks. Now, why does this need to be shared_ptr (as opposed to 
> unique_ptr)? Don't we always only have one?
This `CppFile` may still be referenced from the worker threads that store a 
pending reparse task for this file. Or there may even be an active reparse on a 
separate thread.


https://reviews.llvm.org/D36398



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-08 Thread Ilya Biryukov via Phabricator via cfe-commits
ilya-biryukov updated this revision to Diff 110155.
ilya-biryukov added a comment.

Addressed review comments.

- Added a test for forceReparse.
- Got rid of a redundant `= nullptr` assignment.


https://reviews.llvm.org/D36398

Files:
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnitStore.cpp
  clangd/ClangdUnitStore.h
  unittests/clangd/ClangdTests.cpp

Index: unittests/clangd/ClangdTests.cpp
===
--- unittests/clangd/ClangdTests.cpp
+++ unittests/clangd/ClangdTests.cpp
@@ -500,6 +500,53 @@
 }
 #endif // LLVM_ON_UNIX
 
+TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
+  MockFSProvider FS;
+  ErrorCheckingDiagConsumer DiagConsumer;
+  MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
+  ClangdServer Server(CDB, DiagConsumer, FS,
+  /*RunSynchronously=*/true);
+  // No need to sync reparses, because RunSynchronously is set
+  // to true.
+
+  auto FooCpp = getVirtualTestFilePath("foo.cpp");
+  const auto SourceContents1 = R"cpp(
+template 
+struct foo { T x; };
+)cpp";
+  const auto SourceContents2 = R"cpp(
+template 
+struct bar { T x; };
+)cpp";
+
+  FS.Files[FooCpp] = "";
+  FS.ExpectedFile = FooCpp;
+
+  // First parse files in C mode and check they produce errors.
+  CDB.ExtraClangFlags = {"-xc"};
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+
+  // Now switch to C++ mode.
+  CDB.ExtraClangFlags = {"-xc++"};
+  // Currently, addDocument never checks if CompileCommand has changed, so we
+  // expect to see the errors.
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
+  // But forceReparse should reparse the file with proper flags.
+  Server.forceReparse(FooCpp);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+  // Subsequent addDocument calls should finish without errors too.
+  Server.addDocument(FooCpp, SourceContents1);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+  Server.addDocument(FooCpp, SourceContents2);
+  EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
+}
+
 class ClangdCompletionTest : public ClangdVFSTest {
 protected:
   bool ContainsItem(std::vector const , StringRef Name) {
Index: clangd/ClangdUnitStore.h
===
--- clangd/ClangdUnitStore.h
+++ clangd/ClangdUnitStore.h
@@ -42,6 +42,25 @@
 return It->second;
   }
 
+  struct RecreateResult {
+/// A CppFile, stored in this CppFileCollection for the corresponding
+/// filepath after calling recreateFileIfCompileCommandChanged.
+std::shared_ptr FileInCollection;
+/// If a new CppFile had to be created to account for changed
+/// CompileCommand, a previous CppFile instance will be returned in this
+/// field.
+std::shared_ptr RemovedFile;
+  };
+
+  /// Similar to getOrCreateFile, but will replace a current CppFile for \p File
+  /// with a new one if CompileCommand, provided by \p CDB has changed.
+  /// If a currently stored CppFile had to be replaced, the previous instance
+  /// will be returned in RecreateResult.RemovedFile.
+  RecreateResult recreateFileIfCompileCommandChanged(
+  PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+  std::shared_ptr PCHs,
+  IntrusiveRefCntPtr VFS);
+
   std::shared_ptr getFile(PathRef File) {
 std::lock_guard Lock(Mutex);
 
@@ -59,6 +78,9 @@
   tooling::CompileCommand getCompileCommand(GlobalCompilationDatabase ,
 PathRef File, PathRef ResourceDir);
 
+  bool compileCommandsAreEqual(tooling::CompileCommand const ,
+   tooling::CompileCommand const );
+
   std::mutex Mutex;
   llvm::StringMap OpenedFiles;
 };
Index: clangd/ClangdUnitStore.cpp
===
--- clangd/ClangdUnitStore.cpp
+++ clangd/ClangdUnitStore.cpp
@@ -9,6 +9,7 @@
 
 #include "ClangdUnitStore.h"
 #include "llvm/Support/Path.h"
+#include 
 
 using namespace clang::clangd;
 using namespace clang;
@@ -25,6 +26,32 @@
   return Result;
 }
 
+CppFileCollection::RecreateResult
+CppFileCollection::recreateFileIfCompileCommandChanged(
+PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+std::shared_ptr PCHs,
+IntrusiveRefCntPtr VFS) {
+  auto NewCommand = getCompileCommand(CDB, File, ResourceDir);
+
+  std::lock_guard Lock(Mutex);
+
+  RecreateResult Result;
+
+  auto It = OpenedFiles.find(File);
+  if (It == OpenedFiles.end()) {
+It = OpenedFiles
+ .try_emplace(File, CppFile::Create(File, std::move(NewCommand),
+std::move(PCHs)))
+ .first;
+  } else if 

[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-08 Thread Manuel Klimek via Phabricator via cfe-commits
klimek added a comment.

Also missing tests :)




Comment at: clangd/ClangdUnitStore.cpp:45
+ .first;
+Result.RemovedFile = nullptr;
+  } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),

ilya-biryukov wrote:
> klimek wrote:
> > Just say RemovedFile = nullptr in the struct?
> I'd argue that having it in the code, rather than declaration makes it more 
> readable. Besides, default ctor of shared_ptr gives us nullptr anyway, so we 
> can also leave it as is and remove the assignments altogether.
> 
> I've moved `=nullptr` assignments out of the if/else bodies. Let me know if 
> it still looks ugly.
Ah, I missed that it's a smart pointer; in that case, yes, remove the = nullptr.



Comment at: clangd/ClangdUnitStore.h:45-48
+  struct RecreateResult {
+std::shared_ptr FileInCollection;
+std::shared_ptr RemovedFile;
+  };

ilya-biryukov wrote:
> klimek wrote:
> > Not obvious to me what things in there mean.
> Added a comment. Hopefully makes sense now.
Better, thanks. Now, why does this need to be shared_ptr (as opposed to 
unique_ptr)? Don't we always only have one?


https://reviews.llvm.org/D36398



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-07 Thread Ilya Biryukov via Phabricator via cfe-commits
ilya-biryukov added inline comments.



Comment at: clangd/ClangdUnitStore.cpp:45
+ .first;
+Result.RemovedFile = nullptr;
+  } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),

klimek wrote:
> Just say RemovedFile = nullptr in the struct?
I'd argue that having it in the code, rather than declaration makes it more 
readable. Besides, default ctor of shared_ptr gives us nullptr anyway, so we 
can also leave it as is and remove the assignments altogether.

I've moved `=nullptr` assignments out of the if/else bodies. Let me know if it 
still looks ugly.



Comment at: clangd/ClangdUnitStore.h:45-48
+  struct RecreateResult {
+std::shared_ptr FileInCollection;
+std::shared_ptr RemovedFile;
+  };

klimek wrote:
> Not obvious to me what things in there mean.
Added a comment. Hopefully makes sense now.


https://reviews.llvm.org/D36398



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-07 Thread Ilya Biryukov via Phabricator via cfe-commits
ilya-biryukov updated this revision to Diff 110016.
ilya-biryukov added a comment.

- Moved assignment `RemoveFile = nullptr` around a bit.
- Added a comment to recreateFileIfCompileCommandChanged.


https://reviews.llvm.org/D36398

Files:
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnitStore.cpp
  clangd/ClangdUnitStore.h

Index: clangd/ClangdUnitStore.h
===
--- clangd/ClangdUnitStore.h
+++ clangd/ClangdUnitStore.h
@@ -42,6 +42,25 @@
 return It->second;
   }
 
+  struct RecreateResult {
+/// A CppFile, stored in this CppFileCollection for the corresponding
+/// filepath after calling recreateFileIfCompileCommandChanged.
+std::shared_ptr FileInCollection;
+/// If a new CppFile had to be created to account for changed
+/// CompileCommand, a previous CppFile instance will be returned in this
+/// field.
+std::shared_ptr RemovedFile;
+  };
+
+  /// Similar to getOrCreateFile, but will replace a current CppFile for \p File
+  /// with a new one if CompileCommand, provided by \p CDB has changed.
+  /// If a currently stored CppFile had to be replaced, the previous instance
+  /// will be returned in RecreateResult.RemovedFile.
+  RecreateResult recreateFileIfCompileCommandChanged(
+  PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+  std::shared_ptr PCHs,
+  IntrusiveRefCntPtr VFS);
+
   std::shared_ptr getFile(PathRef File) {
 std::lock_guard Lock(Mutex);
 
@@ -59,6 +78,9 @@
   tooling::CompileCommand getCompileCommand(GlobalCompilationDatabase ,
 PathRef File, PathRef ResourceDir);
 
+  bool compileCommandsAreEqual(tooling::CompileCommand const ,
+   tooling::CompileCommand const );
+
   std::mutex Mutex;
   llvm::StringMap OpenedFiles;
 };
Index: clangd/ClangdUnitStore.cpp
===
--- clangd/ClangdUnitStore.cpp
+++ clangd/ClangdUnitStore.cpp
@@ -9,6 +9,7 @@
 
 #include "ClangdUnitStore.h"
 #include "llvm/Support/Path.h"
+#include 
 
 using namespace clang::clangd;
 using namespace clang;
@@ -25,6 +26,33 @@
   return Result;
 }
 
+CppFileCollection::RecreateResult
+CppFileCollection::recreateFileIfCompileCommandChanged(
+PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+std::shared_ptr PCHs,
+IntrusiveRefCntPtr VFS) {
+  auto NewCommand = getCompileCommand(CDB, File, ResourceDir);
+
+  std::lock_guard Lock(Mutex);
+
+  RecreateResult Result;
+  Result.RemovedFile = nullptr;
+
+  auto It = OpenedFiles.find(File);
+  if (It == OpenedFiles.end()) {
+It = OpenedFiles
+ .try_emplace(File, CppFile::Create(File, std::move(NewCommand),
+std::move(PCHs)))
+ .first;
+  } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),
+  NewCommand)) {
+Result.RemovedFile = std::move(It->second);
+It->second = CppFile::Create(File, std::move(NewCommand), std::move(PCHs));
+  }
+  Result.FileInCollection = It->second;
+  return Result;
+}
+
 tooling::CompileCommand
 CppFileCollection::getCompileCommand(GlobalCompilationDatabase ,
  PathRef File, PathRef ResourceDir) {
@@ -39,3 +67,12 @@
  std::string(ResourceDir));
   return std::move(Commands.front());
 }
+
+bool CppFileCollection::compileCommandsAreEqual(
+tooling::CompileCommand const , tooling::CompileCommand const ) {
+  // tooling::CompileCommand.Output is ignored, it's not relevant for clangd.
+  return LHS.Directory == RHS.Directory &&
+ LHS.CommandLine.size() == RHS.CommandLine.size() &&
+ std::equal(LHS.CommandLine.begin(), LHS.CommandLine.end(),
+RHS.CommandLine.begin());
+}
Index: clangd/ClangdServer.h
===
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -192,10 +192,13 @@
   std::future addDocument(PathRef File, StringRef Contents);
   /// Remove \p File from list of tracked files, schedule a request to free
   /// resources associated with it.
-  /// \return A future that will become ready the file is removed and all
-  /// associated reosources are freed.
+  /// \return A future that will become ready when the file is removed and all
+  /// associated resources are freed.
   std::future removeDocument(PathRef File);
   /// Force \p File to be reparsed using the latest contents.
+  /// Will also check if CompileCommand, provided by GlobalCompilationDatabase
+  /// for \p File has changed. If it has, will remove currently stored Preamble
+  /// and AST and rebuild them from scratch.
   std::future forceReparse(PathRef File);
 
   /// Run code completion for \p File at \p Pos. If \p OverridenContents is not
Index: clangd/ClangdServer.cpp

[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-07 Thread Manuel Klimek via Phabricator via cfe-commits
klimek added inline comments.



Comment at: clangd/ClangdUnitStore.cpp:45
+ .first;
+Result.RemovedFile = nullptr;
+  } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),

Just say RemovedFile = nullptr in the struct?



Comment at: clangd/ClangdUnitStore.h:45-48
+  struct RecreateResult {
+std::shared_ptr FileInCollection;
+std::shared_ptr RemovedFile;
+  };

Not obvious to me what things in there mean.


https://reviews.llvm.org/D36398



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D36398: [clangd] Check if CompileCommand has changed on forceReparse.

2017-08-07 Thread Ilya Biryukov via Phabricator via cfe-commits
ilya-biryukov created this revision.

https://reviews.llvm.org/D36398

Files:
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnitStore.cpp
  clangd/ClangdUnitStore.h

Index: clangd/ClangdUnitStore.h
===
--- clangd/ClangdUnitStore.h
+++ clangd/ClangdUnitStore.h
@@ -42,6 +42,16 @@
 return It->second;
   }
 
+  struct RecreateResult {
+std::shared_ptr FileInCollection;
+std::shared_ptr RemovedFile;
+  };
+
+  RecreateResult recreateFileIfCompileCommandChanged(
+  PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+  std::shared_ptr PCHs,
+  IntrusiveRefCntPtr VFS);
+
   std::shared_ptr getFile(PathRef File) {
 std::lock_guard Lock(Mutex);
 
@@ -59,6 +69,9 @@
   tooling::CompileCommand getCompileCommand(GlobalCompilationDatabase ,
 PathRef File, PathRef ResourceDir);
 
+  bool compileCommandsAreEqual(tooling::CompileCommand const ,
+   tooling::CompileCommand const );
+
   std::mutex Mutex;
   llvm::StringMap OpenedFiles;
 };
Index: clangd/ClangdUnitStore.cpp
===
--- clangd/ClangdUnitStore.cpp
+++ clangd/ClangdUnitStore.cpp
@@ -9,6 +9,7 @@
 
 #include "ClangdUnitStore.h"
 #include "llvm/Support/Path.h"
+#include 
 
 using namespace clang::clangd;
 using namespace clang;
@@ -25,6 +26,34 @@
   return Result;
 }
 
+CppFileCollection::RecreateResult
+CppFileCollection::recreateFileIfCompileCommandChanged(
+PathRef File, PathRef ResourceDir, GlobalCompilationDatabase ,
+std::shared_ptr PCHs,
+IntrusiveRefCntPtr VFS) {
+  auto NewCommand = getCompileCommand(CDB, File, ResourceDir);
+
+  std::lock_guard Lock(Mutex);
+
+  RecreateResult Result;
+  auto It = OpenedFiles.find(File);
+  if (It == OpenedFiles.end()) {
+It = OpenedFiles
+ .try_emplace(File, CppFile::Create(File, std::move(NewCommand),
+std::move(PCHs)))
+ .first;
+Result.RemovedFile = nullptr;
+  } else if (!compileCommandsAreEqual(It->second->getCompileCommand(),
+  NewCommand)) {
+Result.RemovedFile = std::move(It->second);
+It->second = CppFile::Create(File, std::move(NewCommand), std::move(PCHs));
+  } else {
+Result.RemovedFile = nullptr;
+  }
+  Result.FileInCollection = It->second;
+  return Result;
+}
+
 tooling::CompileCommand
 CppFileCollection::getCompileCommand(GlobalCompilationDatabase ,
  PathRef File, PathRef ResourceDir) {
@@ -39,3 +68,12 @@
  std::string(ResourceDir));
   return std::move(Commands.front());
 }
+
+bool CppFileCollection::compileCommandsAreEqual(
+tooling::CompileCommand const , tooling::CompileCommand const ) {
+  // tooling::CompileCommand.Output is ignored, it's not relevant for clangd.
+  return LHS.Directory == RHS.Directory &&
+ LHS.CommandLine.size() == RHS.CommandLine.size() &&
+ std::equal(LHS.CommandLine.begin(), LHS.CommandLine.end(),
+RHS.CommandLine.begin());
+}
Index: clangd/ClangdServer.h
===
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -192,10 +192,13 @@
   std::future addDocument(PathRef File, StringRef Contents);
   /// Remove \p File from list of tracked files, schedule a request to free
   /// resources associated with it.
-  /// \return A future that will become ready the file is removed and all
-  /// associated reosources are freed.
+  /// \return A future that will become ready when the file is removed and all
+  /// associated resources are freed.
   std::future removeDocument(PathRef File);
   /// Force \p File to be reparsed using the latest contents.
+  /// Will also check if CompileCommand, provided by GlobalCompilationDatabase
+  /// for \p File has changed. If it has, will remove currently stored Preamble
+  /// and AST and rebuild them from scratch.
   std::future forceReparse(PathRef File);
 
   /// Run code completion for \p File at \p Pos. If \p OverridenContents is not
Index: clangd/ClangdServer.cpp
===
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -154,9 +154,20 @@
 }
 
 std::future ClangdServer::forceReparse(PathRef File) {
-  // The addDocument schedules the reparse even if the contents of the file
-  // never changed, so we just call it here.
-  return addDocument(File, getDocument(File));
+  auto FileContents = DraftMgr.getDraft(File);
+  assert(FileContents.Draft &&
+ "forceReparse() was called for non-added document");
+
+  auto TaggedFS = FSProvider.getTaggedFileSystem(File);
+  auto Recreated = Units.recreateFileIfCompileCommandChanged(
+  File, ResourceDir, CDB, PCHs, TaggedFS.Value);
+
+  // Note