[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