[clang-tools-extra] [clangd] [C++20] [Modules] Introduce initial support for C++20 Modules (PR #66462)
Chuanqi Xu via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 14 23:31:39 PDT 2023
https://github.com/ChuanqiXu9 updated https://github.com/llvm/llvm-project/pull/66462
>From ec7d378ef14a8740c29d046f266f430c434f4142 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
Date: Fri, 15 Sep 2023 11:33:53 +0800
Subject: [PATCH] [clangd] [C++20] [Modules] Introduce initial support for
C++20 Modules
Alternatives to https://reviews.llvm.org/D153114.
Try to address https://github.com/clangd/clangd/issues/1293.
See the links for design ideas.
This is the initial support for C++20 Modules in clangd.
As suggested by sammccall in https://reviews.llvm.org/D153114,
we should minimize the scope of the initial patch to make it easier
to review and understand so that every one are in the same page:
> Don't attempt any cross-file or cross-version coordination: i.e. don't
> try to reuse BMIs between different files, don't try to reuse BMIs
> between (preamble) reparses of the same file, don't try to persist the
> module graph. Instead, when building a preamble, synchronously scan
> for the module graph, build the required PCMs on the single preamble
> thread with filenames private to that preamble, and then proceed to
> build the preamble.
And this patch reflects the above opinions.
---
clang-tools-extra/clangd/CMakeLists.txt | 3 +
clang-tools-extra/clangd/ClangdServer.cpp | 1 +
clang-tools-extra/clangd/ClangdServer.h | 3 +
.../clangd/GlobalCompilationDatabase.cpp | 21 ++
.../clangd/GlobalCompilationDatabase.h | 6 +
.../clangd/ModuleDependencyScanner.cpp | 86 ++++++
.../clangd/ModuleDependencyScanner.h | 78 +++++
clang-tools-extra/clangd/ModuleFilesInfo.cpp | 282 ++++++++++++++++++
clang-tools-extra/clangd/ModuleFilesInfo.h | 118 ++++++++
clang-tools-extra/clangd/ParsedAST.cpp | 8 +
clang-tools-extra/clangd/Preamble.cpp | 23 +-
clang-tools-extra/clangd/Preamble.h | 7 +
clang-tools-extra/clangd/TUScheduler.cpp | 13 +
clang-tools-extra/clangd/TUScheduler.h | 3 +
clang-tools-extra/clangd/test/CMakeLists.txt | 1 +
clang-tools-extra/clangd/test/modules.test | 79 +++++
clang-tools-extra/clangd/tool/Check.cpp | 8 +-
clang-tools-extra/clangd/tool/ClangdMain.cpp | 8 +
.../clangd/unittests/CMakeLists.txt | 2 +
.../clangd/unittests/CodeCompleteTests.cpp | 18 +-
.../clangd/unittests/FileIndexTests.cpp | 5 +-
.../unittests/ModuleDependencyScannerTest.cpp | 173 +++++++++++
.../clangd/unittests/ModuleFilesInfoTest.cpp | 223 ++++++++++++++
.../clangd/unittests/ModulesTestSetup.h | 105 +++++++
.../clangd/unittests/ParsedASTTests.cpp | 8 +-
.../clangd/unittests/PreambleTests.cpp | 6 +-
clang-tools-extra/clangd/unittests/TestTU.cpp | 14 +-
clang-tools-extra/docs/ReleaseNotes.rst | 3 +
28 files changed, 1284 insertions(+), 21 deletions(-)
create mode 100644 clang-tools-extra/clangd/ModuleDependencyScanner.cpp
create mode 100644 clang-tools-extra/clangd/ModuleDependencyScanner.h
create mode 100644 clang-tools-extra/clangd/ModuleFilesInfo.cpp
create mode 100644 clang-tools-extra/clangd/ModuleFilesInfo.h
create mode 100644 clang-tools-extra/clangd/test/modules.test
create mode 100644 clang-tools-extra/clangd/unittests/ModuleDependencyScannerTest.cpp
create mode 100644 clang-tools-extra/clangd/unittests/ModuleFilesInfoTest.cpp
create mode 100644 clang-tools-extra/clangd/unittests/ModulesTestSetup.h
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 3911fb6c6c746a8..bcfb49551a02591 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -97,6 +97,8 @@ add_clang_library(clangDaemon
IncludeFixer.cpp
InlayHints.cpp
JSONTransport.cpp
+ ModuleDependencyScanner.cpp
+ ModuleFilesInfo.cpp
PathMapping.cpp
Protocol.cpp
Quality.cpp
@@ -161,6 +163,7 @@ clang_target_link_libraries(clangDaemon
clangAST
clangASTMatchers
clangBasic
+ clangDependencyScanning
clangDriver
clangFormat
clangFrontend
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 13d788162817fb4..e4c85858b6882ae 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -202,6 +202,7 @@ ClangdServer::Options::operator TUScheduler::Options() const {
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
+ Opts.ExperimentalModulesSupport = ExperimentalModulesSupport;
return Opts;
}
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index a416602251428b0..dc546b118cb8f5e 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -112,6 +112,9 @@ class ClangdServer {
/// This throttler controls which preambles may be built at a given time.
clangd::PreambleThrottler *PreambleThrottler = nullptr;
+ /// Enable experimental support for modules.
+ bool ExperimentalModulesSupport = false;
+
/// If true, ClangdServer builds a dynamic in-memory index for symbols in
/// opened files and uses the index to augment code completion results.
bool BuildDynamicSymbolIndex = false;
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index d1833759917a30f..bcc8f4f0dd9e5ac 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -729,6 +729,20 @@ DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
return Res->PI;
}
+std::vector<std::string>
+DirectoryBasedGlobalCompilationDatabase::getAllFilesInProjectOf(
+ PathRef File) const {
+ CDBLookupRequest Req;
+ Req.FileName = File;
+ Req.ShouldBroadcast = false;
+ Req.FreshTime = Req.FreshTimeMissing =
+ std::chrono::steady_clock::time_point::min();
+ auto Res = lookupCDB(Req);
+ if (!Res)
+ return {};
+ return Res->CDB->getAllFiles();
+}
+
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
std::vector<std::string> FallbackFlags,
CommandMangler Mangler)
@@ -805,6 +819,13 @@ std::optional<ProjectInfo> DelegatingCDB::getProjectInfo(PathRef File) const {
return Base->getProjectInfo(File);
}
+std::vector<std::string>
+DelegatingCDB::getAllFilesInProjectOf(PathRef File) const {
+ if (!Base)
+ return {};
+ return Base->getAllFilesInProjectOf(File);
+}
+
tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
if (!Base)
return GlobalCompilationDatabase::getFallbackCommand(File);
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 2bf8c973c534c6f..eaeff8d627a0960 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -45,6 +45,10 @@ class GlobalCompilationDatabase {
return std::nullopt;
}
+ virtual std::vector<std::string> getAllFilesInProjectOf(PathRef File) const {
+ return {};
+ }
+
/// Makes a guess at how to build a file.
/// The default implementation just runs clang on the file.
/// Clangd should treat the results as unreliable.
@@ -75,6 +79,7 @@ class DelegatingCDB : public GlobalCompilationDatabase {
getCompileCommand(PathRef File) const override;
std::optional<ProjectInfo> getProjectInfo(PathRef File) const override;
+ std::vector<std::string> getAllFilesInProjectOf(PathRef File) const override;
tooling::CompileCommand getFallbackCommand(PathRef File) const override;
@@ -121,6 +126,7 @@ class DirectoryBasedGlobalCompilationDatabase
/// Returns the path to first directory containing a compilation database in
/// \p File's parents.
std::optional<ProjectInfo> getProjectInfo(PathRef File) const override;
+ std::vector<std::string> getAllFilesInProjectOf(PathRef File) const override;
bool blockUntilIdle(Deadline Timeout) const override;
diff --git a/clang-tools-extra/clangd/ModuleDependencyScanner.cpp b/clang-tools-extra/clangd/ModuleDependencyScanner.cpp
new file mode 100644
index 000000000000000..d706d2eb2fc8d6e
--- /dev/null
+++ b/clang-tools-extra/clangd/ModuleDependencyScanner.cpp
@@ -0,0 +1,86 @@
+//===---------------- ModuleDependencyScanner.cpp ----------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ModuleDependencyScanner.h"
+
+namespace clang {
+namespace clangd {
+using P1689Rule = tooling::dependencies::P1689Rule;
+
+std::optional<P1689Rule> ModuleDependencyScanner::scan(PathRef FilePath) {
+ if (ScanningCache.count(FilePath))
+ return ScanningCache[FilePath];
+
+ std::optional<tooling::CompileCommand> Cmd = CDB.getCompileCommand(FilePath);
+
+ if (!Cmd)
+ return std::nullopt;
+
+ using namespace clang::tooling::dependencies;
+
+ llvm::SmallString<128> FilePathDir(FilePath);
+ llvm::sys::path::remove_filename(FilePathDir);
+ DependencyScanningTool ScanningTool(
+ Service,
+ TFS ? TFS->view(FilePathDir) : llvm::vfs::createPhysicalFileSystem());
+
+ llvm::Expected<P1689Rule> Result =
+ ScanningTool.getP1689ModuleDependencyFile(*Cmd, Cmd->Directory);
+
+ if (auto E = Result.takeError()) {
+ // Ignore any error.
+ llvm::consumeError(std::move(E));
+ return std::nullopt;
+ }
+
+ if (Result->Provides)
+ ModuleNameToSourceMapper[Result->Provides->ModuleName] = FilePath;
+
+ ScanningCache[FilePath] = *Result;
+ return *Result;
+}
+
+void ModuleDependencyScanner::globalScan(PathRef File) {
+ std::vector<std::string> AllFiles = CDB.getAllFilesInProjectOf(File);
+
+ for (auto &File : AllFiles)
+ scan(File);
+}
+
+PathRef ModuleDependencyScanner::getSourceForModuleName(StringRef ModuleName) const {
+ if (!ModuleNameToSourceMapper.count(ModuleName))
+ return {};
+
+ return ModuleNameToSourceMapper[ModuleName];
+}
+
+std::vector<std::string>
+ModuleDependencyScanner::getRequiredModules(PathRef File) const {
+ if (!ScanningCache.count(File))
+ return {};
+
+ const P1689Rule &CachedResult = ScanningCache[File];
+ std::vector<std::string> Result;
+
+ for (const auto &Info : CachedResult.Requires)
+ Result.push_back(Info.ModuleName);
+
+ return Result;
+}
+
+StringRef ModuleDependencyScanner::getModuleName(PathRef File) const {
+ if (!ScanningCache.count(File))
+ return {};
+
+ if (!ScanningCache[File].Provides)
+ return {};
+
+ return ScanningCache[File].Provides->ModuleName;
+}
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/ModuleDependencyScanner.h b/clang-tools-extra/clangd/ModuleDependencyScanner.h
new file mode 100644
index 000000000000000..1d6eb58fda59e20
--- /dev/null
+++ b/clang-tools-extra/clangd/ModuleDependencyScanner.h
@@ -0,0 +1,78 @@
+//===-------------- ModuleDependencyScanner.h --------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULEDEPENDENCYSCANNER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULEDEPENDENCYSCANNER_H
+
+#include "GlobalCompilationDatabase.h"
+#include "support/Path.h"
+#include "support/ThreadsafeFS.h"
+
+#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
+#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
+
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang {
+namespace clangd {
+
+/// A scanner to produce P1689 format for C++20 Modules.
+///
+/// The scanner can scan a single file with `scan(PathRef)` member function
+/// or scan the whole project with `globalScan(PathRef)` member function. See
+/// the comments of `globalScan` to see the details.
+class ModuleDependencyScanner {
+public:
+ ModuleDependencyScanner(const GlobalCompilationDatabase &CDB,
+ const ThreadsafeFS *TFS)
+ : CDB(CDB), TFS(TFS),
+ Service(tooling::dependencies::ScanningMode::CanonicalPreprocessing,
+ tooling::dependencies::ScanningOutputFormat::P1689) {}
+
+ /// Scanning the single file specified by \param FilePath.
+ std::optional<clang::tooling::dependencies::P1689Rule> scan(PathRef FilePath);
+
+ /// Scanning every source file in the current project to get the
+ /// <module-name> to <module-unit-source> map.
+ /// It looks unefficiency to scan the whole project especially for
+ /// every version of every file!
+ /// TODO: We should find a efficient method to get the <module-name>
+ /// to <module-unit-source> map. We can make it either by providing
+ /// a global module dependency scanner to monitor every file. Or we
+ /// can simply require the build systems (or even if the end users)
+ /// to provide the map.
+ void globalScan(PathRef File);
+
+ PathRef getSourceForModuleName(StringRef ModuleName) const;
+
+ /// Return the direct required modules. Indirect required modules are not
+ /// included.
+ std::vector<std::string> getRequiredModules(PathRef File) const;
+ StringRef getModuleName(PathRef File) const;
+
+ const ThreadsafeFS *getThreadsafeFS() const { return TFS; }
+
+ const GlobalCompilationDatabase &getCompilationDatabase() const { return CDB; }
+
+private:
+ const GlobalCompilationDatabase &CDB;
+ const ThreadsafeFS *TFS;
+
+ clang::tooling::dependencies::DependencyScanningService Service;
+
+ // Map source file to P1689 Result.
+ llvm::StringMap<clang::tooling::dependencies::P1689Rule> ScanningCache;
+ // Map module name to source file path.
+ llvm::StringMap<std::string> ModuleNameToSourceMapper;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clang-tools-extra/clangd/ModuleFilesInfo.cpp b/clang-tools-extra/clangd/ModuleFilesInfo.cpp
new file mode 100644
index 000000000000000..845ff01ca09dff3
--- /dev/null
+++ b/clang-tools-extra/clangd/ModuleFilesInfo.cpp
@@ -0,0 +1,282 @@
+//===----------------- ModuleFilesInfo.cpp -----------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ModuleFilesInfo.h"
+#include "support/Logger.h"
+
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Serialization/ASTReader.h"
+
+namespace clang {
+namespace clangd {
+
+namespace {
+llvm::SmallString<128> getAbsolutePath(const tooling::CompileCommand &Cmd) {
+ llvm::SmallString<128> AbsolutePath;
+ if (llvm::sys::path::is_absolute(Cmd.Filename)) {
+ AbsolutePath = Cmd.Filename;
+ } else {
+ AbsolutePath = Cmd.Directory;
+ llvm::sys::path::append(AbsolutePath, Cmd.Filename);
+ llvm::sys::path::remove_dots(AbsolutePath, true);
+ }
+ return AbsolutePath;
+}
+} // namespace
+
+ModuleFilesInfo::ModuleFilesInfo(PathRef MainFile,
+ const GlobalCompilationDatabase &CDB) {
+ std::optional<ProjectInfo> PI = CDB.getProjectInfo(MainFile);
+ if (!PI)
+ return;
+
+ llvm::SmallString<128> Result(PI->SourceRoot);
+ llvm::sys::path::append(Result, ".cache");
+ llvm::sys::path::append(Result, "clangd");
+ llvm::sys::path::append(Result, "module_files");
+ llvm::sys::fs::create_directories(Result, /*IgnoreExisting=*/true);
+
+ llvm::sys::path::append(Result, llvm::sys::path::filename(MainFile));
+ llvm::sys::fs::createUniqueDirectory(Result, UniqueModuleFilesPathPrefix);
+
+ log("Initialized module files to {0}", UniqueModuleFilesPathPrefix.str());
+}
+
+ModuleFilesInfo::~ModuleFilesInfo() {
+ DependentModuleNames.clear();
+ Successed = false;
+
+ if (UniqueModuleFilesPathPrefix.empty())
+ return;
+
+ llvm::sys::fs::remove_directories(UniqueModuleFilesPathPrefix);
+ UniqueModuleFilesPathPrefix.clear();
+}
+
+llvm::SmallString<256>
+ModuleFilesInfo::getModuleFilePath(StringRef ModuleName) const {
+ llvm::SmallString<256> ModuleFilePath;
+
+ ModuleFilePath = UniqueModuleFilesPathPrefix;
+ auto [PrimaryModuleName, PartitionName] = ModuleName.split(':');
+ llvm::sys::path::append(ModuleFilePath, PrimaryModuleName);
+ if (!PartitionName.empty()) {
+ ModuleFilePath.append("-");
+ ModuleFilePath.append(PartitionName);
+ }
+ ModuleFilePath.append(".pcm");
+
+ return ModuleFilePath;
+}
+
+bool ModuleFilesInfo::IsModuleUnitBuilt(StringRef ModuleName) const {
+ if (!DependentModuleNames.count(ModuleName))
+ return false;
+
+ auto BMIPath = getModuleFilePath(ModuleName);
+ if (llvm::sys::fs::exists(BMIPath))
+ return true;
+
+ Successed = false;
+
+ DependentModuleNames.erase(ModuleName);
+ return false;
+}
+
+void ModuleFilesInfo::ReplaceHeaderSearchOptions(
+ HeaderSearchOptions &Options) const {
+ if (!IsInited())
+ return;
+
+ Options.PrebuiltModulePaths.insert(Options.PrebuiltModulePaths.begin(),
+ UniqueModuleFilesPathPrefix.str().str());
+
+ for (auto Iter = Options.PrebuiltModuleFiles.begin();
+ Iter != Options.PrebuiltModuleFiles.end();) {
+ if (IsModuleUnitBuilt(Iter->first)) {
+ Iter = Options.PrebuiltModuleFiles.erase(Iter);
+ continue;
+ }
+
+ Iter++;
+ }
+}
+
+void ModuleFilesInfo::ReplaceCompileCommands(
+ tooling::CompileCommand &Cmd) const {
+ if (!IsInited())
+ return;
+
+ std::vector<std::string> CommandLine(std::move(Cmd.CommandLine));
+
+ Cmd.CommandLine.emplace_back(CommandLine[0]);
+ Cmd.CommandLine.emplace_back(
+ llvm::Twine("-fprebuilt-module-path=" + UniqueModuleFilesPathPrefix)
+ .str());
+
+ for (std::size_t I = 1; I < CommandLine.size(); I++) {
+ const std::string &Arg = CommandLine[I];
+ const auto &[LHS, RHS] = StringRef(Arg).split("=");
+
+ // Remove original `-fmodule-file=<module-name>=<module-path>` form if it
+ // already built.
+ if (LHS == "-fmodule-file" && RHS.contains("=")) {
+ const auto &[ModuleName, _] = RHS.split("=");
+ if (IsModuleUnitBuilt(ModuleName))
+ continue;
+ }
+
+ Cmd.CommandLine.emplace_back(Arg);
+ }
+}
+
+void ModuleFilesInfo::ReplaceCompileCommands(tooling::CompileCommand &Cmd,
+ StringRef OutputModuleName) const {
+ if (!IsInited())
+ return;
+
+ ReplaceCompileCommands(Cmd);
+
+ Cmd.Output = getModuleFilePath(OutputModuleName).str().str();
+}
+
+bool ModuleFilesInfo::buildModuleFile(PathRef ModuleUnitFileName,
+ ModuleDependencyScanner &Scanner) {
+ if (ModuleUnitFileName.empty())
+ return false;
+
+ for (auto &ModuleName : Scanner.getRequiredModules(ModuleUnitFileName)) {
+ // Return early if there are errors building the module file.
+ if (!IsModuleUnitBuilt(ModuleName) &&
+ !buildModuleFile(Scanner.getSourceForModuleName(ModuleName), Scanner)) {
+ log("Failed to build module {0}", ModuleName);
+ return false;
+ }
+ }
+
+ auto Cmd =
+ Scanner.getCompilationDatabase().getCompileCommand(ModuleUnitFileName);
+ if (!Cmd)
+ return false;
+
+ ReplaceCompileCommands(*Cmd, Scanner.getModuleName(ModuleUnitFileName));
+
+ ParseInputs Inputs;
+ Inputs.TFS = Scanner.getThreadsafeFS();
+ Inputs.CompileCommand = std::move(*Cmd);
+
+ IgnoreDiagnostics IgnoreDiags;
+ auto CI = buildCompilerInvocation(Inputs, IgnoreDiags);
+ if (!CI)
+ return false;
+
+ auto FS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
+ auto AbsolutePath = getAbsolutePath(Inputs.CompileCommand);
+ auto Buf = FS->getBufferForFile(AbsolutePath);
+ if (!Buf)
+ return false;
+
+ // Hash the contents of input files and store the hash value to the BMI files.
+ // So that we can check if the files are still valid when we want to reuse the
+ // BMI files.
+ CI->getHeaderSearchOpts().ValidateASTInputFilesContent = true;
+
+ CI->getFrontendOpts().OutputFile = Inputs.CompileCommand.Output;
+ auto Clang =
+ prepareCompilerInstance(std::move(CI), /*Preamble=*/nullptr,
+ std::move(*Buf), std::move(FS), IgnoreDiags);
+ if (!Clang)
+ return false;
+
+ GenerateModuleInterfaceAction Action;
+ Clang->ExecuteAction(Action);
+
+ if (Clang->getDiagnostics().hasErrorOccurred())
+ return false;
+
+ DependentModuleNames.insert(Scanner.getModuleName(ModuleUnitFileName));
+
+ return true;
+}
+
+ModuleFilesInfo
+ModuleFilesInfo::buildModuleFilesInfoFor(PathRef File, const ThreadsafeFS *TFS,
+ const GlobalCompilationDatabase &CDB) {
+ ModuleDependencyScanner Scanner(CDB, TFS);
+
+ std::optional<tooling::dependencies::P1689Rule> ScanningResult =
+ Scanner.scan(File);
+ if (!ScanningResult)
+ return {};
+
+ ModuleFilesInfo ModulesInfo(File, CDB);
+
+ Scanner.globalScan(File);
+
+ for (auto &Info : ScanningResult->Requires)
+ // Return early if there is any error.
+ if (!ModulesInfo.buildModuleFile(
+ Scanner.getSourceForModuleName(Info.ModuleName), Scanner)) {
+ log("Failed to build module {0}", Info.ModuleName);
+ return ModulesInfo;
+ }
+
+ ModulesInfo.Successed = true;
+ return ModulesInfo;
+}
+
+bool ModuleFilesInfo::CanReuse(
+ const CompilerInvocation &CI,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
+ // Try to avoid expensive check as much as possible.
+ if (!IsInited())
+ return true;
+
+ if (!Successed)
+ return false;
+
+ CompilerInstance Clang;
+
+ Clang.setInvocation(std::make_shared<CompilerInvocation>(CI));
+ IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
+ CompilerInstance::createDiagnostics(new DiagnosticOptions());
+ Clang.setDiagnostics(Diags.get());
+
+ FileManager *FM = Clang.createFileManager(VFS);
+ Clang.createSourceManager(*FM);
+
+ if (!Clang.createTarget())
+ return false;
+
+ ReplaceHeaderSearchOptions(Clang.getHeaderSearchOpts());
+ // Since we don't need to compile the source code actually, the TU kind here
+ // doesn't matter.
+ Clang.createPreprocessor(TU_Complete);
+ Clang.getHeaderSearchOpts().ForceCheckCXX20ModulesInputFiles = true;
+ Clang.getHeaderSearchOpts().ValidateASTInputFilesContent = true;
+
+ Clang.createASTReader();
+ for (auto &Iter : DependentModuleNames) {
+ StringRef ModuleName = Iter.first();
+ auto BMIPath = getModuleFilePath(ModuleName);
+ auto ReadResult =
+ Clang.getASTReader()->ReadAST(BMIPath, serialization::MK_MainFile,
+ SourceLocation(), ASTReader::ARR_None);
+
+ if (ReadResult != ASTReader::Success) {
+ log("Failed to reuse {0}", BMIPath);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/ModuleFilesInfo.h b/clang-tools-extra/clangd/ModuleFilesInfo.h
new file mode 100644
index 000000000000000..557233f87076c7a
--- /dev/null
+++ b/clang-tools-extra/clangd/ModuleFilesInfo.h
@@ -0,0 +1,118 @@
+//===----------------- ModuleFilesInfo.h -------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Experimental support for C++20 Modules.
+//
+// Currently we simplify the implementations by preventing reusing module files
+// across different versions and different source files. But this is clearly a
+// waste of time and space in the end of the day.
+//
+// FIXME: Supporting reusing module files across different versions and different
+// source files.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULEFILESINFO_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULEFILESINFO_H
+
+#include "Compiler.h"
+#include "GlobalCompilationDatabase.h"
+#include "ModuleDependencyScanner.h"
+#include "support/Path.h"
+
+#include "clang/Lex/HeaderSearchOptions.h"
+
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang {
+namespace clangd {
+
+/// Store module files information for a single file.
+///
+/// A ModuleFilesInfo should only be initialized by
+/// `ModuleFilesInfo::buildModuleFilesInfoFor(PathRef, const ThreadsafeFS *, const GlobalCompilationDatabase &)`
+/// static member method. This method will block the current thread and build all the needed
+/// module files. All the built module files won't be shared with other source files.
+///
+/// Users can detect whether the ModuleFilesInfo is still up to date by calling
+/// the `CanReuse()` member function.
+///
+/// The users should call `ReplaceHeaderSearchOptions(...)` or `ReplaceCompileCommands(CompileCommand&)`
+/// member function to update the compilation commands to select the built module files first.
+struct ModuleFilesInfo {
+ ModuleFilesInfo() = default;
+ ~ModuleFilesInfo();
+
+ ModuleFilesInfo(const ModuleFilesInfo &) = delete;
+ ModuleFilesInfo operator=(const ModuleFilesInfo &) = delete;
+
+ ModuleFilesInfo(ModuleFilesInfo &&Other)
+ : Successed(std::exchange(Other.Successed, false)),
+ UniqueModuleFilesPathPrefix(
+ std::move(Other.UniqueModuleFilesPathPrefix)),
+ DependentModuleNames(std::move(Other.DependentModuleNames)) {
+ Other.UniqueModuleFilesPathPrefix.clear();
+ }
+ ModuleFilesInfo &operator=(ModuleFilesInfo &&Other) {
+ if (this == &Other)
+ return *this;
+
+ this->~ModuleFilesInfo();
+ new (this) ModuleFilesInfo(std::move(Other));
+
+ return *this;
+ }
+
+ /// Build all the required module files for \param File.
+ /// Note that only the module files recorded by \param CDB can be built.
+ static ModuleFilesInfo
+ buildModuleFilesInfoFor(PathRef File, const ThreadsafeFS *TFS,
+ const GlobalCompilationDatabase &CDB);
+
+ /// Return true if the modile file specified by ModuleName is built.
+ /// Note that this interface will only check the existence of the module
+ /// file instead of checking the validness of the module file.
+ bool IsModuleUnitBuilt(StringRef ModuleName) const;
+
+ /// Change commands to load the module files recorded in this ModuleFilesInfo
+ /// first.
+ void ReplaceHeaderSearchOptions(HeaderSearchOptions &Options) const;
+ void ReplaceCompileCommands(tooling::CompileCommand &Cmd) const;
+ void ReplaceCompileCommands(tooling::CompileCommand &Cmd,
+ StringRef OutputModuleName) const;
+
+ /// Whether or not the built module files are up to date.
+ /// Note that this can only be used after building the module files.
+ bool CanReuse(const CompilerInvocation &CI,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const;
+
+ /// NOTE: This shouldn't be used by external users except unittests.
+ llvm::SmallString<256> getModuleFilePath(StringRef ModuleName) const;
+
+private:
+ ModuleFilesInfo(PathRef MainFile, const GlobalCompilationDatabase &CDB);
+
+ bool IsInited() const { return !UniqueModuleFilesPathPrefix.empty(); }
+ bool buildModuleFile(PathRef ModuleUnitFileName,
+ ModuleDependencyScanner &Scanner);
+
+ // Whether the last built is successed.
+ // May change in `IsModuleUnitBuilt`.
+ mutable bool Successed = false;
+
+ llvm::SmallString<256> UniqueModuleFilesPathPrefix;
+ // The language guarantees that the module name is unique in a program.
+ // May change in `IsModuleUnitBuilt`.
+ mutable llvm::StringSet<> DependentModuleNames;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index 3f6c0eaebac6c41..10b50c491d1b41c 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -429,6 +429,11 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
L->sawDiagnostic(D, Diag);
});
+ //
+ if (Preamble)
+ Preamble->DependentModulesInfo.ReplaceHeaderSearchOptions(
+ CI->getHeaderSearchOpts());
+
std::optional<PreamblePatch> Patch;
// We might use an ignoring diagnostic consumer if they are going to be
// dropped later on to not pay for extra latency by processing them.
@@ -442,6 +447,9 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
std::move(CI), PreamblePCH,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, Filename), VFS,
*DiagConsumer);
+
+ // Clangd Modules TODO: refactor the command line options of `Clang` here.
+
if (!Clang) {
// The last diagnostic contains information about the reason of this
// failure.
diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp
index 60c0f936d6f3810..6e0a39f40e7fa5c 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -587,11 +587,11 @@ class DiagPatcher {
};
} // namespace
-std::shared_ptr<const PreambleData>
-buildPreamble(PathRef FileName, CompilerInvocation CI,
- const ParseInputs &Inputs, bool StoreInMemory,
- PreambleParsedCallback PreambleCallback,
- PreambleBuildStats *Stats) {
+std::shared_ptr<const PreambleData> buildPreamble(
+ PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs,
+ bool StoreInMemory, bool ExperimentalModulesSupport,
+ const GlobalCompilationDatabase &CDB,
+ PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats) {
// Note that we don't need to copy the input contents, preamble can live
// without those.
auto ContentsBuffer =
@@ -660,10 +660,17 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
WallTimer PreambleTimer;
PreambleTimer.startTimer();
+
+ ModuleFilesInfo DependentModulesInfo =
+ !ExperimentalModulesSupport
+ ? ModuleFilesInfo{}
+ : ModuleFilesInfo::buildModuleFilesInfoFor(FileName, Inputs.TFS, CDB);
+
auto BuiltPreamble = PrecompiledPreamble::Build(
CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine,
Stats ? TimedFS : StatCacheFS, std::make_shared<PCHContainerOperations>(),
StoreInMemory, /*StoragePath=*/"", CapturedInfo);
+
PreambleTimer.stopTimer();
// We have to setup DiagnosticConsumer that will be alife
@@ -696,6 +703,9 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
Result->Includes = CapturedInfo.takeIncludes();
Result->Pragmas = std::make_shared<const include_cleaner::PragmaIncludes>(
CapturedInfo.takePragmaIncludes());
+
+ // FIXME: If there is no headers?
+ Result->DependentModulesInfo = std::move(DependentModulesInfo);
Result->Macros = CapturedInfo.takeMacros();
Result->Marks = CapturedInfo.takeMarks();
Result->StatCache = StatCache;
@@ -736,7 +746,8 @@ bool isPreambleCompatible(const PreambleData &Preamble,
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
return compileCommandsAreEqual(Inputs.CompileCommand,
Preamble.CompileCommand) &&
- Preamble.Preamble.CanReuse(CI, *ContentsBuffer, Bounds, *VFS);
+ Preamble.Preamble.CanReuse(CI, *ContentsBuffer, Bounds, *VFS) &&
+ Preamble.DependentModulesInfo.CanReuse(CI, VFS);
}
void escapeBackslashAndQuotes(llvm::StringRef Text, llvm::raw_ostream &OS) {
diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h
index 21a281aac0cce95..fb390c025597452 100644
--- a/clang-tools-extra/clangd/Preamble.h
+++ b/clang-tools-extra/clangd/Preamble.h
@@ -27,6 +27,9 @@
#include "Diagnostics.h"
#include "FS.h"
#include "Headers.h"
+
+#include "ModuleFilesInfo.h"
+
#include "clang-include-cleaner/Record.h"
#include "support/Path.h"
#include "clang/Basic/SourceManager.h"
@@ -104,6 +107,8 @@ struct PreambleData {
IncludeStructure Includes;
// Captures #include-mapping information in #included headers.
std::shared_ptr<const include_cleaner::PragmaIncludes> Pragmas;
+ // Information about module files for this preamble.
+ ModuleFilesInfo DependentModulesInfo;
// Macros defined in the preamble section of the main file.
// Users care about headers vs main-file, not preamble vs non-preamble.
// These should be treated as main-file entities e.g. for code completion.
@@ -147,6 +152,8 @@ struct PreambleBuildStats {
std::shared_ptr<const PreambleData>
buildPreamble(PathRef FileName, CompilerInvocation CI,
const ParseInputs &Inputs, bool StoreInMemory,
+ bool ExperimentalModulesSupport,
+ const GlobalCompilationDatabase &CDB,
PreambleParsedCallback PreambleCallback,
PreambleBuildStats *Stats = nullptr);
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 324ba1fc8cb8952..35e0f4205a9c854 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -652,6 +652,12 @@ class ASTWorker {
TUScheduler::FileStats stats() const;
bool isASTCached() const;
+ const GlobalCompilationDatabase &getCompilationDatabase() { return CDB; }
+
+ bool isExperimentalModulesSupportEnabled() const {
+ return ExperimentalModulesSupport;
+ }
+
private:
// Details of an update request that are relevant to scheduling.
struct UpdateType {
@@ -710,6 +716,10 @@ class ASTWorker {
TUScheduler::ASTCache &IdleASTs;
TUScheduler::HeaderIncluderCache &HeaderIncluders;
const bool RunSync;
+
+ /// Enable experimental support for modules.
+ bool ExperimentalModulesSupport = false;
+
/// Time to wait after an update to see whether another update obsoletes it.
const DebouncePolicy UpdateDebounce;
/// File that ASTWorker is responsible for.
@@ -834,6 +844,7 @@ ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
const TUScheduler::Options &Opts,
ParsingCallbacks &Callbacks)
: IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
+ ExperimentalModulesSupport(Opts.ExperimentalModulesSupport),
UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
Barrier(Barrier), Done(false), Status(FileName, Callbacks),
@@ -1084,6 +1095,8 @@ void PreambleThread::build(Request Req) {
bool IsFirstPreamble = !LatestBuild;
LatestBuild = clang::clangd::buildPreamble(
FileName, *Req.CI, Inputs, StoreInMemory,
+ ASTPeer.isExperimentalModulesSupportEnabled(),
+ ASTPeer.getCompilationDatabase(),
[&](CapturedASTCtx ASTCtx,
std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
Callbacks.onPreambleAST(FileName, Inputs.Version, std::move(ASTCtx),
diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h
index fb936d46bbcf7e9..706a13c59dd31d0 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -222,6 +222,9 @@ class TUScheduler {
/// Cache (large) preamble data in RAM rather than temporary files on disk.
bool StorePreamblesInMemory = false;
+ /// Enable experimental support for modules.
+ bool ExperimentalModulesSupport = false;
+
/// Time to wait after an update to see if another one comes along.
/// This tries to ensure we rebuild once the user stops typing.
DebouncePolicy UpdateDebounce;
diff --git a/clang-tools-extra/clangd/test/CMakeLists.txt b/clang-tools-extra/clangd/test/CMakeLists.txt
index d073267066e0b4c..b51f461a4986659 100644
--- a/clang-tools-extra/clangd/test/CMakeLists.txt
+++ b/clang-tools-extra/clangd/test/CMakeLists.txt
@@ -2,6 +2,7 @@ set(CLANGD_TEST_DEPS
clangd
ClangdTests
clangd-indexer
+ split-file
# No tests for it, but we should still make sure they build.
dexp
)
diff --git a/clang-tools-extra/clangd/test/modules.test b/clang-tools-extra/clangd/test/modules.test
new file mode 100644
index 000000000000000..a5d4fe7ab2dd687
--- /dev/null
+++ b/clang-tools-extra/clangd/test/modules.test
@@ -0,0 +1,79 @@
+# A smoke test to check the modules can work basically.
+#
+# RUN: rm -fr %t
+# RUN: mkdir -p %t
+# RUN: split-file %s %t
+#
+# RUN: sed -e "s|DIR|%/t|g" %t/compile_commands.json.tmpl > %t/compile_commands.json.tmp
+# RUN: sed -e "s|CLANG_CC|%clang|g" %t/compile_commands.json.tmp > %t/compile_commands.json
+# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc
+#
+# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \
+# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc
+
+#--- A.cppm
+export module A;
+export void printA() {}
+
+#--- Use.cpp
+import A;
+void foo() {
+ print
+}
+
+#--- compile_commands.json.tmpl
+[
+ {
+ "directory": "DIR",
+ "command": "CLANG_CC -fprebuilt-module-path=DIR -std=c++20 -o DIR/main.cpp.o -c DIR/Use.cpp",
+ "file": "DIR/Use.cpp"
+ },
+ {
+ "directory": "DIR",
+ "command": "CLANG_CC -std=c++20 DIR/A.cppm --precompile -o DIR/A.pcm",
+ "file": "DIR/A.cppm"
+ }
+]
+
+#--- definition.jsonrpc.tmpl
+{
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "initialize",
+ "params": {
+ "processId": 123,
+ "rootPath": "clangd",
+ "capabilities": {
+ "textDocument": {
+ "completion": {
+ "completionItem": {
+ "snippetSupport": true
+ }
+ }
+ }
+ },
+ "trace": "off"
+ }
+}
+---
+{
+ "jsonrpc": "2.0",
+ "method": "textDocument/didOpen",
+ "params": {
+ "textDocument": {
+ "uri": "file://DIR/Use.cpp",
+ "languageId": "cpp",
+ "version": 1,
+ "text": "import A;\nvoid foo() {\n print\n}\n"
+ }
+ }
+}
+
+# CHECK: "message"{{.*}}printA{{.*}}(fix available)
+
+---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}}
+---
+{"jsonrpc":"2.0","id":2,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index 46fcab0b69ce005..b3ae59ae7c4f5b1 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -140,6 +140,8 @@ class Checker {
ClangdLSPServer::Options Opts;
// from buildCommand
tooling::CompileCommand Cmd;
+ std::unique_ptr<GlobalCompilationDatabase> BaseCDB;
+ std::unique_ptr<OverlayCDB> CDB;
// from buildInvocation
ParseInputs Inputs;
std::unique_ptr<CompilerInvocation> Invocation;
@@ -162,14 +164,14 @@ class Checker {
DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
CDBOpts.CompileCommandsDir =
Config::current().CompileFlags.CDBSearch.FixedCDBPath;
- std::unique_ptr<GlobalCompilationDatabase> BaseCDB =
+ BaseCDB =
std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
auto Mangler = CommandMangler::detect();
Mangler.SystemIncludeExtractor =
getSystemIncludeExtractor(llvm::ArrayRef(Opts.QueryDriverGlobs));
if (Opts.ResourceDir)
Mangler.ResourceDir = *Opts.ResourceDir;
- auto CDB = std::make_unique<OverlayCDB>(
+ CDB = std::make_unique<OverlayCDB>(
BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
if (auto TrueCmd = CDB->getCompileCommand(File)) {
@@ -230,6 +232,8 @@ class Checker {
log("Building preamble...");
Preamble = buildPreamble(
File, *Invocation, Inputs, /*StoreInMemory=*/true,
+ /*ExperimentalModulesSupport*/ Opts.ExperimentalModulesSupport,
+ *CDB.get(),
[&](CapturedASTCtx Ctx,
std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
if (!Opts.BuildDynamicSymbolIndex)
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index f656a8c587c6533..e38b7c567fb7a97 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -550,6 +550,13 @@ opt<std::string> ProjectRoot{
};
#endif
+opt<bool> ExperimentalModulesSupport{
+ "experimental-modules-support",
+ cat(Features),
+ desc("Experimental support for standard c++ modules"),
+ init(false),
+};
+
/// Supports a test URI scheme with relaxed constraints for lit tests.
/// The path in a test URI will be combined with a platform-specific fake
/// directory to form an absolute path. For example, test:///a.cpp is resolved
@@ -861,6 +868,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
ClangdLSPServer::Options Opts;
Opts.UseDirBasedCDB = (CompileArgsFrom == FilesystemCompileArgs);
+ Opts.ExperimentalModulesSupport = ExperimentalModulesSupport;
switch (PCHStorage) {
case PCHStorageFlag::Memory:
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index 8d02b91fdd71669..cd79534c6a97923 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -72,6 +72,8 @@ add_unittest(ClangdUnitTests ClangdTests
LoggerTests.cpp
LSPBinderTests.cpp
LSPClient.cpp
+ ModuleDependencyScannerTest.cpp
+ ModuleFilesInfoTest.cpp
ModulesTests.cpp
ParsedASTTests.cpp
PathMappingTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 671c0b7da97c6cf..095c05210cafe8f 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -132,8 +132,11 @@ CodeCompleteResult completions(const TestTU &TU, Position Point,
ADD_FAILURE() << "Couldn't build CompilerInvocation";
return {};
}
+
+ MockCompilationDatabase CDB;
auto Preamble = buildPreamble(testPath(TU.Filename), *CI, Inputs,
- /*InMemory=*/true, /*Callback=*/nullptr);
+ /*ExperimentalModulesSupport=*/false,
+ /*InMemory=*/true, CDB, /*Callback=*/nullptr);
return codeComplete(testPath(TU.Filename), Point, Preamble.get(), Inputs,
Opts);
}
@@ -1322,8 +1325,11 @@ signatures(llvm::StringRef Text, Position Point,
ADD_FAILURE() << "Couldn't build CompilerInvocation";
return {};
}
+
+ MockCompilationDatabase CDB;
auto Preamble = buildPreamble(testPath(TU.Filename), *CI, Inputs,
- /*InMemory=*/true, /*Callback=*/nullptr);
+ /*ExperimentalModulesSupport=*/false,
+ /*InMemory=*/true, CDB, /*Callback=*/nullptr);
if (!Preamble) {
ADD_FAILURE() << "Couldn't build Preamble";
return {};
@@ -1604,8 +1610,12 @@ TEST(SignatureHelpTest, StalePreamble) {
auto Inputs = TU.inputs(FS);
auto CI = buildCompilerInvocation(Inputs, Diags);
ASSERT_TRUE(CI);
- auto EmptyPreamble = buildPreamble(testPath(TU.Filename), *CI, Inputs,
- /*InMemory=*/true, /*Callback=*/nullptr);
+
+ MockCompilationDatabase CDB;
+ auto EmptyPreamble =
+ buildPreamble(testPath(TU.Filename), *CI, Inputs,
+ /*ExperimentalModulesSupport=*/false,
+ /*InMemory=*/true, CDB, /*Callback=*/nullptr);
ASSERT_TRUE(EmptyPreamble);
TU.AdditionalFiles["a.h"] = "int foo(int x);";
diff --git a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
index cf30b388d234dfd..d8d1c076a7da0f3 100644
--- a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp
@@ -336,9 +336,12 @@ TEST(FileIndexTest, RebuildWithPreamble) {
FileIndex Index;
bool IndexUpdated = false;
+
+ MockCompilationDatabase CDB;
buildPreamble(
FooCpp, *CI, PI,
- /*StoreInMemory=*/true,
+ /*ExperimentalModulesSupport=*/false,
+ /*StoreInMemory=*/true, CDB,
[&](CapturedASTCtx ASTCtx,
std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
auto &Ctx = ASTCtx.getASTContext();
diff --git a/clang-tools-extra/clangd/unittests/ModuleDependencyScannerTest.cpp b/clang-tools-extra/clangd/unittests/ModuleDependencyScannerTest.cpp
new file mode 100644
index 000000000000000..0a860225b9406ff
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/ModuleDependencyScannerTest.cpp
@@ -0,0 +1,173 @@
+//===------------ ModuleDependencyScannerTest.cpp ---------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ModuleDependencyScanner.h"
+#include "ModulesTestSetup.h"
+#include "TestFS.h"
+
+using namespace clang;
+using namespace clang::clangd;
+using namespace clang::tooling::dependencies;
+
+namespace {
+class ModuleDependencyScannerTests : public ModuleTestSetup {
+
+};
+
+TEST_F(ModuleDependencyScannerTests, SingleFile) {
+ addFile("foo.h", R"cpp(
+import foo;
+ )cpp");
+
+ addFile("A.cppm", R"cpp(
+module;
+#include "foo.h"
+export module A;
+export import :partA;
+import :partB;
+import C;
+
+module :private;
+import D;
+ )cpp");
+
+ MockCompilationDatabase CDB(TestDir);
+ CDB.ExtraClangFlags.push_back("-std=c++20");
+
+ ModuleDependencyScanner Scanner(CDB, &TFS);
+ std::optional<P1689Rule> ScanningResult = Scanner.scan(getFullPath("A.cppm"));
+ EXPECT_TRUE(ScanningResult);
+
+ EXPECT_TRUE(ScanningResult->Provides);
+ EXPECT_EQ(ScanningResult->Provides->ModuleName, "A");
+
+ EXPECT_EQ(ScanningResult->Requires.size(), 5u);
+ EXPECT_EQ(ScanningResult->Requires[0].ModuleName, "foo");
+ EXPECT_EQ(ScanningResult->Requires[1].ModuleName, "A:partA");
+ EXPECT_EQ(ScanningResult->Requires[2].ModuleName, "A:partB");
+ EXPECT_EQ(ScanningResult->Requires[3].ModuleName, "C");
+ EXPECT_EQ(ScanningResult->Requires[4].ModuleName, "D");
+}
+
+TEST_F(ModuleDependencyScannerTests, GlobalScanning) {
+ addFile("build/compile_commands.json", R"cpp(
+[
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/foo.cppm -fmodule-output=__DIR__/foo.pcm -c -o __DIR__/foo.o",
+ "file": "__DIR__/foo.cppm",
+ "output": "__DIR__/foo.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/C.cppm -fmodule-output=__DIR__/C.pcm -c -o __DIR__/C.o",
+ "file": "__DIR__/C.cppm",
+ "output": "__DIR__/C.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/D.cppm -fmodule-output=__DIR__/D.pcm -c -o __DIR__/D.o",
+ "file": "__DIR__/D.cppm",
+ "output": "__DIR__/D.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/A-partA.cppm -fmodule-file=foo=__DIR__/foo.pcm -fmodule-output=__DIR__/A-partA.pcm -c -o __DIR__/A-partA.o",
+ "file": "__DIR__/A-partA.cppm",
+ "output": "__DIR__/A-partA.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/A-partB.cppm -fmodule-file=C=__DIR__/C.pcm -fmodule-output=__DIR__/A-partB.pcm -c -o __DIR__/A-partB.o",
+ "file": "__DIR__/A-partB.cppm",
+ "output": "__DIR__/A-partB.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/A.cppm -fmodule-file=A:partB=__DIR__/A-partB.pcm -fmodule-file=A:partA=__DIR__/A-partA.pcm -fmodule-file=foo=__DIR__/foo.pcm -fmodule-file=C=__DIR__/C.pcm -fmodule-file=D=__DIR__/C.pcm -fmodule-output=__DIR__/A.pcm -c -o __DIR__/A.o",
+ "file": "__DIR__/A.cppm",
+ "output": "__DIR__/A.o"
+},
+]
+ )cpp");
+
+ addFile("foo.cppm", R"cpp(
+export module foo;
+ )cpp");
+
+ addFile("foo.h", R"cpp(
+import foo;
+ )cpp");
+
+ addFile("A-partA.cppm", R"cpp(
+export module A:partA;
+import foo;
+ )cpp");
+
+ addFile("A-partB.cppm", R"cpp(
+module A:partB;
+import C;
+ )cpp");
+
+ addFile("C.cppm", R"cpp(
+export module C;
+ )cpp");
+
+ addFile("D.cppm", R"cpp(
+export module D;
+ )cpp");
+
+ addFile("A.cppm", R"cpp(
+module;
+#include "foo.h"
+export module A;
+export import :partA;
+import :partB;
+import C;
+
+module :private;
+import D;
+ )cpp");
+
+ std::unique_ptr<GlobalCompilationDatabase> CDB =
+ getGlobalCompilationDatabase();
+ ModuleDependencyScanner Scanner(*CDB.get(), &TFS);
+ Scanner.globalScan(getFullPath("A.cppm"));
+
+ EXPECT_TRUE(Scanner.getSourceForModuleName("foo").endswith("foo.cppm"));
+ EXPECT_TRUE(Scanner.getSourceForModuleName("A").endswith("A.cppm"));
+ EXPECT_TRUE(Scanner.getSourceForModuleName("A:partA").endswith("A-partA.cppm"));
+ EXPECT_TRUE(Scanner.getSourceForModuleName("A:partB").endswith("A-partB.cppm"));
+ EXPECT_TRUE(Scanner.getSourceForModuleName("C").endswith("C.cppm"));
+ EXPECT_TRUE(Scanner.getSourceForModuleName("D").endswith("D.cppm"));
+
+ EXPECT_TRUE(Scanner.getRequiredModules(getFullPath("foo.cppm")).empty());
+ EXPECT_TRUE(Scanner.getRequiredModules(getFullPath("C.cppm")).empty());
+ EXPECT_TRUE(Scanner.getRequiredModules(getFullPath("D.cppm")).empty());
+
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A-partA.cppm")).size(), 1u);
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A-partA.cppm"))[0], "foo");
+
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A-partB.cppm")).size(), 1u);
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A-partB.cppm"))[0], "C");
+
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm")).size(), 5u);
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm"))[0], "foo");
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm"))[1], "A:partA");
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm"))[2], "A:partB");
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm"))[3], "C");
+ EXPECT_EQ(Scanner.getRequiredModules(getFullPath("A.cppm"))[4], "D");
+
+ EXPECT_EQ(Scanner.getModuleName(getFullPath("A.cppm")), "A");
+ EXPECT_EQ(Scanner.getModuleName(getFullPath("A-partA.cppm")), "A:partA");
+ EXPECT_EQ(Scanner.getModuleName(getFullPath("A-partB.cppm")), "A:partB");
+ EXPECT_EQ(Scanner.getModuleName(getFullPath("C.cppm")), "C");
+ EXPECT_EQ(Scanner.getModuleName(getFullPath("D.cppm")), "D");
+}
+
+} // namespace
diff --git a/clang-tools-extra/clangd/unittests/ModuleFilesInfoTest.cpp b/clang-tools-extra/clangd/unittests/ModuleFilesInfoTest.cpp
new file mode 100644
index 000000000000000..b5c34f536898d0d
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/ModuleFilesInfoTest.cpp
@@ -0,0 +1,223 @@
+//===--------------- ModuleFilesInfoTests.cpp -------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ModuleFilesInfo.h"
+#include "ModulesTestSetup.h"
+
+using namespace clang;
+using namespace clang::clangd;
+
+namespace {
+class ModuleFilesInfoTests : public ModuleTestSetup {
+
+};
+
+TEST_F(ModuleFilesInfoTests, ModuleFilesInfoTest) {
+ addFile("build/compile_commands.json", R"cpp(
+[
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/M.cppm -fmodule-output=__DIR__/M.pcm -c -o __DIR__/M.o",
+ "file": "__DIR__/M.cppm",
+ "output": "__DIR__/M.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/N.cppm -fmodule-file=M=__DIR__/M.pcm -fmodule-file=N:Part=__DIR__/N-partition.pcm -fprebuilt-module-path=__DIR__ -fmodule-output=__DIR__/N.pcm -c -o __DIR__/N.o",
+ "file": "__DIR__/N.cppm",
+ "output": "__DIR__/N.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/N-part.cppm -fmodule-output=__DIR__/N-partition.pcm -c -o __DIR__/N-part.o",
+ "file": "__DIR__/N-part.cppm",
+ "output": "__DIR__/N-part.o"
+},
+{
+ "directory": "__DIR__",
+ "command": "clang++ -std=c++20 __DIR__/L.cppm -fmodule-output=__DIR__/L.pcm -c -o __DIR__/L.o",
+ "file": "__DIR__/L.cppm",
+ "output": "__DIR__/L.o"
+}
+]
+ )cpp");
+
+ addFile("foo.h", R"cpp(
+inline void foo() {}
+ )cpp");
+
+ addFile("M.cppm", R"cpp(
+module;
+#include "foo.h"
+export module M;
+ )cpp");
+
+ addFile("N.cppm", R"cpp(
+export module N;
+import :Part;
+import M;
+ )cpp");
+
+ addFile("N-part.cppm", R"cpp(
+// Different name with filename intentionally.
+export module N:Part;
+ )cpp");
+
+ addFile("bar.h", R"cpp(
+inline void bar() {}
+ )cpp");
+
+ addFile("L.cppm", R"cpp(
+module;
+#include "bar.h"
+export module L;
+ )cpp");
+
+ std::unique_ptr<GlobalCompilationDatabase> CDB =
+ getGlobalCompilationDatabase();
+ auto MInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("M.cppm"),
+ &TFS, *CDB);
+ // buildModuleFilesInfoFor won't built the module itself.
+ EXPECT_FALSE(MInfo.IsModuleUnitBuilt("M"));
+
+ // Module N shouldn't be able to be built.
+ auto NInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("N.cppm"),
+ &TFS, *CDB);
+ EXPECT_TRUE(NInfo.IsModuleUnitBuilt("M"));
+ EXPECT_TRUE(NInfo.getModuleFilePath("M").endswith("M.pcm"));
+ EXPECT_TRUE(NInfo.IsModuleUnitBuilt("N:Part"));
+ EXPECT_TRUE(NInfo.getModuleFilePath("N:Part").endswith("N-Part.pcm"));
+
+ ParseInputs NInput = getInputs("N.cppm", *CDB);
+ std::vector<std::string> CC1Args;
+ std::unique_ptr<CompilerInvocation> Invocation =
+ getCompilerInvocation(NInput);
+ // Test that `ModuleFilesInfo::CanReuse` works basically.
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ // Test that we can still reuse the NInfo after we touch a unrelated file.
+ {
+ addFile("L.cppm", R"cpp(
+module;
+#include "bar.h"
+export module L;
+export int ll = 43;
+ )cpp");
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ addFile("bar.h", R"cpp(
+inline void bar() {}
+inline void bar(int) {}
+ )cpp");
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+ }
+
+ // Test that we can't reuse the NInfo after we touch a related file.
+ {
+ addFile("M.cppm", R"cpp(
+module;
+#include "foo.h"
+export module M;
+export int mm = 44;
+ )cpp");
+ EXPECT_FALSE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ NInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("N.cppm"),
+ &TFS, *CDB);
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ addFile("foo.h", R"cpp(
+inline void foo() {}
+inline void foo(int) {}
+ )cpp");
+ EXPECT_FALSE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ NInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("N.cppm"),
+ &TFS, *CDB);
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+ }
+
+ addFile("N-part.cppm", R"cpp(
+export module N:Part;
+// Intentioned to make it uncompilable.
+export int NPart = 4LIdjwldijaw
+ )cpp");
+ EXPECT_FALSE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+ NInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("N.cppm"), &TFS,
+ *CDB);
+ // So NInfo should be unreusable even after rebuild.
+ EXPECT_FALSE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ addFile("N-part.cppm", R"cpp(
+export module N:Part;
+export int NPart = 43;
+ )cpp");
+ EXPECT_FALSE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+ NInfo = ModuleFilesInfo::buildModuleFilesInfoFor(getFullPath("N.cppm"), &TFS,
+ *CDB);
+ // So NInfo should be unreusable even after rebuild.
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ // Test that if we changed the modification time of the file, the module files
+ // info is still reusable if its content doesn't change.
+ addFile("N-part.cppm", R"cpp(
+export module N:Part;
+export int NPart = 43;
+ )cpp");
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ addFile("N.cppm", R"cpp(
+export module N;
+import :Part;
+import M;
+
+export int nn = 43;
+ )cpp");
+ // NInfo should be reusable after we change its content.
+ EXPECT_TRUE(NInfo.CanReuse(*Invocation, TFS.view(TestDir)));
+
+ {
+ llvm::StringRef MPath = NInfo.getModuleFilePath("M");
+ llvm::SmallString<256> ModuleFilesPath = MPath;
+ llvm::sys::path::remove_filename(ModuleFilesPath);
+
+ // Check that
+ // `ModuleFilesInfo::ReplaceCompileCommands(tooling::CompileCommand &Cmd)`
+ // can replace compile commands correctly.
+ std::optional<tooling::CompileCommand> NCmd =
+ CDB->getCompileCommand(getFullPath("N.cppm"));
+ EXPECT_TRUE(NCmd);
+ NInfo.ReplaceCompileCommands(*NCmd);
+ EXPECT_EQ(
+ NCmd->CommandLine[1],
+ llvm::Twine("-fprebuilt-module-path=" + ModuleFilesPath.str()).str());
+ for (auto &Argument : NCmd->CommandLine) {
+ EXPECT_FALSE(llvm::StringRef(Argument).startswith("-fmodule-file=M="));
+ EXPECT_FALSE(
+ llvm::StringRef(Argument).startswith("-fmodule-file=N:Part="));
+ }
+
+ // Check that
+ // `ModuleFilesInfo::ReplaceHeaderSearchOptions(HeaderSearchOptions&)` can
+ // replace HeaderSearchOptions correctly.
+ ParseInputs NInput = getInputs("N.cppm", *CDB);
+ std::vector<std::string> CC1Args;
+ std::unique_ptr<CompilerInvocation> NInvocation =
+ getCompilerInvocation(NInput);
+ HeaderSearchOptions &HSOpts = NInvocation->getHeaderSearchOpts();
+ NInfo.ReplaceHeaderSearchOptions(HSOpts);
+
+ EXPECT_EQ(HSOpts.PrebuiltModulePaths.front(), ModuleFilesPath);
+ for (auto &[ModuleName, _] : HSOpts.PrebuiltModuleFiles) {
+ EXPECT_NE(ModuleName, "M");
+ EXPECT_NE(ModuleName, "N:Part");
+ }
+ }
+}
+
+} // namespace
diff --git a/clang-tools-extra/clangd/unittests/ModulesTestSetup.h b/clang-tools-extra/clangd/unittests/ModulesTestSetup.h
new file mode 100644
index 000000000000000..22cbd04353fe999
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/ModulesTestSetup.h
@@ -0,0 +1,105 @@
+//===-- ModulesTestSetup.h - Setup the module test environment --*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Compiler.h"
+#include "support/ThreadsafeFS.h"
+
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+class ModuleTestSetup : public ::testing::Test {
+protected:
+ void SetUp() override {
+ ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
+ }
+
+ void TearDown() override {
+ llvm::sys::fs::remove_directories(TestDir);
+ }
+
+public:
+ // Add files to the working testing directory and repalce all the
+ // `__DIR__` to TestDir.
+ void addFile(StringRef Path, StringRef Contents) {
+ ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
+
+ SmallString<256> AbsPath(TestDir);
+ llvm::sys::path::append(AbsPath, Path);
+
+ ASSERT_FALSE(llvm::sys::fs::create_directories(
+ llvm::sys::path::parent_path(AbsPath)));
+
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(AbsPath, EC);
+ ASSERT_FALSE(EC);
+
+ std::size_t Pos = Contents.find("__DIR__");
+ while (Pos != llvm::StringRef::npos) {
+ OS << Contents.take_front(Pos);
+ OS << TestDir;
+ Contents = Contents.drop_front(Pos + sizeof("__DIR__") - 1);
+ Pos = Contents.find("__DIR__");
+ }
+
+ OS << Contents;
+ }
+
+ // Get the absolute path for file specified by Path under testing working
+ // directory.
+ std::string getFullPath(StringRef Path) {
+ SmallString<128> Result(TestDir);
+ llvm::sys::path::append(Result, Path);
+ EXPECT_TRUE(llvm::sys::fs::exists(Result.str()));
+ return Result.str().str();
+ }
+
+ std::unique_ptr<GlobalCompilationDatabase> getGlobalCompilationDatabase() {
+ // The compilation flags with modules are much complex so it looks better
+ // to use DirectoryBasedGlobalCompilationDatabase than a mocked compilation
+ // database.
+ DirectoryBasedGlobalCompilationDatabase::Options Opts(TFS);
+ return std::make_unique<DirectoryBasedGlobalCompilationDatabase>(Opts);
+ }
+
+ ParseInputs getInputs(StringRef FileName,
+ const GlobalCompilationDatabase &CDB) {
+ std::string FullPathName = getFullPath(FileName);
+
+ ParseInputs Inputs;
+ std::optional<tooling::CompileCommand> Cmd =
+ CDB.getCompileCommand(FullPathName);
+ EXPECT_TRUE(Cmd);
+ Inputs.CompileCommand = std::move(*Cmd);
+ Inputs.TFS = &TFS;
+
+ if (auto Contents = TFS.view(TestDir)->getBufferForFile(FullPathName))
+ Inputs.Contents = Contents->get()->getBuffer().str();
+
+ return Inputs;
+ }
+
+ std::unique_ptr<CompilerInvocation>
+ getCompilerInvocation(const ParseInputs &Inputs) {
+ std::vector<std::string> CC1Args;
+ return buildCompilerInvocation(Inputs, DiagConsumer, &CC1Args);
+ }
+
+ SmallString<256> TestDir;
+ // Noticed MockFS but its member variable 'OverlayRealFileSystemForModules'
+ // implies that it will better to use RealThreadsafeFS directly.
+ RealThreadsafeFS TFS;
+
+ DiagnosticConsumer DiagConsumer;
+};
+}
+}
diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
index ec8132645f81fb5..4c7e6c66974dc03 100644
--- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -375,8 +375,10 @@ TEST(ParsedASTTest, PatchesAdditionalIncludes) {
MockFS FS;
auto Inputs = TU.inputs(FS);
auto CI = buildCompilerInvocation(Inputs, Diags);
+ MockCompilationDatabase CDB;
auto EmptyPreamble =
- buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
+ buildPreamble(testPath("foo.cpp"), *CI, Inputs,
+ /*ExperimentalModulesSupport=*/false, true, CDB, nullptr);
ASSERT_TRUE(EmptyPreamble);
EXPECT_THAT(EmptyPreamble->Includes.MainFileIncludes, IsEmpty());
@@ -417,8 +419,10 @@ TEST(ParsedASTTest, PatchesDeletedIncludes) {
MockFS FS;
auto Inputs = TU.inputs(FS);
auto CI = buildCompilerInvocation(Inputs, Diags);
+ MockCompilationDatabase CDB;
auto BaselinePreamble =
- buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
+ buildPreamble(testPath("foo.cpp"), *CI, Inputs,
+ /*ExperimentalModulesSupport=*/false, true, CDB, nullptr);
ASSERT_TRUE(BaselinePreamble);
EXPECT_THAT(BaselinePreamble->Includes.MainFileIncludes,
ElementsAre(testing::Field(&Inclusion::Written, "<foo.h>")));
diff --git a/clang-tools-extra/clangd/unittests/PreambleTests.cpp b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
index 6da98c55e392706..1d8a014d5d70494 100644
--- a/clang-tools-extra/clangd/unittests/PreambleTests.cpp
+++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
@@ -196,8 +196,10 @@ TEST(PreamblePatchTest, PatchesPreambleIncludes) {
TU.AdditionalFiles["b.h"] = "";
TU.AdditionalFiles["c.h"] = "";
auto PI = TU.inputs(FS);
- auto BaselinePreamble = buildPreamble(
- TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);
+ MockCompilationDatabase CDB;
+ auto BaselinePreamble =
+ buildPreamble(TU.Filename, *buildCompilerInvocation(PI, Diags), PI,
+ /*ExperimentalModulesSupport=*/false, true, CDB, nullptr);
// We drop c.h from modified and add a new header. Since the latter is patched
// we should only get a.h in preamble includes. d.h shouldn't be part of the
// preamble, as it's coming from a disabled region.
diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp
index e65ae825b416773..70c64fa3f8db881 100644
--- a/clang-tools-extra/clangd/unittests/TestTU.cpp
+++ b/clang-tools-extra/clangd/unittests/TestTU.cpp
@@ -107,8 +107,11 @@ TestTU::preamble(PreambleParsedCallback PreambleCallback) const {
initializeModuleCache(*CI);
auto ModuleCacheDeleter = llvm::make_scope_exit(
std::bind(deleteModuleCache, CI->getHeaderSearchOpts().ModuleCachePath));
+ MockCompilationDatabase CDB;
return clang::clangd::buildPreamble(testPath(Filename), *CI, Inputs,
- /*StoreInMemory=*/true, PreambleCallback);
+ /*ExperimentalModulesSupport=*/false,
+ /*StoreInMemory=*/true, CDB,
+ PreambleCallback);
}
ParsedAST TestTU::build() const {
@@ -123,9 +126,12 @@ ParsedAST TestTU::build() const {
auto ModuleCacheDeleter = llvm::make_scope_exit(
std::bind(deleteModuleCache, CI->getHeaderSearchOpts().ModuleCachePath));
- auto Preamble = clang::clangd::buildPreamble(testPath(Filename), *CI, Inputs,
- /*StoreInMemory=*/true,
- /*PreambleCallback=*/nullptr);
+ MockCompilationDatabase CDB;
+ auto Preamble =
+ clang::clangd::buildPreamble(testPath(Filename), *CI, Inputs,
+ /*ExperimentalModulesSupport=*/false,
+ /*StoreInMemory=*/true, CDB,
+ /*PreambleCallback=*/nullptr);
auto AST = ParsedAST::build(testPath(Filename), Inputs, std::move(CI),
Diags.take(), Preamble);
if (!AST) {
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 19c977977f9044c..fe56bd1d2db4a11 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -48,6 +48,9 @@ Major New Features
Improvements to clangd
----------------------
+- Introduced exmperimental support for C++20 Modules. The experimental support can
+ be enabled by `-experimental-modules-support` option.
+
Inlay hints
^^^^^^^^^^^
More information about the cfe-commits
mailing list