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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
