[clang] [clang][modules] Add single-module-parse-mode callback (PR #179714)
Jan Svoboda via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 4 20:53:06 PST 2026
https://github.com/jansvoboda11 updated https://github.com/llvm/llvm-project/pull/179714
>From 4241a6d06b8bda654d6cc71799ffec897c2fbcbc Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 3 Feb 2026 21:28:07 -0800
Subject: [PATCH 1/3] [clang][modules] Support every import syntax in
single-module-parse-mode
Previously, `-fmodules-single-module-parse-mode` only prevented module compilation/loading when initiated from an `#include` or `#import` directive. This PR does the same for `@import`, `#pragma clang module import` and `#pragma clang module load`. This is done by sinking the logic down into `CompilerInstance::loadModule()`.
---
clang/lib/Frontend/CompilerInstance.cpp | 16 +++++++++
clang/lib/Lex/PPDirectives.cpp | 9 ++---
.../single-module-parse-mode-compiles.m | 36 +++++++++++++++++++
3 files changed, 54 insertions(+), 7 deletions(-)
create mode 100644 clang/test/Modules/single-module-parse-mode-compiles.m
diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp
index ec4e80832b963..ddfabab5fe856 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -1988,6 +1988,22 @@ CompilerInstance::loadModule(SourceLocation ImportLoc,
// * `Preprocessor::HandleHeaderIncludeOrImport` will never call this
// function as the `#include` or `#import` is textual.
+ MM.cacheModuleLoad(*Path[0].getIdentifierInfo(), Module);
+ } else if (getPreprocessorOpts().SingleModuleParseMode) {
+ // This mimics how findOrCompileModuleAndReadAST() find the module.
+ Module = getPreprocessor().getHeaderSearchInfo().lookupModule(
+ ModuleName, ImportLoc, true, !IsInclusionDirective);
+ if (Module) {
+ // Mark the module and its submodules as if they were loaded from a PCM.
+ std::vector Worklist{Module};
+ while (!Worklist.empty()) {
+ auto *M = Worklist.back();
+ Worklist.pop_back();
+ M->IsFromModuleFile = true;
+ for (auto *SubM : M->submodules())
+ Worklist.push_back(SubM);
+ }
+ }
MM.cacheModuleLoad(*Path[0].getIdentifierInfo(), Module);
} else {
SourceLocation ModuleNameEndLoc = Path.back().getLoc().getLocWithOffset(
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index 8d4c9c49f756a..c4b9f9e61c876 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2494,15 +2494,10 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
(getLangOpts().CPlusPlusModules || getLangOpts().Modules) &&
ModuleToImport && !ModuleToImport->isHeaderUnit();
- if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule) &&
- PPOpts.SingleModuleParseMode) {
- Action = IncludeLimitReached;
- }
// Determine whether we should try to import the module for this #include, if
// there is one. Don't do so if precompiled module support is disabled or we
// are processing this module textually (because we're building the module).
- else if (MaybeTranslateInclude &&
- (UsableHeaderUnit || UsableClangHeaderModule)) {
+ if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule)) {
// If this include corresponds to a module but that module is
// unavailable, diagnose the situation and bail out.
// FIXME: Remove this; loadModule does the same check (but produces
@@ -2539,7 +2534,7 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
"the imported module is different than the suggested one");
if (Imported) {
- Action = Import;
+ Action = PPOpts.SingleModuleParseMode ? Skip : Import;
} else if (Imported.isMissingExpected()) {
markClangModuleAsAffecting(
static_cast<Module *>(Imported)->getTopLevelModule());
diff --git a/clang/test/Modules/single-module-parse-mode-compiles.m b/clang/test/Modules/single-module-parse-mode-compiles.m
new file mode 100644
index 0000000000000..8e9b92c86fe61
--- /dev/null
+++ b/clang/test/Modules/single-module-parse-mode-compiles.m
@@ -0,0 +1,36 @@
+// This test checks that with -fmodules-single-module-parse-mode, no modules get
+// compiled into PCM files from any of the import syntax Clang supports.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: mkdir %t/cache
+
+// With -fmodules-single-module-parse-mode, no modules get compiled.
+// RUN: %clang_cc1 -x objective-c -fmodules -fmodules-cache-path=%t/cache \
+// RUN: -emit-module %t/module.modulemap -fmodule-name=Mod -o %t/Mod.pcm \
+// RUN: -fmodules-single-module-parse-mode
+// RUN: find %t/cache -name "*.pcm" | count 0
+
+// Without -fmodules-single-module-parse-mode, loaded modules get compiled.
+// RUN: %clang_cc1 -x objective-c -fmodules -fmodules-cache-path=%t/cache \
+// RUN: -emit-module %t/module.modulemap -fmodule-name=Mod -o %t/Mod.pcm
+// RUN: find %t/cache -name "*.pcm" | count 5
+
+//--- module.modulemap
+module Mod { header "Mod.h" }
+module Load1 { header "Load1.h" }
+module Load2 { header "Load2.h" }
+module Load3 { header "Load3.h" }
+module Load4 { header "Load4.h" }
+module Load5 { header "Load5.h" }
+//--- Mod.h
+#include "Load1.h"
+#import "Load2.h"
+ at import Load3;
+#pragma clang module import Load4
+#pragma clang module load Load5
+//--- Load1.h
+//--- Load2.h
+//--- Load3.h
+//--- Load4.h
+//--- Load5.h
>From 48ceca5d968cbc3cdd01964373173d281fd4c331 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 3 Feb 2026 21:43:34 -0800
Subject: [PATCH 2/3] Undo (likely) unnecessary change
---
clang/lib/Lex/PPDirectives.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index c4b9f9e61c876..85edbabf09ed3 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2534,7 +2534,7 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
"the imported module is different than the suggested one");
if (Imported) {
- Action = PPOpts.SingleModuleParseMode ? Skip : Import;
+ Action = Import;
} else if (Imported.isMissingExpected()) {
markClangModuleAsAffecting(
static_cast<Module *>(Imported)->getTopLevelModule());
>From 0be1a4f26ce2b2a246c1868dc1e3aa2d6d8302fd Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 4 Feb 2026 09:34:06 -0800
Subject: [PATCH 3/3] [clang][modules] Add single-module-parse-mode callback
---
clang/include/clang/Lex/PPCallbacks.h | 12 ++++++++++++
clang/lib/Frontend/CompilerInstance.cpp | 2 ++
2 files changed, 14 insertions(+)
diff --git a/clang/include/clang/Lex/PPCallbacks.h b/clang/include/clang/Lex/PPCallbacks.h
index e6120c5648798..51c6a31e143b8 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -212,6 +212,13 @@ class PPCallbacks {
const Module *Imported) {
}
+ /// Callback invoked whenever a module load was skipped due to enabled
+ /// single-module-parse-mode.
+ ///
+ /// \param Skipped The module that was not loaded.
+ ///
+ virtual void moduleLoadSkipped(Module *Skipped) {}
+
/// Callback invoked when the end of the main file is reached.
///
/// No subsequent callbacks will be made.
@@ -554,6 +561,11 @@ class PPChainedCallbacks : public PPCallbacks {
Second->moduleImport(ImportLoc, Path, Imported);
}
+ void moduleLoadSkipped(Module *Skipped) override {
+ First->moduleLoadSkipped(Skipped);
+ Second->moduleLoadSkipped(Skipped);
+ }
+
void EndOfMainFile() override {
First->EndOfMainFile();
Second->EndOfMainFile();
diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp
index ddfabab5fe856..5d5da7a9e5643 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -1994,6 +1994,8 @@ CompilerInstance::loadModule(SourceLocation ImportLoc,
Module = getPreprocessor().getHeaderSearchInfo().lookupModule(
ModuleName, ImportLoc, true, !IsInclusionDirective);
if (Module) {
+ if (PPCallbacks *PPCb = getPreprocessor().getPPCallbacks())
+ PPCb->moduleLoadSkipped(Module);
// Mark the module and its submodules as if they were loaded from a PCM.
std::vector Worklist{Module};
while (!Worklist.empty()) {
More information about the cfe-commits
mailing list