[clang-tools-extra] 982f736 - [clangd] Introduce --skip-preamble-build command line option (#189284)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 16 00:06:13 PDT 2026
Author: Aleksandr Platonov
Date: 2026-04-16T10:06:09+03:00
New Revision: 982f736c852c06e8ad58300980c8a7b4a79045ff
URL: https://github.com/llvm/llvm-project/commit/982f736c852c06e8ad58300980c8a7b4a79045ff
DIFF: https://github.com/llvm/llvm-project/commit/982f736c852c06e8ad58300980c8a7b4a79045ff.diff
LOG: [clangd] Introduce --skip-preamble-build command line option (#189284)
This option allows to disable preamble optimization in clangd. By
default it's false, but became true for TUs which import modules (and
experimental modules support is enabled).
This PR is a try to address C++20 modules problems described here
https://github.com/llvm/llvm-project/pull/187432
Fixes https://github.com/llvm/llvm-project/issues/181770
Added:
Modified:
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/CodeComplete.cpp
clang-tools-extra/clangd/Compiler.h
clang-tools-extra/clangd/ModulesBuilder.cpp
clang-tools-extra/clangd/ModulesBuilder.h
clang-tools-extra/clangd/ParsedAST.cpp
clang-tools-extra/clangd/Preamble.cpp
clang-tools-extra/clangd/Preamble.h
clang-tools-extra/clangd/tool/ClangdMain.cpp
clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index f1a87dd12d905..9c9290b8b6b1b 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -223,6 +223,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
UseDirtyHeaders(Opts.UseDirtyHeaders),
LineFoldingOnly(Opts.LineFoldingOnly),
PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),
+ SkipPreambleBuild(Opts.SkipPreambleBuild),
ImportInsertions(Opts.ImportInsertions),
PublishInactiveRegions(Opts.PublishInactiveRegions),
WorkspaceRoot(Opts.WorkspaceRoot),
@@ -313,6 +314,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
Inputs.ClangTidyProvider = ClangTidyProvider;
Inputs.FeatureModules = FeatureModules;
Inputs.ModulesManager = ModulesManager;
+ adjustParseInputs(Inputs, File);
bool NewFile = WorkScheduler->update(File, Inputs, WantDiags);
// If we loaded Foo.h, we want to make sure Foo.cpp is indexed.
if (NewFile && BackgroundIdx)
@@ -459,6 +461,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
Config::current().Completion.HeaderInsertion;
CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;
CodeCompleteOpts.MacroFilter = Config::current().Completion.MacroFilter;
+ adjustParseInputs(ParseInput, File);
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CodeCompleteResult Result = clangd::codeComplete(
@@ -1189,5 +1192,16 @@ void ClangdServer::profile(MemoryTree &MT) const {
BackgroundIdx->profile(MT.child("background_index"));
WorkScheduler->profile(MT.child("tuscheduler"));
}
+
+void ClangdServer::adjustParseInputs(ParseInputs &Inputs, PathRef File) const {
+ // FIXME: Don't perform optimization when the TU requires C++20
+ // named modules. Mixing PCH and modules may cause
diff erent issues (incorrect
+ // diagnostics, crashes) due to instability of such scenario support in the
+ // clang.
+ Inputs.Opts.SkipPreambleBuild =
+ SkipPreambleBuild ||
+ (ModulesManager && ModulesManager->hasRequiredModules(File));
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 3ffaf67553dce..36d320af2f8a1 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -191,6 +191,9 @@ class ClangdServer {
// If true, parse emplace-like functions in the preamble.
bool PreambleParseForwardingFunctions = true;
+ // If true, skip preamble build.
+ bool SkipPreambleBuild = false;
+
/// Whether include fixer insertions for Objective-C code should use #import
/// instead of #include.
bool ImportInsertions = false;
@@ -482,6 +485,8 @@ class ClangdServer {
}
const ThreadsafeFS &TFS;
+ void adjustParseInputs(ParseInputs &Inputs, PathRef File) const;
+
Path ResourceDir;
// The index used to look up symbols. This could be:
// - null (all index functionality is optional)
@@ -508,6 +513,8 @@ class ClangdServer {
bool PreambleParseForwardingFunctions = true;
+ bool SkipPreambleBuild = false;
+
bool ImportInsertions = false;
bool PublishInactiveRegions = false;
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 28f81cd5267d5..8414ffe20526a 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1420,7 +1420,8 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
// overriding the preamble will break sema completion. Fortunately we can just
// skip all includes in this case; these completions are really simple.
PreambleBounds PreambleRegion =
- ComputePreambleBounds(CI->getLangOpts(), *ContentsBuffer, 0);
+ computePreambleBounds(CI->getLangOpts(), *ContentsBuffer,
+ Input.ParseInput.Opts.SkipPreambleBuild);
bool CompletingInPreamble = Input.Offset < PreambleRegion.Size ||
(!PreambleRegion.PreambleEndsAtStartOfLine &&
Input.Offset == PreambleRegion.Size);
@@ -1433,7 +1434,10 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
if (Input.Preamble.StatCache)
VFS = Input.Preamble.StatCache->getConsumingFS(std::move(VFS));
auto Clang = prepareCompilerInstance(
- std::move(CI), !CompletingInPreamble ? &Input.Preamble.Preamble : nullptr,
+ std::move(CI),
+ (!CompletingInPreamble && !Input.ParseInput.Opts.SkipPreambleBuild)
+ ? &Input.Preamble.Preamble
+ : nullptr,
std::move(ContentsBuffer), std::move(VFS), IgnoreDiags);
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());
diff --git a/clang-tools-extra/clangd/Compiler.h b/clang-tools-extra/clangd/Compiler.h
index e513e4c40794a..5e5e23d5b9682 100644
--- a/clang-tools-extra/clangd/Compiler.h
+++ b/clang-tools-extra/clangd/Compiler.h
@@ -43,6 +43,8 @@ struct ParseOptions {
bool PreambleParseForwardingFunctions = true;
bool ImportInsertions = false;
+
+ bool SkipPreambleBuild = false;
};
/// Information required to run clang, e.g. to parse AST or do code completion.
diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp
index f9581d51446ff..c4a0c54a8fc2f 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.cpp
+++ b/clang-tools-extra/clangd/ModulesBuilder.cpp
@@ -658,6 +658,16 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
return llvm::Error::success();
}
+bool ModulesBuilder::hasRequiredModules(PathRef File) {
+ std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File);
+ if (!MDB)
+ return false;
+
+ CachingProjectModules CachedMDB(std::move(MDB),
+ Impl->getProjectModulesCache());
+ return !CachedMDB.getRequiredModules(File).empty();
+}
+
std::unique_ptr<PrerequisiteModules>
ModulesBuilder::buildPrerequisiteModulesFor(PathRef File,
const ThreadsafeFS &TFS) {
diff --git a/clang-tools-extra/clangd/ModulesBuilder.h b/clang-tools-extra/clangd/ModulesBuilder.h
index f40a9006e9169..b0e110b92b6a7 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.h
+++ b/clang-tools-extra/clangd/ModulesBuilder.h
@@ -97,6 +97,8 @@ class ModulesBuilder {
std::unique_ptr<PrerequisiteModules>
buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS);
+ bool hasRequiredModules(PathRef File);
+
private:
class ModulesBuilderImpl;
std::unique_ptr<ModulesBuilderImpl> Impl;
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index 4e873f1257a17..e2a49f384a3e9 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -464,7 +464,7 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
Patch->apply(*CI);
}
auto Clang = prepareCompilerInstance(
- std::move(CI), PreamblePCH,
+ std::move(CI), Inputs.Opts.SkipPreambleBuild ? nullptr : PreamblePCH,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, Filename), VFS,
*DiagConsumer);
diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp
index f5e512793e98e..58da7edcf3b93 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -320,8 +320,9 @@ struct ScannedPreamble {
/// running preprocessor over \p Contents. Returned includes do not contain
/// resolved paths. \p Cmd is used to build the compiler invocation, which might
/// stat/read files.
-llvm::Expected<ScannedPreamble>
-scanPreamble(llvm::StringRef Contents, const tooling::CompileCommand &Cmd) {
+llvm::Expected<ScannedPreamble> scanPreamble(llvm::StringRef Contents,
+ const tooling::CompileCommand &Cmd,
+ bool SkipPreambleBuild) {
class EmptyFS : public ThreadsafeFS {
private:
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
@@ -345,7 +346,8 @@ scanPreamble(llvm::StringRef Contents, const tooling::CompileCommand &Cmd) {
// This means we're scanning (though not preprocessing) the preamble section
// twice. However, it's important to precisely follow the preamble bounds used
// elsewhere.
- auto Bounds = ComputePreambleBounds(CI->getLangOpts(), *ContentsBuffer, 0);
+ auto Bounds = computePreambleBounds(CI->getLangOpts(), *ContentsBuffer,
+ SkipPreambleBuild);
auto PreambleContents = llvm::MemoryBuffer::getMemBufferCopy(
llvm::StringRef(PI.Contents).take_front(Bounds.Size));
auto Clang = prepareCompilerInstance(
@@ -576,7 +578,8 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
// without those.
auto ContentsBuffer =
llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
- auto Bounds = ComputePreambleBounds(CI.getLangOpts(), *ContentsBuffer, 0);
+ auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
+ Inputs.Opts.SkipPreambleBuild);
trace::Span Tracer("BuildPreamble");
SPAN_ATTACH(Tracer, "File", FileName);
@@ -722,7 +725,8 @@ bool isPreambleCompatible(const PreambleData &Preamble,
const CompilerInvocation &CI) {
auto ContentsBuffer =
llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
- auto Bounds = ComputePreambleBounds(CI.getLangOpts(), *ContentsBuffer, 0);
+ auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
+ Inputs.Opts.SkipPreambleBuild);
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
return compileCommandsAreEqual(Inputs.CompileCommand,
Preamble.CompileCommand) &&
@@ -785,13 +789,15 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
// - If scanning for Modified fails, cannot figure out newly added ones so
// there's nothing to do but generate an empty patch.
auto BaselineScan =
- scanPreamble(Baseline.Preamble.getContents(), Modified.CompileCommand);
+ scanPreamble(Baseline.Preamble.getContents(), Modified.CompileCommand,
+ Modified.Opts.SkipPreambleBuild);
if (!BaselineScan) {
elog("Failed to scan baseline of {0}: {1}", FileName,
BaselineScan.takeError());
return PreamblePatch::unmodified(Baseline);
}
- auto ModifiedScan = scanPreamble(Modified.Contents, Modified.CompileCommand);
+ auto ModifiedScan = scanPreamble(Modified.Contents, Modified.CompileCommand,
+ Modified.Opts.SkipPreambleBuild);
if (!ModifiedScan) {
elog("Failed to scan modified contents of {0}: {1}", FileName,
ModifiedScan.takeError());
@@ -957,5 +963,12 @@ OptionalFileEntryRef PreamblePatch::getPatchEntry(llvm::StringRef MainFilePath,
auto PatchFilePath = getPatchName(MainFilePath);
return SM.getFileManager().getOptionalFileRef(PatchFilePath);
}
+
+PreambleBounds computePreambleBounds(const LangOptions &LangOpts,
+ const llvm::MemoryBufferRef &Buffer,
+ bool SkipPreambleBuild) {
+ return SkipPreambleBuild ? PreambleBounds(0, false)
+ : ComputePreambleBounds(LangOpts, Buffer, 0);
+}
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h
index 7f2eb233ee1aa..2051930dae05d 100644
--- a/clang-tools-extra/clangd/Preamble.h
+++ b/clang-tools-extra/clangd/Preamble.h
@@ -239,6 +239,10 @@ class PreamblePatch {
MainFileMacros PatchedMacros;
};
+PreambleBounds computePreambleBounds(const LangOptions &LangOpts,
+ const llvm::MemoryBufferRef &Buffer,
+ bool SkipPreambleBuild);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 54af3662470db..e6d49008c22f9 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -525,6 +525,14 @@ opt<bool> PreambleParseForwardingFunctions{
init(ParseOptions().PreambleParseForwardingFunctions),
};
+opt<bool> SkipPreambleBuild{
+ "skip-preamble-build",
+ cat(Misc),
+ desc("If ture, skip preamble build"),
+ Hidden,
+ init(ParseOptions().SkipPreambleBuild),
+};
+
#if defined(__GLIBC__) && CLANGD_MALLOC_TRIM
opt<bool> EnableMallocTrim{
"malloc-trim",
@@ -1005,6 +1013,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
}
Opts.UseDirtyHeaders = UseDirtyHeaders;
Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;
+ Opts.SkipPreambleBuild = SkipPreambleBuild;
Opts.ImportInsertions = ImportInsertions;
Opts.QueryDriverGlobs = std::move(QueryDriverGlobs);
Opts.TweakFilter = [&](const Tweak &T) {
diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index 488d7d7e6323c..1a5d805d730f6 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -28,6 +28,8 @@
namespace clang::clangd {
namespace {
+MATCHER_P(named, Name, "") { return arg.Name == Name; }
+
class GlobalScanningCounterProjectModules : public ProjectModules {
public:
GlobalScanningCounterProjectModules(
@@ -830,6 +832,56 @@ int use() { return m_value; }
<< "\nRelative path used: " << RelativeBMPath;
}
+TEST_F(PrerequisiteModulesTests, ModuleImportThroughInclude) {
+ MockDirectoryCompilationDatabase CDB(TestDir, FS);
+
+ Annotations UseCpp(R"cpp(
+#include "Header.hpp"
+void use() {
+ TypeFrom^Module t1;
+ TypeFromHeader t2;
+}
+)cpp");
+
+ CDB.addFile("M.cppm", R"cpp(
+export module M;
+export struct TypeFromModule {};
+)cpp");
+
+ CDB.addFile("Header.hpp", R"cpp(
+import M;
+struct TypeFromHeader {};
+)cpp");
+
+ CDB.addFile("Use.cpp", UseCpp.code());
+
+ ModulesBuilder Builder(CDB);
+
+ auto Inputs = getInputs("Use.cpp", CDB);
+ Inputs.ModulesManager = &Builder;
+ Inputs.Opts.SkipPreambleBuild = true;
+
+ auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
+ ASSERT_TRUE(CI);
+
+ auto Preamble =
+ buildPreamble(getFullPath("Use.cpp"), *CI, Inputs, /*StoreInMemory=*/true,
+ /*PeambleCallback=*/nullptr);
+ ASSERT_TRUE(Preamble);
+ EXPECT_EQ(Preamble->Preamble.getBounds().Size, 0u);
+
+ auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), {},
+ Preamble);
+ ASSERT_TRUE(AST);
+ EXPECT_TRUE(AST->getDiagnostics().empty());
+
+ auto Result = codeComplete(getFullPath("Use.cpp"), UseCpp.point(),
+ Preamble.get(), Inputs, {});
+ EXPECT_THAT(Result.Completions,
+ testing::UnorderedElementsAre(named("TypeFromModule"),
+ named("TypeFromHeader")));
+}
+
} // namespace
} // namespace clang::clangd
More information about the cfe-commits
mailing list