[clang] 63e0da0 - [C++][Modules] Don't insert `#include` before GMF when `-include` used (#212533)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 25 20:28:24 PDT 2026
Author: Yihan Wang
Date: 2026-08-26T11:28:19+08:00
New Revision: 63e0da0e62f26d89e22ff5a3d9c4c370bd9bf1cc
URL: https://github.com/llvm/llvm-project/commit/63e0da0e62f26d89e22ff5a3d9c4c370bd9bf1cc
DIFF: https://github.com/llvm/llvm-project/commit/63e0da0e62f26d89e22ff5a3d9c4c370bd9bf1cc.diff
LOG: [C++][Modules] Don't insert `#include` before GMF when `-include` used (#212533)
Clang currently emits command-line implicit inputs such as `-imacros`,
`-include-pch`/`-include-pth`, and `-include` as part of the predefines
buffer. This causes them to be processed before the main source file.
For a C++20 module unit with a global module fragment:
```cpp
module;
export module M;
```
a force-included header containing declarations was effectively
processed as:
```cpp
#include "Header.h"
module;
export module M;
```
This places declarations before the global module fragment introducer
and makes the module unit ill-formed. The same problem affects named
module units without an explicit GMF, including CUDA compilations that
use force-included runtime headers.
[Compiler Explorer](https://godbolt.org/z/5Gzdra43c).
This change records implicit command-line inputs during preprocessor
initialization and determines their placement when entering the main
source file:
- Ordinary translation units retain the existing behavior by appending
the implicit inputs to Predefines.
- Module units beginning with `module;` process the inputs immediately
after the GMF introducer.
- Named module interface and implementation units without a GMF receive
a synthesized `module;`, followed by the implicit inputs.
---------
Signed-off-by: yronglin <yronglin777 at gmail.com>
Added:
clang/test/Modules/cxx20-force-include.cpp
Modified:
clang/include/clang/Lex/DependencyDirectivesScanner.h
clang/include/clang/Lex/Preprocessor.h
clang/lib/Frontend/InitPreprocessor.cpp
clang/lib/Frontend/PrintPreprocessedOutput.cpp
clang/lib/Lex/DependencyDirectivesScanner.cpp
clang/lib/Lex/PPDirectives.cpp
clang/lib/Lex/Preprocessor.cpp
clang/lib/Serialization/ASTReader.cpp
clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
Removed:
################################################################################
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 e752010dd2062..2967b6e6344c3 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 7cc481b595f36..784ff9951a25a 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 79477d70ff397..23da82769e2ae 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -1041,6 +1041,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..76a2a0e698a8a 100644
--- a/clang/lib/Lex/DependencyDirectivesScanner.cpp
+++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp
@@ -40,6 +40,15 @@ 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,7 +93,8 @@ struct Scanner {
/// \returns True on error.
bool scan(SmallVectorImpl<Directive> &Directives);
- friend bool clang::scanInputForCXX20ModulesUsage(StringRef Source);
+ friend CXX20ModuleDirectiveKind
+ scanFirstCXX20ModuleDirective(StringRef Source);
friend bool clang::isPreprocessedModuleFile(StringRef Source);
private:
@@ -1134,35 +1144,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);
+ 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)
+ ? CXX20ModuleDirectiveKind::GlobalModuleFragment
+ : CXX20ModuleDirectiveKind::NamedModuleDeclaration;
+ case dependency_directives_scan::cxx_export_module_decl:
+ 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/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index b431fb8e1d221..a1290d518f8af 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -2885,10 +2885,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();
@@ -4419,7 +4421,12 @@ 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);
}
@@ -4428,6 +4435,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 780f7936cd00c..84907adc5d744 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -591,6 +591,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)) {
@@ -623,6 +628,8 @@ 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));
@@ -632,6 +639,34 @@ 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>");
@@ -667,6 +702,20 @@ 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 200253c836939..a11e774d7bb41 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -924,6 +924,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];
@@ -948,16 +958,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..6e0a05a3ef7b9 100644
--- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
+++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp
@@ -1270,4 +1270,55 @@ TEST(MinimizeSourceToDependencyDirectivesTest, ScanningPreprocessedModuleFile) {
ASSERT_TRUE(clang::isPreprocessedModuleFile(Source));
}
+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,
+ 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;"));
+ EXPECT_EQ(
+ ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("# 7 \"input.cppm\"\nexport module M;"));
+ EXPECT_EQ(ModuleUnitKind::NamedModuleWithoutGlobalModuleFragment,
+ scanInputForCXX20ModuleUnit("#pragma once\nexport module M;"));
+}
+
} // end anonymous namespace
More information about the cfe-commits
mailing list