https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/212533
>From b35d020d461086311c835e506af5deb7c3fff28a Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> 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 <[email protected]> --- .../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 <[email protected]> Date: Tue, 28 Jul 2026 09:35:08 -0700 Subject: [PATCH 2/4] [clang] format Signed-off-by: yronglin <[email protected]> --- 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 <[email protected]> Date: Tue, 28 Jul 2026 19:45:06 -0700 Subject: [PATCH 3/4] Merge scan* functions Signed-off-by: yronglin <[email protected]> --- 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 <[email protected]> Date: Tue, 28 Jul 2026 19:51:03 -0700 Subject: [PATCH 4/4] Format Signed-off-by: yronglin <[email protected]> --- 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;")); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
