[clang] [C++][Modules] Don't insert `#include` before GMF when `-include` used (PR #212533)
Yihan Wang via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 28 19:51:38 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/212533
>From b35d020d461086311c835e506af5deb7c3fff28a Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 28 Jul 2026 09:23:49 -0700
Subject: [PATCH 1/4] [C++][Modules] Don't insert #include before GMF when
-include used
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
.../clang/Lex/DependencyDirectivesScanner.h | 15 ++
clang/include/clang/Lex/Preprocessor.h | 32 +++-
clang/lib/Frontend/InitPreprocessor.cpp | 39 +++--
.../lib/Frontend/PrintPreprocessedOutput.cpp | 1 +
clang/lib/Lex/DependencyDirectivesScanner.cpp | 28 ++++
clang/lib/Lex/PPDirectives.cpp | 24 ++-
clang/lib/Lex/Preprocessor.cpp | 52 ++++++
clang/lib/Serialization/ASTReader.cpp | 20 +--
clang/test/Modules/cxx20-force-include.cpp | 157 ++++++++++++++++++
.../Lex/DependencyDirectivesScannerTest.cpp | 40 +++++
10 files changed, 380 insertions(+), 28 deletions(-)
create mode 100644 clang/test/Modules/cxx20-force-include.cpp
diff --git a/clang/include/clang/Lex/DependencyDirectivesScanner.h b/clang/include/clang/Lex/DependencyDirectivesScanner.h
index b21da166a96e5..1523f6b9b777a 100644
--- a/clang/include/clang/Lex/DependencyDirectivesScanner.h
+++ b/clang/include/clang/Lex/DependencyDirectivesScanner.h
@@ -142,6 +142,21 @@ void printDependencyDirectivesAsSource(
/// \returns true if any C++20 named modules related directive was found.
bool scanInputForCXX20ModulesUsage(StringRef Source);
+/// Describes how a source input starts a C++20 module unit.
+enum class ModuleUnitKind {
+ NotModuleUnit,
+ HasGlobalModuleFragment,
+ NamedModuleWithoutGlobalModuleFragment,
+};
+
+/// Scan an input source buffer to determine whether it starts a C++20 module
+/// unit, and whether that module unit has a global module fragment.
+///
+/// \param Source The input source buffer.
+///
+/// \returns the kind of C++20 module unit found in the input.
+ModuleUnitKind scanInputForCXX20ModuleUnit(StringRef Source);
+
/// Scan an input source buffer, and check whether the input source is a
/// preprocessed output.
///
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 1c917dcfe7b7e..58b7ac3f612f5 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -711,6 +711,25 @@ class Preprocessor {
/// This is used when loading a precompiled preamble.
std::pair<int, bool> SkipMainFilePreamble;
+ /// Implicit input directives waiting to be entered after a global module
+ /// fragment introducer, if the main file starts a module unit.
+ std::string DeferredGMFInputs;
+
+ /// The synthesized buffer used to enter deferred implicit input files.
+ FileID DeferredGMFInputsFileID;
+
+ /// Whether the predefines buffer contains a synthesized GMF introducer.
+ bool HasSynthesizedGMF = false;
+
+ /// Whether setPredefines() replaced a previously initialized buffer.
+ bool PredefinesWereReplaced = false;
+ bool PredefinesInitialized = false;
+
+ bool hasDeferredGMFInputs() const { return !DeferredGMFInputs.empty(); }
+
+ /// Enter implicit input files after the global module fragment introducer.
+ void EnterDeferredGMFInputs(SourceLocation IncludeLoc);
+
/// Whether we hit an error due to reaching max allowed include depth. Allows
/// to avoid hitting the same error over and over again.
bool HasReachedMaxIncludeDepth = false;
@@ -1569,7 +1588,18 @@ class Preprocessor {
/// Set the predefines for this Preprocessor.
///
/// These predefines are automatically injected when parsing the main file.
- void setPredefines(std::string P) { Predefines = std::move(P); }
+ void setPredefines(std::string P) {
+ PredefinesWereReplaced |= PredefinesInitialized;
+ PredefinesInitialized = true;
+ Predefines = std::move(P);
+ }
+
+ /// Record implicit macro, PCH, and regular include directives to be entered
+ /// before the main file or inside its global module fragment.
+ void setDeferredGMFInputs(std::string Inputs) {
+ assert(DeferredGMFInputs.empty());
+ DeferredGMFInputs = std::move(Inputs);
+ }
/// Return information about the specified preprocessor
/// identifier token.
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 8b6ff844d0daa..ee5d4f2af2c51 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -1647,20 +1647,31 @@ void clang::InitializePreprocessor(Preprocessor &PP,
// Exit the command line and go back to <built-in> (2 is LC_LEAVE).
Builder.append("# 1 \"<built-in>\" 2");
- // If -imacros are specified, include them now. These are processed before
- // any -include directives.
- for (unsigned i = 0, e = InitOpts.MacroIncludes.size(); i != e; ++i)
- AddImplicitIncludeMacros(Builder, InitOpts.MacroIncludes[i]);
-
- // Process -include-pch/-include-pth directives.
- if (!InitOpts.ImplicitPCHInclude.empty())
- AddImplicitIncludePCH(Builder, PP, PCHContainerRdr,
- InitOpts.ImplicitPCHInclude);
-
- // Process -include directives.
- for (unsigned i = 0, e = InitOpts.Includes.size(); i != e; ++i) {
- const std::string &Path = InitOpts.Includes[i];
- AddImplicitInclude(Builder, Path);
+ auto AddImplicitInputs = [&](MacroBuilder &ImplicitBuilder) {
+ // If -imacros are specified, include them now. These are processed before
+ // any -include directives.
+ for (unsigned i = 0, e = InitOpts.MacroIncludes.size(); i != e; ++i)
+ AddImplicitIncludeMacros(ImplicitBuilder, InitOpts.MacroIncludes[i]);
+
+ // Process -include-pch/-include-pth directives.
+ if (!InitOpts.ImplicitPCHInclude.empty())
+ AddImplicitIncludePCH(ImplicitBuilder, PP, PCHContainerRdr,
+ InitOpts.ImplicitPCHInclude);
+
+ // Process -include directives.
+ for (unsigned i = 0, e = InitOpts.Includes.size(); i != e; ++i)
+ AddImplicitInclude(ImplicitBuilder, InitOpts.Includes[i]);
+ };
+
+ if (LangOpts.CPlusPlusModules) {
+ std::string ImplicitInputs;
+ llvm::raw_string_ostream ImplicitInputsStream(ImplicitInputs);
+ MacroBuilder ImplicitBuilder(ImplicitInputsStream);
+ AddImplicitInputs(ImplicitBuilder);
+ if (!ImplicitInputs.empty())
+ PP.setDeferredGMFInputs(std::move(ImplicitInputs));
+ } else {
+ AddImplicitInputs(Builder);
}
// Instruct the preprocessor to skip the preamble.
diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
index 02266882c4c4a..e000748d7e2f1 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -1012,6 +1012,7 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok,
IsCXXModuleDirective = false;
IsStartOfLine = true;
*Callbacks->OS << ';';
+ Callbacks->setEmittedTokensOnThisLine();
PP.Lex(Tok);
continue;
} else if (IdentifierInfo *II = Tok.getIdentifierInfo()) {
diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp
index ede5d49860fa4..54408ba30a130 100644
--- a/clang/lib/Lex/DependencyDirectivesScanner.cpp
+++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp
@@ -85,6 +85,7 @@ struct Scanner {
bool scan(SmallVectorImpl<Directive> &Directives);
friend bool clang::scanInputForCXX20ModulesUsage(StringRef Source);
+ friend ModuleUnitKind clang::scanInputForCXX20ModuleUnit(StringRef Source);
friend bool clang::isPreprocessedModuleFile(StringRef Source);
private:
@@ -1165,6 +1166,33 @@ bool clang::scanInputForCXX20ModulesUsage(StringRef Source) {
return llvm::any_of(S.DirsWithToks, IsCXXNamedModuleDirective);
}
+ModuleUnitKind clang::scanInputForCXX20ModuleUnit(StringRef Source) {
+ const char *First = Source.begin();
+ const char *const End = Source.end();
+ skipUntilMaybeCXX20ModuleDirective(First, End);
+ if (First == End || !(*First == 'e' || *First == 'm'))
+ return ModuleUnitKind::NotModuleUnit;
+
+ llvm::SmallVector<dependency_directives_scan::Token> Tokens;
+ Scanner S(StringRef(First, End - First), Tokens, nullptr, SourceLocation());
+ S.TheLexer.setParsingPreprocessorDirective(true);
+ if (S.lexModule(First, End) || S.DirsWithToks.size() != 1)
+ return ModuleUnitKind::NotModuleUnit;
+
+ const DirectiveWithTokens &Directive = S.DirsWithToks.front();
+ switch (Directive.Kind) {
+ case dependency_directives_scan::cxx_module_decl:
+ assert(Directive.NumTokens >= 2);
+ return Tokens[1].is(tok::semi)
+ ? ModuleUnitKind::HasGlobalModuleFragment
+ : ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment;
+ case dependency_directives_scan::cxx_export_module_decl:
+ return ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment;
+ default:
+ return ModuleUnitKind::NotModuleUnit;
+ }
+}
+
bool clang::isPreprocessedModuleFile(StringRef Source) {
const char *First = Source.begin();
const char *const End = Source.end();
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index c161f6a03593e..20e3491f2f308 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2900,10 +2900,12 @@ void Preprocessor::HandleImportDirective(SourceLocation HashLoc,
/// effects on the preprocessor).
void Preprocessor::HandleIncludeMacrosDirective(SourceLocation HashLoc,
Token &IncludeMacrosTok) {
- // This directive should only occur in the predefines buffer. If not, emit an
+ // This directive should only occur in the predefines buffer or the internal
+ // buffer used to enter deferred implicit inputs in a GMF. If not, emit an
// error and reject it.
SourceLocation Loc = IncludeMacrosTok.getLocation();
- if (SourceMgr.getBufferName(Loc) != "<built-in>") {
+ FileID FID = SourceMgr.getFileID(Loc);
+ if (FID != getPredefinesFileID() && FID != DeferredGMFInputsFileID) {
Diag(IncludeMacrosTok.getLocation(),
diag::pp_include_macros_out_of_predefines);
DiscardUntilEndOfDirective();
@@ -4449,7 +4451,14 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) {
: DirToks.pop_back_val().getLocation();
- if (!IncludeMacroStack.empty()) {
+ bool IsGMFIntroducer = DirToks.size() == 2 &&
+ DirToks[0].is(tok::kw_module) &&
+ DirToks[1].is(tok::semi);
+ bool IsSynthesizedGMF =
+ IsGMFIntroducer && HasSynthesizedGMF &&
+ CurPPLexer->getFileID() == getPredefinesFileID();
+
+ if (!IncludeMacroStack.empty() && !IsSynthesizedGMF) {
Diag(StartLoc, diag::err_pp_module_decl_in_header)
<< SourceRange(StartLoc, End);
}
@@ -4458,6 +4467,15 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) {
Diag(StartLoc, diag::err_pp_cond_span_module_decl)
<< SourceRange(StartLoc, End);
}
+
+ // For the global-module-fragment introducer (`module;`), enter any implicit
+ // macro, PCH, and regular include files that were deferred to the GMF now,
+ // before re-entering the `module;` token stream. Because the include stack is
+ // LIFO, the `module;` tokens are consumed first and the included files are
+ // then lexed inside the fragment (ahead of the rest of the main file).
+ if (IsGMFIntroducer)
+ EnterDeferredGMFInputs(End);
+
EnterModuleSuffixTokenStream(DirToks);
}
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 212b78154610f..c341335ac680a 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -589,6 +589,11 @@ void Preprocessor::EnterMainSourceFile() {
assert(NumEnteredSourceFiles == 0 && "Cannot reenter the main file!");
FileID MainFileID = SourceMgr.getMainFileID();
+ // Whether and how the main file starts a C++20 module unit. Implicit inputs
+ // are placed in its existing global module fragment, or in a synthesized one
+ // for a named module without a GMF.
+ ModuleUnitKind MainFileModuleUnitKind = ModuleUnitKind::NotModuleUnit;
+
// If MainFileID is loaded it means we loaded an AST file, no need to enter
// a main file.
if (!SourceMgr.isLoadedFileID(MainFileID)) {
@@ -621,6 +626,9 @@ void Preprocessor::EnterMainSourceFile() {
if (!isPreprocessedModuleFile() && Input)
MainFileIsPreprocessedModuleFile =
clang::isPreprocessedModuleFile(*Input);
+ if (Input && !MainFileIsPreprocessedModuleFile &&
+ hasDeferredGMFInputs())
+ MainFileModuleUnitKind = scanInputForCXX20ModuleUnit(*Input);
auto Tracer = std::make_unique<NoTrivialPPDirectiveTracer>(*this);
DirTracer = Tracer.get();
addPPCallbacks(std::move(Tracer));
@@ -630,6 +638,35 @@ void Preprocessor::EnterMainSourceFile() {
}
}
+ // Preserve the historical placement in the predefines buffer for ordinary
+ // translation units. A module unit opening with `module;` leaves the inputs
+ // deferred until the introducer has been lexed. For a named module without a
+ // GMF, synthesize the introducer before the main file and use the same
+ // deferred-input path.
+ if (hasDeferredGMFInputs()) {
+ if (PredefinesWereReplaced) {
+ // Loading an implicit PCH replaces Predefines with the directives
+ // suggested by ASTReader. For a module unit, those are the only implicit
+ // inputs that still need to be processed in the GMF.
+ if (MainFileModuleUnitKind != ModuleUnitKind::NotModuleUnit) {
+ DeferredGMFInputs = std::move(Predefines);
+ Predefines.clear();
+ }
+ } else if (MainFileModuleUnitKind == ModuleUnitKind::NotModuleUnit) {
+ // Preserve the historical predefines ordering for an ordinary
+ // translation unit.
+ Predefines += DeferredGMFInputs;
+ }
+ if (MainFileModuleUnitKind ==
+ ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment) {
+ Predefines +=
+ "# 1 \"<implicit-global-module-fragment>\" 1\nmodule;\n";
+ HasSynthesizedGMF = true;
+ } else if (MainFileModuleUnitKind == ModuleUnitKind::NotModuleUnit) {
+ DeferredGMFInputs.clear();
+ }
+ }
+
// Preprocess Predefines to populate the initial preprocessor state.
std::unique_ptr<llvm::MemoryBuffer> SB =
llvm::MemoryBuffer::getMemBufferCopy(Predefines, "<built-in>");
@@ -665,6 +702,21 @@ void Preprocessor::EnterMainSourceFile() {
SkipTokensWhileUsingPCH();
}
+void Preprocessor::EnterDeferredGMFInputs(SourceLocation IncludeLoc) {
+ if (!hasDeferredGMFInputs())
+ return;
+ // Synthesize the implicit input directives and enter them inside the global
+ // module fragment. Attribute the buffer to IncludeLoc so it is ordered within
+ // the translation unit.
+ std::unique_ptr<llvm::MemoryBuffer> MB =
+ llvm::MemoryBuffer::getMemBufferCopy(DeferredGMFInputs,
+ "<gmf-command-line-inputs>");
+ DeferredGMFInputs.clear();
+ DeferredGMFInputsFileID =
+ SourceMgr.createFileID(std::move(MB), SrcMgr::C_User, 0, 0, IncludeLoc);
+ EnterSourceFile(DeferredGMFInputsFileID, nullptr, IncludeLoc);
+}
+
void Preprocessor::setPCHThroughHeaderFileID(FileID FID) {
assert(PCHThroughHeaderFileID.isInvalid() &&
"PCHThroughHeaderFileID already set!");
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 8b7eaaac021eb..56f998bfe7a28 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -922,6 +922,16 @@ static bool checkPreprocessorOptions(
}
// Compute the #include and #include_macros lines we need.
+ for (unsigned I = 0, N = ExistingPPOpts.MacroIncludes.size(); I != N; ++I) {
+ StringRef File = ExistingPPOpts.MacroIncludes[I];
+ if (llvm::is_contained(PPOpts.MacroIncludes, File))
+ continue;
+
+ SuggestedPredefines += "#__include_macros \"";
+ SuggestedPredefines += File;
+ SuggestedPredefines += "\"\n##\n";
+ }
+
for (unsigned I = 0, N = ExistingPPOpts.Includes.size(); I != N; ++I) {
StringRef File = ExistingPPOpts.Includes[I];
@@ -946,16 +956,6 @@ static bool checkPreprocessorOptions(
SuggestedPredefines += "\"\n";
}
- for (unsigned I = 0, N = ExistingPPOpts.MacroIncludes.size(); I != N; ++I) {
- StringRef File = ExistingPPOpts.MacroIncludes[I];
- if (llvm::is_contained(PPOpts.MacroIncludes, File))
- continue;
-
- SuggestedPredefines += "#__include_macros \"";
- SuggestedPredefines += File;
- SuggestedPredefines += "\"\n##\n";
- }
-
return false;
}
diff --git a/clang/test/Modules/cxx20-force-include.cpp b/clang/test/Modules/cxx20-force-include.cpp
new file mode 100644
index 0000000000000..08ee6351eec5a
--- /dev/null
+++ b/clang/test/Modules/cxx20-force-include.cpp
@@ -0,0 +1,157 @@
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h \
+// RUN: -include %t/second.h %t/M.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -x cuda -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h \
+// RUN: -include %t/second.h %t/M.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h \
+// RUN: -include %t/second.h %t/NoGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -x cuda -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h \
+// RUN: -include %t/second.h %t/NoGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -E -imacros %t/macros.h -include %t/first.h \
+// RUN: -include %t/second.h %t/M.cppm -o %t/M.ii
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -x c++-cpp-output %t/M.ii
+// RUN: %clang_cc1 -std=c++20 -E -imacros %t/macros.h -include %t/first.h \
+// RUN: -include %t/second.h %t/NoGMF.cppm -o %t/NoGMF.ii
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -x c++-cpp-output %t/NoGMF.ii
+// RUN: %clang_cc1 -std=c++20 -E -imacros %t/macros.h %t/MacroOnly.cppm \
+// RUN: | FileCheck %s --check-prefix=MACRO-ONLY
+// RUN: %clang_cc1 -std=c++20 -E -include %t/Header.h %t/Preprocess.cppm \
+// RUN: | FileCheck %s --check-prefix=PREPROCESS
+// RUN: %clang_cc1 -std=c++20 -x c++-header -emit-pch %t/pch.h -o %t/pch.pch
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include-pch %t/pch.pch -include %t/first.h %t/PCHGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include-pch %t/pch.pch -include %t/first.h %t/PCHNoGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -include-pch %t/pch.pch \
+// RUN: %t/PCHOnlyGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -include-pch %t/pch.pch \
+// RUN: %t/PCHOnlyNoGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -E -imacros %t/macros.h \
+// RUN: -include-pch %t/pch.pch -include %t/first.h %t/PCHNoGMF.cppm \
+// RUN: | FileCheck %s --check-prefix=PCH-ONLY
+// RUN: %clang_cc1 -std=c++20 -E -include-pch %t/pch.pch \
+// RUN: %t/PCHOnlyNoGMF.cppm \
+// RUN: | FileCheck %s --check-prefix=PCH-ONLY-NO-GMF
+// RUN: %clang_cc1 -std=c++20 -x cuda -emit-pch %t/pch.h -o %t/cuda.pch
+// RUN: %clang_cc1 -std=c++20 -x cuda -fsyntax-only -imacros %t/macros.h \
+// RUN: -include-pch %t/cuda.pch -include %t/first.h %t/PCHGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -x cuda -fsyntax-only -imacros %t/macros.h \
+// RUN: -include-pch %t/cuda.pch -include %t/first.h %t/PCHNoGMF.cppm -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include-pch %t/pch.pch -include %t/first.h %t/pch-tu.cpp -verify
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/Base.cppm \
+// RUN: -o %t/Base.pcm
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h -include %t/second.h \
+// RUN: -fmodule-file=Base=%t/Base.pcm %t/Base-impl.cpp -verify
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -imacros %t/macros.h \
+// RUN: -include %t/first.h \
+// RUN: -include %t/second.h %t/tu.cpp -verify
+
+// MACRO-ONLY: __preprocessed_module{{ *}};
+// PREPROCESS: # 1 "<implicit-global-module-fragment>" 1
+// PREPROCESS-NEXT: # 1 "<gmf-command-line-inputs>" 1
+// PREPROCESS-NEXT: __preprocessed_module;
+// PREPROCESS-NEXT: # 1 "{{.*}}Header.h" 1
+// PREPROCESS-NEXT: struct Lexer {};
+// PREPROCESS-NEXT: # 2 "<gmf-command-line-inputs>" 2
+// PREPROCESS-NEXT: # 2 "<implicit-global-module-fragment>" 2
+// PREPROCESS-NEXT: # 1 "{{.*}}Preprocess.cppm" 2
+// PREPROCESS-NEXT: export __preprocessed_module M;
+// PCH-ONLY: __preprocessed_module{{ *}};
+// PCH-ONLY-NO-GMF: __preprocessed_module;
+// PCH-ONLY-NO-GMF: export __preprocessed_module PCHOnlyNoGMF;
+
+//--- macros.h
+#define IMPLICIT_MACRO 3
+
+//--- first.h
+#define FIRST 1
+static_assert(IMPLICIT_MACRO == 3);
+struct FromFirst {};
+
+//--- second.h
+static_assert(FIRST == 1);
+#define SECOND 2
+
+//--- M.cppm
+// expected-no-diagnostics
+/* A leading comment and an escaped newline exercise raw-token detection. */
+module \
+;
+static_assert(SECOND == 2);
+export module M;
+export FromFirst from_first();
+
+//--- NoGMF.cppm
+// expected-no-diagnostics
+export module NoGMF;
+static_assert(SECOND == 2);
+export FromFirst no_gmf();
+
+//--- MacroOnly.cppm
+export module MacroOnly;
+static_assert(IMPLICIT_MACRO == 3);
+
+//--- Header.h
+struct Lexer {};
+
+//--- Preprocess.cppm
+export module M;
+export int count = 0;
+
+//--- pch.h
+#pragma once
+struct FromPCH {};
+
+//--- PCHGMF.cppm
+// expected-no-diagnostics
+module;
+static_assert(IMPLICIT_MACRO == 3);
+export module PCHGMF;
+export FromPCH from_pch_gmf();
+export FromFirst from_first_pch_gmf();
+
+//--- PCHNoGMF.cppm
+// expected-no-diagnostics
+export module PCHNoGMF;
+static_assert(IMPLICIT_MACRO == 3);
+export FromPCH from_pch_no_gmf();
+export FromFirst from_first_pch_no_gmf();
+
+//--- PCHOnlyGMF.cppm
+// expected-no-diagnostics
+module;
+export module PCHOnlyGMF;
+export FromPCH from_pch_only_gmf();
+
+//--- PCHOnlyNoGMF.cppm
+// expected-no-diagnostics
+export module PCHOnlyNoGMF;
+export FromPCH from_pch_only_no_gmf();
+
+//--- pch-tu.cpp
+// expected-no-diagnostics
+static_assert(IMPLICIT_MACRO == 3);
+FromPCH from_pch_tu;
+FromFirst from_first_pch_tu;
+
+//--- Base.cppm
+export module Base;
+export void base();
+
+//--- Base-impl.cpp
+// expected-no-diagnostics
+module Base;
+static_assert(SECOND == 2);
+FromFirst from_impl;
+
+//--- tu.cpp
+// expected-no-diagnostics
+static_assert(FIRST == 1);
+static_assert(SECOND == 2);
+FromFirst from_first;
diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
index 91bda85a43f57..18aff51c4bdef 100644
--- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
+++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
@@ -1270,4 +1270,44 @@ TEST(MinimizeSourceToDependencyDirectivesTest, ScanningPreprocessedModuleFile) {
ASSERT_TRUE(clang::isPreprocessedModuleFile(Source));
}
+TEST(MinimizeSourceToDependencyDirectivesTest, CXX20ModuleUnitKind) {
+ EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
+ scanInputForCXX20ModuleUnit("int x;"));
+ EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
+ scanInputForCXX20ModuleUnit("import M;"));
+ EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
+ scanInputForCXX20ModuleUnit("export import M;"));
+ EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
+ scanInputForCXX20ModuleUnit("module"));
+ EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
+ scanInputForCXX20ModuleUnit("export module"));
+
+ EXPECT_EQ(ModuleUnitKind::HasGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("module;"));
+ EXPECT_EQ(ModuleUnitKind::HasGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit(R"(
+ // Leading comments and line splices are ignored.
+ module \
+ ;
+ export module M;
+ )"));
+
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("export module M;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("module M;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("export module M:Part;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("module M:Part;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("module \"M\";"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("export module 42;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("export module M any pp tokens;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("#line 7\nexport module M;"));
+}
+
} // end anonymous namespace
>From 7a2674fab12f750c1ae01d6710f70079735b290e Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 28 Jul 2026 09:35:08 -0700
Subject: [PATCH 2/4] [clang] format
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/Frontend/InitPreprocessor.cpp | 2 +-
clang/lib/Lex/PPDirectives.cpp | 8 +++-----
clang/lib/Lex/Preprocessor.cpp | 11 ++++-------
3 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index ee5d4f2af2c51..5eba21116b347 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -1652,7 +1652,7 @@ void clang::InitializePreprocessor(Preprocessor &PP,
// any -include directives.
for (unsigned i = 0, e = InitOpts.MacroIncludes.size(); i != e; ++i)
AddImplicitIncludeMacros(ImplicitBuilder, InitOpts.MacroIncludes[i]);
-
+
// Process -include-pch/-include-pth directives.
if (!InitOpts.ImplicitPCHInclude.empty())
AddImplicitIncludePCH(ImplicitBuilder, PP, PCHContainerRdr,
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index 20e3491f2f308..1e4a25a819f67 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -4451,12 +4451,10 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) {
: DirToks.pop_back_val().getLocation();
- bool IsGMFIntroducer = DirToks.size() == 2 &&
- DirToks[0].is(tok::kw_module) &&
+ bool IsGMFIntroducer = DirToks.size() == 2 && DirToks[0].is(tok::kw_module) &&
DirToks[1].is(tok::semi);
- bool IsSynthesizedGMF =
- IsGMFIntroducer && HasSynthesizedGMF &&
- CurPPLexer->getFileID() == getPredefinesFileID();
+ bool IsSynthesizedGMF = IsGMFIntroducer && HasSynthesizedGMF &&
+ CurPPLexer->getFileID() == getPredefinesFileID();
if (!IncludeMacroStack.empty() && !IsSynthesizedGMF) {
Diag(StartLoc, diag::err_pp_module_decl_in_header)
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index c341335ac680a..d53362942dfac 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -626,8 +626,7 @@ void Preprocessor::EnterMainSourceFile() {
if (!isPreprocessedModuleFile() && Input)
MainFileIsPreprocessedModuleFile =
clang::isPreprocessedModuleFile(*Input);
- if (Input && !MainFileIsPreprocessedModuleFile &&
- hasDeferredGMFInputs())
+ if (Input && !MainFileIsPreprocessedModuleFile && hasDeferredGMFInputs())
MainFileModuleUnitKind = scanInputForCXX20ModuleUnit(*Input);
auto Tracer = std::make_unique<NoTrivialPPDirectiveTracer>(*this);
DirTracer = Tracer.get();
@@ -659,8 +658,7 @@ void Preprocessor::EnterMainSourceFile() {
}
if (MainFileModuleUnitKind ==
ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment) {
- Predefines +=
- "# 1 \"<implicit-global-module-fragment>\" 1\nmodule;\n";
+ Predefines += "# 1 \"<implicit-global-module-fragment>\" 1\nmodule;\n";
HasSynthesizedGMF = true;
} else if (MainFileModuleUnitKind == ModuleUnitKind::NotModuleUnit) {
DeferredGMFInputs.clear();
@@ -708,9 +706,8 @@ void Preprocessor::EnterDeferredGMFInputs(SourceLocation IncludeLoc) {
// Synthesize the implicit input directives and enter them inside the global
// module fragment. Attribute the buffer to IncludeLoc so it is ordered within
// the translation unit.
- std::unique_ptr<llvm::MemoryBuffer> MB =
- llvm::MemoryBuffer::getMemBufferCopy(DeferredGMFInputs,
- "<gmf-command-line-inputs>");
+ std::unique_ptr<llvm::MemoryBuffer> MB = llvm::MemoryBuffer::getMemBufferCopy(
+ DeferredGMFInputs, "<gmf-command-line-inputs>");
DeferredGMFInputs.clear();
DeferredGMFInputsFileID =
SourceMgr.createFileID(std::move(MB), SrcMgr::C_User, 0, 0, IncludeLoc);
>From f81023138adec29f159c55952807f5ba55d0b45f Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 28 Jul 2026 19:45:06 -0700
Subject: [PATCH 3/4] Merge scan* functions
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/Lex/DependencyDirectivesScanner.cpp | 83 +++++++++++--------
.../Lex/DependencyDirectivesScannerTest.cpp | 11 +++
2 files changed, 58 insertions(+), 36 deletions(-)
diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp
index 54408ba30a130..097bf7e69e63a 100644
--- a/clang/lib/Lex/DependencyDirectivesScanner.cpp
+++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp
@@ -40,6 +40,16 @@ struct DirectiveWithTokens {
: Kind(Kind), NumTokens(NumTokens) {}
};
+enum class CXX20ModuleDirectiveKind {
+ None,
+ GlobalModuleFragment,
+ NamedModuleDeclaration,
+ ImportDeclaration,
+};
+
+static CXX20ModuleDirectiveKind
+scanFirstCXX20ModuleDirective(StringRef Source);
+
/// Does an efficient "scan" of the sources to detect the presence of
/// preprocessor (or module import) directives and collects the raw lexed tokens
/// for those directives so that the \p Lexer can "replay" them when the file is
@@ -84,8 +94,8 @@ struct Scanner {
/// \returns True on error.
bool scan(SmallVectorImpl<Directive> &Directives);
- friend bool clang::scanInputForCXX20ModulesUsage(StringRef Source);
- friend ModuleUnitKind clang::scanInputForCXX20ModuleUnit(StringRef Source);
+ friend CXX20ModuleDirectiveKind
+ scanFirstCXX20ModuleDirective(StringRef Source);
friend bool clang::isPreprocessedModuleFile(StringRef Source);
private:
@@ -1135,62 +1145,63 @@ static void skipUntilMaybeCXX20ModuleDirective(const char *&First,
}
}
-bool clang::scanInputForCXX20ModulesUsage(StringRef Source) {
+namespace {
+
+static CXX20ModuleDirectiveKind
+scanFirstCXX20ModuleDirective(StringRef Source) {
const char *First = Source.begin();
const char *const End = Source.end();
skipUntilMaybeCXX20ModuleDirective(First, End);
if (First == End)
- return false;
+ return CXX20ModuleDirectiveKind::None;
// Check if the next token can even be a module directive before creating a
// full lexer.
if (!(*First == 'i' || *First == 'e' || *First == 'm'))
- return false;
+ return CXX20ModuleDirectiveKind::None;
llvm::SmallVector<dependency_directives_scan::Token> Tokens;
Scanner S(StringRef(First, End - First), Tokens, nullptr, SourceLocation());
S.TheLexer.setParsingPreprocessorDirective(true);
- if (S.lexModule(First, End))
- return false;
- auto IsCXXNamedModuleDirective = [](const DirectiveWithTokens &D) {
- switch (D.Kind) {
- case dependency_directives_scan::cxx_module_decl:
- case dependency_directives_scan::cxx_import_decl:
- case dependency_directives_scan::cxx_export_module_decl:
- case dependency_directives_scan::cxx_export_import_decl:
- return true;
- default:
- return false;
- }
- };
- return llvm::any_of(S.DirsWithToks, IsCXXNamedModuleDirective);
-}
-
-ModuleUnitKind clang::scanInputForCXX20ModuleUnit(StringRef Source) {
- const char *First = Source.begin();
- const char *const End = Source.end();
- skipUntilMaybeCXX20ModuleDirective(First, End);
- if (First == End || !(*First == 'e' || *First == 'm'))
- return ModuleUnitKind::NotModuleUnit;
-
- llvm::SmallVector<dependency_directives_scan::Token> Tokens;
- Scanner S(StringRef(First, End - First), Tokens, nullptr, SourceLocation());
- S.TheLexer.setParsingPreprocessorDirective(true);
- if (S.lexModule(First, End) || S.DirsWithToks.size() != 1)
- return ModuleUnitKind::NotModuleUnit;
+ if (S.lexModule(First, End) || S.DirsWithToks.empty())
+ return CXX20ModuleDirectiveKind::None;
+ assert(S.DirsWithToks.size() == 1);
const DirectiveWithTokens &Directive = S.DirsWithToks.front();
switch (Directive.Kind) {
case dependency_directives_scan::cxx_module_decl:
assert(Directive.NumTokens >= 2);
return Tokens[1].is(tok::semi)
- ? ModuleUnitKind::HasGlobalModuleFragment
- : ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment;
+ ? CXX20ModuleDirectiveKind::GlobalModuleFragment
+ : CXX20ModuleDirectiveKind::NamedModuleDeclaration;
case dependency_directives_scan::cxx_export_module_decl:
- return ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment;
+ return CXX20ModuleDirectiveKind::NamedModuleDeclaration;
+ case dependency_directives_scan::cxx_import_decl:
+ case dependency_directives_scan::cxx_export_import_decl:
+ return CXX20ModuleDirectiveKind::ImportDeclaration;
default:
+ llvm_unreachable("unexpected C++20 module directive kind");
+ }
+}
+
+} // namespace
+
+bool clang::scanInputForCXX20ModulesUsage(StringRef Source) {
+ return scanFirstCXX20ModuleDirective(Source) !=
+ CXX20ModuleDirectiveKind::None;
+}
+
+ModuleUnitKind clang::scanInputForCXX20ModuleUnit(StringRef Source) {
+ switch (scanFirstCXX20ModuleDirective(Source)) {
+ case CXX20ModuleDirectiveKind::GlobalModuleFragment:
+ return ModuleUnitKind::HasGlobalModuleFragment;
+ case CXX20ModuleDirectiveKind::NamedModuleDeclaration:
+ return ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment;
+ case CXX20ModuleDirectiveKind::None:
+ case CXX20ModuleDirectiveKind::ImportDeclaration:
return ModuleUnitKind::NotModuleUnit;
}
+ llvm_unreachable("unexpected C++20 module directive kind");
}
bool clang::isPreprocessedModuleFile(StringRef Source) {
diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
index 18aff51c4bdef..556def02f87de 100644
--- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
+++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
@@ -1271,6 +1271,12 @@ TEST(MinimizeSourceToDependencyDirectivesTest, ScanningPreprocessedModuleFile) {
}
TEST(MinimizeSourceToDependencyDirectivesTest, CXX20ModuleUnitKind) {
+ EXPECT_FALSE(scanInputForCXX20ModulesUsage("int x;"));
+ EXPECT_TRUE(scanInputForCXX20ModulesUsage("module;"));
+ EXPECT_TRUE(scanInputForCXX20ModulesUsage("export module M;"));
+ EXPECT_TRUE(scanInputForCXX20ModulesUsage("import M;"));
+ EXPECT_TRUE(scanInputForCXX20ModulesUsage("export import M;"));
+
EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
scanInputForCXX20ModuleUnit("int x;"));
EXPECT_EQ(ModuleUnitKind::NotModuleUnit,
@@ -1308,6 +1314,11 @@ TEST(MinimizeSourceToDependencyDirectivesTest, CXX20ModuleUnitKind) {
scanInputForCXX20ModuleUnit("export module M any pp tokens;"));
EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
scanInputForCXX20ModuleUnit("#line 7\nexport module M;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit(
+ "# 7 \"input.cppm\"\nexport module M;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("#pragma once\nexport module M;"));
}
} // end anonymous namespace
>From b98e1883ceed135f23a313707918a66a9b7c7378 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 28 Jul 2026 19:51:03 -0700
Subject: [PATCH 4/4] Format
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/Lex/DependencyDirectivesScanner.cpp | 3 +--
clang/unittests/Lex/DependencyDirectivesScannerTest.cpp | 6 +++---
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp
index 097bf7e69e63a..76a2a0e698a8a 100644
--- a/clang/lib/Lex/DependencyDirectivesScanner.cpp
+++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp
@@ -47,8 +47,7 @@ enum class CXX20ModuleDirectiveKind {
ImportDeclaration,
};
-static CXX20ModuleDirectiveKind
-scanFirstCXX20ModuleDirective(StringRef Source);
+static CXX20ModuleDirectiveKind scanFirstCXX20ModuleDirective(StringRef Source);
/// Does an efficient "scan" of the sources to detect the presence of
/// preprocessor (or module import) directives and collects the raw lexed tokens
diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
index 556def02f87de..6e0a05a3ef7b9 100644
--- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
+++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
@@ -1314,9 +1314,9 @@ TEST(MinimizeSourceToDependencyDirectivesTest, CXX20ModuleUnitKind) {
scanInputForCXX20ModuleUnit("export module M any pp tokens;"));
EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
scanInputForCXX20ModuleUnit("#line 7\nexport module M;"));
- EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
- scanInputForCXX20ModuleUnit(
- "# 7 \"input.cppm\"\nexport module M;"));
+ EXPECT_EQ(
+ ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("# 7 \"input.cppm\"\nexport module M;"));
EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
scanInputForCXX20ModuleUnit("#pragma once\nexport module M;"));
}
More information about the cfe-commits
mailing list