From 2cce3d119d755a805b093a69b391d541d50beb78 Mon Sep 17 00:00:00 2001 From: yronglin Date: Sat, 17 Aug 2024 16:54:26 +0800 Subject: [PATCH 01/22] [Clang] Add peekNextPPToken, makes peek next token without side-effects Signed-off-by: yronglin --- clang/include/clang/Lex/Lexer.h | 10 ++++---- clang/include/clang/Lex/Preprocessor.h | 8 ++++++- clang/include/clang/Lex/TokenLexer.h | 7 +++--- clang/lib/Lex/Lexer.cpp | 21 +++++++++-------- clang/lib/Lex/PPMacroExpansion.cpp | 32 ++++++++++++-------------- clang/lib/Lex/TokenLexer.cpp | 10 ++++---- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h index bb65ae010cffa..a595cda1eaa77 100644 --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -124,7 +124,7 @@ class Lexer : public PreprocessorLexer { //===--------------------------------------------------------------------===// // Context that changes as the file is lexed. // NOTE: any state that mutates when in raw mode must have save/restore code - // in Lexer::isNextPPTokenLParen. + // in Lexer::peekNextPPToken. // BufferPtr - Current pointer into the buffer. This is the next character // to be lexed. @@ -642,10 +642,10 @@ class Lexer : public PreprocessorLexer { BufferPtr = TokEnd; } - /// isNextPPTokenLParen - Return 1 if the next unexpanded token will return a - /// tok::l_paren token, 0 if it is something else and 2 if there are no more - /// tokens in the buffer controlled by this lexer. - unsigned isNextPPTokenLParen(); + /// peekNextPPToken - Return std::nullopt if there are no more tokens in the + /// buffer controlled by this lexer, otherwise return the next unexpanded + /// token. + std::optional peekNextPPToken(); //===--------------------------------------------------------------------===// // Lexer character reading interfaces. diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index f2dfd3a349b8b..52f4765280582 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -2282,7 +2282,9 @@ class Preprocessor { /// Determine whether the next preprocessor token to be /// lexed is a '('. If so, consume the token and return true, if not, this /// method should have no observable side-effect on the lexed tokens. - bool isNextPPTokenLParen(); + bool isNextPPTokenLParen() { + return peekNextPPToken().value_or(Token{}).is(tok::l_paren); + } private: /// Identifiers used for SEH handling in Borland. These are only @@ -2661,6 +2663,10 @@ class Preprocessor { void removeCachedMacroExpandedTokensOfLastLexer(); + /// Peek the next token. If so, return the token, if not, this + /// method should have no observable side-effect on the lexed tokens. + std::optional peekNextPPToken(); + /// After reading "MACRO(", this method is invoked to read all of the formal /// arguments specified for the macro invocation. Returns null on error. MacroArgs *ReadMacroCallArgumentList(Token &MacroName, MacroInfo *MI, diff --git a/clang/include/clang/Lex/TokenLexer.h b/clang/include/clang/Lex/TokenLexer.h index 4d229ae610674..777b4e6266c71 100644 --- a/clang/include/clang/Lex/TokenLexer.h +++ b/clang/include/clang/Lex/TokenLexer.h @@ -139,10 +139,9 @@ class TokenLexer { void Init(const Token *TokArray, unsigned NumToks, bool DisableMacroExpansion, bool OwnsTokens, bool IsReinject); - /// If the next token lexed will pop this macro off the - /// expansion stack, return 2. If the next unexpanded token is a '(', return - /// 1, otherwise return 0. - unsigned isNextTokenLParen() const; + /// If the next token lexed will pop this macro off the expansion stack, + /// return std::nullopt, otherwise return the next unexpanded token. + std::optional peekNextPPToken() const; /// Lex and return a token from this macro stream. bool Lex(Token &Tok); diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 93200458f04b4..8e977eac8c983 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -3200,18 +3200,19 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) { return PP->HandleEndOfFile(Result, isPragmaLexer()); } -/// isNextPPTokenLParen - Return 1 if the next unexpanded token lexed from -/// the specified lexer will return a tok::l_paren token, 0 if it is something -/// else and 2 if there are no more tokens in the buffer controlled by the -/// lexer. -unsigned Lexer::isNextPPTokenLParen() { +/// peekNextPPToken - Return std::nullopt if there are no more tokens in the +/// buffer controlled by this lexer, otherwise return the next unexpanded +/// token. +std::optional Lexer::peekNextPPToken() { assert(!LexingRawMode && "How can we expand a macro from a skipping buffer?"); if (isDependencyDirectivesLexer()) { if (NextDepDirectiveTokenIndex == DepDirectives.front().Tokens.size()) - return 2; - return DepDirectives.front().Tokens[NextDepDirectiveTokenIndex].is( - tok::l_paren); + return std::nullopt; + Token Result; + (void)convertDependencyDirectiveToken( + DepDirectives.front().Tokens[NextDepDirectiveTokenIndex], Result); + return Result; } // Switch to 'skipping' mode. This will ensure that we can lex a token @@ -3240,8 +3241,8 @@ unsigned Lexer::isNextPPTokenLParen() { LexingRawMode = false; if (Tok.is(tok::eof)) - return 2; - return Tok.is(tok::l_paren); + return std::nullopt; + return Tok; } /// Find the end of a version control conflict marker. diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index 37ac1bf07e9c0..585990f60c98a 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -418,42 +418,40 @@ static bool isTrivialSingleTokenExpansion(const MacroInfo *MI, return !llvm::is_contained(MI->params(), II); } -/// isNextPPTokenLParen - Determine whether the next preprocessor token to be -/// lexed is a '('. If so, consume the token and return true, if not, this -/// method should have no observable side-effect on the lexed tokens. -bool Preprocessor::isNextPPTokenLParen() { +/// isNextPPTokenLParen - Peek the next token. If so, return the token, if not, +/// this method should have no observable side-effect on the lexed tokens. +std::optional Preprocessor::peekNextPPToken() { // Do some quick tests for rejection cases. - unsigned Val; + std::optional Val; if (CurLexer) - Val = CurLexer->isNextPPTokenLParen(); + Val = CurLexer->peekNextPPToken(); else - Val = CurTokenLexer->isNextTokenLParen(); + Val = CurTokenLexer->peekNextPPToken(); - if (Val == 2) { + if (!Val) { // We have run off the end. If it's a source file we don't // examine enclosing ones (C99 5.1.1.2p4). Otherwise walk up the // macro stack. if (CurPPLexer) - return false; + return std::nullopt; for (const IncludeStackInfo &Entry : llvm::reverse(IncludeMacroStack)) { if (Entry.TheLexer) - Val = Entry.TheLexer->isNextPPTokenLParen(); + Val = Entry.TheLexer->peekNextPPToken(); else - Val = Entry.TheTokenLexer->isNextTokenLParen(); + Val = Entry.TheTokenLexer->peekNextPPToken(); - if (Val != 2) + if (Val) break; // Ran off the end of a source file? if (Entry.ThePPLexer) - return false; + return std::nullopt; } } - // Okay, if we know that the token is a '(', lex it and return. Otherwise we - // have found something that isn't a '(' or we found the end of the - // translation unit. In either case, return false. - return Val == 1; + // Okay, we found the token and return. Otherwise we found the end of the + // translation unit. + return Val; } /// HandleMacroExpandedIdentifier - If an identifier token is read that is to be diff --git a/clang/lib/Lex/TokenLexer.cpp b/clang/lib/Lex/TokenLexer.cpp index 6e93416e01c0c..fbb8c4262d6da 100644 --- a/clang/lib/Lex/TokenLexer.cpp +++ b/clang/lib/Lex/TokenLexer.cpp @@ -921,13 +921,13 @@ bool TokenLexer::pasteTokens(Token &LHSTok, ArrayRef TokenStream, } /// isNextTokenLParen - If the next token lexed will pop this macro off the -/// expansion stack, return 2. If the next unexpanded token is a '(', return -/// 1, otherwise return 0. -unsigned TokenLexer::isNextTokenLParen() const { +/// expansion stack, return std::nullopt, otherwise return the next unexpanded +/// token. +std::optional TokenLexer::peekNextPPToken() const { // Out of tokens? if (isAtEnd()) - return 2; - return Tokens[CurTokenIdx].is(tok::l_paren); + return std::nullopt; + return Tokens[CurTokenIdx]; } /// isParsingPreprocessorDirective - Return true if we are in the middle of a From 04ddbf6499b27d4094efdb528825c85683c64ea6 Mon Sep 17 00:00:00 2001 From: yronglin Date: Mon, 26 May 2025 23:17:00 +0800 Subject: [PATCH 02/22] [C++20][Modules] Implement P1857R3 Modules Dependency Discovery Signed-off-by: yronglin --- .../AnnotateFunctions/AnnotateFunctions.cpp | 2 +- .../include/clang/Basic/DiagnosticLexKinds.td | 20 +- .../clang/Basic/DiagnosticParseKinds.td | 62 ++- clang/include/clang/Basic/IdentifierTable.h | 26 +- clang/include/clang/Basic/TokenKinds.def | 6 + .../include/clang/Frontend/CompilerInstance.h | 2 +- .../include/clang/Lex/CodeCompletionHandler.h | 8 + clang/include/clang/Lex/Preprocessor.h | 115 ++++-- clang/include/clang/Lex/Token.h | 7 + clang/include/clang/Parse/Parser.h | 2 + clang/include/clang/Sema/Sema.h | 6 +- clang/lib/Basic/IdentifierTable.cpp | 4 +- clang/lib/Frontend/CompilerInstance.cpp | 12 +- .../lib/Frontend/PrintPreprocessedOutput.cpp | 8 +- clang/lib/Lex/DependencyDirectivesScanner.cpp | 29 +- clang/lib/Lex/Lexer.cpp | 43 +- clang/lib/Lex/PPDirectives.cpp | 254 +++++++++++- clang/lib/Lex/Preprocessor.cpp | 382 +++++++++--------- clang/lib/Lex/TokenConcatenation.cpp | 8 +- clang/lib/Lex/TokenLexer.cpp | 4 +- clang/lib/Parse/Parser.cpp | 93 ++--- clang/lib/Sema/SemaModule.cpp | 75 ++-- .../DependencyScanning/ModuleDepCollector.cpp | 2 +- clang/test/CXX/basic/basic.link/p1.cpp | 145 +++++-- clang/test/CXX/basic/basic.link/p3.cpp | 67 +-- .../basic.scope/basic.scope.namespace/p2.cpp | 82 ++-- clang/test/CXX/lex/lex.pptoken/p3-2a.cpp | 15 +- .../CXX/module/basic/basic.def.odr/p6.cppm | 174 ++++++-- .../basic/basic.link/module-declaration.cpp | 64 +-- .../dcl.module/dcl.module.import/p1.cppm | 38 +- .../dcl.module/dcl.module.interface/p1.cppm | 39 +- .../test/CXX/module/dcl.dcl/dcl.module/p1.cpp | 44 +- .../test/CXX/module/dcl.dcl/dcl.module/p5.cpp | 65 ++- clang/test/CXX/module/module.interface/p1.cpp | 42 +- clang/test/CXX/module/module.interface/p2.cpp | 26 +- clang/test/CXX/module/module.unit/p8.cpp | 48 ++- clang/test/Driver/modules.cpp | 31 +- clang/test/Modules/pr121066.cpp | 3 +- ...-aware-new-delete-transparent-contexts.cpp | 20 +- .../ASTMatchers/ASTMatchersNodeTest.cpp | 3 +- .../Lex/DependencyDirectivesScannerTest.cpp | 10 +- clang/unittests/Lex/ModuleDeclStateTest.cpp | 2 +- 42 files changed, 1376 insertions(+), 712 deletions(-) diff --git a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp index d872020c2d8a3..22a3eb97f938b 100644 --- a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp +++ b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp @@ -65,7 +65,7 @@ class PragmaAnnotateHandler : public PragmaHandler { Token Tok; PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; if (HandledDecl) { DiagnosticsEngine &D = PP.getDiagnostics(); diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 723f5d48b4f5f..b2df3e7929434 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -466,6 +466,9 @@ def err_pp_embed_device_file : Error< def ext_pp_extra_tokens_at_eol : ExtWarn< "extra tokens at end of #%0 directive">, InGroup; +def ext_pp_extra_tokens_at_module_directive_eol + : ExtWarn<"extra tokens at end of '%0' directive">, + InGroup; def ext_pp_comma_expr : Extension<"comma operator in operand of #if">; def ext_pp_bad_vaargs_use : Extension< @@ -495,8 +498,8 @@ def warn_cxx98_compat_variadic_macro : Warning< InGroup, DefaultIgnore; def ext_named_variadic_macro : Extension< "named variadic macros are a GNU extension">, InGroup; -def err_embedded_directive : Error< - "embedding a #%0 directive within macro arguments is not supported">; +def err_embedded_directive : Error<"embedding a %select{#|C++ }0%1 directive " + "within macro arguments is not supported">; def ext_embedded_directive : Extension< "embedding a directive within macro arguments has undefined behavior">, InGroup>; @@ -983,6 +986,19 @@ def warn_module_conflict : Warning< InGroup; // C++20 modules +def err_pp_expected_module_name_or_header_name + : Error<"expected module name or header name">; +def err_pp_expected_semi_after_module_or_import + : Error<"'%select{module|import}0' directive must end with a ';' on the " + "same line">; +def err_module_decl_in_header + : Error<"module declaration must not come from an #include directive">; +def err_pp_cond_span_module_decl + : Error<"preprocessor conditionals shall not span a module declaration">; +def err_pp_module_expected_ident + : Error<"expected a module name after '%select{module|import}0'">; +def err_pp_unsupported_module_partition + : Error<"module partitions are only supported for C++20 onwards">; def err_header_import_semi_in_macro : Error< "semicolon terminating header import declaration cannot be produced " "by a macro">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 3aa36ad59d0b9..ac10fbfd67e17 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1760,38 +1760,36 @@ def ext_bit_int : Extension< } // end of Parse Issue category. let CategoryName = "Modules Issue" in { -def err_unexpected_module_decl : Error< - "module declaration can only appear at the top level">; -def err_module_expected_ident : Error< - "expected a module name after '%select{module|import}0'">; -def err_attribute_not_module_attr : Error< - "%0 attribute cannot be applied to a module">; -def err_keyword_not_module_attr : Error< - "%0 cannot be applied to a module">; -def err_attribute_not_import_attr : Error< - "%0 attribute cannot be applied to a module import">; -def err_keyword_not_import_attr : Error< - "%0 cannot be applied to a module import">; -def err_module_expected_semi : Error< - "expected ';' after module name">; -def err_global_module_introducer_not_at_start : Error< - "'module;' introducing a global module fragment can appear only " - "at the start of the translation unit">; -def err_module_fragment_exported : Error< - "%select{global|private}0 module fragment cannot be exported">; -def err_private_module_fragment_expected_semi : Error< - "expected ';' after private module fragment declaration">; -def err_missing_before_module_end : Error<"expected %0 at end of module">; -def err_unsupported_module_partition : Error< - "module partitions are only supported for C++20 onwards">; -def err_import_not_allowed_here : Error< - "imports must immediately follow the module declaration">; -def err_partition_import_outside_module : Error< - "module partition imports must be within a module purview">; -def err_import_in_wrong_fragment : Error< - "module%select{| partition}0 imports cannot be in the %select{global|private}1 module fragment">; - -def err_export_empty : Error<"export declaration cannot be empty">; + def err_unexpected_module_import_decl + : Error<"%select{module|import}0 declaration can only appear at the top " + "level">; + def err_module_expected_ident + : Error<"expected a module name after '%select{module|import}0'">; + def err_attribute_not_module_attr + : Error<"%0 attribute cannot be applied to a module">; + def err_keyword_not_module_attr : Error<"%0 cannot be applied to a module">; + def err_attribute_not_import_attr + : Error<"%0 attribute cannot be applied to a module import">; + def err_keyword_not_import_attr + : Error<"%0 cannot be applied to a module import">; + def err_module_expected_semi : Error<"expected ';' after module name">; + def err_global_module_introducer_not_at_start + : Error<"'module;' introducing a global module fragment can appear only " + "at the start of the translation unit">; + def err_module_fragment_exported + : Error<"%select{global|private}0 module fragment cannot be exported">; + def err_private_module_fragment_expected_semi + : Error<"expected ';' after private module fragment declaration">; + def err_missing_before_module_end : Error<"expected %0 at end of module">; + def err_import_not_allowed_here + : Error<"imports must immediately follow the module declaration">; + def err_partition_import_outside_module + : Error<"module partition imports must be within a module purview">; + def err_import_in_wrong_fragment + : Error<"module%select{| partition}0 imports cannot be in the " + "%select{global|private}1 module fragment">; + + def err_export_empty : Error<"export declaration cannot be empty">; } let CategoryName = "Generics Issue" in { diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h index 54540193cfcc0..add6c6ac629a1 100644 --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -179,6 +179,10 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo { LLVM_PREFERRED_TYPE(bool) unsigned IsModulesImport : 1; + // True if this is the 'module' contextual keyword. + LLVM_PREFERRED_TYPE(bool) + unsigned IsModulesDecl : 1; + // True if this is a mangled OpenMP variant name. LLVM_PREFERRED_TYPE(bool) unsigned IsMangledOpenMPVariantName : 1; @@ -215,8 +219,9 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo { IsCPPOperatorKeyword(false), NeedsHandleIdentifier(false), IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false), RevertedTokenID(false), OutOfDate(false), IsModulesImport(false), - IsMangledOpenMPVariantName(false), IsDeprecatedMacro(false), - IsRestrictExpansion(false), IsFinal(false), IsKeywordInCpp(false) {} + IsModulesDecl(false), IsMangledOpenMPVariantName(false), + IsDeprecatedMacro(false), IsRestrictExpansion(false), IsFinal(false), + IsKeywordInCpp(false) {} public: IdentifierInfo(const IdentifierInfo &) = delete; @@ -528,6 +533,18 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo { RecomputeNeedsHandleIdentifier(); } + /// Determine whether this is the contextual keyword \c module. + bool isModulesDeclaration() const { return IsModulesDecl; } + + /// Set whether this identifier is the contextual keyword \c module. + void setModulesDeclaration(bool I) { + IsModulesDecl = I; + if (I) + NeedsHandleIdentifier = true; + else + RecomputeNeedsHandleIdentifier(); + } + /// Determine whether this is the mangled name of an OpenMP variant. bool isMangledOpenMPVariantName() const { return IsMangledOpenMPVariantName; } @@ -745,10 +762,11 @@ class IdentifierTable { // contents. II->Entry = &Entry; - // If this is the 'import' contextual keyword, mark it as such. + // If this is the 'import' or 'module' contextual keyword, mark it as such. if (Name == "import") II->setModulesImport(true); - + else if (Name == "module") + II->setModulesDeclaration(true); return *II; } diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 94e72fea56a68..7750c84dbef78 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -133,6 +133,9 @@ PPKEYWORD(pragma) // C23 & C++26 #embed PPKEYWORD(embed) +// C++20 Module Directive +PPKEYWORD(module) + // GNU Extensions. PPKEYWORD(import) PPKEYWORD(include_next) @@ -1023,6 +1026,9 @@ ANNOTATION(module_include) ANNOTATION(module_begin) ANNOTATION(module_end) +// Annotations for C++, Clang and Objective-C named modules. +ANNOTATION(module_name) + // Annotation for a header_name token that has been looked up and transformed // into the name of a header unit. ANNOTATION(header_unit) diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h index 0ae490f0e8073..112d3b00160fd 100644 --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -863,7 +863,7 @@ class CompilerInstance : public ModuleLoader { /// load it. ModuleLoadResult findOrCompileModuleAndReadAST(StringRef ModuleName, SourceLocation ImportLoc, - SourceLocation ModuleNameLoc, + SourceRange ModuleNameRange, bool IsInclusionDirective); /// Creates a \c CompilerInstance for compiling a module. diff --git a/clang/include/clang/Lex/CodeCompletionHandler.h b/clang/include/clang/Lex/CodeCompletionHandler.h index bd3e05a36bb33..2ef29743415ae 100644 --- a/clang/include/clang/Lex/CodeCompletionHandler.h +++ b/clang/include/clang/Lex/CodeCompletionHandler.h @@ -13,12 +13,15 @@ #ifndef LLVM_CLANG_LEX_CODECOMPLETIONHANDLER_H #define LLVM_CLANG_LEX_CODECOMPLETIONHANDLER_H +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/SourceLocation.h" #include "llvm/ADT/StringRef.h" namespace clang { class IdentifierInfo; class MacroInfo; +using ModuleIdPath = ArrayRef; /// Callback handler that receives notifications when performing code /// completion within the preprocessor. @@ -70,6 +73,11 @@ class CodeCompletionHandler { /// file where we expect natural language, e.g., a comment, string, or /// \#error directive. virtual void CodeCompleteNaturalLanguage() { } + + /// Callback invoked when performing code completion inside the module name + /// part of an import directive. + virtual void CodeCompleteModuleImport(SourceLocation ImportLoc, + ModuleIdPath Path) {} }; } diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 52f4765280582..32dfcfea4256b 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -48,6 +48,7 @@ #include "llvm/Support/Allocator.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Registry.h" +#include "llvm/Support/TrailingObjects.h" #include #include #include @@ -82,6 +83,7 @@ class PreprocessorLexer; class PreprocessorOptions; class ScratchBuffer; class TargetInfo; +class ModuleNameLoc; namespace Builtin { class Context; @@ -332,8 +334,9 @@ class Preprocessor { /// lexed, if any. SourceLocation ModuleImportLoc; - /// The import path for named module that we're currently processing. - SmallVector NamedModuleImportPath; + /// The source location of the \c module contextual keyword we just + /// lexed, if any. + SourceLocation ModuleDeclLoc; llvm::DenseMap> CheckPoints; unsigned CheckPointCounter = 0; @@ -344,6 +347,21 @@ class Preprocessor { /// Whether the last token we lexed was an '@'. bool LastTokenWasAt = false; + /// Whether we're importing a standard C++20 named Modules. + bool ImportingCXXNamedModules = false; + + /// Whether we're declaring a standard C++20 named Modules. + bool DeclaringCXXNamedModules = false; + + struct ExportContextualKeywordInfo { + Token ExportTok; + bool TokAtPhysicalStartOfLine; + }; + + /// Whether the last token we lexed was an 'export' keyword. + std::optional LastTokenWasExportKeyword = + std::nullopt; + /// A position within a C++20 import-seq. class StdCXXImportSeq { public: @@ -547,12 +565,7 @@ class Preprocessor { reset(); } - void handleIdentifier(IdentifierInfo *Identifier) { - if (isModuleCandidate() && Identifier) - Name += Identifier->getName().str(); - else if (!isNamedModule()) - reset(); - } + void handleModuleName(ModuleNameLoc *Path); void handleColon() { if (isModuleCandidate()) @@ -561,13 +574,6 @@ class Preprocessor { reset(); } - void handlePeriod() { - if (isModuleCandidate()) - Name += "."; - else if (!isNamedModule()) - reset(); - } - void handleSemi() { if (!Name.empty() && isModuleCandidate()) { if (State == InterfaceCandidate) @@ -622,10 +628,6 @@ class Preprocessor { ModuleDeclSeq ModuleDeclState; - /// Whether the module import expects an identifier next. Otherwise, - /// it expects a '.' or ';'. - bool ModuleImportExpectsIdentifier = false; - /// The identifier and source location of the currently-active /// \#pragma clang arc_cf_code_audited begin. IdentifierLoc PragmaARCCFCodeAuditedInfo; @@ -1759,6 +1761,21 @@ class Preprocessor { /// Lex the parameters for an #embed directive, returns nullopt on error. std::optional LexEmbedParameters(Token &Current, bool ForHasEmbed); + bool LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, + SmallVectorImpl &Path, + bool AllowMacroExpansion = true); + void EnterModuleSuffixTokenStream(ArrayRef Toks); + void HandleCXXImportDirective(Token Import); + void HandleCXXModuleDirective(Token Module); + + /// Callback invoked when the lexer sees one of export, import or module token + /// at the start of a line. + /// + /// This consumes the import, module directive, modifies the + /// lexer/preprocessor state, and advances the lexer(s) so that the next token + /// read is the correct one. + bool HandleModuleContextualKeyword(Token &Result, + bool TokAtPhysicalStartOfLine); bool LexAfterModuleImport(Token &Result); void CollectPpImportSuffix(SmallVectorImpl &Toks); @@ -2344,7 +2361,7 @@ class Preprocessor { /// /// \return The location of the end of the directive (the terminating /// newline). - SourceLocation CheckEndOfDirective(const char *DirType, + SourceLocation CheckEndOfDirective(StringRef DirType, bool EnableMacros = false); /// Read and discard all tokens remaining on the current line until @@ -2426,11 +2443,12 @@ class Preprocessor { } /// If we're importing a standard C++20 Named Modules. - bool isInImportingCXXNamedModules() const { - // NamedModuleImportPath will be non-empty only if we're importing - // Standard C++ named modules. - return !NamedModuleImportPath.empty() && getLangOpts().CPlusPlusModules && - !IsAtImport; + bool isImportingCXXNamedModules() const { + return getLangOpts().CPlusPlusModules && ImportingCXXNamedModules; + } + + bool isDeclaringCXXNamedModules() const { + return getLangOpts().CPlusPlusModules && DeclaringCXXNamedModules; } /// Allocate a new MacroInfo object with the provided SourceLocation. @@ -3084,6 +3102,53 @@ struct EmbedAnnotationData { StringRef FileName; }; +/// Represents module name annotation data. +/// +/// module-name: +/// module-name-qualifier[opt] identifier +/// +/// partition-name: [C++20] +/// : module-name-qualifier[opt] identifier +/// +/// module-name-qualifier +/// module-name-qualifier[opt] identifier . +class ModuleNameLoc final + : llvm::TrailingObjects { + friend TrailingObjects; + unsigned NumIdentifierLocs; + + unsigned numTrailingObjects(OverloadToken) const { + return getNumIdentifierLocs(); + } + + ModuleNameLoc(ModuleIdPath Path) : NumIdentifierLocs(Path.size()) { + (void)llvm::copy(Path, getTrailingObjects()); + } + +public: + static std::string stringFromModuleIdPath(ModuleIdPath Path); + static ModuleNameLoc *Create(Preprocessor &PP, ModuleIdPath Path); + static Token CreateAnnotToken(Preprocessor &PP, ModuleIdPath Path); + unsigned getNumIdentifierLocs() const { return NumIdentifierLocs; } + ModuleIdPath getModuleIdPath() const { + return {getTrailingObjects(), getNumIdentifierLocs()}; + } + + SourceLocation getBeginLoc() const { + return getModuleIdPath().front().getLoc(); + } + SourceLocation getEndLoc() const { + auto &Last = getModuleIdPath().back(); + return Last.getLoc().getLocWithOffset( + Last.getIdentifierInfo()->getLength()); + } + SourceRange getRange() const { return {getBeginLoc(), getEndLoc()}; } + + std::string str() const; + void print(llvm::raw_ostream &OS) const; + void dump() const { print(llvm::errs()); } +}; + /// Registry of pragma handlers added by plugins using PragmaHandlerRegistry = llvm::Registry; diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h index 4f29fb7d11415..8e81207ddf8d7 100644 --- a/clang/include/clang/Lex/Token.h +++ b/clang/include/clang/Lex/Token.h @@ -231,6 +231,9 @@ class Token { PtrData = const_cast(Ptr); } + template T getAnnotationValueAs() const { + return static_cast(getAnnotationValue()); + } void *getAnnotationValue() const { assert(isAnnotation() && "Used AnnotVal on non-annotation token"); return PtrData; @@ -289,6 +292,10 @@ class Token { /// Return the ObjC keyword kind. tok::ObjCKeywordKind getObjCKeywordID() const; + /// Return true if we have an C++20 Modules contextual keyword(export, import + /// or module). + bool isModuleContextualKeyword(bool AllowExport = true) const; + bool isSimpleTypeSpecifier(const LangOptions &LangOpts) const; /// Return true if this token has trigraphs or escaped newlines in it. diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index c4bef4729fd36..a59a99bbac7c6 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1079,6 +1079,8 @@ class Parser : public CodeCompletionHandler { unsigned ArgumentIndex) override; void CodeCompleteIncludedFile(llvm::StringRef Dir, bool IsAngled) override; void CodeCompleteNaturalLanguage() override; + void CodeCompleteModuleImport(SourceLocation ImportLoc, + ModuleIdPath Path) override; ///@} diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6ce9ae588b637..5fa6b17c2df63 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -146,6 +146,7 @@ class MangleNumberingContext; typedef ArrayRef ModuleIdPath; class ModuleLoader; class MultiLevelTemplateArgumentList; +class ModuleNameLoc; struct NormalizedConstraint; class ObjCInterfaceDecl; class ObjCMethodDecl; @@ -9918,7 +9919,8 @@ class Sema final : public SemaBase { /// of a module interface or implementation. DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, ModuleDeclKind MDK, - ModuleIdPath Path, ModuleIdPath Partition, + ModuleNameLoc *PathLoc, + ModuleNameLoc *PartitionLoc, ModuleImportState &ImportState); /// The parser has processed a global-module-fragment declaration that begins @@ -9943,7 +9945,7 @@ class Sema final : public SemaBase { /// \param IsPartition If the name is for a partition. DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, ModuleIdPath Path, + SourceLocation ImportLoc, ModuleNameLoc *PathLoc, bool IsPartition = false); DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 099f2e8a18da5..fd9ed7721cb27 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -350,8 +350,9 @@ void IdentifierTable::AddKeywords(const LangOptions &LangOpts) { if (LangOpts.IEEE128) AddKeyword("__ieee128", tok::kw___float128, KEYALL, LangOpts, *this); - // Add the 'import' contextual keyword. + // Add the 'import' and 'module' contextual keyword. get("import").setModulesImport(true); + get("module").setModulesDeclaration(true); } /// Checks if the specified token kind represents a keyword in the @@ -483,6 +484,7 @@ tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const { CASE( 6, 'd', 'f', define); CASE( 6, 'i', 'n', ifndef); CASE( 6, 'i', 'p', import); + CASE(6, 'm', 'd', module); CASE( 6, 'p', 'a', pragma); CASE( 7, 'd', 'f', defined); diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index cc39049167687..b3b9c1abc3618 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -1866,8 +1866,8 @@ static ModuleSource selectModuleSource( } ModuleLoadResult CompilerInstance::findOrCompileModuleAndReadAST( - StringRef ModuleName, SourceLocation ImportLoc, - SourceLocation ModuleNameLoc, bool IsInclusionDirective) { + StringRef ModuleName, SourceLocation ImportLoc, SourceRange ModuleNameRange, + bool IsInclusionDirective) { // Search for a module with the given name. HeaderSearch &HS = PP->getHeaderSearchInfo(); Module *M = @@ -1884,10 +1884,11 @@ ModuleLoadResult CompilerInstance::findOrCompileModuleAndReadAST( std::string ModuleFilename; ModuleSource Source = selectModuleSource(M, ModuleName, ModuleFilename, BuiltModules, HS); + SourceLocation ModuleNameLoc = ModuleNameRange.getBegin(); if (Source == MS_ModuleNotFound) { // We can't find a module, error out here. getDiagnostics().Report(ModuleNameLoc, diag::err_module_not_found) - << ModuleName << SourceRange(ImportLoc, ModuleNameLoc); + << ModuleName << ModuleNameRange; return nullptr; } if (ModuleFilename.empty()) { @@ -2073,8 +2074,11 @@ CompilerInstance::loadModule(SourceLocation ImportLoc, MM.cacheModuleLoad(*Path[0].getIdentifierInfo(), Module); } else { + SourceLocation ModuleNameEndLoc = Path.back().getLoc().getLocWithOffset( + Path.back().getIdentifierInfo()->getLength()); ModuleLoadResult Result = findOrCompileModuleAndReadAST( - ModuleName, ImportLoc, ModuleNameLoc, IsInclusionDirective); + ModuleName, ImportLoc, SourceRange{ModuleNameLoc, ModuleNameEndLoc}, + IsInclusionDirective); if (!Result.isNormal()) return Result; if (!Result) diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp index 22ba4cee182af..79f0769b8bdf5 100644 --- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp +++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp @@ -759,7 +759,8 @@ void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok, if (Tok.is(tok::eof) || (Tok.isAnnotation() && !Tok.is(tok::annot_header_unit) && !Tok.is(tok::annot_module_begin) && !Tok.is(tok::annot_module_end) && - !Tok.is(tok::annot_repl_input_end) && !Tok.is(tok::annot_embed))) + !Tok.is(tok::annot_repl_input_end) && !Tok.is(tok::annot_embed) && + !Tok.is(tok::annot_module_name))) return; // EmittedDirectiveOnThisLine takes priority over RequireSameLine. @@ -979,6 +980,11 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok, *Callbacks->OS << static_cast(Byte); PrintComma = true; } + } else if (Tok.is(tok::annot_module_name)) { + Tok.getAnnotationValueAs()->print(*Callbacks->OS); + PP.Lex(Tok); + IsStartOfLine = true; + continue; } else if (Tok.isAnnotation()) { // Ignore annotation tokens created by pragmas - the pragmas themselves // will be reproduced in the preprocessed output. diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index 088d1cc96e3a2..245ce25b823d9 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -497,21 +497,32 @@ bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, const char *DirectiveLoc = Input.data() + CurDirToks.front().Offset; for (;;) { const dependency_directives_scan::Token &Tok = lexToken(First, End); - if (Tok.is(tok::eof)) + if (Tok.isOneOf(tok::eof, tok::eod)) return reportError( DirectiveLoc, diag::err_dep_source_scanner_missing_semi_after_at_import); if (Tok.is(tok::semi)) break; } + + // Skip extra tokens after semi in C++20 Modules directive. + bool IsCXXModules = Kind == DirectiveKind::cxx_export_import_decl || + Kind == DirectiveKind::cxx_export_module_decl || + Kind == DirectiveKind::cxx_import_decl || + Kind == DirectiveKind::cxx_module_decl; + if (IsCXXModules) + lexPPDirectiveBody(First, End); pushDirective(Kind); skipWhitespace(First, End); if (First == End) return false; - if (!isVerticalWhitespace(*First)) - return reportError( - DirectiveLoc, diag::err_dep_source_scanner_unexpected_tokens_at_import); - skipNewline(First, End); + if (!IsCXXModules) { + if (!isVerticalWhitespace(*First)) + return reportError( + DirectiveLoc, + diag::err_dep_source_scanner_unexpected_tokens_at_import); + skipNewline(First, End); + } return false; } @@ -846,8 +857,9 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { if (*First == '@') return lexAt(First, End); - if (*First == 'i' || *First == 'e' || *First == 'm') - return lexModule(First, End); + // if (!LangOpts.CPlusPlusModules && (*First == 'i' || *First == 'e' || *First + // == 'm')) + // return lexModule(First, End); if (*First == '_') { if (isNextIdentifierOrSkipLine("_Pragma", First, End)) @@ -860,7 +872,8 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { TheLexer.setParsingPreprocessorDirective(true); auto ScEx2 = make_scope_exit( [&]() { TheLexer.setParsingPreprocessorDirective(false); }); - + if (*First == 'i' || *First == 'e' || *First == 'm') + return lexModule(First, End); // Lex '#'. const dependency_directives_scan::Token &HashTok = lexToken(First, End); if (HashTok.is(tok::hashhash)) { diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 8e977eac8c983..df9e0f1350903 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -73,6 +73,19 @@ tok::ObjCKeywordKind Token::getObjCKeywordID() const { return specId ? specId->getObjCKeywordID() : tok::objc_not_keyword; } +/// Return true if we have an C++20 Modules contextual keyword(export, import +/// or module). +bool Token::isModuleContextualKeyword(bool AllowExport) const { + if (AllowExport && is(tok::kw_export)) + return true; + if (isOneOf(tok::kw_import, tok::kw_module)) + return true; + if (isNot(tok::identifier)) + return false; + const auto *II = getIdentifierInfo(); + return II->isModulesImport() || II->isModulesDeclaration(); +} + /// Determine whether the token kind starts a simple-type-specifier. bool Token::isSimpleTypeSpecifier(const LangOptions &LangOpts) const { switch (getKind()) { @@ -4024,11 +4037,17 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': /*'u'*/ case 'v': case 'w': case 'x': case 'y': case 'z': - case '_': + case '_': { // Notify MIOpt that we read a non-whitespace/non-comment token. MIOpt.ReadToken(); - return LexIdentifierContinue(Result, CurPtr); - + bool returnedToken = LexIdentifierContinue(Result, CurPtr); + if (returnedToken && Result.isModuleContextualKeyword() && + LangOpts.CPlusPlusModules && + PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine) && + !LexingRawMode && !Is_PragmaLexer) + goto HandleDirective; + return returnedToken; + } case '$': // $ in identifiers. if (LangOpts.DollarIdents) { if (!isLexingRawMode()) @@ -4511,8 +4530,8 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { HandleDirective: // We parsed a # character and it's the start of a preprocessing directive. - - FormTokenWithChars(Result, CurPtr, tok::hash); + if (!Result.isOneOf(tok::kw_import, tok::kw_module)) + FormTokenWithChars(Result, CurPtr, tok::hash); PP->HandleDirective(Result); if (PP->hadModuleLoaderFatalFailure()) @@ -4535,6 +4554,10 @@ const char *Lexer::convertDependencyDirectiveToken( Result.setKind(DDTok.Kind); Result.setFlag((Token::TokenFlags)DDTok.Flags); Result.setLength(DDTok.Length); + if (Result.is(tok::raw_identifier)) + Result.setRawIdentifierData(TokPtr); + else if (Result.isLiteral()) + Result.setLiteralData(TokPtr); BufferPtr = TokPtr + DDTok.Length; return TokPtr; } @@ -4589,15 +4612,19 @@ bool Lexer::LexDependencyDirectiveToken(Token &Result) { Result.setRawIdentifierData(TokPtr); if (!isLexingRawMode()) { const IdentifierInfo *II = PP->LookUpIdentifierInfo(Result); + if (Result.isModuleContextualKeyword() && + PP->HandleModuleContextualKeyword(Result, Result.isAtStartOfLine())) { + PP->HandleDirective(Result); + return false; + } if (II->isHandleIdentifierCase()) return PP->HandleIdentifier(Result); } return true; } - if (Result.isLiteral()) { - Result.setLiteralData(TokPtr); + if (Result.isLiteral()) return true; - } + if (Result.is(tok::colon)) { // Convert consecutive colons to 'tok::coloncolon'. if (*BufferPtr == ':') { diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 04a30f66fb736..f95b9d582c5cc 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -439,7 +439,7 @@ void Preprocessor::ReadMacroName(Token &MacroNameTok, MacroUse isDefineUndef, /// true, then we consider macros that expand to zero tokens as being ok. /// /// Returns the location of the end of the directive. -SourceLocation Preprocessor::CheckEndOfDirective(const char *DirType, +SourceLocation Preprocessor::CheckEndOfDirective(StringRef DirType, bool EnableMacros) { Token Tmp; // Lex unexpanded tokens for most directives: macros might expand to zero @@ -466,7 +466,14 @@ SourceLocation Preprocessor::CheckEndOfDirective(const char *DirType, if ((LangOpts.GNUMode || LangOpts.C99 || LangOpts.CPlusPlus) && !CurTokenLexer) Hint = FixItHint::CreateInsertion(Tmp.getLocation(),"//"); - Diag(Tmp, diag::ext_pp_extra_tokens_at_eol) << DirType << Hint; + + unsigned DiagID = diag::ext_pp_extra_tokens_at_eol; + // C++20 import or module directive has no '#' prefix. + if (getLangOpts().CPlusPlusModules && + (DirType == "import" || DirType == "module")) + DiagID = diag::ext_pp_extra_tokens_at_module_directive_eol; + + Diag(Tmp, DiagID) << DirType << Hint; return DiscardUntilEndOfDirective().getEnd(); } @@ -1245,9 +1252,14 @@ void Preprocessor::HandleDirective(Token &Result) { // Save the '#' token in case we need to return it later. Token SavedHash = Result; + bool IsCXX20ImportOrModuleDirective = + getLangOpts().CPlusPlusModules && + Result.isModuleContextualKeyword(/*AllowExport=*/false); + // Read the next token, the directive flavor. This isn't expanded due to // C99 6.10.3p8. - LexUnexpandedToken(Result); + if (!IsCXX20ImportOrModuleDirective) + LexUnexpandedToken(Result); // C99 6.10.3p11: Is this preprocessor directive in macro invocation? e.g.: // #define A(x) #x @@ -1266,7 +1278,9 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp___include_macros: case tok::pp_pragma: case tok::pp_embed: - Diag(Result, diag::err_embedded_directive) << II->getName(); + case tok::pp_module: + Diag(Result, diag::err_embedded_directive) + << IsCXX20ImportOrModuleDirective << II->getName(); Diag(*ArgMacro, diag::note_macro_expansion_here) << ArgMacro->getIdentifierInfo(); DiscardUntilEndOfDirective(); @@ -1357,9 +1371,12 @@ void Preprocessor::HandleDirective(Token &Result) { // C99 6.10.6 - Pragma Directive. case tok::pp_pragma: return HandlePragmaDirective({PIK_HashPragma, SavedHash.getLocation()}); - + case tok::pp_module: + return HandleCXXModuleDirective(Result); // GNU Extensions. case tok::pp_import: + if (IsCXX20ImportOrModuleDirective) + return HandleCXXImportDirective(Result); return HandleImportDirective(SavedHash.getLocation(), Result); case tok::pp_include_next: return HandleIncludeNextDirective(SavedHash.getLocation(), Result); @@ -4065,3 +4082,230 @@ void Preprocessor::HandleEmbedDirective(SourceLocation HashLoc, Token &EmbedTok, StringRef(static_cast(Mem), OriginalFilename.size()); HandleEmbedDirectiveImpl(HashLoc, *Params, BinaryContents, FilenameToGo); } + +void Preprocessor::HandleCXXImportDirective(Token ImportTok) { + assert(getLangOpts().CPlusPlusModules && ImportTok.is(tok::kw_import)); + llvm::SaveAndRestore SaveImportingCXXModules( + this->ImportingCXXNamedModules); + ImportingCXXNamedModules = true; + + if (LastTokenWasExportKeyword) + LastTokenWasExportKeyword.reset(); + + Token Tok; + if (LexHeaderName(Tok)) { + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); + return; + } + + SourceLocation UseLoc = ImportTok.getLocation(); + SmallVector DirToks{ImportTok}; + SmallVector Path; + bool ImportingHeader = false; + bool IsPartition = false; + std::string FlatName; + switch (Tok.getKind()) { + case tok::header_name: + ImportingHeader = true; + DirToks.push_back(Tok); + break; + case tok::colon: + IsPartition = true; + DirToks.push_back(Tok); + UseLoc = Tok.getLocation(); + Lex(Tok); + [[fallthrough]]; + case tok::identifier: { + if (LexModuleNameContinue(Tok, UseLoc, Path)) { + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); + return; + } + + bool IsValid = + (IsPartition && ModuleDeclState.isNamedModule()) || !IsPartition; + if (Callbacks && IsValid) { + if (IsPartition && ModuleDeclState.isNamedModule()) { + FlatName += ModuleDeclState.getPrimaryName(); + FlatName += ":"; + } + + FlatName += ModuleNameLoc::stringFromModuleIdPath(Path); + SourceLocation StartLoc = IsPartition ? UseLoc : Path[0].getLoc(); + IdentifierLoc FlatNameLoc(StartLoc, getIdentifierInfo(FlatName)); + + // We don't/shouldn't load the standard c++20 modules when preprocessing. + // so the imported module is nullptr. + Callbacks->moduleImport(ImportTok.getLocation(), + ModuleIdPath(FlatNameLoc), + /*Imported=*/nullptr); + } + DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); + DirToks.push_back(Tok); + break; + } + default: + Diag(ImportTok, diag::err_pp_expected_module_name_or_header_name); + break; + } + + // Consume the pp-import-suffix and expand any macros in it now, if we're not + // at the semicolon already. + if (!DirToks.back().isOneOf(tok::semi, tok::eod)) + CollectPpImportSuffix(DirToks); + + // This is not a pp-import after all. + if (DirToks.back().isNot(tok::semi)) { + Diag(DirToks.back(), diag::err_pp_expected_semi_after_module_or_import) + << /*IsImport*/ true + << FixItHint::CreateInsertion(DirToks.back().getLocation(), + tok::getPunctuatorSpelling(tok::semi)); + return; + } + + if (DirToks.back().isNot(tok::eod)) + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); + else + DirToks.pop_back(); + + // C++2a [cpp.module]p1: + // The ';' preprocessing-token terminating a pp-import shall not have + // been produced by macro replacement. + SourceLocation SemiLoc = DirToks.back().getLocation(); + if (SemiLoc.isMacroID()) + Diag(SemiLoc, diag::err_header_import_semi_in_macro); + + if (ImportingHeader) { + auto Action = HandleHeaderIncludeOrImport( + /*HashLoc*/ SourceLocation(), ImportTok, Tok, SemiLoc); + switch (Action.Kind) { + case ImportAction::None: + break; + + case ImportAction::ModuleBegin: + // Let the parser know we're textually entering the module. + DirToks.emplace_back(); + DirToks.back().startToken(); + DirToks.back().setKind(tok::annot_module_begin); + DirToks.back().setLocation(SemiLoc); + DirToks.back().setAnnotationEndLoc(SemiLoc); + DirToks.back().setAnnotationValue(Action.ModuleForHeader); + [[fallthrough]]; + + case ImportAction::ModuleImport: + case ImportAction::HeaderUnitImport: + case ImportAction::SkippedModuleImport: + // We chose to import (or textually enter) the file. Convert the + // header-name token into a header unit annotation token. + DirToks[1].setKind(tok::annot_header_unit); + DirToks[1].setAnnotationEndLoc(DirToks[0].getLocation()); + DirToks[1].setAnnotationValue(Action.ModuleForHeader); + // FIXME: Call the moduleImport callback? + break; + case ImportAction::Failure: + assert(TheModuleLoader.HadFatalFailure && + "This should be an early exit only to a fatal error"); + CurLexer->cutOffLexing(); + return; + } + } + + EnterModuleSuffixTokenStream(DirToks); +} + +void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { + assert(getLangOpts().CPlusPlusModules && ModuleTok.is(tok::kw_module)); + SourceLocation StartLoc = ModuleTok.getLocation(); + if (LastTokenWasExportKeyword) { + StartLoc = LastTokenWasExportKeyword->ExportTok.getLocation(); + LastTokenWasExportKeyword.reset(); + } + bool IsInHeaderInclusion = !IncludeMacroStack.empty(); + bool IsInConditionBlock = CurPPLexer->getConditionalStackDepth() != 0; + + Token Tok; + SourceLocation UseLoc = ModuleTok.getLocation(); + SmallVector DirToks{ModuleTok}; + SmallVector Path, Partition; + LexUnexpandedToken(Tok); + + switch (Tok.getKind()) { + // Global Module Fragment. + case tok::semi: + DirToks.push_back(Tok); + break; + case tok::colon: + DirToks.push_back(Tok); + LexUnexpandedToken(Tok); + if (Tok.isNot(tok::kw_private)) { + Diag(DirToks.back().getLocation(), diag::err_pp_module_expected_ident) + << /*IsImport=*/false + << FixItHint::CreateReplacement( + {Tok.getLocation(), Tok.getEndLoc()}, + tok::getKeywordSpelling(tok::kw_private)); + return; + } + DirToks.push_back(Tok); + break; + case tok::identifier: { + if (LexModuleNameContinue(Tok, UseLoc, Path)) { + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + return; + } + + DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); + + // C++20 [cpp.module]p + // The pp-tokens, if any, of a pp-module shall be of the form: + // pp-module-name pp-module-partition[opt] pp-tokens[opt] + if (Tok.is(tok::colon)) { + DirToks.push_back(Tok); + LexUnexpandedToken(Tok); + if (LexModuleNameContinue(Tok, UseLoc, Partition)) { + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + return; + } + DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Partition)); + } + DirToks.push_back(Tok); + break; + } + default: + break; + ; + } + + // Consume the pp-import-suffix and expand any macros in it now, if we're not + // at the semicolon already. + SourceLocation End = DirToks.back().getLocation(); + if (!DirToks.back().isOneOf(tok::semi, tok::eod)) { + CollectPpImportSuffix(DirToks); + End = DirToks.back().getLocation(); + } + + // This is not a pp-import after all. + if (DirToks.back().isNot(tok::semi)) { + Diag(DirToks.back(), diag::err_pp_expected_semi_after_module_or_import) + << /*IsImport*/ false + << FixItHint::CreateInsertion(DirToks.back().getLocation(), + tok::getPunctuatorSpelling(tok::semi)); + return; + } + + if (DirToks.back().isNot(tok::eod)) + End = CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + else + End = DirToks.pop_back_val().getLocation(); + if (IsInHeaderInclusion) + Diag(StartLoc, diag::err_module_decl_in_header) + << SourceRange(StartLoc, End); + + if (IsInConditionBlock) + Diag(StartLoc, diag::err_pp_cond_span_module_decl) + << SourceRange(StartLoc, End); + + EnterModuleSuffixTokenStream(DirToks); +} diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 21fc7a2b6fae2..f1d94561f365c 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -859,17 +859,21 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { if (((LastTokenWasAt && II.isModulesImport()) || Identifier.is(tok::kw_import)) && !InMacroArgs && !DisableMacroExpansion && - (getLangOpts().Modules || getLangOpts().DebuggerSupport) && CurLexerCallback != CLK_CachingLexer) { ModuleImportLoc = Identifier.getLocation(); - NamedModuleImportPath.clear(); IsAtImport = true; - ModuleImportExpectsIdentifier = true; CurLexerCallback = CLK_LexAfterModuleImport; } return true; } +void Preprocessor::ModuleDeclSeq::handleModuleName(ModuleNameLoc *Path) { + if (isModuleCandidate() && Path) + Name += Path->str(); + else if (!isNamedModule()) + reset(); +} + void Preprocessor::Lex(Token &Result) { ++LexLevel; @@ -913,6 +917,7 @@ void Preprocessor::Lex(Token &Result) { // This token is injected to represent the translation of '#include "a.h"' // into "import a.h;". Mimic the notional ';'. case tok::annot_module_include: + case tok::annot_repl_input_end: case tok::semi: TrackGMFState.handleSemi(); StdCXXImportSeqState.handleSemi(); @@ -930,31 +935,21 @@ void Preprocessor::Lex(Token &Result) { case tok::colon: ModuleDeclState.handleColon(); break; - case tok::period: - ModuleDeclState.handlePeriod(); + case tok::kw_import: + if (StdCXXImportSeqState.atTopLevel()) { + TrackGMFState.handleImport(StdCXXImportSeqState.afterTopLevelSeq()); + StdCXXImportSeqState.handleImport(); + } break; - case tok::identifier: - // Check "import" and "module" when there is no open bracket. The two - // identifiers are not meaningful with open brackets. + case tok::kw_module: if (StdCXXImportSeqState.atTopLevel()) { - if (Result.getIdentifierInfo()->isModulesImport()) { - TrackGMFState.handleImport(StdCXXImportSeqState.afterTopLevelSeq()); - StdCXXImportSeqState.handleImport(); - if (StdCXXImportSeqState.afterImportSeq()) { - ModuleImportLoc = Result.getLocation(); - NamedModuleImportPath.clear(); - IsAtImport = false; - ModuleImportExpectsIdentifier = true; - CurLexerCallback = CLK_LexAfterModuleImport; - } - break; - } else if (Result.getIdentifierInfo() == getIdentifierInfo("module")) { - TrackGMFState.handleModule(StdCXXImportSeqState.afterTopLevelSeq()); - ModuleDeclState.handleModule(); - break; - } + TrackGMFState.handleModule(StdCXXImportSeqState.afterTopLevelSeq()); + ModuleDeclState.handleModule(); } - ModuleDeclState.handleIdentifier(Result.getIdentifierInfo()); + break; + case tok::annot_module_name: + ModuleDeclState.handleModuleName( + Result.getAnnotationValueAs()); if (ModuleDeclState.isModuleCandidate()) break; [[fallthrough]]; @@ -972,6 +967,9 @@ void Preprocessor::Lex(Token &Result) { } LastTokenWasAt = Result.is(tok::at); + if (Result.isNot(tok::kw_export)) + LastTokenWasExportKeyword.reset(); + --LexLevel; if ((LexLevel == 0 || PreprocessToken) && @@ -1094,43 +1092,171 @@ bool Preprocessor::LexHeaderName(Token &FilenameTok, bool AllowMacroExpansion) { return false; } +ModuleNameLoc *ModuleNameLoc::Create(Preprocessor &PP, ModuleIdPath Path) { + assert(!Path.empty() && "expect at least one identifier in a module name"); + void *Mem = PP.getPreprocessorAllocator().Allocate( + totalSizeToAlloc(Path.size()), alignof(ModuleNameLoc)); + return new (Mem) ModuleNameLoc(Path); +} + +Token ModuleNameLoc::CreateAnnotToken(Preprocessor &PP, ModuleIdPath Path) { + auto *NameLoc = Create(PP, Path); + Token ModuleNameTok; + ModuleNameTok.startToken(); + ModuleNameTok.setKind(tok::annot_module_name); + ModuleNameTok.setAnnotationRange(NameLoc->getRange()); + ModuleNameTok.setAnnotationValue(static_cast(NameLoc)); + return ModuleNameTok; +} + +// We represent the primary and partition names as 'Paths' which are sections +// of the hierarchical access path for a clang module. However for C++20 +// the periods in a name are just another character, and we will need to +// flatten them into a string. +std::string ModuleNameLoc::stringFromModuleIdPath(ModuleIdPath Path) { + std::string Name; + if (Path.empty()) + return Name; + + for (auto &Piece : Path) { + assert(Piece.getIdentifierInfo() && Piece.getLoc().isValid()); + if (!Name.empty()) + Name += "."; + Name += Piece.getIdentifierInfo()->getName(); + } + return Name; +} + +std::string ModuleNameLoc::str() const { + return stringFromModuleIdPath(getModuleIdPath()); +} + +void ModuleNameLoc::print(llvm::raw_ostream &OS) const { OS << str(); } + +bool Preprocessor::LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, + SmallVectorImpl &Path, + bool AllowMacroExpansion) { + auto ConsumeToken = [&]() { + return AllowMacroExpansion ? Lex(Tok) : LexUnexpandedToken(Tok); + }; + + while (true) { + if (Tok.isNot(tok::identifier)) { + if (Tok.is(tok::code_completion)) { + CurLexer->cutOffLexing(); + Tok.setKind(tok::eof); + this->getCodeCompletionHandler()->CodeCompleteModuleImport(UseLoc, + Path); + } + Diag(Tok.getLocation(), diag::err_pp_expected_module_name) + << Path.empty(); + return true; + } + + // Record this part of the module path. + Path.emplace_back(Tok.getLocation(), Tok.getIdentifierInfo()); + ConsumeToken(); + + if (Tok.isNot(tok::period)) + return false; + + ConsumeToken(); + } +} + +/// P1857R3: Modules Dependency Discovery +/// +/// At the start of phase 4 an import or module token is treated as starting a +/// directive and are converted to their respective keywords iff: +/// • After skipping horizontal whitespace are +/// • at the start of a logical line, or +/// • preceded by an 'export' at the start of the logical line. +/// • Are followed by an identifier pp token (before macro expansion), or +/// • <, ", or : (but not ::) pp tokens for 'import', or +/// • ; for 'module' +/// Otherwise the token is treated as an identifier. +bool Preprocessor::HandleModuleContextualKeyword( + Token &Result, bool TokAtPhysicalStartOfLine) { + if (!getLangOpts().CPlusPlusModules || !Result.isModuleContextualKeyword()) + return false; + + if (Result.is(tok::kw_export)) { + LastTokenWasExportKeyword = {Result, TokAtPhysicalStartOfLine}; + return false; + } + + if (LastTokenWasExportKeyword) { + auto Export = *LastTokenWasExportKeyword; + // The export keyword was not at the start of line, it's not a + // directive-introducing token. + if (!Export.TokAtPhysicalStartOfLine) + return false; + // [cpp.pre]/1.4 + // export // not a preprocessing directive + // import foo; // preprocessing directive (ill-formed at phase + // 7) + if (TokAtPhysicalStartOfLine) + return false; + } else if (!TokAtPhysicalStartOfLine) + return false; + + bool SavedParsingPreprocessorDirective = + CurPPLexer->ParsingPreprocessorDirective; + CurPPLexer->ParsingPreprocessorDirective = true; + // Peek next token. + auto NextTok = peekNextPPToken().value_or(Token{}); + CurPPLexer->ParsingPreprocessorDirective = SavedParsingPreprocessorDirective; + if (Result.getIdentifierInfo()->isModulesImport() && + NextTok.isOneOf(tok::raw_identifier, tok::less, tok::string_literal, + tok::colon)) { + Result.setKind(tok::kw_import); + ModuleImportLoc = Result.getLocation(); + IsAtImport = false; + return true; + } + if (Result.getIdentifierInfo()->isModulesDeclaration() && + NextTok.isOneOf(tok::raw_identifier, tok::colon, tok::semi)) { + Result.setKind(tok::kw_module); + ModuleDeclLoc = Result.getLocation(); + return true; + } + + // Ok, it's an identifier. + return false; +} + /// Collect the tokens of a C++20 pp-import-suffix. void Preprocessor::CollectPpImportSuffix(SmallVectorImpl &Toks) { // FIXME: For error recovery, consider recognizing attribute syntax here // and terminating / diagnosing a missing semicolon if we find anything // else? (Can we leave that to the parser?) - unsigned BracketDepth = 0; while (true) { Toks.emplace_back(); Lex(Toks.back()); switch (Toks.back().getKind()) { - case tok::l_paren: case tok::l_square: case tok::l_brace: - ++BracketDepth; - break; - - case tok::r_paren: case tok::r_square: case tok::r_brace: - if (BracketDepth == 0) - return; - --BracketDepth; - break; - case tok::semi: - if (BracketDepth == 0) - return; - break; - + case tok::eod: case tok::eof: return; - default: break; } } } +// Allocate a holding buffer for a sequence of tokens and introduce it into +// the token stream. +void Preprocessor::EnterModuleSuffixTokenStream(ArrayRef Toks) { + if (Toks.empty()) + return; + auto ToksCopy = std::make_unique(Toks.size()); + std::copy(Toks.begin(), Toks.end(), ToksCopy.get()); + EnterTokenStream(std::move(ToksCopy), Toks.size(), + /*DisableMacroExpansion*/ true, /*IsReinject*/ false); +} -/// Lex a token following the 'import' contextual keyword. +// Lex a token following the 'import' contextual keyword. /// /// pp-import: [C++20] /// import header-name pp-import-suffix[opt] ; @@ -1153,186 +1279,42 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) { // Figure out what kind of lexer we actually have. recomputeCurLexerKind(); - // Lex the next token. The header-name lexing rules are used at the start of - // a pp-import. - // - // For now, we only support header-name imports in C++20 mode. - // FIXME: Should we allow this in all language modes that support an import - // declaration as an extension? - if (NamedModuleImportPath.empty() && getLangOpts().CPlusPlusModules) { - if (LexHeaderName(Result)) - return true; - - if (Result.is(tok::colon) && ModuleDeclState.isNamedModule()) { - std::string Name = ModuleDeclState.getPrimaryName().str(); - Name += ":"; - NamedModuleImportPath.emplace_back(Result.getLocation(), - getIdentifierInfo(Name)); - CurLexerCallback = CLK_LexAfterModuleImport; - return true; - } - } else { - Lex(Result); - } - - // Allocate a holding buffer for a sequence of tokens and introduce it into - // the token stream. - auto EnterTokens = [this](ArrayRef Toks) { - auto ToksCopy = std::make_unique(Toks.size()); - std::copy(Toks.begin(), Toks.end(), ToksCopy.get()); - EnterTokenStream(std::move(ToksCopy), Toks.size(), - /*DisableMacroExpansion*/ true, /*IsReinject*/ false); - }; - - bool ImportingHeader = Result.is(tok::header_name); - // Check for a header-name. SmallVector Suffix; - if (ImportingHeader) { - // Enter the header-name token into the token stream; a Lex action cannot - // both return a token and cache tokens (doing so would corrupt the token - // cache if the call to Lex comes from CachingLex / PeekAhead). - Suffix.push_back(Result); - - // Consume the pp-import-suffix and expand any macros in it now. We'll add - // it back into the token stream later. - CollectPpImportSuffix(Suffix); - if (Suffix.back().isNot(tok::semi)) { - // This is not a pp-import after all. - EnterTokens(Suffix); - return false; - } - - // C++2a [cpp.module]p1: - // The ';' preprocessing-token terminating a pp-import shall not have - // been produced by macro replacement. - SourceLocation SemiLoc = Suffix.back().getLocation(); - if (SemiLoc.isMacroID()) - Diag(SemiLoc, diag::err_header_import_semi_in_macro); - - // Reconstitute the import token. - Token ImportTok; - ImportTok.startToken(); - ImportTok.setKind(tok::kw_import); - ImportTok.setLocation(ModuleImportLoc); - ImportTok.setIdentifierInfo(getIdentifierInfo("import")); - ImportTok.setLength(6); - - auto Action = HandleHeaderIncludeOrImport( - /*HashLoc*/ SourceLocation(), ImportTok, Suffix.front(), SemiLoc); - switch (Action.Kind) { - case ImportAction::None: - break; - - case ImportAction::ModuleBegin: - // Let the parser know we're textually entering the module. - Suffix.emplace_back(); - Suffix.back().startToken(); - Suffix.back().setKind(tok::annot_module_begin); - Suffix.back().setLocation(SemiLoc); - Suffix.back().setAnnotationEndLoc(SemiLoc); - Suffix.back().setAnnotationValue(Action.ModuleForHeader); - [[fallthrough]]; - - case ImportAction::ModuleImport: - case ImportAction::HeaderUnitImport: - case ImportAction::SkippedModuleImport: - // We chose to import (or textually enter) the file. Convert the - // header-name token into a header unit annotation token. - Suffix[0].setKind(tok::annot_header_unit); - Suffix[0].setAnnotationEndLoc(Suffix[0].getLocation()); - Suffix[0].setAnnotationValue(Action.ModuleForHeader); - // FIXME: Call the moduleImport callback? - break; - case ImportAction::Failure: - assert(TheModuleLoader.HadFatalFailure && - "This should be an early exit only to a fatal error"); - Result.setKind(tok::eof); - CurLexer->cutOffLexing(); - EnterTokens(Suffix); - return true; - } - - EnterTokens(Suffix); + SmallVector Path; + Lex(Result); + if (LexModuleNameContinue(Result, ModuleImportLoc, Path)) return false; - } - // The token sequence - // - // import identifier (. identifier)* - // - // indicates a module import directive. We already saw the 'import' - // contextual keyword, so now we're looking for the identifiers. - if (ModuleImportExpectsIdentifier && Result.getKind() == tok::identifier) { - // We expected to see an identifier here, and we did; continue handling - // identifiers. - NamedModuleImportPath.emplace_back(Result.getLocation(), - Result.getIdentifierInfo()); - ModuleImportExpectsIdentifier = false; - CurLexerCallback = CLK_LexAfterModuleImport; - return true; - } - - // If we're expecting a '.' or a ';', and we got a '.', then wait until we - // see the next identifier. (We can also see a '[[' that begins an - // attribute-specifier-seq here under the Standard C++ Modules.) - if (!ModuleImportExpectsIdentifier && Result.getKind() == tok::period) { - ModuleImportExpectsIdentifier = true; - CurLexerCallback = CLK_LexAfterModuleImport; - return true; - } - - // If we didn't recognize a module name at all, this is not a (valid) import. - if (NamedModuleImportPath.empty() || Result.is(tok::eof)) - return true; + Suffix.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); + Suffix.push_back(Result); // Consume the pp-import-suffix and expand any macros in it now, if we're not // at the semicolon already. - SourceLocation SemiLoc = Result.getLocation(); - if (Result.isNot(tok::semi)) { - Suffix.push_back(Result); - CollectPpImportSuffix(Suffix); + SourceLocation SemiLoc = Suffix.back().getLocation(); + if (Suffix.back().isNot(tok::semi)) { + if (Result.isNot(tok::eof)) + CollectPpImportSuffix(Suffix); if (Suffix.back().isNot(tok::semi)) { // This is not an import after all. - EnterTokens(Suffix); + EnterModuleSuffixTokenStream(Suffix); return false; } SemiLoc = Suffix.back().getLocation(); } - // Under the standard C++ Modules, the dot is just part of the module name, - // and not a real hierarchy separator. Flatten such module names now. - // - // FIXME: Is this the right level to be performing this transformation? - std::string FlatModuleName; - if (getLangOpts().CPlusPlusModules) { - for (auto &Piece : NamedModuleImportPath) { - // If the FlatModuleName ends with colon, it implies it is a partition. - if (!FlatModuleName.empty() && FlatModuleName.back() != ':') - FlatModuleName += "."; - FlatModuleName += Piece.getIdentifierInfo()->getName(); - } - SourceLocation FirstPathLoc = NamedModuleImportPath[0].getLoc(); - NamedModuleImportPath.clear(); - NamedModuleImportPath.emplace_back(FirstPathLoc, - getIdentifierInfo(FlatModuleName)); - } - Module *Imported = nullptr; - // We don't/shouldn't load the standard c++20 modules when preprocessing. - if (getLangOpts().Modules && !isInImportingCXXNamedModules()) { - Imported = TheModuleLoader.loadModule(ModuleImportLoc, - NamedModuleImportPath, - Module::Hidden, + if (getLangOpts().Modules) { + Imported = TheModuleLoader.loadModule(ModuleImportLoc, Path, Module::Hidden, /*IsInclusionDirective=*/false); if (Imported) makeModuleVisible(Imported, SemiLoc); } if (Callbacks) - Callbacks->moduleImport(ModuleImportLoc, NamedModuleImportPath, Imported); + Callbacks->moduleImport(ModuleImportLoc, Path, Imported); if (!Suffix.empty()) { - EnterTokens(Suffix); + EnterModuleSuffixTokenStream(Suffix); return false; } return true; diff --git a/clang/lib/Lex/TokenConcatenation.cpp b/clang/lib/Lex/TokenConcatenation.cpp index 05f4203bd722b..f94caee24dc11 100644 --- a/clang/lib/Lex/TokenConcatenation.cpp +++ b/clang/lib/Lex/TokenConcatenation.cpp @@ -161,7 +161,8 @@ bool TokenConcatenation::AvoidConcat(const Token &PrevPrevTok, const Token &PrevTok, const Token &Tok) const { // No space is required between header unit name in quote and semi. - if (PrevTok.is(tok::annot_header_unit) && Tok.is(tok::semi)) + if (PrevTok.isOneOf(tok::annot_header_unit, tok::annot_module_name) && + Tok.is(tok::semi)) return false; // Conservatively assume that every annotation token that has a printable @@ -197,11 +198,12 @@ bool TokenConcatenation::AvoidConcat(const Token &PrevPrevTok, if (Tok.isAnnotation()) { // Modules annotation can show up when generated automatically for includes. assert(Tok.isOneOf(tok::annot_module_include, tok::annot_module_begin, - tok::annot_module_end, tok::annot_embed) && + tok::annot_module_end, tok::annot_embed, + tok::annot_module_name) && "unexpected annotation in AvoidConcat"); ConcatInfo = 0; - if (Tok.is(tok::annot_embed)) + if (Tok.isOneOf(tok::annot_embed, tok::annot_module_name)) return true; } diff --git a/clang/lib/Lex/TokenLexer.cpp b/clang/lib/Lex/TokenLexer.cpp index fbb8c4262d6da..968654405203d 100644 --- a/clang/lib/Lex/TokenLexer.cpp +++ b/clang/lib/Lex/TokenLexer.cpp @@ -699,7 +699,9 @@ bool TokenLexer::Lex(Token &Tok) { HasLeadingSpace = false; // Handle recursive expansion! - if (!Tok.isAnnotation() && Tok.getIdentifierInfo() != nullptr) { + if (!Tok.isAnnotation() && Tok.getIdentifierInfo() != nullptr && + (!PP.getLangOpts().CPlusPlusModules || + !Tok.isModuleContextualKeyword())) { // Change the kind of this identifier to the appropriate token kind, e.g. // turning "for" into a keyword. IdentifierInfo *II = Tok.getIdentifierInfo(); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index db65c05cc114a..508a4ce0e5ff8 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -517,8 +517,6 @@ void Parser::Initialize() { Ident_trivially_relocatable_if_eligible = nullptr; Ident_replaceable_if_eligible = nullptr; Ident_GNU_final = nullptr; - Ident_import = nullptr; - Ident_module = nullptr; Ident_super = &PP.getIdentifierTable().get("super"); @@ -574,11 +572,6 @@ void Parser::Initialize() { PP.SetPoisonReason(Ident_AbnormalTermination,diag::err_seh___finally_block); } - if (getLangOpts().CPlusPlusModules) { - Ident_import = PP.getIdentifierInfo("import"); - Ident_module = PP.getIdentifierInfo("module"); - } - Actions.Initialize(); // Prime the lexer look-ahead. @@ -626,24 +619,8 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, switch (NextToken().getKind()) { case tok::kw_module: goto module_decl; - - // Note: no need to handle kw_import here. We only form kw_import under - // the Standard C++ Modules, and in that case 'export import' is parsed as - // an export-declaration containing an import-declaration. - - // Recognize context-sensitive C++20 'export module' and 'export import' - // declarations. - case tok::identifier: { - IdentifierInfo *II = NextToken().getIdentifierInfo(); - if ((II == Ident_module || II == Ident_import) && - GetLookAheadToken(2).isNot(tok::coloncolon)) { - if (II == Ident_module) - goto module_decl; - else - goto import_decl; - } - break; - } + case tok::kw_import: + goto import_decl; default: break; @@ -713,21 +690,6 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, //else don't tell Sema that we ended parsing: more input might come. return true; - case tok::identifier: - // C++2a [basic.link]p3: - // A token sequence beginning with 'export[opt] module' or - // 'export[opt] import' and not immediately followed by '::' - // is never interpreted as the declaration of a top-level-declaration. - if ((Tok.getIdentifierInfo() == Ident_module || - Tok.getIdentifierInfo() == Ident_import) && - NextToken().isNot(tok::coloncolon)) { - if (Tok.getIdentifierInfo() == Ident_module) - goto module_decl; - else - goto import_decl; - } - break; - default: break; } @@ -920,8 +882,9 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs, case tok::kw_import: { Sema::ModuleImportState IS = Sema::ModuleImportState::NotACXX20Module; if (getLangOpts().CPlusPlusModules) { - llvm_unreachable("not expecting a c++20 import here"); - ProhibitAttributes(Attrs); + Diag(Tok, diag::err_unexpected_module_import_decl) << /*IsImport*/ true; + SkipUntil(tok::semi); + return nullptr; } SingleDecl = ParseModuleImport(SourceLocation(), IS); } break; @@ -1013,7 +976,7 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs, return nullptr; case tok::kw_module: - Diag(Tok, diag::err_unexpected_module_decl); + Diag(Tok, diag::err_unexpected_module_import_decl) << /*IsImport*/ false; SkipUntil(tok::semi); return nullptr; @@ -2231,6 +2194,11 @@ void Parser::CodeCompleteNaturalLanguage() { Actions.CodeCompletion().CodeCompleteNaturalLanguage(); } +void Parser::CodeCompleteModuleImport(SourceLocation ImportLoc, + ModuleIdPath Path) { + Actions.CodeCompletion().CodeCompleteModuleImport(ImportLoc, Path); +} + bool Parser::ParseMicrosoftIfExistsCondition(IfExistsCondition& Result) { assert((Tok.is(tok::kw___if_exists) || Tok.is(tok::kw___if_not_exists)) && "Expected '__if_exists' or '__if_not_exists'"); @@ -2384,20 +2352,20 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { return Actions.ActOnPrivateModuleFragmentDecl(ModuleLoc, PrivateLoc); } - SmallVector Path; - if (ParseModuleName(ModuleLoc, Path, /*IsImport*/ false)) + ModuleNameLoc *Path = nullptr; + if (Tok.isNot(tok::annot_module_name)) return nullptr; + Path = Tok.getAnnotationValueAs(); + ConsumeAnnotationToken(); // Parse the optional module-partition. - SmallVector Partition; + ModuleNameLoc *Partition = nullptr; if (Tok.is(tok::colon)) { - SourceLocation ColonLoc = ConsumeToken(); - if (!getLangOpts().CPlusPlusModules) - Diag(ColonLoc, diag::err_unsupported_module_partition) - << SourceRange(ColonLoc, Partition.back().getLoc()); - // Recover by ignoring the partition name. - else if (ParseModuleName(ModuleLoc, Partition, /*IsImport*/ false)) + ConsumeToken(); + if (Tok.isNot(tok::annot_module_name)) return nullptr; + Partition = Tok.getAnnotationValueAs(); + ConsumeAnnotationToken(); } // We don't support any module attributes yet; just parse them and diagnose. @@ -2428,7 +2396,7 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, SourceLocation ImportLoc = ConsumeToken(); // For C++20 modules, we can have "name" or ":Partition name" as valid input. - SmallVector Path; + ModuleNameLoc *Path = nullptr; bool IsPartition = false; Module *HeaderUnit = nullptr; if (Tok.is(tok::header_name)) { @@ -2441,18 +2409,17 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, HeaderUnit = reinterpret_cast(Tok.getAnnotationValue()); ConsumeAnnotationToken(); } else if (Tok.is(tok::colon)) { - SourceLocation ColonLoc = ConsumeToken(); - if (!getLangOpts().CPlusPlusModules) - Diag(ColonLoc, diag::err_unsupported_module_partition) - << SourceRange(ColonLoc, Path.back().getLoc()); - // Recover by leaving partition empty. - else if (ParseModuleName(ColonLoc, Path, /*IsImport*/ true)) + ConsumeToken(); + if (Tok.isNot(tok::annot_module_name)) return nullptr; - else - IsPartition = true; + IsPartition = true; + Path = Tok.getAnnotationValueAs(); + ConsumeAnnotationToken(); } else { - if (ParseModuleName(ImportLoc, Path, /*IsImport*/ true)) + if (Tok.isNot(tok::annot_module_name)) return nullptr; + Path = Tok.getAnnotationValueAs(); + ConsumeAnnotationToken(); } ParsedAttributes Attrs(AttrFactory); @@ -2520,7 +2487,7 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, if (HeaderUnit) Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, HeaderUnit); - else if (!Path.empty()) + else if (Path) Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path, IsPartition); if (Import.isInvalid()) diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 6c4df0aa35af5..98c60bab409be 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -57,23 +57,6 @@ static void checkModuleImportContext(Sema &S, Module *M, } } -// We represent the primary and partition names as 'Paths' which are sections -// of the hierarchical access path for a clang module. However for C++20 -// the periods in a name are just another character, and we will need to -// flatten them into a string. -static std::string stringFromPath(ModuleIdPath Path) { - std::string Name; - if (Path.empty()) - return Name; - - for (auto &Piece : Path) { - if (!Name.empty()) - Name += "."; - Name += Piece.getIdentifierInfo()->getName(); - } - return Name; -} - /// Helper function for makeTransitiveImportsVisible to decide whether /// the \param Imported module unit is in the same module with the \param /// CurrentModule. @@ -255,10 +238,12 @@ static bool DiagReservedModuleName(Sema &S, const IdentifierInfo *II, llvm_unreachable("fell off a fully covered switch"); } -Sema::DeclGroupPtrTy -Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, - ModuleDeclKind MDK, ModuleIdPath Path, - ModuleIdPath Partition, ModuleImportState &ImportState) { +Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, + SourceLocation ModuleLoc, + ModuleDeclKind MDK, + ModuleNameLoc *PathLoc, + ModuleNameLoc *PartitionLoc, + ModuleImportState &ImportState) { assert(getLangOpts().CPlusPlusModules && "should only have module decl in standard C++ modules"); @@ -268,8 +253,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // module state; ImportState = ModuleImportState::NotACXX20Module; - bool IsPartition = !Partition.empty(); - if (IsPartition) + if (PartitionLoc) switch (MDK) { case ModuleDeclKind::Implementation: MDK = ModuleDeclKind::PartitionImplementation; @@ -351,17 +335,18 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // Test the first part of the path to see if it's std[0-9]+ but allow the // name in a system header. - StringRef FirstComponentName = Path[0].getIdentifierInfo()->getName(); - if (!getSourceManager().isInSystemHeader(Path[0].getLoc()) && + StringRef FirstComponentName = + PathLoc->getModuleIdPath()[0].getIdentifierInfo()->getName(); + if (!getSourceManager().isInSystemHeader(PathLoc->getBeginLoc()) && (FirstComponentName == "std" || (FirstComponentName.starts_with("std") && llvm::all_of(FirstComponentName.drop_front(3), &llvm::isDigit)))) - Diag(Path[0].getLoc(), diag::warn_reserved_module_name) - << Path[0].getIdentifierInfo(); + Diag(PathLoc->getBeginLoc(), diag::warn_reserved_module_name) + << PathLoc->getModuleIdPath()[0].getIdentifierInfo(); // Then test all of the components in the path to see if any of them are // using another kind of reserved or invalid identifier. - for (auto Part : Path) { + for (auto Part : PathLoc->getModuleIdPath()) { if (DiagReservedModuleName(*this, Part.getIdentifierInfo(), Part.getLoc())) return nullptr; } @@ -369,20 +354,17 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // Flatten the dots in a module name. Unlike Clang's hierarchical module map // modules, the dots here are just another character that can appear in a // module name. - std::string ModuleName = stringFromPath(Path); - if (IsPartition) { + std::string ModuleName = PathLoc->str(); + if (PartitionLoc) { ModuleName += ":"; - ModuleName += stringFromPath(Partition); + ModuleName += PartitionLoc->str(); } // If a module name was explicitly specified on the command line, it must be // correct. if (!getLangOpts().CurrentModule.empty() && getLangOpts().CurrentModule != ModuleName) { - Diag(Path.front().getLoc(), diag::err_current_module_name_mismatch) - << SourceRange(Path.front().getLoc(), IsPartition - ? Partition.back().getLoc() - : Path.back().getLoc()) - << getLangOpts().CurrentModule; + Diag(PathLoc->getBeginLoc(), diag::err_current_module_name_mismatch) + << PathLoc->getRange() << getLangOpts().CurrentModule; return nullptr; } const_cast(getLangOpts()).CurrentModule = ModuleName; @@ -396,7 +378,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // We can't have parsed or imported a definition of this module or parsed a // module map defining it already. if (auto *M = Map.findOrLoadModule(ModuleName)) { - Diag(Path[0].getLoc(), diag::err_module_redefinition) << ModuleName; + Diag(PathLoc->getBeginLoc(), diag::err_module_redefinition) << ModuleName; if (M->DefinitionLoc.isValid()) Diag(M->DefinitionLoc, diag::note_prev_module_definition); else if (OptionalFileEntryRef FE = M->getASTFile()) @@ -419,7 +401,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // keyword nor a module-partition implicitly imports the primary // module interface unit of the module as if by a module-import- // declaration. - IdentifierLoc ModuleNameLoc(Path[0].getLoc(), + IdentifierLoc ModuleNameLoc(PathLoc->getBeginLoc(), PP.getIdentifierInfo(ModuleName)); // The module loader will assume we're trying to import the module that @@ -492,7 +474,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // Make the import decl for the interface in the impl module. ImportDecl *Import = ImportDecl::Create(Context, CurContext, ModuleLoc, - Interface, Path[0].getLoc()); + Interface, PathLoc->getBeginLoc()); CurContext->addDecl(Import); // Sequence initialization of the imported module before that of the current @@ -574,8 +556,8 @@ Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc, DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, ModuleIdPath Path, - bool IsPartition) { + SourceLocation ImportLoc, + ModuleNameLoc *PathLoc, bool IsPartition) { assert((!IsPartition || getLangOpts().CPlusPlusModules) && "partition seen in non-C++20 code?"); @@ -584,6 +566,7 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, IdentifierLoc ModuleNameLoc; std::string ModuleName; + ModuleIdPath Path; if (IsPartition) { // We already checked that we are in a module purview in the parser. assert(!ModuleScopes.empty() && "in a module purview, but no module?"); @@ -592,15 +575,17 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, // otherwise, the name of the importing named module. ModuleName = NamedMod->getPrimaryModuleInterfaceName().str(); ModuleName += ":"; - ModuleName += stringFromPath(Path); + ModuleName += PathLoc->str(); ModuleNameLoc = - IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); + IdentifierLoc(PathLoc->getBeginLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); } else if (getLangOpts().CPlusPlusModules) { - ModuleName = stringFromPath(Path); + ModuleName = PathLoc->str(); ModuleNameLoc = - IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); + IdentifierLoc(PathLoc->getBeginLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); + } else { + Path = PathLoc->getModuleIdPath(); } // Diagnose self-import before attempting a load. diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 774bfd725d0a5..f772cefe6111b 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -654,7 +654,7 @@ void ModuleDepCollectorPP::InclusionDirective( void ModuleDepCollectorPP::moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) { - if (MDC.ScanInstance.getPreprocessor().isInImportingCXXNamedModules()) { + if (MDC.ScanInstance.getPreprocessor().isImportingCXXNamedModules()) { P1689ModuleInfo RequiredModule; RequiredModule.ModuleName = Path[0].getIdentifierInfo()->getName().str(); RequiredModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule; diff --git a/clang/test/CXX/basic/basic.link/p1.cpp b/clang/test/CXX/basic/basic.link/p1.cpp index c6a119aa7f47c..b8f37d152b0e0 100644 --- a/clang/test/CXX/basic/basic.link/p1.cpp +++ b/clang/test/CXX/basic/basic.link/p1.cpp @@ -1,57 +1,130 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_PRIVATE_FRAG %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_PRIVATE_FRAG %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL %s -// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s -// RUN: %clang_cc1 -std=c++2a -verify -DEXPORT_FRAGS %s - -#ifndef NO_GLOBAL_FRAG -#ifdef EXPORT_FRAGS -export // expected-error {{global module fragment cannot be exported}} -#endif -module; -#ifdef NO_MODULE_DECL -// expected-error@-2 {{missing 'module' declaration at end of global module fragment introduced here}} -#endif -#endif +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++2a -verify %t/M.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFrag.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoModuleDecl.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoPrivateFrag.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoModuleDeclAndNoPrivateFrag.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoPrivateFrag.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoModuleDecl.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/NoGlobalFragAndNoModuleDeclAndNoPrivateFrag.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/ExportFrags.cppm +//--- M.cppm +module; extern int a; // #a1 +export module Foo; + +int a; // expected-error {{declaration of 'a' in module Foo follows declaration in the global module}} + // expected-note@#a1 {{previous decl}} +extern int b; + +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} +module :private; // #priv-frag +int b; // ok +module :private; // expected-error {{private module fragment redefined}} + // expected-note@#priv-frag {{previous definition is here}} -#ifndef NO_MODULE_DECL +//--- NoGlobalFrag.cppm + +extern int a; // #a1 export module Foo; -#ifdef NO_GLOBAL_FRAG + // expected-error@-2 {{module declaration must occur at the start of the translation unit}} // expected-note@1 {{add 'module;' to the start of the file to introduce a global module fragment}} -#endif // expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}} // expected-note@#a1 {{previous decl}} -#endif +int a; // #a2 +extern int b; +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} +module :private; // #priv-frag +int b; // ok +module :private; // expected-error {{private module fragment redefined}} +// expected-note@#priv-frag {{previous definition is here}} + +//--- NoModuleDecl.cppm +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} +extern int a; // #a1 +int a; // #a2 +extern int b; +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} +module :private; // expected-error {{private module fragment declaration with no preceding module declaration}} +int b; // ok + +//--- NoPrivateFrag.cppm +module; +extern int a; // #a1 +export module Foo; + +// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}} +// expected-note@#a1 {{previous decl}} int a; // #a2 extern int b; module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} +int b; // ok + -#ifndef NO_PRIVATE_FRAG -#ifdef EXPORT_FRAGS -export // expected-error {{private module fragment cannot be exported}} -#endif +//--- NoModuleDeclAndNoPrivateFrag.cppm +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} +extern int a; // #a1 +int a; // #a2 +extern int b; + +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} + +int b; // ok + +//--- NoGlobalFragAndNoPrivateFrag.cppm +extern int a; // #a1 +export module Foo; // expected-error {{module declaration must occur at the start of the translation unit}} +// expected-note@1 {{add 'module;' to the start of the file to introduce a global module fragment}} + +// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}} +// expected-note@#a1 {{previous decl}} + +int a; // #a2 +extern int b; + +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} + +int b; // ok + +//--- NoGlobalFragAndNoModuleDecl.cppm +extern int a; // #a1 +int a; // #a2 +extern int b; +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} module :private; // #priv-frag -#ifdef NO_MODULE_DECL -// expected-error@-2 {{private module fragment declaration with no preceding module declaration}} -#endif -#endif +// expected-error@-1 {{private module fragment declaration with no preceding module declaration}} +int b; // ok + + +//--- NoGlobalFragAndNoModuleDeclAndNoPrivateFrag.cppm +extern int a; // #a1 +int a; // #a2 +extern int b; +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} int b; // ok +//--- ExportFrags.cppm +export module; // expected-error {{global module fragment cannot be exported}} +extern int a; // #a1 +export module Foo; +// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}} +// expected-note@#a1 {{previous decl}} + +int a; // #a2 +extern int b; + +module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}} + +module :private; // #priv-frag -#ifndef NO_PRIVATE_FRAG -#ifndef NO_MODULE_DECL +int b; // ok module :private; // expected-error {{private module fragment redefined}} -// expected-note@#priv-frag {{previous definition is here}} -#endif -#endif + // expected-note@#priv-frag {{previous definition is here}} diff --git a/clang/test/CXX/basic/basic.link/p3.cpp b/clang/test/CXX/basic/basic.link/p3.cpp index 01202264d2591..1cf2b750a8a81 100644 --- a/clang/test/CXX/basic/basic.link/p3.cpp +++ b/clang/test/CXX/basic/basic.link/p3.cpp @@ -1,35 +1,18 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s -// RUN: %clang_cc1 -std=c++2a -verify %s -DIMPORT_ERROR=1 -// RUN: %clang_cc1 -std=c++2a -verify %s -DIMPORT_ERROR=2 +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++2a -verify %t/M.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/ImportError1.cppm +// RUN: %clang_cc1 -std=c++2a -verify %t/ImportError2.cppm + +//--- M.cppm module; -#if IMPORT_ERROR != 2 struct import { struct inner {}; }; -#endif struct module { struct inner {}; }; - constexpr int n = 123; export module m; // #1 - -// Import errors are fatal, so we test them in isolation. -#if IMPORT_ERROR == 1 -import x = {}; // expected-error {{expected ';' after module name}} - // expected-error@-1 {{module 'x' not found}} - -#elif IMPORT_ERROR == 2 -struct X; -template struct import; -template<> struct import { - static X y; -}; - -// This is not valid because the 'import ' is a pp-import, even though it -// grammatically can't possibly be an import declaration. -struct X {} import::y; // expected-error {{'n' file not found}} - -#else module y = {}; // expected-error {{multiple module declarations}} expected-error 2{{}} // expected-note@#1 {{previous module declaration}} @@ -40,8 +23,8 @@ import::inner xi = {}; module::inner yi = {}; namespace N { - module a; - import b; + module a; // expected-error {{module declaration can only appear at the top level}} + import b; // expected-error {{import declaration can only appear at the top level}} } extern "C++" module cxxm; @@ -51,4 +34,34 @@ template module module_var_template; // This is a variable named 'import' that shadows the type 'import' above. struct X {} import; -#endif + +//--- ImportError1.cppm +module; + +struct import { struct inner {}; }; +struct module { struct inner {}; }; + +constexpr int n = 123; + +export module m; // #1 + +import x = {}; // expected-error {{expected ';' after module name}} + // expected-error@-1 {{module 'x' not found}} + +//--- ImportError2.cppm +// expected-no-diagnostics +module; + +struct module { struct inner {}; }; + +constexpr int n = 123; + +export module m; // #1 + +struct X; +template struct import; +template<> struct import { + static X y; +}; + +struct X {} import::y; diff --git a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp index d70eb7de22c6a..a57919f48afdd 100644 --- a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp +++ b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp @@ -1,14 +1,16 @@ // RUN: rm -rf %t -// RUN: mkdir -p %t -// RUN: echo '#ifndef FOO_H' > %t/foo.h -// RUN: echo '#define FOO_H' >> %t/foo.h -// RUN: echo 'extern int in_header;' >> %t/foo.h -// RUN: echo '#endif' >> %t/foo.h -// RUN: %clang_cc1 -std=c++2a -I%t -emit-module-interface -DINTERFACE %s -o %t.pcm -// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm -DIMPLEMENTATION %s -verify -fno-modules-error-recovery -// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %s -verify -fno-modules-error-recovery - -#ifdef INTERFACE +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++2a -I%t -emit-module-interface %t/interface.cppm -o %t.pcm +// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %t/implA.cppm -verify -fno-modules-error-recovery +// RUN: %clang_cc1 -std=c++2a -I%t -fmodule-file=A=%t.pcm %t/implB.cppm -verify -fno-modules-error-recovery + +//--- foo.h +#ifndef FOO_H +#define FOO_H +extern int in_header; +#endif + +//--- interface.cppm module; #include "foo.h" // FIXME: The following need to be moved to a header file. The global module @@ -22,11 +24,9 @@ static int internal; module :private; int not_exported_private; static int internal_private; -#else -#ifdef IMPLEMENTATION +//--- implA.cppm module; -#endif void test_early() { in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}} @@ -46,11 +46,7 @@ void test_early() { internal_private = 1; // expected-error {{undeclared identifier}} } -#ifdef IMPLEMENTATION module A; -#else -import A; -#endif void test_late() { in_header = 1; // expected-error {{missing '#include "foo.h"'; 'in_header' must be declared before it is used}} @@ -61,20 +57,54 @@ void test_late() { exported = 1; not_exported = 1; -#ifndef IMPLEMENTATION - // expected-error@-2 {{use of undeclared identifier 'not_exported'; did you mean 'exported'?}} - // expected-note@p2.cpp:18 {{'exported' declared here}} -#endif internal = 1; // expected-error {{use of undeclared identifier 'internal'}} not_exported_private = 1; -#ifndef IMPLEMENTATION - // FIXME: should not be visible here - // expected-error@-3 {{undeclared identifier}} -#endif internal_private = 1; // expected-error {{use of undeclared identifier 'internal_private'}} } -#endif +//--- implB.cppm +module; + +void test_early() { + in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}} + // expected-note@* {{not visible}} + + global_module_fragment = 1; // expected-error {{use of undeclared identifier 'global_module_fragment'}} + + exported = 1; // expected-error {{use of undeclared identifier 'exported'}} + + not_exported = 1; // expected-error {{use of undeclared identifier 'not_exported'}} + + // FIXME: We need better diagnostic message for static variable. + internal = 1; // expected-error {{use of undeclared identifier 'internal'}} + + not_exported_private = 1; // expected-error {{undeclared identifier}} + + internal_private = 1; // expected-error {{undeclared identifier}} +} + +export module B; +import A; + +void test_late() { + in_header = 1; // expected-error {{missing '#include "foo.h"'; 'in_header' must be declared before it is used}} + // expected-note@* {{not visible}} + + global_module_fragment = 1; // expected-error {{missing '#include'; 'global_module_fragment' must be declared before it is used}} + + exported = 1; + + not_exported = 1; // expected-error {{use of undeclared identifier 'not_exported'; did you mean 'exported'?}} + // expected-note@* {{'exported' declared here}} + + internal = 1; // expected-error {{use of undeclared identifier 'internal'}} + + not_exported_private = 1; + // FIXME: should not be visible here + // expected-error@-2 {{undeclared identifier}} + + internal_private = 1; // expected-error {{use of undeclared identifier 'internal_private'}} +} diff --git a/clang/test/CXX/lex/lex.pptoken/p3-2a.cpp b/clang/test/CXX/lex/lex.pptoken/p3-2a.cpp index 0e0e5fec6e9d8..f51066806947f 100644 --- a/clang/test/CXX/lex/lex.pptoken/p3-2a.cpp +++ b/clang/test/CXX/lex/lex.pptoken/p3-2a.cpp @@ -15,7 +15,7 @@ import ; // CHECK: import ; import ; -// CHECK: foo; import ; +// CHECK: foo; import ; foo; import ; // CHECK: foo import ; @@ -45,7 +45,7 @@ export export import ; import ; UNBALANCED_PAREN -// CHECK: import ; +// CHECK: import ; import ; ) @@ -57,14 +57,19 @@ import ; // CHECK: import ; import HEADER; -// CHECK: import ; +// CHECK: {{^}}foo{{$}} +// CHECK-NEXT: {{^}} bar{{$}} +// CHECK-NEXT: {{^}}>;{{$}} import < foo bar >; // CHECK: import{{$}} -// CHECK: {{^}}; +// CHECK-NEXT: {{^}}<{{$}} +// CHECK-NEXT: {{^}}foo{{$}} +// CHECK-NEXT: {{^}} bar{{$}} +// CHECK-NEXT: {{^}}>;{{$}} import < foo @@ -72,7 +77,7 @@ foo >; // CHECK: import{{$}} -// CHECK: {{^}}; +// CHECK: {{^}}; import ; diff --git a/clang/test/CXX/module/basic/basic.def.odr/p6.cppm b/clang/test/CXX/module/basic/basic.def.odr/p6.cppm index 8e7917dc63ea5..c532e7ad40a10 100644 --- a/clang/test/CXX/module/basic/basic.def.odr/p6.cppm +++ b/clang/test/CXX/module/basic/basic.def.odr/p6.cppm @@ -3,29 +3,28 @@ // RUN: split-file %s %t // // RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm -// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm -DEXPORT -// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module.cppm -DUSING +// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module-export.cppm +// RUN: %clang_cc1 -std=c++20 -verify %t/global-vs-module-using.cppm // -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/global-vs-module.cppm -o %t/M.pcm -DNO_GLOBAL -DEXPORT +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/M.cppm -o %t/M.pcm // RUN: %clang_cc1 -std=c++20 -verify %t/module-vs-global.cpp -fmodule-file=M=%t/M.pcm // // Some of the following tests intentionally have no -verify in their RUN // lines; we are testing that those cases do not produce errors. // -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -DMODULE_INTERFACE -verify -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -DMODULE_INTERFACE -DNO_IMPORT +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -verify +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -DNO_IMPORT // -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N.pcm -DMODULE_INTERFACE -DNO_ERRORS -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -verify +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N.pcm -DNO_ERRORS +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -verify // -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -DNO_IMPORT -verify +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N.pcm -DNO_IMPORT -verify // -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N-no-M.pcm -DMODULE_INTERFACE -DNO_ERRORS -DNO_IMPORT -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N-no-M.pcm -verify -// RUN: %clang_cc1 -std=c++20 %t/module-vs-module.cpp -fmodule-file=N=%t/N-no-M.pcm -DNO_IMPORT +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-interface.cpp -fmodule-file=M=%t/M.pcm -emit-module-interface -o %t/N-no-M.pcm -DNO_ERRORS -DNO_IMPORT +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=M=%t/M.pcm -fmodule-file=N=%t/N-no-M.pcm -verify +// RUN: %clang_cc1 -std=c++20 %t/module-vs-module-impl.cpp -fmodule-file=N=%t/N-no-M.pcm -DNO_IMPORT //--- global-vs-module.cppm -#ifndef NO_GLOBAL module; extern int var; // expected-note {{previous declaration is here}} int func(); // expected-note {{previous declaration is here}} @@ -40,11 +39,75 @@ template using type_tpl = int; // expected-note {{previous declaration typedef int type; namespace ns { using ::func; } namespace ns_alias = ns; -#endif export module M; -#ifdef USING +extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}} +int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}} +struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}} +using type = int; + +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; + +//--- global-vs-module-export.cppm +module; +extern int var; // expected-note {{previous declaration is here}} +int func(); // expected-note {{previous declaration is here}} +struct str; // expected-note {{previous declaration is here}} +using type = int; + +template extern int var_tpl; // expected-note {{previous declaration is here}} +template int func_tpl(); // expected-note {{previous declaration is here}} +template struct str_tpl; // expected-note {{previous declaration is here}} +template using type_tpl = int; // expected-note {{previous declaration is here}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; + +export module M; + +export { +extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}} +int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}} +struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}} +using type = int; + +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; +} + +//--- global-vs-module-using.cppm +module; +extern int var; // expected-note {{previous declaration is here}} +int func(); // expected-note {{previous declaration is here}} +struct str; // expected-note {{previous declaration is here}} +using type = int; + +template extern int var_tpl; // expected-note {{previous declaration is here}} +template int func_tpl(); // expected-note {{previous declaration is here}} +template struct str_tpl; // expected-note {{previous declaration is here}} +template using type_tpl = int; // expected-note {{previous declaration is here}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; + +export module M; + using ::var; using ::func; using ::str; @@ -53,11 +116,6 @@ using ::var_tpl; using ::func_tpl; using ::str_tpl; using ::type_tpl; -#endif - -#ifdef EXPORT -export { -#endif extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}} int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}} @@ -73,51 +131,87 @@ typedef int type; namespace ns { using ::func; } namespace ns_alias = ns; -#ifdef EXPORT +//--- M.cppm +export module M; + +export { +extern int var; // expected-error {{declaration of 'var' in module M follows declaration in the global module}} +int func(); // expected-error {{declaration of 'func' in module M follows declaration in the global module}} +struct str; // expected-error {{declaration of 'str' in module M follows declaration in the global module}} +using type = int; + +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module M follows declaration in the global module}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module M follows declaration in the global module}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module M follows declaration in the global module}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module M follows declaration in the global module}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; } -#endif //--- module-vs-global.cpp +module; import M; -extern int var; // expected-error {{declaration of 'var' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:35 {{previous}} -int func(); // expected-error {{declaration of 'func' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:36 {{previous}} -struct str; // expected-error {{declaration of 'str' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:37 {{previous}} +extern int var; // expected-error {{declaration of 'var' in the global module follows declaration in module M}} expected-note@M.cppm:4 {{previous}} +int func(); // expected-error {{declaration of 'func' in the global module follows declaration in module M}} expected-note@M.cppm:5 {{previous}} +struct str; // expected-error {{declaration of 'str' in the global module follows declaration in module M}} expected-note@M.cppm:6 {{previous}} using type = int; -template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:40 {{previous}} -template int func_tpl(); // expected-error {{declaration of 'func_tpl' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:41 {{previous}} -template struct str_tpl; // expected-error {{declaration of 'str_tpl' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:42 {{previous}} -template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in the global module follows declaration in module M}} expected-note@global-vs-module.cppm:43 {{previous}} +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in the global module follows declaration in module M}} expected-note@M.cppm:9 {{previous}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in the global module follows declaration in module M}} expected-note@M.cppm:10 {{previous}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in the global module follows declaration in module M}} expected-note@M.cppm:11 {{previous}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in the global module follows declaration in module M}} expected-note@M.cppm:12 {{previous}} typedef int type; namespace ns { using ::func; } namespace ns_alias = ns; -//--- module-vs-module.cpp -#ifdef MODULE_INTERFACE export module N; -#else -module N; -#endif + +//--- module-vs-module-interface.cpp +export module N; #ifndef NO_IMPORT import M; #endif #ifndef NO_ERRORS -extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:35 {{previous}} -int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:36 {{previous}} -struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:37 {{previous}} +extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note@M.cppm:4 {{previous}} +int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note@M.cppm:5 {{previous}} +struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note@M.cppm:6 {{previous}} using type = int; -template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:40 {{previous}} -template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:41 {{previous}} -template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:42 {{previous}} -template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note@global-vs-module.cppm:43 {{previous}} +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note@M.cppm:9 {{previous}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note@M.cppm:10 {{previous}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note@M.cppm:11 {{previous}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note@M.cppm:12 {{previous}} typedef int type; namespace ns { using ::func; } namespace ns_alias = ns; #endif +//--- module-vs-module-impl.cpp +module N; + +#ifndef NO_IMPORT +import M; +#endif + +#ifndef NO_ERRORS +extern int var; // expected-error {{declaration of 'var' in module N follows declaration in module M}} expected-note@M.cppm:4 {{previous}} +int func(); // expected-error {{declaration of 'func' in module N follows declaration in module M}} expected-note@M.cppm:5 {{previous}} +struct str; // expected-error {{declaration of 'str' in module N follows declaration in module M}} expected-note@M.cppm:6 {{previous}} +using type = int; + +template extern int var_tpl; // expected-error {{declaration of 'var_tpl' in module N follows declaration in module M}} expected-note@M.cppm:9 {{previous}} +template int func_tpl(); // expected-error {{declaration of 'func_tpl' in module N follows declaration in module M}} expected-note@M.cppm:10 {{previous}} +template struct str_tpl; // expected-error {{declaration of 'str_tpl' in module N follows declaration in module M}} expected-note@M.cppm:11 {{previous}} +template using type_tpl = int; // expected-error {{declaration of 'type_tpl' in module N follows declaration in module M}} expected-note@M.cppm:12 {{previous}} + +typedef int type; +namespace ns { using ::func; } +namespace ns_alias = ns; +#endif diff --git a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp index d71358cc7a571..4bdcc9e5f278e 100644 --- a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp +++ b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp @@ -8,27 +8,19 @@ // RUN: %clang_cc1 -std=c++20 -emit-module-interface -fmodule-file=x=%t/x.pcm %t/x.y.cppm -o %t/x.y.pcm // // Module implementation for unknown and known module. (The former is ill-formed.) -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/M.cpp \ -// RUN: -DTEST=1 -DEXPORT= -DMODULE_NAME=z -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x=%t/x.pcm -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/M.cpp \ -// RUN: -DTEST=2 -DEXPORT= -DMODULE_NAME=x +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/z_impl.cppm +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x=%t/x.pcm -fmodule-file=x.y=%t/x.y.pcm -verify -x c++ %t/x_impl.cppm // // Module interface for unknown and known module. (The latter is ill-formed due to // redefinition.) -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=3 -DEXPORT=export -DMODULE_NAME=z -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=4 -DEXPORT=export -DMODULE_NAME=x +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/z_interface.cppm +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/x_interface.cppm // // Miscellaneous syntax. -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=7 -DEXPORT=export -DMODULE_NAME='z elderberry' -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=8 -DEXPORT=export -DMODULE_NAME='z [[]]' -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=9 -DEXPORT=export -DMODULE_NAME='z [[fancy]]' -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/M.cpp \ -// RUN: -DTEST=10 -DEXPORT=export -DMODULE_NAME='z [[maybe_unused]]' +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/invalid_module_name.cppm +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/empty_attribute.cppm +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/fancy_attribute.cppm +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -verify %t/maybe_unused_attribute.cppm //--- x.cppm export module x; @@ -38,17 +30,31 @@ int a, b; export module x.y; int c; -//--- M.cpp - -EXPORT module MODULE_NAME; -#if TEST == 7 -// expected-error@-2 {{expected ';'}} expected-error@-2 {{a type specifier is required}} -#elif TEST == 9 -// expected-warning@-4 {{unknown attribute 'fancy' ignored}} -#elif TEST == 10 -// expected-error-re@-6 {{'maybe_unused' attribute cannot be applied to a module{{$}}}} -#elif TEST == 1 -// expected-error@-8 {{module 'z' not found}} -#else +//--- z_impl.cppm +module z; // expected-error {{module 'z' not found}} + +//--- x_impl.cppm +// expected-no-diagnostics +module x; + +//--- z_interface.cppm // expected-no-diagnostics -#endif +export module z; + +//--- x_interface.cppm +// expected-no-diagnostics +export module x; + +//--- invalid_module_name.cppm +export module z elderberry; // expected-error {{expected ';'}} \ + // expected-error {{a type specifier is required}} + +//--- empty_attribute.cppm +// expected-no-diagnostics +export module z [[]]; + +//--- fancy_attribute.cppm +export module z [[fancy]]; // expected-warning {{unknown attribute 'fancy' ignored}} + +//--- maybe_unused_attribute.cppm +export module z [[maybe_unused]]; // expected-error-re {{'maybe_unused' attribute cannot be applied to a module{{$}}}} diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm index 873e4c0edeac2..afc8c9a5b3be2 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm @@ -6,10 +6,10 @@ // RUN: %clang_cc1 -std=c++20 -emit-module-interface -fmodule-file=x=%t/x.pcm %t/x.y.cppm -o %t/x.y.pcm // RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/a.b.cppm -o %t/a.b.pcm // -// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm -verify %t/test.cpp \ -// RUN: -DMODULE_NAME=z -DINTERFACE // RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm \ -// RUN: -fmodule-file=a.b=%t/a.b.pcm -verify %t/test.cpp -DMODULE_NAME=a.b +// RUN: -verify %t/test.interface.cpp +// RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm \ +// RUN: -fmodule-file=a.b=%t/a.b.pcm -verify %t/test.implementation.cpp // RUN: %clang_cc1 -std=c++20 -I%t -fmodule-file=x.y=%t/x.y.pcm -fmodule-file=x=%t/x.pcm -verify %t/test.x.cpp //--- x.cppm @@ -33,12 +33,27 @@ int use_2 = b; // ok // There is no relation between module x and module x.y. int use_3 = c; // expected-error {{use of undeclared identifier 'c'}} -//--- test.cpp -#ifdef INTERFACE -export module MODULE_NAME; -#else -module MODULE_NAME; -#endif +//--- test.interface.cpp +export module z; + +import x; + +import x [[]]; +import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}} +import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}} +import x [[blarg::noreturn]]; // expected-warning {{unknown attribute 'noreturn' ignored}} + +import x.y; +import x.; // expected-error {{expected identifier after '.' in module name}} +import .x; // expected-error {{unknown type name 'import'}} \ + // expected-error {{cannot use dot operator on a type}} + +import blarg; // expected-error {{module 'blarg' not found}} + +int use_4 = c; // ok + +//--- test.implementation.cpp +module a.b; import x; @@ -48,8 +63,9 @@ import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applie import x [[blarg::noreturn]]; // expected-warning {{unknown attribute 'noreturn' ignored}} import x.y; -import x.; // expected-error {{expected a module name after 'import'}} -import .x; // expected-error {{expected a module name after 'import'}} +import x.; // expected-error {{expected identifier after '.' in module name}} +import .x; // expected-error {{unknown type name 'import'}} \ + // expected-error {{cannot use dot operator on a type}} import blarg; // expected-error {{module 'blarg' not found}} diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm index 84ef85126c369..2158d7fa84b86 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.interface/p1.cppm @@ -1,29 +1,26 @@ -// RUN: %clang_cc1 -std=c++20 %s -verify -emit-module-interface -o /dev/null -// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -verify -emit-module-interface -o %t -// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -verify -fmodule-file=A=%t -o /dev/null +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 %t/ExportDeclNotInModulePurview.cppm -verify -emit-module-interface -o /dev/null +// RUN: %clang_cc1 -std=c++20 %t/A.cppm -verify -emit-module-interface -o %t/A.pcm +// RUN: %clang_cc1 -std=c++20 %t/AddExport.cppm -verify -fmodule-file=A=%t/A.pcm -o /dev/null // -// RUN: %clang_cc1 -std=c++20 %s -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null -// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null -// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -DBUILT_AS_INTERFACE -emit-module-interface -verify -o /dev/null +// RUN: %clang_cc1 -std=c++20 %t/AddExport2.cppm -emit-module-interface -verify -o /dev/null -#if INTERFACE +//--- ExportDeclNotInModulePurview.cppm +// expected-error@* {{missing 'export module' declaration in module interface unit}} +export int b; // expected-error {{export declaration can only be used within a module purview}} + +//--- A.cppm // expected-no-diagnostics export module A; -#elif IMPLEMENTATION -module A; // #module-decl - #ifdef BUILT_AS_INTERFACE - // expected-error@-2 {{missing 'export' specifier in module declaration while building module interface}} - #define INTERFACE - #endif -#else // Not in a module -// expected-error@* {{missing 'export module' declaration in module interface unit}} -#endif +export int a; -#ifndef INTERFACE +//--- AddExport.cppm +module A; // #module-decl export int b; // expected-error {{export declaration can only be used within a module purview}} -#ifdef IMPLEMENTATION // expected-note@#module-decl {{add 'export' here}} -#endif -#else + +//--- AddExport2.cppm +module A; // expected-error {{missing 'export' specifier in module declaration while building module interface}} export int a; -#endif diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp b/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp index db86b5dd34c38..95d087e0f6c78 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/p1.cpp @@ -1,14 +1,30 @@ -// RUN: %clang_cc1 -std=c++20 -verify %s -DFOO=export -DBAR=export -// RUN: %clang_cc1 -std=c++20 -verify %s -DFOO=export -DBAR= -// RUN: %clang_cc1 -std=c++20 %s -DFOO=export -emit-module-interface -o %t -// RUN: %clang_cc1 -std=c++20 %s -fmodule-file=foo=%t -DFOO= -// RUN: %clang_cc1 -std=c++20 %s -fmodule-file=foo=%t -DBAR=export -// RUN: %clang_cc1 -std=c++20 -verify %s -fmodule-file=foo=%t -DFOO= -DBAR=export - -#ifdef FOO -FOO module foo; // expected-note {{previous module declaration is here}} -#endif - -#ifdef BAR -BAR module bar; // expected-error {{translation unit contains multiple module declarations}} -#endif +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 -verify %t/A.cppm +// RUN: %clang_cc1 -std=c++20 -verify %t/B.cppm +// RUN: %clang_cc1 -std=c++20 %t/C.cppm -emit-module-interface -o %t/C.pcm +// RUN: %clang_cc1 -std=c++20 %t/D.cppm -fmodule-file=foo=%t/C.pcm +// RUN: %clang_cc1 -std=c++20 %t/E.cppm -fmodule-file=foo=%t/C.pcm +// RUN: %clang_cc1 -std=c++20 -verify %t/F.cppm -fmodule-file=foo=%t/C.pcm + +//--- A.cppm +export module foo; // expected-note {{previous module declaration is here}} +export module bar; // expected-error {{translation unit contains multiple module declarations}} + +//--- B.cppm +export module foo; // expected-note {{previous module declaration is here}} +module bar; // expected-error {{translation unit contains multiple module declarations}} + +//--- C.cppm +export module foo; + +//--- D.cppm +module foo; + +//--- E.cppm +export module bar; + +//--- F.cppm +module foo; // expected-note {{previous module declaration is here}} +export module bar; // expected-error {{translation unit contains multiple module declarations}} diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp b/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp index ca100443a4c67..a0d30233809f9 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/p5.cpp @@ -1,22 +1,47 @@ // RUN: rm -rf %t -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t -DINTERFACE -// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DIMPLEMENTATION -// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DEARLY_IMPLEMENTATION -// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t %s -verify -DUSER +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/interface.cppm -o %t/interface.pcm +// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/implementation.cppm -verify -DIMPLEMENTATION +// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/early_impl.cppm -verify -DEARLY_IMPLEMENTATION +// RUN: %clang_cc1 -std=c++20 -fmodule-file=Foo=%t/interface.pcm %t/user.cppm -verify -DUSER + +//--- interface.cppm // expected-no-diagnostics +module; -#if defined(INTERFACE) || defined(EARLY_IMPLEMENTATION) || defined(IMPLEMENTATION) +template struct type_template { + typedef T type; + void f(type); +}; + +template void type_template::f(type) {} + +template class = type_template> +struct default_template_args {}; + +export module Foo; + +//--- implementation.cppm +// expected-no-diagnostics module; -#endif -#ifdef USER -import Foo; -#endif +template struct type_template { + typedef T type; + void f(type); +}; + +template void type_template::f(type) {} + +template class = type_template> +struct default_template_args {}; -#ifdef EARLY_IMPLEMENTATION module Foo; -#endif + +//--- early_impl.cppm +// expected-no-diagnostics +module; +module Foo; template struct type_template { typedef T type; @@ -28,10 +53,16 @@ template void type_template::f(type) {} template class = type_template> struct default_template_args {}; -#ifdef INTERFACE -export module Foo; -#endif +//--- user.cppm +// expected-no-diagnostics +import Foo; -#ifdef IMPLEMENTATION -module Foo; -#endif +template struct type_template { + typedef T type; + void f(type); +}; + +template void type_template::f(type) {} + +template class = type_template> +struct default_template_args {}; diff --git a/clang/test/CXX/module/module.interface/p1.cpp b/clang/test/CXX/module/module.interface/p1.cpp index 54a201e502323..9b3ec1348f9cf 100644 --- a/clang/test/CXX/module/module.interface/p1.cpp +++ b/clang/test/CXX/module/module.interface/p1.cpp @@ -1,28 +1,19 @@ -// RUN: %clang_cc1 -std=c++2a %s -DERRORS -verify -// RUN: %clang_cc1 -std=c++2a %s -emit-module-interface -o %t.pcm -// RUN: %clang_cc1 -std=c++2a %s -fmodule-file=M=%t.pcm -DIMPLEMENTATION -verify -Db=b2 -Dc=c2 +// RUN: rm -rf %t +// RUN: split-file %s %t -module; +// RUN: %clang_cc1 -std=c++2a %t/errors.cppm -verify +// RUN: %clang_cc1 -std=c++2a %t/M.cppm -emit-module-interface -o %t/M.pcm +// RUN: %clang_cc1 -std=c++2a %t/impl.cppm -fmodule-file=M=%t/M.pcm -verify -#ifdef ERRORS +//--- errors.cppm +module; export int a; // expected-error {{export declaration can only be used within a module purview}} -#endif - -#ifndef IMPLEMENTATION -export -#else -// expected-error@#1 {{export declaration can only be used within a module purview}} -// expected-error@#2 {{export declaration can only be used within a module purview}} -// expected-note@+2 1+{{add 'export'}} -#endif -module M; - +export module M; export int b; // #1 namespace N { export int c; // #2 } -#ifdef ERRORS namespace { // expected-note 2{{anonymous namespace begins here}} export int d1; // expected-error {{export declaration appears within anonymous namespace}} namespace X { @@ -35,4 +26,19 @@ export { export int f; } // expected-error {{within another export declaration}} module :private; // expected-note {{private module fragment begins here}} export int priv; // expected-error {{export declaration cannot be used in a private module fragment}} -#endif + +//--- M.cppm +export module M; +export int b; +namespace N { + export int c; +} + +//--- impl.cppm +module M; // #M + +export int b2; // expected-error {{export declaration can only be used within a module purview}} +namespace N { + export int c2; // expected-error {{export declaration can only be used within a module purview}} +} +// expected-note@#M 2+{{add 'export'}} diff --git a/clang/test/CXX/module/module.interface/p2.cpp b/clang/test/CXX/module/module.interface/p2.cpp index 4f06b9f386869..8221c400ecd62 100644 --- a/clang/test/CXX/module/module.interface/p2.cpp +++ b/clang/test/CXX/module/module.interface/p2.cpp @@ -1,24 +1,26 @@ // RUN: rm -rf %t // RUN: mkdir -p %t +// RUN: split-file %s %t +// // RUN: %clang_cc1 -std=c++20 -x c++-header %S/Inputs/header.h -emit-header-unit -o %t/h.pcm -// RUN: %clang_cc1 -std=c++20 %s -DX_INTERFACE -emit-module-interface -o %t/x.pcm -// RUN: %clang_cc1 -std=c++20 %s -DY_INTERFACE -emit-module-interface -o %t/y.pcm -// RUN: %clang_cc1 -std=c++20 %s -DINTERFACE -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -emit-module-interface -o %t/m.pcm -// RUN: %clang_cc1 -std=c++20 %s -DIMPLEMENTATION -I%S/Inputs -fmodule-file=%t/h.pcm \ +// RUN: %clang_cc1 -std=c++20 %t/x.cppm -emit-module-interface -o %t/x.pcm +// RUN: %clang_cc1 -std=c++20 %t/y.cppm -emit-module-interface -o %t/y.pcm +// RUN: %clang_cc1 -std=c++20 %t/interface.cppm -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -emit-module-interface -o %t/m.pcm +// RUN: %clang_cc1 -std=c++20 %t/impl.cppm -I%S/Inputs -fmodule-file=%t/h.pcm \ // RUN: -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -fmodule-file=p2=%t/m.pcm -verify \ // RUN: -Wno-experimental-header-units -// RUN: %clang_cc1 -std=c++20 %s -DUSER -I%S/Inputs -fmodule-file=%t/h.pcm -fmodule-file=p2=%t/m.pcm \ +// RUN: %clang_cc1 -std=c++20 %t/user.cppm -I%S/Inputs -fmodule-file=%t/h.pcm -fmodule-file=p2=%t/m.pcm \ // RUN: -fmodule-file=X=%t/x.pcm -fmodule-file=Y=%t/y.pcm -Wno-experimental-header-units -verify -#if defined(X_INTERFACE) +//--- x.cppm export module X; export int x; -#elif defined(Y_INTERFACE) +//--- y.cppm export module Y; export int y; -#elif defined(INTERFACE) +//--- interface.cppm export module p2; export import X; import Y; // not exported @@ -39,7 +41,7 @@ namespace C {} namespace D { int f(); } export namespace D {} -#elif defined(IMPLEMENTATION) +//--- impl.cppm module p2; import "header.h"; @@ -66,7 +68,7 @@ void use() { int use_header() { return foo + bar::baz(); } -#elif defined(USER) +//--- user.cppm import p2; import "header.h"; @@ -96,7 +98,3 @@ void use() { } int use_header() { return foo + bar::baz(); } - -#else -#error unknown mode -#endif diff --git a/clang/test/CXX/module/module.unit/p8.cpp b/clang/test/CXX/module/module.unit/p8.cpp index a5c01c493558e..fb190257d3a23 100644 --- a/clang/test/CXX/module/module.unit/p8.cpp +++ b/clang/test/CXX/module/module.unit/p8.cpp @@ -1,37 +1,45 @@ -// RUN: echo 'export module foo; export int n;' > %t.cppm +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// RUN: echo 'export module foo;' > %t.cppm +// RUN: echo 'export int n;' >> %t.cppm // RUN: %clang_cc1 -std=c++2a %t.cppm -emit-module-interface -o %t.pcm -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=0 %s -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=1 %s -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=2 %s -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=3 %s -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=4 %s -// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=5 %s - -#if MODE == 0 +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=0 %t/A.cppm +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=1 %t/B.cppm +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=2 %t/C.cppm +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=3 %t/D.cppm +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=4 %t/E.cppm +// RUN: %clang_cc1 -std=c++2a -fmodule-file=foo=%t.pcm -verify -DMODE=5 %t/F.cppm + +//--- A.cppm // no module declaration +// expected-no-diagnostics -#elif MODE == 1 +//--- B.cppm // expected-no-diagnostics module foo; // Implementation, implicitly imports foo. #define IMPORTED -#elif MODE == 2 +int k = n; + +//--- C.cppm export module foo; -#elif MODE == 3 +int k = n; // expected-error {{use of undeclared identifier 'n'}} + +//--- D.cppm export module bar; // A different module -#elif MODE == 4 +int k = n; // expected-error {{use of undeclared identifier 'n'}} + +//--- E.cppm module foo:bar; // Partition implementation //#define IMPORTED (we don't import foo here) -#elif MODE == 5 +int k = n; // expected-error {{use of undeclared identifier 'n'}} + +//--- F.cppm export module foo:bar; // Partition interface //#define IMPORTED (we don't import foo here) -#endif - -int k = n; -#ifndef IMPORTED -// expected-error@-2 {{use of undeclared identifier 'n'}} -#endif +int k = n; // expected-error {{use of undeclared identifier 'n'}} diff --git a/clang/test/Driver/modules.cpp b/clang/test/Driver/modules.cpp index b0d1f2280d254..088a73230f81e 100644 --- a/clang/test/Driver/modules.cpp +++ b/clang/test/Driver/modules.cpp @@ -1,43 +1,48 @@ // RUN: rm -rf %t // RUN: mkdir %t +// RUN: split-file %s %t // Check compiling a module interface to a .pcm file. // -// RUN: %clang -std=c++2a -x c++-module --precompile %s -o %t/module.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE -// RUN: %clang -std=gnu++2a -x c++-module --precompile %s -o %t/module-gnu.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE +// RUN: %clang -std=c++2a -x c++-module --precompile %t/foo.cpp -o %t/foo.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE +// RUN: %clang -std=gnu++2a -x c++-module --precompile %t/foo.cpp -o %t/foo-gnu.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE // // CHECK-PRECOMPILE: -cc1 {{.*}} -emit-module-interface // CHECK-PRECOMPILE-SAME: -o {{.*}}.pcm // CHECK-PRECOMPILE-SAME: -x c++ -// CHECK-PRECOMPILE-SAME: modules.cpp +// CHECK-PRECOMPILE-SAME: foo.cpp // Check compiling a .pcm file to a .o file. // -// RUN: %clang -std=c++2a %t/module.pcm -S -o %t/module.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-COMPILE +// RUN: %clang -std=c++2a %t/foo.pcm -S -o %t/foo.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-COMPILE // // CHECK-COMPILE: -cc1 {{.*}} {{-emit-obj|-S}} -// CHECK-COMPILE-SAME: -o {{.*}}module{{2*}}.pcm.o +// CHECK-COMPILE-SAME: -o {{.*}}foo{{2*}}.pcm.o // CHECK-COMPILE-SAME: -x pcm // CHECK-COMPILE-SAME: {{.*}}.pcm // Check use of a .pcm file in another compilation. // -// RUN: %clang -std=c++2a -fmodule-file=%t/module.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE -// RUN: %clang -std=c++20 -fmodule-file=%t/module.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE -// RUN: %clang -std=gnu++20 -fmodule-file=%t/module-gnu.pcm -Dexport= %s -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE +// RUN: %clang -std=c++2a -fmodule-file=foo=%t/foo.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE +// RUN: %clang -std=c++20 -fmodule-file=foo=%t/foo.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE +// RUN: %clang -std=gnu++20 -fmodule-file=foo=%t/foo-gnu.pcm %t/foo_impl.cpp -S -o %t/module.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-USE // // CHECK-USE: -cc1 {{.*}} {{-emit-obj|-S}} -// CHECK-USE-SAME: -fmodule-file={{.*}}.pcm +// CHECK-USE-SAME: -fmodule-file=foo={{.*}}.pcm // CHECK-USE-SAME: -o {{.*}}.{{o|s}}{{"?}} {{.*}}-x c++ -// CHECK-USE-SAME: modules.cpp +// CHECK-USE-SAME: foo_impl.cpp // Check combining precompile and compile steps works. // -// RUN: %clang -std=c++2a -x c++-module %s -S -o %t/module2.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE --check-prefix=CHECK-COMPILE +// RUN: %clang -std=c++2a -x c++-module %t/foo.cpp -S -o %t/foo2.pcm.o -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE --check-prefix=CHECK-COMPILE // Check that .cppm is treated as a module implicitly. // -// RUN: cp %s %t/module.cppm -// RUN: %clang -std=c++2a --precompile %t/module.cppm -o %t/module.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE +// RUN: cp %t/foo.cpp %t/foo.cppm +// RUN: %clang -std=c++2a --precompile %t/foo.cppm -o %t/foo.pcm -v 2>&1 | FileCheck %s --check-prefix=CHECK-PRECOMPILE +//--- foo.cpp export module foo; + +//--- foo_impl.cpp +module foo; diff --git a/clang/test/Modules/pr121066.cpp b/clang/test/Modules/pr121066.cpp index e92a81c53d683..849488e938d50 100644 --- a/clang/test/Modules/pr121066.cpp +++ b/clang/test/Modules/pr121066.cpp @@ -1,4 +1,3 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only %s -verify -import mod // expected-error {{expected ';' after module name}} - // expected-error@-1 {{module 'mod' not found}} +import mod // expected-error {{'import' directive must end with a ';' on the same line}} diff --git a/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp b/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp index 7c0b967a3c03f..30fea464a8dc5 100644 --- a/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp +++ b/clang/test/SemaCXX/type-aware-new-delete-transparent-contexts.cpp @@ -1,12 +1,22 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=0 -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=1 -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++26 -fexceptions -DTRANSPARENT_DECL=2 +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -fsyntax-only -verify %t/testing.cpp -std=c++26 -fexceptions -DTRANSPARENT_DECL=0 +// RUN: %clang_cc1 -fsyntax-only -verify %t/testing.cpp -std=c++26 -fexceptions -DTRANSPARENT_DECL=1 +// RUN: %clang_cc1 -fsyntax-only -verify %t/module_testing.cppm -std=c++26 -fexceptions -DTRANSPARENT_DECL=2 + +//--- module_testing.cppm // expected-no-diagnostics -#if TRANSPARENT_DECL==2 export module Testing; -#endif +#include "testing.inc" + +//--- testing.cpp +// expected-no-diagnostics +#include "testing.inc" + +//--- testing.inc namespace std { template struct type_identity {}; using size_t = __SIZE_TYPE__; diff --git a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp index 07450a0c59ec6..042e8bd80a50b 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp @@ -193,7 +193,8 @@ TEST_P(ASTMatchersTest, ExportDecl) { if (!GetParam().isCXX20OrLater()) { return; } - const std::string moduleHeader = "module;export module ast_matcher_test;"; + const std::string moduleHeader = + "module;\n export module ast_matcher_test;\n"; EXPECT_TRUE(matches(moduleHeader + "export void foo();", exportDecl(has(functionDecl())))); EXPECT_TRUE(matches(moduleHeader + "export { void foo(); int v; }", diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp index bdb5e23510118..49c005b245d20 100644 --- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp +++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp @@ -986,11 +986,11 @@ ort \ ASSERT_FALSE( minimizeSourceToDependencyDirectives(Source, Out, Tokens, Directives)); EXPECT_STREQ("#include \"textual-header.h\"\nexport module m;" - "exp\\\nort import:l[[rename]];" - "import<<=3;import a b d e d e f e;" - "import foo[[no_unique_address]];import foo();" - "import f(:sefse);import f(->a=3);" - "\n", + "\nexp\\\nort import:l[[rename]];" + "\nimport<<=3;\nimport a b d e d e f e;" + "\nimport foo[[no_unique_address]];\nimport foo();" + "\nimport f(:sefse);\nimport f(->a=3);" + "\n\n", Out.data()); ASSERT_EQ(Directives.size(), 11u); EXPECT_EQ(Directives[0].Kind, pp_include); diff --git a/clang/unittests/Lex/ModuleDeclStateTest.cpp b/clang/unittests/Lex/ModuleDeclStateTest.cpp index 6ecba4de3187c..052dccd6fa8d5 100644 --- a/clang/unittests/Lex/ModuleDeclStateTest.cpp +++ b/clang/unittests/Lex/ModuleDeclStateTest.cpp @@ -40,7 +40,7 @@ class CheckNamedModuleImportingCB : public PPCallbacks { void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) override { ASSERT_TRUE(NextCheckingIndex < IsImportingNamedModulesAssertions.size()); - EXPECT_EQ(PP.isInImportingCXXNamedModules(), + EXPECT_EQ(PP.isImportingCXXNamedModules(), IsImportingNamedModulesAssertions[NextCheckingIndex]); NextCheckingIndex++; From 8529a7fc297b6474ee85c3f7312b11aa362c76c3 Mon Sep 17 00:00:00 2001 From: yronglin Date: Mon, 2 Jun 2025 23:31:44 +0800 Subject: [PATCH 03/22] =?UTF-8?q?P3034R1=20Module=20Declarations=20Shouldn?= =?UTF-8?q?=E2=80=99t=20be=20Macros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yronglin --- clang/lib/Lex/PPDirectives.cpp | 8 +++++++- clang/lib/Lex/Preprocessor.cpp | 12 ++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index f95b9d582c5cc..a0a6c8ceb299e 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -4249,7 +4249,13 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { DirToks.push_back(Tok); break; case tok::identifier: { - if (LexModuleNameContinue(Tok, UseLoc, Path)) { + // C++ [cpp.module]p3: Any preprocessing tokens after the module + // preprocessing token in the module directive are processed just as in + // normal text. + // + // P3034R1 Module Declarations Shouldn’t be Macros. + if (LexModuleNameContinue(Tok, UseLoc, Path, + /*AllowMacroExpansion=*/false)) { if (Tok.isNot(tok::eod)) CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); return; diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index f1d94561f365c..d9237e710e7f9 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1168,12 +1168,12 @@ bool Preprocessor::LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, /// /// At the start of phase 4 an import or module token is treated as starting a /// directive and are converted to their respective keywords iff: -/// • After skipping horizontal whitespace are -/// • at the start of a logical line, or -/// • preceded by an 'export' at the start of the logical line. -/// • Are followed by an identifier pp token (before macro expansion), or -/// • <, ", or : (but not ::) pp tokens for 'import', or -/// • ; for 'module' +/// - After skipping horizontal whitespace are +/// - at the start of a logical line, or +/// - preceded by an 'export' at the start of the logical line. +/// - Are followed by an identifier pp token (before macro expansion), or +/// - <, ", or : (but not ::) pp tokens for 'import', or +/// - ; for 'module' /// Otherwise the token is treated as an identifier. bool Preprocessor::HandleModuleContextualKeyword( Token &Result, bool TokAtPhysicalStartOfLine) { From ece6382d037522f82be0112a1a34f87b17d29d84 Mon Sep 17 00:00:00 2001 From: yronglin Date: Sun, 8 Jun 2025 23:33:04 +0800 Subject: [PATCH 04/22] [clang-scan-deps] Only check eod token in C++ modules Signed-off-by: yronglin --- clang/lib/Lex/DependencyDirectivesScanner.cpp | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index 8cd9c8a0473b6..1cca0351a9f62 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -505,7 +505,7 @@ bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, First = Previous; return false; } - if (Tok.isOneOf(tok::eof, tok::eod)) + if (Tok.is(tok::eof) || (LangOpts.CPlusPlusModules && Tok.is(tok::eod))) return reportError( DirectiveLoc, diag::err_dep_source_scanner_missing_semi_after_at_import); @@ -865,10 +865,6 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { if (*First == '@') return lexAt(First, End); - // Handle module directives for C++20 modules. - if (*First == 'i' || *First == 'e' || *First == 'm') - return lexModule(First, End); - if (*First == '_') { if (isNextIdentifierOrSkipLine("_Pragma", First, End)) return lex_Pragma(First, End); @@ -880,8 +876,27 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { TheLexer.setParsingPreprocessorDirective(true); auto ScEx2 = make_scope_exit( [&]() { TheLexer.setParsingPreprocessorDirective(false); }); + + // Since P1857R3, the standard handling C++ module/import as a directive: + // + // [cpp.pre]p1: + // A preprocessing directive consists of a sequence of preprocessing tokens + // that satisfies the following constraints: At the start of translation + // phase 4, the first preprocessing token in the sequence, referred to as a + // directive-introducing token, begins with the first character in the source + // file (optionally after whitespace containing no new-line characters) or + // follows whitespace containing at least one new-line character, and is + // - a # preprocessing token, or + // - an import preprocessing token immediately followed on the same logical + // source line by a header-name, <, identifier, string-literal, or : + // preprocessing token, or + // - a module preprocessing token immediately followed on the same logical + // source line by an identifier, :, or ; preprocessing token, or + // - an export preprocessing token immediately followed on the same logical + // source line by one of the two preceding forms. if (*First == 'i' || *First == 'e' || *First == 'm') return lexModule(First, End); + // Lex '#'. const dependency_directives_scan::Token &HashTok = lexToken(First, End); if (HashTok.is(tok::hashhash)) { From 008844c7c2542554b4b3c3d2c782558ee74f9133 Mon Sep 17 00:00:00 2001 From: yronglin Date: Thu, 12 Jun 2025 19:34:39 +0800 Subject: [PATCH 05/22] A module directive may only appear as the first preprocessing tokens in a file Signed-off-by: yronglin --- clang/include/clang/Lex/Lexer.h | 3 + clang/include/clang/Lex/Preprocessor.h | 32 ++++++-- clang/include/clang/Lex/Token.h | 5 ++ clang/include/clang/Sema/Sema.h | 6 +- clang/lib/Lex/Lexer.cpp | 11 +++ clang/lib/Lex/PPDirectives.cpp | 32 ++++---- clang/lib/Lex/Preprocessor.cpp | 5 +- clang/lib/Parse/Parser.cpp | 12 ++- clang/lib/Sema/SemaModule.cpp | 55 +++++++------- clang/test/CXX/basic/basic.link/p2.cpp | 26 +++---- clang/test/Modules/named-modules-adl-3.cppm | 1 + clang/test/Modules/reserved-names-1.cppm | 10 +++ .../reserved-names-system-header-1.cpp | 1 + .../reserved-names-system-header-2.cpp | 1 + clang/test/SemaCXX/modules.cppm | 75 ++++++++++++------- 15 files changed, 177 insertions(+), 98 deletions(-) diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h index a595cda1eaa77..7a178b5b5c85b 100644 --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -143,6 +143,9 @@ class Lexer : public PreprocessorLexer { /// True if this is the first time we're lexing the input file. bool IsFirstTimeLexingFile; + /// True if current lexing token is the first pp-token. + bool IsFirstPPToken; + // NewLinePtr - A pointer to new line character '\n' being lexed. For '\r\n', // it also points to '\n.' const char *NewLinePtr; diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 8bf4a3e3a3fb1..1a10336a7390c 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -137,6 +137,24 @@ struct CXXStandardLibraryVersionInfo { std::uint64_t Version; }; +class ExportContextualKeywordInfo { + Token ExportTok; + bool AtPhysicalStartOfLine = false; + +public: + ExportContextualKeywordInfo() = default; + ExportContextualKeywordInfo(const Token &Tok, bool AtPhysicalStartOfLine) + : ExportTok(Tok), AtPhysicalStartOfLine(AtPhysicalStartOfLine) {} + + bool isValid() const { return ExportTok.is(tok::kw_export); } + bool isAtPhysicalStartOfLine() const { return AtPhysicalStartOfLine; } + Token getExportTok() const { return ExportTok; } + void reset() { + ExportTok.startToken(); + AtPhysicalStartOfLine = false; + } +}; + /// Engages in a tight little dance with the lexer to efficiently /// preprocess tokens. /// @@ -359,14 +377,11 @@ class Preprocessor { /// Whether we're declaring a standard C++20 named Modules. bool DeclaringCXXNamedModules = false; - struct ExportContextualKeywordInfo { - Token ExportTok; - bool TokAtPhysicalStartOfLine; - }; - /// Whether the last token we lexed was an 'export' keyword. - std::optional LastTokenWasExportKeyword = - std::nullopt; + ExportContextualKeywordInfo LastTokenWasExportKeyword; + + /// First pp-token in current translation unit. + Token FirstPPToken; /// A position within a C++20 import-seq. class StdCXXImportSeq { @@ -1774,6 +1789,9 @@ class Preprocessor { void HandleCXXImportDirective(Token Import); void HandleCXXModuleDirective(Token Module); + void setFirstPPToken(const Token &Tok) { FirstPPToken = Tok; } + Token getFirstPPToken() const { return FirstPPToken; } + /// Callback invoked when the lexer sees one of export, import or module token /// at the start of a line. /// diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h index 8e81207ddf8d7..3984d5d007f67 100644 --- a/clang/include/clang/Lex/Token.h +++ b/clang/include/clang/Lex/Token.h @@ -89,6 +89,8 @@ class Token { IsReinjected = 0x800, // A phase 4 token that was produced before and // re-added, e.g. via EnterTokenStream. Annotation // tokens are *not* reinjected. + FirstPPToken = 0x1000, // This token is the first pp token in the + // translation unit. }; tok::TokenKind getKind() const { return Kind; } @@ -325,6 +327,9 @@ class Token { /// represented as characters between '<#' and '#>' in the source code. The /// lexer uses identifier tokens to represent placeholders. bool isEditorPlaceholder() const { return getFlag(IsEditorPlaceholder); } + + /// Returns true if this token is the first pp-token. + bool isFirstPPToken() const { return getFlag(FirstPPToken); } }; /// Information about the conditional stack (\#if directives) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 47afe61571826..1e2b2e260b72f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9937,9 +9937,9 @@ class Sema final : public SemaBase { /// of a module interface or implementation. DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, ModuleDeclKind MDK, - ModuleNameLoc *PathLoc, - ModuleNameLoc *PartitionLoc, - ModuleImportState &ImportState); + ModuleIdPath Path, ModuleIdPath Partition, + ModuleImportState &ImportState, + bool AtStartOfTU); /// The parser has processed a global-module-fragment declaration that begins /// the definition of the global module fragment of the current module unit. diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index df9e0f1350903..7acdc5500c743 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -187,6 +187,8 @@ void Lexer::InitLexer(const char *BufStart, const char *BufPtr, ExtendedTokenMode = 0; NewLinePtr = nullptr; + + IsFirstPPToken = true; } /// Lexer constructor - Create a new lexer object for the specified buffer @@ -3739,11 +3741,20 @@ bool Lexer::Lex(Token &Result) { HasLeadingEmptyMacro = false; } + if (IsFirstPPToken) { + Result.setFlag(Token::FirstPPToken); + IsFirstPPToken = false; + } + bool atPhysicalStartOfLine = IsAtPhysicalStartOfLine; IsAtPhysicalStartOfLine = false; bool isRawLex = isLexingRawMode(); (void) isRawLex; bool returnedToken = LexTokenInternal(Result, atPhysicalStartOfLine); + + if (returnedToken && Result.isFirstPPToken() && PP) + PP->setFirstPPToken(Result); + // (After the LexTokenInternal call, the lexer might be destroyed.) assert((returnedToken || !isRawLex) && "Raw lex must succeed"); return returnedToken; diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index a0a6c8ceb299e..de6df604a47e1 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -4089,7 +4089,7 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { this->ImportingCXXNamedModules); ImportingCXXNamedModules = true; - if (LastTokenWasExportKeyword) + if (LastTokenWasExportKeyword.isValid()) LastTokenWasExportKeyword.reset(); Token Tok; @@ -4216,13 +4216,26 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { assert(getLangOpts().CPlusPlusModules && ModuleTok.is(tok::kw_module)); - SourceLocation StartLoc = ModuleTok.getLocation(); - if (LastTokenWasExportKeyword) { - StartLoc = LastTokenWasExportKeyword->ExportTok.getLocation(); + Token Introducer = ModuleTok; + if (LastTokenWasExportKeyword.isValid()) { + Introducer = LastTokenWasExportKeyword.getExportTok(); LastTokenWasExportKeyword.reset(); } - bool IsInHeaderInclusion = !IncludeMacroStack.empty(); - bool IsInConditionBlock = CurPPLexer->getConditionalStackDepth() != 0; + + SourceLocation StartLoc = Introducer.getLocation(); + if (!IncludeMacroStack.empty()) { + SourceLocation End = DiscardUntilEndOfDirective().getEnd(); + Diag(StartLoc, diag::err_module_decl_in_header) + << SourceRange(StartLoc, End); + return; + } + + if (CurPPLexer->getConditionalStackDepth() != 0) { + SourceLocation End = DiscardUntilEndOfDirective().getEnd(); + Diag(StartLoc, diag::err_pp_cond_span_module_decl) + << SourceRange(StartLoc, End); + return; + } Token Tok; SourceLocation UseLoc = ModuleTok.getLocation(); @@ -4305,13 +4318,6 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { End = CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); else End = DirToks.pop_back_val().getLocation(); - if (IsInHeaderInclusion) - Diag(StartLoc, diag::err_module_decl_in_header) - << SourceRange(StartLoc, End); - - if (IsInConditionBlock) - Diag(StartLoc, diag::err_pp_cond_span_module_decl) - << SourceRange(StartLoc, End); EnterModuleSuffixTokenStream(DirToks); } diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index d9237e710e7f9..a535df53c62ab 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1185,11 +1185,10 @@ bool Preprocessor::HandleModuleContextualKeyword( return false; } - if (LastTokenWasExportKeyword) { - auto Export = *LastTokenWasExportKeyword; + if (LastTokenWasExportKeyword.isValid()) { // The export keyword was not at the start of line, it's not a // directive-introducing token. - if (!Export.TokAtPhysicalStartOfLine) + if (!LastTokenWasExportKeyword.isAtPhysicalStartOfLine()) return false; // [cpp.pre]/1.4 // export // not a preprocessing directive diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 508a4ce0e5ff8..48677fc88ff1c 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -17,6 +17,7 @@ #include "clang/AST/DeclTemplate.h" #include "clang/Basic/DiagnosticParse.h" #include "clang/Basic/StackExhaustionHandler.h" +#include "clang/Lex/ModuleLoader.h" #include "clang/Parse/RAIIObjectsForParser.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/EnterExpressionEvaluationContext.h" @@ -2303,7 +2304,8 @@ void Parser::ParseMicrosoftIfExistsExternalDeclaration() { Parser::DeclGroupPtrTy Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { - SourceLocation StartLoc = Tok.getLocation(); + Token Introducer = Tok; + SourceLocation StartLoc = Introducer.getLocation(); Sema::ModuleDeclKind MDK = TryConsumeToken(tok::kw_export) ? Sema::ModuleDeclKind::Interface @@ -2322,7 +2324,7 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { // Parse a global-module-fragment, if present. if (getLangOpts().CPlusPlusModules && Tok.is(tok::semi)) { SourceLocation SemiLoc = ConsumeToken(); - if (ImportState != Sema::ModuleImportState::FirstDecl) { + if (!Introducer.isFirstPPToken()) { Diag(StartLoc, diag::err_global_module_introducer_not_at_start) << SourceRange(StartLoc, SemiLoc); return nullptr; @@ -2378,8 +2380,10 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { ExpectAndConsumeSemi(diag::err_module_expected_semi); - return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, Partition, - ImportState); + return Actions.ActOnModuleDecl( + StartLoc, ModuleLoc, MDK, Path->getModuleIdPath(), + Partition ? Partition->getModuleIdPath() : ModuleIdPath{}, ImportState, + Introducer.isFirstPPToken()); } Decl *Parser::ParseModuleImport(SourceLocation AtLoc, diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 98c60bab409be..708c7f142841b 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -13,7 +13,9 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTMutationListener.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/ModuleLoader.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/ParsedAttr.h" #include "clang/Sema/SemaInternal.h" @@ -238,22 +240,21 @@ static bool DiagReservedModuleName(Sema &S, const IdentifierInfo *II, llvm_unreachable("fell off a fully covered switch"); } -Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, - SourceLocation ModuleLoc, - ModuleDeclKind MDK, - ModuleNameLoc *PathLoc, - ModuleNameLoc *PartitionLoc, - ModuleImportState &ImportState) { +Sema::DeclGroupPtrTy +Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, + ModuleDeclKind MDK, ModuleIdPath Path, + ModuleIdPath Partition, ModuleImportState &ImportState, + bool AtStartOfTU) { assert(getLangOpts().CPlusPlusModules && "should only have module decl in standard C++ modules"); - bool IsFirstDecl = ImportState == ModuleImportState::FirstDecl; bool SeenGMF = ImportState == ModuleImportState::GlobalFragment; // If any of the steps here fail, we count that as invalidating C++20 // module state; ImportState = ModuleImportState::NotACXX20Module; - if (PartitionLoc) + bool IsPartition = !Partition.empty(); + if (IsPartition) switch (MDK) { case ModuleDeclKind::Implementation: MDK = ModuleDeclKind::PartitionImplementation; @@ -282,7 +283,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // We were asked to compile a module interface unit but this is a module // implementation unit. Diag(ModuleLoc, diag::err_module_interface_implementation_mismatch) - << FixItHint::CreateInsertion(ModuleLoc, "export "); + << FixItHint::CreateInsertion(ModuleLoc, "export "); MDK = ModuleDeclKind::Interface; break; @@ -314,7 +315,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // In C++20, the module-declaration must be the first declaration if there // is no global module fragment. - if (getLangOpts().CPlusPlusModules && !IsFirstDecl && !SeenGMF) { + if (getLangOpts().CPlusPlusModules && !AtStartOfTU && !SeenGMF) { Diag(ModuleLoc, diag::err_module_decl_not_at_start); SourceLocation BeginLoc = ModuleScopes.empty() @@ -335,18 +336,17 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // Test the first part of the path to see if it's std[0-9]+ but allow the // name in a system header. - StringRef FirstComponentName = - PathLoc->getModuleIdPath()[0].getIdentifierInfo()->getName(); - if (!getSourceManager().isInSystemHeader(PathLoc->getBeginLoc()) && + StringRef FirstComponentName = Path[0].getIdentifierInfo()->getName(); + if (!getSourceManager().isInSystemHeader(Path[0].getLoc()) && (FirstComponentName == "std" || (FirstComponentName.starts_with("std") && llvm::all_of(FirstComponentName.drop_front(3), &llvm::isDigit)))) - Diag(PathLoc->getBeginLoc(), diag::warn_reserved_module_name) - << PathLoc->getModuleIdPath()[0].getIdentifierInfo(); + Diag(Path[0].getLoc(), diag::warn_reserved_module_name) + << Path[0].getIdentifierInfo(); // Then test all of the components in the path to see if any of them are // using another kind of reserved or invalid identifier. - for (auto Part : PathLoc->getModuleIdPath()) { + for (auto Part : Path) { if (DiagReservedModuleName(*this, Part.getIdentifierInfo(), Part.getLoc())) return nullptr; } @@ -354,20 +354,23 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // Flatten the dots in a module name. Unlike Clang's hierarchical module map // modules, the dots here are just another character that can appear in a // module name. - std::string ModuleName = PathLoc->str(); - if (PartitionLoc) { + std::string ModuleName = ModuleNameLoc::stringFromModuleIdPath(Path); + if (IsPartition) { ModuleName += ":"; - ModuleName += PartitionLoc->str(); + ModuleName += ModuleNameLoc::stringFromModuleIdPath(Partition); } // If a module name was explicitly specified on the command line, it must be // correct. if (!getLangOpts().CurrentModule.empty() && getLangOpts().CurrentModule != ModuleName) { - Diag(PathLoc->getBeginLoc(), diag::err_current_module_name_mismatch) - << PathLoc->getRange() << getLangOpts().CurrentModule; + Diag(Path.front().getLoc(), diag::err_current_module_name_mismatch) + << SourceRange(Path.front().getLoc(), IsPartition + ? Partition.back().getLoc() + : Path.back().getLoc()) + << getLangOpts().CurrentModule; return nullptr; } - const_cast(getLangOpts()).CurrentModule = ModuleName; + const_cast(getLangOpts()).CurrentModule = ModuleName; auto &Map = PP.getHeaderSearchInfo().getModuleMap(); Module *Mod; // The module we are creating. @@ -378,7 +381,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // We can't have parsed or imported a definition of this module or parsed a // module map defining it already. if (auto *M = Map.findOrLoadModule(ModuleName)) { - Diag(PathLoc->getBeginLoc(), diag::err_module_redefinition) << ModuleName; + Diag(Path[0].getLoc(), diag::err_module_redefinition) << ModuleName; if (M->DefinitionLoc.isValid()) Diag(M->DefinitionLoc, diag::note_prev_module_definition); else if (OptionalFileEntryRef FE = M->getASTFile()) @@ -401,7 +404,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // keyword nor a module-partition implicitly imports the primary // module interface unit of the module as if by a module-import- // declaration. - IdentifierLoc ModuleNameLoc(PathLoc->getBeginLoc(), + IdentifierLoc ModuleNameLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); // The module loader will assume we're trying to import the module that @@ -412,7 +415,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, Interface = getModuleLoader().loadModule(ModuleLoc, {ModuleNameLoc}, Module::AllVisible, /*IsInclusionDirective=*/false); - const_cast(getLangOpts()).CurrentModule = ModuleName; + const_cast(getLangOpts()).CurrentModule = ModuleName; if (!Interface) { Diag(ModuleLoc, diag::err_module_not_defined) << ModuleName; @@ -474,7 +477,7 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, // Make the import decl for the interface in the impl module. ImportDecl *Import = ImportDecl::Create(Context, CurContext, ModuleLoc, - Interface, PathLoc->getBeginLoc()); + Interface, Path[0].getLoc()); CurContext->addDecl(Import); // Sequence initialization of the imported module before that of the current diff --git a/clang/test/CXX/basic/basic.link/p2.cpp b/clang/test/CXX/basic/basic.link/p2.cpp index ccad42022ee80..94cbc62490b2f 100644 --- a/clang/test/CXX/basic/basic.link/p2.cpp +++ b/clang/test/CXX/basic/basic.link/p2.cpp @@ -1,16 +1,16 @@ -// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -verify -// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -emit-module-interface -o %t.pcm -// RUN: %clang_cc1 -std=c++2a -UEXPORT %s -verify -fmodule-file=M=%t.pcm +// RUN: rm -rf %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++2a %t/pmf_in_interface.cpp -verify +// RUN: %clang_cc1 -std=c++2a %t/pmf_in_interface.cpp -emit-module-interface -o %t.pcm +// RUN: %clang_cc1 -std=c++2a %t/pmf_in_implementation.cpp -verify -fmodule-file=M=%t.pcm -#ifdef EXPORT -// expected-no-diagnostics -export -#else -// expected-note@+2 {{add 'export' here}} -#endif -module M; -#ifndef EXPORT -// expected-error@+2 {{private module fragment in module implementation unit}} -#endif +//--- pmf_in_interface.cpp +// expected-no-diagnostics +export module M; module :private; + +//--- pmf_in_implementation.cpp +module M; // expected-note {{add 'export' here}} +module :private; // expected-error {{private module fragment in module implementation unit}} diff --git a/clang/test/Modules/named-modules-adl-3.cppm b/clang/test/Modules/named-modules-adl-3.cppm index d70946fa068b3..a3644b45a5347 100644 --- a/clang/test/Modules/named-modules-adl-3.cppm +++ b/clang/test/Modules/named-modules-adl-3.cppm @@ -58,6 +58,7 @@ void b(T x) { } //--- c.cppm +module; #ifdef EXPORT_OPERATOR // expected-no-diagnostics #endif diff --git a/clang/test/Modules/reserved-names-1.cppm b/clang/test/Modules/reserved-names-1.cppm index e780f1e35b3b7..35b264bcb573b 100644 --- a/clang/test/Modules/reserved-names-1.cppm +++ b/clang/test/Modules/reserved-names-1.cppm @@ -88,12 +88,14 @@ export module module; // expected-error {{'module' is an invalid name for a modu export module import; // expected-error {{'import' is an invalid name for a module}} //--- _Test.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif export module _Test; // loud-warning {{'_Test' is a reserved name for a module}} //--- __test.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif @@ -101,6 +103,7 @@ export module __test; // loud-warning {{'__test' is a reserved name for a module export int a = 43; //--- te__st.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif @@ -108,6 +111,7 @@ export module te__st; // loud-warning {{'te__st' is a reserved name for a module export int a = 43; //--- std.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif @@ -116,36 +120,42 @@ export module std; // loud-warning {{'std' is a reserved name for a module}} export int a = 43; //--- std.foo.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif export module std.foo;// loud-warning {{'std' is a reserved name for a module}} //--- std0.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif export module std0; // loud-warning {{'std0' is a reserved name for a module}} //--- std1000000.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif export module std1000000; // loud-warning {{'std1000000' is a reserved name for a module}} //--- should_diag._Test.cppm +module; #ifdef NODIAGNOSTICS // expected-no-diagnostics #endif export module should_diag._Test; // loud-warning {{'_Test' is a reserved name for a module}} //--- system-module.cppm +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} // Show that being in a system header doesn't save you from diagnostics about // use of an invalid module-name identifier. # 34 "reserved-names-1.cpp" 1 3 export module module; // expected-error {{'module' is an invalid name for a module}} //--- system._Test.import.cppm +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} # 34 "reserved-names-1.cpp" 1 3 export module _Test.import; // expected-error {{'import' is an invalid name for a module}} diff --git a/clang/test/Modules/reserved-names-system-header-1.cpp b/clang/test/Modules/reserved-names-system-header-1.cpp index 2db4c08add1d9..fa869483980f6 100644 --- a/clang/test/Modules/reserved-names-system-header-1.cpp +++ b/clang/test/Modules/reserved-names-system-header-1.cpp @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s // expected-no-diagnostics +module; // Show that we suppress the reserved identifier diagnostic in a system header. # 100 "file.cpp" 1 3 // Enter a system header export module std; diff --git a/clang/test/Modules/reserved-names-system-header-2.cpp b/clang/test/Modules/reserved-names-system-header-2.cpp index 2087f487721cb..d429e58dc1714 100644 --- a/clang/test/Modules/reserved-names-system-header-2.cpp +++ b/clang/test/Modules/reserved-names-system-header-2.cpp @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s // expected-no-diagnostics +module; // Show that we suppress the reserved identifier diagnostic in a system header. # 100 "file.cpp" 1 3 // Enter a system header export module __test; diff --git a/clang/test/SemaCXX/modules.cppm b/clang/test/SemaCXX/modules.cppm index 41204be76eafa..5d0d6da44a2ed 100644 --- a/clang/test/SemaCXX/modules.cppm +++ b/clang/test/SemaCXX/modules.cppm @@ -1,19 +1,20 @@ -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t.0.pcm -verify -DTEST=0 -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t.1.pcm -verify -DTEST=1 -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -fmodule-file=foo=%t.0.pcm -o %t.2.pcm -verify -DTEST=2 -// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -fmodule-file=foo=%t.0.pcm -o %t.3.pcm -verify -Dfoo=bar -DTEST=3 +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t -#if TEST == 0 || TEST == 2 -// expected-no-diagnostics -#endif +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test0.cpp -o %t/test0.pcm -verify +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test1.cpp -o %t/test1.pcm -verify +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test2.cpp -fmodule-file=foo=%t/test0.pcm -o %t/test2.pcm -verify +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/test3.cpp -fmodule-file=foo=%t/test0.pcm -o %t/test3.pcm -verify +//--- test0.cpp +// expected-no-diagnostics export module foo; static int m; int n; -#if TEST == 0 export { int a; int b; @@ -27,33 +28,18 @@ export void f() {} export struct T { } t; -#elif TEST == 3 -int use_a = a; // expected-error {{use of undeclared identifier 'a'}} -#undef foo -import foo; // expected-error {{imports must immediately follow the module declaration}} - -export {} -export { - ; // No diagnostic after P2615R1 DR -} -export { - static_assert(true); // No diagnostic after P2615R1 DR -} +//--- test1.cpp +export module foo; -int use_b = b; // expected-error{{use of undeclared identifier 'b'}} -int use_n = n; // FIXME: this should not be visible, because it is not exported +static int m; -extern int n; -static_assert(&n != p); // expected-error{{use of undeclared identifier 'p'}} -#endif +int n; -#if TEST == 1 struct S { export int n; // expected-error {{expected member name or ';'}} export static int n; // expected-error {{expected member name or ';'}} }; -#endif // FIXME: Exports of declarations without external linkage are disallowed. // Exports of declarations with non-external-linkage types are disallowed. @@ -61,7 +47,6 @@ struct S { // Cannot export within another export. This isn't precisely covered by the // language rules right now, but (per personal correspondence between zygoloid // and gdr) is the intent. -#if TEST == 1 export { // expected-note {{export block begins here}} extern "C++" { namespace NestedExport { @@ -71,4 +56,36 @@ export { // expected-note {{export block begins here}} } // namespace NestedExport } } -#endif + +//--- test2.cpp +// expected-no-diagnostics +export module foo; + +static int m; + +int n; + +//--- test3.cpp +export module bar; + +static int m; + +int n; + +int use_a = a; // expected-error {{use of undeclared identifier 'a'}} + +import foo; // expected-error {{imports must immediately follow the module declaration}} + +export {} +export { + ; // No diagnostic after P2615R1 DR +} +export { + static_assert(true); // No diagnostic after P2615R1 DR +} + +int use_b = b; // expected-error{{use of undeclared identifier 'b'}} +int use_n = n; // FIXME: this should not be visible, because it is not exported + +extern int n; +static_assert(&n != p); // expected-error{{use of undeclared identifier 'p'}} From db17bdaf94cfa5342fced1c15288d5d31864fd4e Mon Sep 17 00:00:00 2001 From: yronglin Date: Thu, 12 Jun 2025 19:43:09 +0800 Subject: [PATCH 06/22] Format code Signed-off-by: yronglin --- clang/include/clang/Lex/Token.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h index 3984d5d007f67..2b30f52a0780e 100644 --- a/clang/include/clang/Lex/Token.h +++ b/clang/include/clang/Lex/Token.h @@ -89,7 +89,7 @@ class Token { IsReinjected = 0x800, // A phase 4 token that was produced before and // re-added, e.g. via EnterTokenStream. Annotation // tokens are *not* reinjected. - FirstPPToken = 0x1000, // This token is the first pp token in the + FirstPPToken = 0x1000, // This token is the first pp token in the // translation unit. }; From 1fa309028fa282535d5bbe76f35b251f5e6e88ac Mon Sep 17 00:00:00 2001 From: yronglin Date: Sun, 22 Jun 2025 01:02:02 +0800 Subject: [PATCH 07/22] Resolve merge conflicts Signed-off-by: yronglin --- clang/include/clang/Lex/Preprocessor.h | 3 --- clang/lib/Lex/Lexer.cpp | 3 --- .../dcl.module/dcl.module.import/p1.cppm | 20 +------------------ 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 26c60e0494fc3..b2f4826b8c0a3 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1789,9 +1789,6 @@ class Preprocessor { void HandleCXXImportDirective(Token Import); void HandleCXXModuleDirective(Token Module); - void setFirstPPToken(const Token &Tok) { FirstPPToken = Tok; } - Token getFirstPPToken() const { return FirstPPToken; } - /// Callback invoked when the lexer sees one of export, import or module token /// at the start of a line. /// diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index f210c24847f21..f866f203de4e5 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -3752,9 +3752,6 @@ bool Lexer::Lex(Token &Result) { (void) isRawLex; bool returnedToken = LexTokenInternal(Result, atPhysicalStartOfLine); - if (returnedToken && Result.isFirstPPToken() && PP) - PP->setFirstPPToken(Result); - // (After the LexTokenInternal call, the lexer might be destroyed.) assert((returnedToken || !isRawLex) && "Raw lex must succeed"); diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm index 6ab02b94a6354..53fd3ea29eccb 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm @@ -43,24 +43,6 @@ import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}} import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}} import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg::noreturn' ignored{{.*}}}} -import x.y; -import x.; // expected-error {{expected a module name after 'import'}} -import .x; // expected-error {{expected a module name after 'import'}} - -import blarg; // expected-error {{module 'blarg' not found}} - -int use_4 = c; // ok - -//--- test.implementation.cpp -module a.b; - -import x; - -import x [[]]; -import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}} -import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}} -import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg::noreturn' ignored{{.*}}}} - import x.y; import x.; // expected-error {{expected identifier after '.' in module name}} import .x; // expected-error {{unknown type name 'import'}} \ @@ -78,7 +60,7 @@ import x; import x [[]]; import x [[foo]]; // expected-warning {{unknown attribute 'foo' ignored}} import x [[noreturn]]; // expected-error {{'noreturn' attribute cannot be applied to a module import}} -import x [[blarg::noreturn]]; // expected-warning {{unknown attribute 'noreturn' ignored}} +import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg::noreturn' ignored{{.*}}}} import x.y; import x.; // expected-error {{expected identifier after '.' in module name}} From 687cdeed3bcb00158314ab41913acc8d637952a8 Mon Sep 17 00:00:00 2001 From: yronglin Date: Tue, 24 Jun 2025 19:47:32 +0800 Subject: [PATCH 08/22] Use isNextPPTokenOneOf instead of peekNextPPToken Signed-off-by: yronglin --- clang/lib/Lex/Preprocessor.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 4d291c3f5c618..e7630f6ed7aae 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -54,6 +54,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Capacity.h" @@ -1204,19 +1205,22 @@ bool Preprocessor::HandleModuleContextualKeyword( bool SavedParsingPreprocessorDirective = CurPPLexer->ParsingPreprocessorDirective; CurPPLexer->ParsingPreprocessorDirective = true; - // Peek next token. - auto NextTok = peekNextPPToken().value_or(Token{}); - CurPPLexer->ParsingPreprocessorDirective = SavedParsingPreprocessorDirective; + auto _ = llvm::make_scope_exit([&]() { + CurPPLexer->ParsingPreprocessorDirective = + SavedParsingPreprocessorDirective; + }); + if (Result.getIdentifierInfo()->isModulesImport() && - NextTok.isOneOf(tok::raw_identifier, tok::less, tok::string_literal, - tok::colon)) { + isNextPPTokenOneOf()) { Result.setKind(tok::kw_import); ModuleImportLoc = Result.getLocation(); IsAtImport = false; return true; } + if (Result.getIdentifierInfo()->isModulesDeclaration() && - NextTok.isOneOf(tok::raw_identifier, tok::colon, tok::semi)) { + isNextPPTokenOneOf()) { Result.setKind(tok::kw_module); ModuleDeclLoc = Result.getLocation(); return true; From 8ce88a0c7314e9b6db467f8b7c880aa81de9187d Mon Sep 17 00:00:00 2001 From: yronglin Date: Thu, 26 Jun 2025 01:42:21 +0800 Subject: [PATCH 09/22] Follow isNextPPTokenOneOf's change Signed-off-by: yronglin --- clang/lib/Lex/Preprocessor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 5b7c5b1a2ea5f..57651fd47e1b1 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1211,8 +1211,8 @@ bool Preprocessor::HandleModuleContextualKeyword( }); if (Result.getIdentifierInfo()->isModulesImport() && - isNextPPTokenOneOf()) { + isNextPPTokenOneOf(tok::raw_identifier, tok::less, tok::string_literal, + tok::colon)) { Result.setKind(tok::kw_import); ModuleImportLoc = Result.getLocation(); IsAtImport = false; @@ -1220,7 +1220,7 @@ bool Preprocessor::HandleModuleContextualKeyword( } if (Result.getIdentifierInfo()->isModulesDeclaration() && - isNextPPTokenOneOf()) { + isNextPPTokenOneOf(tok::raw_identifier, tok::colon, tok::semi)) { Result.setKind(tok::kw_module); ModuleDeclLoc = Result.getLocation(); return true; From 21cb5ecd43c5116af1154ce18a12e1c744dfcf5c Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Tue, 8 Jul 2025 21:57:13 +0800 Subject: [PATCH 10/22] Follow getTrailingObjects changes Signed-off-by: Wang, Yihan --- clang/include/clang/Lex/Preprocessor.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index f6fc425f004d1..e7319c5808f6f 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -3190,7 +3190,7 @@ class ModuleNameLoc final } ModuleNameLoc(ModuleIdPath Path) : NumIdentifierLocs(Path.size()) { - (void)llvm::copy(Path, getTrailingObjects()); + (void)llvm::copy(Path, getTrailingObjectsNonStrict()); } public: @@ -3199,7 +3199,8 @@ class ModuleNameLoc final static Token CreateAnnotToken(Preprocessor &PP, ModuleIdPath Path); unsigned getNumIdentifierLocs() const { return NumIdentifierLocs; } ModuleIdPath getModuleIdPath() const { - return {getTrailingObjects(), getNumIdentifierLocs()}; + return {getTrailingObjectsNonStrict(), + getNumIdentifierLocs()}; } SourceLocation getBeginLoc() const { From 27ab41f0bd79cf42b3bc8dc00ed2289da42eabca Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Tue, 8 Jul 2025 23:50:26 +0800 Subject: [PATCH 11/22] Revert unnecessary changes and refine code Signed-off-by: Wang, Yihan --- clang/include/clang/Lex/Preprocessor.h | 4 ---- clang/lib/Basic/IdentifierTable.cpp | 5 ++++- clang/lib/Lex/Lexer.cpp | 21 ++++++++++++--------- clang/lib/Lex/PPDirectives.cpp | 9 +++++---- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index e7319c5808f6f..43c54ba222b85 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -2740,10 +2740,6 @@ class Preprocessor { void removeCachedMacroExpandedTokensOfLastLexer(); - /// Peek the next token. If so, return the token, if not, this - /// method should have no observable side-effect on the lexed tokens. - std::optional peekNextPPToken(); - /// After reading "MACRO(", this method is invoked to read all of the formal /// arguments specified for the macro invocation. Returns null on error. MacroArgs *ReadMacroCallArgumentList(Token &MacroName, MacroInfo *MI, diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 8b740da54c305..5f6d34bd3b7ec 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -465,6 +465,8 @@ tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const { unsigned Len = getLength(); if (Len < 2) return tok::pp_not_keyword; const char *Name = getNameStart(); + + // clang-format off switch (HASH(Len, Name[0], Name[2])) { default: return tok::pp_not_keyword; CASE( 2, 'i', '\0', if); @@ -483,7 +485,7 @@ tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const { CASE( 6, 'd', 'f', define); CASE( 6, 'i', 'n', ifndef); CASE( 6, 'i', 'p', import); - CASE(6, 'm', 'd', module); + CASE( 6, 'm', 'd', module); CASE( 6, 'p', 'a', pragma); CASE( 7, 'd', 'f', defined); @@ -503,6 +505,7 @@ tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const { #undef CASE #undef HASH } + // clang-format on } //===----------------------------------------------------------------------===// diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index d3b9be017ab09..66168b57c3055 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -3748,7 +3748,6 @@ bool Lexer::Lex(Token &Result) { bool isRawLex = isLexingRawMode(); (void) isRawLex; bool returnedToken = LexTokenInternal(Result, atPhysicalStartOfLine); - // (After the LexTokenInternal call, the lexer might be destroyed.) assert((returnedToken || !isRawLex) && "Raw lex must succeed"); return returnedToken; @@ -4046,8 +4045,8 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // Notify MIOpt that we read a non-whitespace/non-comment token. MIOpt.ReadToken(); bool returnedToken = LexIdentifierContinue(Result, CurPtr); - if (returnedToken && Result.isModuleContextualKeyword() && - LangOpts.CPlusPlusModules && + if (returnedToken && LangOpts.CPlusPlusModules && + Result.isModuleContextualKeyword() && PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine) && !LexingRawMode && !Is_PragmaLexer) goto HandleDirective; @@ -4255,8 +4254,12 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // it's actually the start of a preprocessing directive. Callback to // the preprocessor to handle it. // TODO: -fpreprocessed mode?? - if (TokAtPhysicalStartOfLine && !LexingRawMode && !Is_PragmaLexer) + if (TokAtPhysicalStartOfLine && !LexingRawMode && !Is_PragmaLexer) { + // We parsed a # character and it's the start of a preprocessing + // directive. + FormTokenWithChars(Result, CurPtr, tok::hash); goto HandleDirective; + } Kind = tok::hash; } @@ -4443,8 +4446,12 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // it's actually the start of a preprocessing directive. Callback to // the preprocessor to handle it. // TODO: -fpreprocessed mode?? - if (TokAtPhysicalStartOfLine && !LexingRawMode && !Is_PragmaLexer) + if (TokAtPhysicalStartOfLine && !LexingRawMode && !Is_PragmaLexer) { + // We parsed a # character and it's the start of a preprocessing + // directive. + FormTokenWithChars(Result, CurPtr, tok::hash); goto HandleDirective; + } Kind = tok::hash; } @@ -4534,9 +4541,6 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { return true; HandleDirective: - // We parsed a # character and it's the start of a preprocessing directive. - if (!Result.isOneOf(tok::kw_import, tok::kw_module)) - FormTokenWithChars(Result, CurPtr, tok::hash); PP->HandleDirective(Result); if (PP->hadModuleLoaderFatalFailure()) @@ -4632,7 +4636,6 @@ bool Lexer::LexDependencyDirectiveToken(Token &Result) { } if (Result.isLiteral()) return true; - if (Result.is(tok::colon)) { // Convert consecutive colons to 'tok::coloncolon'. if (*BufferPtr == ':') { diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 1c3a05f8d04dc..c3a78c20f2788 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -43,6 +43,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" #include "llvm/Support/SaveAndRestore.h" @@ -1252,13 +1253,13 @@ void Preprocessor::HandleDirective(Token &Result) { // Save the '#' token in case we need to return it later. Token SavedHash = Result; - bool IsCXX20ImportOrModuleDirective = + bool IsCXXImportOrModule = getLangOpts().CPlusPlusModules && Result.isModuleContextualKeyword(/*AllowExport=*/false); // Read the next token, the directive flavor. This isn't expanded due to // C99 6.10.3p8. - if (!IsCXX20ImportOrModuleDirective) + if (LLVM_LIKELY(!IsCXXImportOrModule)) LexUnexpandedToken(Result); // C99 6.10.3p11: Is this preprocessor directive in macro invocation? e.g.: @@ -1280,7 +1281,7 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp_embed: case tok::pp_module: Diag(Result, diag::err_embedded_directive) - << IsCXX20ImportOrModuleDirective << II->getName(); + << IsCXXImportOrModule << II->getName(); Diag(*ArgMacro, diag::note_macro_expansion_here) << ArgMacro->getIdentifierInfo(); DiscardUntilEndOfDirective(); @@ -1375,7 +1376,7 @@ void Preprocessor::HandleDirective(Token &Result) { return HandleCXXModuleDirective(Result); // GNU Extensions. case tok::pp_import: - if (IsCXX20ImportOrModuleDirective) + if (IsCXXImportOrModule) return HandleCXXImportDirective(Result); return HandleImportDirective(SavedHash.getLocation(), Result); case tok::pp_include_next: From 63a62aebbfe8a9c5b5fb7f932a2f91fd46c366aa Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Wed, 9 Jul 2025 23:53:39 +0800 Subject: [PATCH 12/22] Update cxx status Signed-off-by: Wang, Yihan --- clang/www/cxx_status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index bb7144b827c3c..f3c39718be55d 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -931,7 +931,7 @@

C++20 implementation status

P1857R3 - No + Clang 21 P2115R0 From 1171f7f351df6cfa81e303e367597b9532fe0f1c Mon Sep 17 00:00:00 2001 From: yronglin Date: Tue, 15 Jul 2025 23:46:04 +0800 Subject: [PATCH 13/22] Fix crash Signed-off-by: yronglin --- clang/lib/Lex/Lexer.cpp | 8 ++++---- clang/lib/Lex/PPDirectives.cpp | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index b47555699f1aa..c1dbf4d901007 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -4046,10 +4046,10 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // Notify MIOpt that we read a non-whitespace/non-comment token. MIOpt.ReadToken(); bool returnedToken = LexIdentifierContinue(Result, CurPtr); - if (returnedToken && LangOpts.CPlusPlusModules && - Result.isModuleContextualKeyword() && - PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine) && - !LexingRawMode && !Is_PragmaLexer) + if (returnedToken && Result.isModuleContextualKeyword() && + LangOpts.CPlusPlusModules && !LexingRawMode && !Is_PragmaLexer && + !ParsingPreprocessorDirective && PP && + PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine)) goto HandleDirective; return returnedToken; } diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index c3a78c20f2788..1c3a05f8d04dc 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -43,7 +43,6 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" -#include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" #include "llvm/Support/SaveAndRestore.h" @@ -1253,13 +1252,13 @@ void Preprocessor::HandleDirective(Token &Result) { // Save the '#' token in case we need to return it later. Token SavedHash = Result; - bool IsCXXImportOrModule = + bool IsCXX20ImportOrModuleDirective = getLangOpts().CPlusPlusModules && Result.isModuleContextualKeyword(/*AllowExport=*/false); // Read the next token, the directive flavor. This isn't expanded due to // C99 6.10.3p8. - if (LLVM_LIKELY(!IsCXXImportOrModule)) + if (!IsCXX20ImportOrModuleDirective) LexUnexpandedToken(Result); // C99 6.10.3p11: Is this preprocessor directive in macro invocation? e.g.: @@ -1281,7 +1280,7 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp_embed: case tok::pp_module: Diag(Result, diag::err_embedded_directive) - << IsCXXImportOrModule << II->getName(); + << IsCXX20ImportOrModuleDirective << II->getName(); Diag(*ArgMacro, diag::note_macro_expansion_here) << ArgMacro->getIdentifierInfo(); DiscardUntilEndOfDirective(); @@ -1376,7 +1375,7 @@ void Preprocessor::HandleDirective(Token &Result) { return HandleCXXModuleDirective(Result); // GNU Extensions. case tok::pp_import: - if (IsCXXImportOrModule) + if (IsCXX20ImportOrModuleDirective) return HandleCXXImportDirective(Result); return HandleImportDirective(SavedHash.getLocation(), Result); case tok::pp_include_next: From eabc2a412bc4f11e1df4235d89763f6939a4d84b Mon Sep 17 00:00:00 2001 From: yronglin Date: Thu, 17 Jul 2025 01:13:57 +0800 Subject: [PATCH 14/22] Fix lit test Signed-off-by: yronglin --- clang/test/CXX/module/module.interface/p1.cpp | 4 +--- clang/www/cxx_status.html | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/clang/test/CXX/module/module.interface/p1.cpp b/clang/test/CXX/module/module.interface/p1.cpp index e055d565bb9b8..04c29d927d1d4 100644 --- a/clang/test/CXX/module/module.interface/p1.cpp +++ b/clang/test/CXX/module/module.interface/p1.cpp @@ -35,7 +35,5 @@ namespace N { } //--- impl.cpp -module M; // #M - +module M; // expected-note {{add 'export'}} export int b2; // expected-error {{export declaration can only be used within a module purview}} -// expected-note@#M 2+{{add 'export'}} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index f3c39718be55d..ed310310ff187 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -915,7 +915,7 @@

C++20 implementation status

P1703R1 - Subsumed by P1857 + Subsumed by P1857 P1874R1 @@ -931,7 +931,7 @@

C++20 implementation status

P1857R3 - Clang 21 + Clang 21 P2115R0 From b53318407649c1a4d73ca2f856b7385b4267f987 Mon Sep 17 00:00:00 2001 From: yronglin Date: Thu, 31 Jul 2025 01:53:33 +0800 Subject: [PATCH 15/22] Rebase and fix conflict Signed-off-by: yronglin --- clang/lib/Lex/DependencyDirectivesScanner.cpp | 36 ++++++++++++------- clang/lib/Parse/Parser.cpp | 1 - .../Lex/DependencyDirectivesScannerTest.cpp | 11 +++--- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index 720beac234f9f..4e55e49b34a60 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -542,6 +542,12 @@ static void skipWhitespace(const char *&First, const char *const End) { bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, const char *const End) { + assert(Kind == DirectiveKind::cxx_export_import_decl || + Kind == DirectiveKind::cxx_export_module_decl || + Kind == DirectiveKind::cxx_import_decl || + Kind == DirectiveKind::cxx_module_decl || + Kind == DirectiveKind::decl_at_import); + const char *DirectiveLoc = Input.data() + CurDirToks.front().Offset; for (;;) { // Keep a copy of the First char incase it needs to be reset. @@ -553,7 +559,7 @@ bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, First = Previous; return false; } - if (Tok.is(tok::eof) || (LangOpts.CPlusPlusModules && Tok.is(tok::eod))) + if (Tok.isOneOf(tok::eof, tok::eod)) return reportError( DirectiveLoc, diag::err_dep_source_scanner_missing_semi_after_at_import); @@ -561,21 +567,24 @@ bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, break; } - // Skip extra tokens after semi in C++20 Modules directive. bool IsCXXModules = Kind == DirectiveKind::cxx_export_import_decl || Kind == DirectiveKind::cxx_export_module_decl || Kind == DirectiveKind::cxx_import_decl || Kind == DirectiveKind::cxx_module_decl; - if (IsCXXModules) + if (IsCXXModules) { lexPPDirectiveBody(First, End); - - if (!IsCXXModules) { - if (!isVerticalWhitespace(*First)) - return reportError( - DirectiveLoc, - diag::err_dep_source_scanner_unexpected_tokens_at_import); - skipNewline(First, End); + pushDirective(Kind); + return false; } + + pushDirective(Kind); + skipWhitespace(First, End); + if (First == End) + return false; + if (!isVerticalWhitespace(*First)) + return reportError( + DirectiveLoc, diag::err_dep_source_scanner_unexpected_tokens_at_import); + skipNewline(First, End); return false; } @@ -920,6 +929,10 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { CurDirToks.clear(); }); + // FIXME: Shoule we handle @import as a preprocessing directive? + if (*First == '@') + return lexAt(First, End); + if (*First == '_') { if (isNextIdentifierOrSkipLine("_Pragma", First, End)) return lex_Pragma(First, End); @@ -932,9 +945,6 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { auto ScEx2 = make_scope_exit( [&]() { TheLexer.setParsingPreprocessorDirective(false); }); - if (*First == '@') - return lexAt(First, End); - // Handle module directives for C++20 modules. if (*First == 'i' || *First == 'e' || *First == 'm') return lexModule(First, End); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 6e8fdbb29f122..1639183d65b82 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -2489,7 +2489,6 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, break; } ExpectAndConsumeSemi(diag::err_module_expected_semi); - TryConsumeToken(tok::eod); if (SeenError) return nullptr; diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp index ddc87921ea084..1e1702c0c8d43 100644 --- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp +++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp @@ -640,7 +640,7 @@ TEST(MinimizeSourceToDependencyDirectivesTest, AtImport) { EXPECT_STREQ("@import A;\n", Out.data()); ASSERT_FALSE(minimizeSourceToDependencyDirectives("@import A\n;", Out)); - EXPECT_STREQ("@import A\n;\n", Out.data()); + EXPECT_STREQ("@import A;\n", Out.data()); ASSERT_FALSE(minimizeSourceToDependencyDirectives("@import A.B;\n", Out)); EXPECT_STREQ("@import A.B;\n", Out.data()); @@ -685,18 +685,19 @@ TEST(MinimizeSourceToDependencyDirectivesTest, ImportFailures) { minimizeSourceToDependencyDirectives("@import MACRO(A);\n", Out)); ASSERT_FALSE(minimizeSourceToDependencyDirectives("@import \" \";\n", Out)); - ASSERT_FALSE(minimizeSourceToDependencyDirectives("import \n" + ASSERT_FALSE(minimizeSourceToDependencyDirectives("import ;\n" "@import Foo;", Out)); - EXPECT_STREQ("@import Foo;\n", Out.data()); + EXPECT_STREQ("import;\n@import Foo;\n", Out.data()); ASSERT_FALSE( - minimizeSourceToDependencyDirectives("import \n" + minimizeSourceToDependencyDirectives("import ;\n" "#import \n" "@;\n" "#pragma clang module import Foo", Out)); - EXPECT_STREQ("#import \n" + EXPECT_STREQ("import;\n" + "#import \n" "#pragma clang module import Foo\n", Out.data()); } From 0164d0d0b88d653a2f7f1d9113d8cdedcbd1b820 Mon Sep 17 00:00:00 2001 From: yronglin Date: Fri, 8 Aug 2025 15:44:37 +0800 Subject: [PATCH 16/22] Stash Signed-off-by: yronglin --- clang/lib/Lex/Preprocessor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 555a5d4eaeccd..f3b6746e2eac9 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -876,7 +876,10 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { // Likewise if this is the standard C++ import keyword. if (((LastTokenWasAt && II.isModulesImport()) || Identifier.is(tok::kw_import)) && - !InMacroArgs && !DisableMacroExpansion && + // FIXME: Can we just ignore DisableMacroExpansion here? + // https://github.com/llvm/llvm-project/pull/137665 disable + // macro expansion when current input file is preprocessed. + !InMacroArgs && /*!DisableMacroExpansion &&*/ CurLexerCallback != CLK_CachingLexer) { ModuleImportLoc = Identifier.getLocation(); IsAtImport = true; From 0588245b2d6e8d285a298d840bb1da5bf3b7dfb7 Mon Sep 17 00:00:00 2001 From: yronglin Date: Fri, 22 Aug 2025 20:02:04 +0800 Subject: [PATCH 17/22] Handle @import in preprocessor when -frewrite-imports enabled Signed-off-by: yronglin --- clang/lib/Lex/Preprocessor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 2f70c69347bf8..b04018bfcae6f 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -881,7 +881,8 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { // FIXME: Can we just ignore DisableMacroExpansion here? // https://github.com/llvm/llvm-project/pull/137665 disable // macro expansion when current input file is preprocessed. - !InMacroArgs && /*!DisableMacroExpansion &&*/ + !InMacroArgs && + (!DisableMacroExpansion || MacroExpansionInDirectivesOverride) && CurLexerCallback != CLK_CachingLexer) { ModuleImportLoc = Identifier.getLocation(); IsAtImport = true; From 49281ec661b311aa43e37c514eb595e612d75ffd Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Wed, 27 Aug 2025 23:12:38 +0800 Subject: [PATCH 18/22] Don't use ModuleNameLoc* as ActOnModuleImport Signed-off-by: Wang, Yihan --- clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Parse/Parser.cpp | 4 ++-- clang/lib/Sema/SemaModule.cpp | 15 ++++++--------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 5635a336e0db5..03a5349b1c31f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9870,7 +9870,7 @@ class Sema final : public SemaBase { /// \param IsPartition If the name is for a partition. DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, ModuleNameLoc *PathLoc, + SourceLocation ImportLoc, ModuleIdPath Path, bool IsPartition = false); DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 3c2d9342f0407..9595cccd93a89 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -2501,8 +2501,8 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, HeaderUnit); else if (Path) - Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path, - IsPartition); + Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, + Path->getModuleIdPath(), IsPartition); if (Import.isInvalid()) return nullptr; diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 6b9a815d35997..e06371ed56300 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -563,8 +563,8 @@ Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc, DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, - ModuleNameLoc *PathLoc, bool IsPartition) { + SourceLocation ImportLoc, ModuleIdPath Path, + bool IsPartition) { assert((!IsPartition || getLangOpts().CPlusPlusModules) && "partition seen in non-C++20 code?"); @@ -573,7 +573,6 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, IdentifierLoc ModuleNameLoc; std::string ModuleName; - ModuleIdPath Path; if (IsPartition) { // We already checked that we are in a module purview in the parser. assert(!ModuleScopes.empty() && "in a module purview, but no module?"); @@ -582,17 +581,15 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, // otherwise, the name of the importing named module. ModuleName = NamedMod->getPrimaryModuleInterfaceName().str(); ModuleName += ":"; - ModuleName += PathLoc->str(); + ModuleName += ModuleNameLoc::stringFromModuleIdPath(Path); ModuleNameLoc = - IdentifierLoc(PathLoc->getBeginLoc(), PP.getIdentifierInfo(ModuleName)); + IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); } else if (getLangOpts().CPlusPlusModules) { - ModuleName = PathLoc->str(); + ModuleName = ModuleNameLoc::stringFromModuleIdPath(Path); ModuleNameLoc = - IdentifierLoc(PathLoc->getBeginLoc(), PP.getIdentifierInfo(ModuleName)); + IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); - } else { - Path = PathLoc->getModuleIdPath(); } // Diagnose self-import before attempting a load. From 1c8b28f361ce4cda23d5d46b43a3e1bb1abfd54f Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Fri, 29 Aug 2025 02:15:44 +0800 Subject: [PATCH 19/22] Remove tok::annot_module_name and added error recovery to continue parsing the ill-formed module/import directive which was not correctly recognized in preprocessor Signed-off-by: Wang, Yihan --- .../include/clang/Basic/DiagnosticLexKinds.td | 9 -- .../clang/Basic/DiagnosticParseKinds.td | 72 ++++++---- clang/include/clang/Basic/TokenKinds.def | 3 - clang/include/clang/Basic/TokenKinds.h | 4 + clang/include/clang/Lex/ModuleLoader.h | 1 + clang/include/clang/Lex/Preprocessor.h | 86 ++++-------- clang/include/clang/Parse/Parser.h | 7 +- clang/include/clang/Sema/Sema.h | 1 - clang/lib/Basic/TokenKinds.cpp | 12 ++ .../lib/Frontend/PrintPreprocessedOutput.cpp | 8 +- clang/lib/Lex/PPDirectives.cpp | 110 +++++++-------- clang/lib/Lex/Preprocessor.cpp | 81 ++++------- clang/lib/Lex/TokenConcatenation.cpp | 8 +- clang/lib/Parse/Parser.cpp | 129 +++++++++++++----- clang/lib/Sema/SemaModule.cpp | 8 +- clang/test/CXX/basic/basic.link/p3.cpp | 2 +- .../basic/basic.link/module-declaration.cpp | 2 +- .../dcl.module/dcl.module.import/p1.cppm | 6 +- clang/test/Modules/pr121066.cpp | 3 + 19 files changed, 282 insertions(+), 270 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 7ad5858c33597..4b0411ed66d77 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -989,19 +989,10 @@ def warn_module_conflict : Warning< InGroup; // C++20 modules -def err_pp_expected_module_name_or_header_name - : Error<"expected module name or header name">; -def err_pp_expected_semi_after_module_or_import - : Error<"'%select{module|import}0' directive must end with a ';' on the " - "same line">; def err_module_decl_in_header : Error<"module declaration must not come from an #include directive">; def err_pp_cond_span_module_decl : Error<"preprocessor conditionals shall not span a module declaration">; -def err_pp_module_expected_ident - : Error<"expected a module name after '%select{module|import}0'">; -def err_pp_unsupported_module_partition - : Error<"module partitions are only supported for C++20 onwards">; def err_header_import_semi_in_macro : Error< "semicolon terminating header import declaration cannot be produced " "by a macro">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index cf43447c53f13..d579fbdc80828 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1775,36 +1775,48 @@ def ext_bit_int : Extension< } // end of Parse Issue category. let CategoryName = "Modules Issue" in { - def err_unexpected_module_import_decl - : Error<"%select{module|import}0 declaration can only appear at the top " - "level">; - def err_module_expected_ident - : Error<"expected a module name after '%select{module|import}0'">; - def err_attribute_not_module_attr - : Error<"%0 attribute cannot be applied to a module">; - def err_keyword_not_module_attr : Error<"%0 cannot be applied to a module">; - def err_attribute_not_import_attr - : Error<"%0 attribute cannot be applied to a module import">; - def err_keyword_not_import_attr - : Error<"%0 cannot be applied to a module import">; - def err_module_expected_semi : Error<"expected ';' after module name">; - def err_global_module_introducer_not_at_start - : Error<"'module;' introducing a global module fragment can appear only " - "at the start of the translation unit">; - def err_module_fragment_exported - : Error<"%select{global|private}0 module fragment cannot be exported">; - def err_private_module_fragment_expected_semi - : Error<"expected ';' after private module fragment declaration">; - def err_missing_before_module_end : Error<"expected %0 at end of module">; - def err_import_not_allowed_here - : Error<"imports must immediately follow the module declaration">; - def err_partition_import_outside_module - : Error<"module partition imports must be within a module purview">; - def err_import_in_wrong_fragment - : Error<"module%select{| partition}0 imports cannot be in the " - "%select{global|private}1 module fragment">; - - def err_export_empty : Error<"export declaration cannot be empty">; +def err_invalid_module_or_import_directive : Error< + "the %select{module|import}0 directive is ill-formed, " + "%select{module contextual keyword must be followed by an identifier " + "or a ';' after being at the start of a line, or preceded by " + "an export keyword at the start of a line|" + "import contextual keyword must be followed by an identifier, " + "'<', '\"', or ':', but not '::', after being at the start of a " + "line or preceded by an export at the start of the line}0">; +def err_unexpected_module_or_import_decl : Error< + "%select{module|import}0 declaration can only appear at the top level">; +def err_module_expected_ident : Error< + "expected %select{identifier after '.' in |}0module name">; +def err_attribute_not_module_attr : Error< + "%0 attribute cannot be applied to a module">; +def err_keyword_not_module_attr : Error< + "%0 cannot be applied to a module">; +def err_attribute_not_import_attr : Error< + "%0 attribute cannot be applied to a module import">; +def err_keyword_not_import_attr : Error< + "%0 cannot be applied to a module import">; +def err_module_expected_semi : Error< + "expected ';' after module name">; +def err_expected_semi_after_module_or_import + : Error<"'%0' directive must end with a ';' on the same line">; +def err_global_module_introducer_not_at_start : Error< + "'module;' introducing a global module fragment can appear only " + "at the start of the translation unit">; +def err_module_fragment_exported : Error< + "%select{global|private}0 module fragment cannot be exported">; +def err_private_module_fragment_expected_semi : Error< + "expected ';' after private module fragment declaration">; +def err_missing_before_module_end : Error<"expected %0 at end of module">; +def err_unsupported_module_partition : Error< + "module partitions are only supported for C++20 onwards">; +def err_import_not_allowed_here : Error< + "imports must immediately follow the module declaration">; +def err_partition_import_outside_module : Error< + "module partition imports must be within a module purview">; +def err_import_in_wrong_fragment : Error< + "module%select{| partition}0 imports cannot be in the %select{global|private}1 module fragment">; + +def err_export_empty : Error<"export declaration cannot be empty">; } let CategoryName = "Generics Issue" in { diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 7750c84dbef78..9df539b7eea09 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -1026,9 +1026,6 @@ ANNOTATION(module_include) ANNOTATION(module_begin) ANNOTATION(module_end) -// Annotations for C++, Clang and Objective-C named modules. -ANNOTATION(module_name) - // Annotation for a header_name token that has been looked up and transformed // into the name of a header unit. ANNOTATION(header_unit) diff --git a/clang/include/clang/Basic/TokenKinds.h b/clang/include/clang/Basic/TokenKinds.h index d84f3598cbf33..f0d11d43bdf97 100644 --- a/clang/include/clang/Basic/TokenKinds.h +++ b/clang/include/clang/Basic/TokenKinds.h @@ -76,6 +76,10 @@ const char *getPunctuatorSpelling(TokenKind Kind) LLVM_READNONE; /// tokens like 'int' and 'dynamic_cast'. Returns NULL for other token kinds. const char *getKeywordSpelling(TokenKind Kind) LLVM_READNONE; +/// Determines the spelling of simple Objective-C keyword tokens like '@import'. +/// Returns NULL for other token kinds. +const char *getObjCKeywordSpelling(ObjCKeywordKind Kind) LLVM_READNONE; + /// Returns the spelling of preprocessor keywords, such as "else". const char *getPPKeywordSpelling(PPKeywordKind Kind) LLVM_READNONE; diff --git a/clang/include/clang/Lex/ModuleLoader.h b/clang/include/clang/Lex/ModuleLoader.h index a58407200c41c..042a5ab1f4a57 100644 --- a/clang/include/clang/Lex/ModuleLoader.h +++ b/clang/include/clang/Lex/ModuleLoader.h @@ -159,6 +159,7 @@ class ModuleLoader { /// \returns Returns true if any modules with that symbol found. virtual bool lookupMissingImports(StringRef Name, SourceLocation TriggerLoc) = 0; + static std::string getFlatNameFromPath(ModuleIdPath Path); bool HadFatalFailure = false; }; diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 727d2000f3d95..47c62ae9c36d5 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -83,7 +83,6 @@ class PreprocessorLexer; class PreprocessorOptions; class ScratchBuffer; class TargetInfo; -class ModuleNameLoc; class NoTrivialPPDirectiveTracer; namespace Builtin { @@ -592,7 +591,12 @@ class Preprocessor { reset(); } - void handleModuleName(ModuleNameLoc *Path); + void handleIdentifier(IdentifierInfo *Identifier) { + if (isModuleCandidate() && Identifier) + Name += Identifier->getName().str(); + else if (!isNamedModule()) + reset(); + } void handleColon() { if (isModuleCandidate()) @@ -601,6 +605,13 @@ class Preprocessor { reset(); } + void handlePeriod() { + if (isModuleCandidate()) + Name += "."; + else if (!isNamedModule()) + reset(); + } + void handleSemi() { if (!Name.empty() && isModuleCandidate()) { if (State == InterfaceCandidate) @@ -1791,6 +1802,7 @@ class Preprocessor { std::optional LexEmbedParameters(Token &Current, bool ForHasEmbed); bool LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, + SmallVectorImpl &Suffix, SmallVectorImpl &Path, bool AllowMacroExpansion = true); void EnterModuleSuffixTokenStream(ArrayRef Toks); @@ -1814,7 +1826,10 @@ class Preprocessor { } bool LexAfterModuleImport(Token &Result); - void CollectPpImportSuffix(SmallVectorImpl &Toks); + void CollectPPImportSuffix(SmallVectorImpl &Toks, + bool StopUntilEOD = false); + bool CollectPPImportSuffixAndEnterStream(SmallVectorImpl &Toks, + bool StopUntilEOD = false); void makeModuleVisible(Module *M, SourceLocation Loc, bool IncludeExports = true); @@ -2430,20 +2445,27 @@ class Preprocessor { /// If \p EnableMacros is true, then we consider macros that expand to zero /// tokens as being ok. /// + /// If \p ExtraToks not null, the extra tokens will be saved in this + /// container. + /// /// \return The location of the end of the directive (the terminating /// newline). - SourceLocation CheckEndOfDirective(StringRef DirType, - bool EnableMacros = false); + SourceLocation + CheckEndOfDirective(StringRef DirType, bool EnableMacros = false, + SmallVectorImpl *ExtraToks = nullptr); /// Read and discard all tokens remaining on the current line until /// the tok::eod token is found. Returns the range of the skipped tokens. - SourceRange DiscardUntilEndOfDirective() { + SourceRange + DiscardUntilEndOfDirective(SmallVectorImpl *DiscardedToks = nullptr) { Token Tmp; - return DiscardUntilEndOfDirective(Tmp); + return DiscardUntilEndOfDirective(Tmp, DiscardedToks); } /// Same as above except retains the token that was found. - SourceRange DiscardUntilEndOfDirective(Token &Tok); + SourceRange + DiscardUntilEndOfDirective(Token &Tok, + SmallVectorImpl *DiscardedToks = nullptr); /// Returns true if the preprocessor has seen a use of /// __DATE__ or __TIME__ in the file so far. @@ -3182,54 +3204,6 @@ struct EmbedAnnotationData { StringRef FileName; }; -/// Represents module name annotation data. -/// -/// module-name: -/// module-name-qualifier[opt] identifier -/// -/// partition-name: [C++20] -/// : module-name-qualifier[opt] identifier -/// -/// module-name-qualifier -/// module-name-qualifier[opt] identifier . -class ModuleNameLoc final - : llvm::TrailingObjects { - friend TrailingObjects; - unsigned NumIdentifierLocs; - - unsigned numTrailingObjects(OverloadToken) const { - return getNumIdentifierLocs(); - } - - ModuleNameLoc(ModuleIdPath Path) : NumIdentifierLocs(Path.size()) { - (void)llvm::copy(Path, getTrailingObjectsNonStrict()); - } - -public: - static std::string stringFromModuleIdPath(ModuleIdPath Path); - static ModuleNameLoc *Create(Preprocessor &PP, ModuleIdPath Path); - static Token CreateAnnotToken(Preprocessor &PP, ModuleIdPath Path); - unsigned getNumIdentifierLocs() const { return NumIdentifierLocs; } - ModuleIdPath getModuleIdPath() const { - return {getTrailingObjectsNonStrict(), - getNumIdentifierLocs()}; - } - - SourceLocation getBeginLoc() const { - return getModuleIdPath().front().getLoc(); - } - SourceLocation getEndLoc() const { - auto &Last = getModuleIdPath().back(); - return Last.getLoc().getLocWithOffset( - Last.getIdentifierInfo()->getLength()); - } - SourceRange getRange() const { return {getBeginLoc(), getEndLoc()}; } - - std::string str() const; - void print(llvm::raw_ostream &OS) const; - void dump() const { print(llvm::errs()); } -}; - /// Registry of pragma handlers added by plugins using PragmaHandlerRegistry = llvm::Registry; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index a50d35148869f..7ff72d1662c8c 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -566,10 +566,6 @@ class Parser : public CodeCompletionHandler { /// Contextual keywords for Microsoft extensions. IdentifierInfo *Ident__except; - // C++2a contextual keywords. - mutable IdentifierInfo *Ident_import; - mutable IdentifierInfo *Ident_module; - std::unique_ptr CommentSemaHandler; /// Gets set to true after calling ProduceSignatureHelp, it is for a @@ -1081,6 +1077,9 @@ class Parser : public CodeCompletionHandler { bool ParseModuleName(SourceLocation UseLoc, SmallVectorImpl &Path, bool IsImport); + void DiagnoseInvalidCXXModuleDecl(const Sema::ModuleImportState &ImportState); + void DiagnoseInvalidCXXModuleImport(); + //===--------------------------------------------------------------------===// // Preprocessor code-completion pass-through void CodeCompleteDirective(bool InConditional) override; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 03a5349b1c31f..12112eb1ad225 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -146,7 +146,6 @@ class MangleNumberingContext; typedef ArrayRef ModuleIdPath; class ModuleLoader; class MultiLevelTemplateArgumentList; -class ModuleNameLoc; struct NormalizedConstraint; class ObjCInterfaceDecl; class ObjCMethodDecl; diff --git a/clang/lib/Basic/TokenKinds.cpp b/clang/lib/Basic/TokenKinds.cpp index c300175ce90ba..a5b8c998d9b8e 100644 --- a/clang/lib/Basic/TokenKinds.cpp +++ b/clang/lib/Basic/TokenKinds.cpp @@ -46,6 +46,18 @@ const char *tok::getKeywordSpelling(TokenKind Kind) { return nullptr; } +const char *tok::getObjCKeywordSpelling(ObjCKeywordKind Kind) { + switch (Kind) { +#define OBJC_AT_KEYWORD(X) \ + case objc_##X: \ + return "@" #X; +#include "clang/Basic/TokenKinds.def" + default: + break; + } + return nullptr; +} + const char *tok::getPPKeywordSpelling(tok::PPKeywordKind Kind) { switch (Kind) { #define PPKEYWORD(x) case tok::pp_##x: return #x; diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp index 68f736ddffd67..9e046633328d7 100644 --- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp +++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp @@ -758,8 +758,7 @@ void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok, if (Tok.is(tok::eof) || (Tok.isAnnotation() && !Tok.is(tok::annot_header_unit) && !Tok.is(tok::annot_module_begin) && !Tok.is(tok::annot_module_end) && - !Tok.is(tok::annot_repl_input_end) && !Tok.is(tok::annot_embed) && - !Tok.is(tok::annot_module_name))) + !Tok.is(tok::annot_repl_input_end) && !Tok.is(tok::annot_embed))) return; // EmittedDirectiveOnThisLine takes priority over RequireSameLine. @@ -979,11 +978,6 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok, *Callbacks->OS << static_cast(Byte); PrintComma = true; } - } else if (Tok.is(tok::annot_module_name)) { - Tok.getAnnotationValueAs()->print(*Callbacks->OS); - PP.Lex(Tok); - IsStartOfLine = true; - continue; } else if (Tok.isAnnotation()) { // Ignore annotation tokens created by pragmas - the pragmas themselves // will be reproduced in the preprocessed output. diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 6a3c2f067c043..ab239fc01ac1a 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -82,14 +82,19 @@ Preprocessor::AllocateVisibilityMacroDirective(SourceLocation Loc, /// Read and discard all tokens remaining on the current line until /// the tok::eod token is found. -SourceRange Preprocessor::DiscardUntilEndOfDirective(Token &Tmp) { +SourceRange Preprocessor::DiscardUntilEndOfDirective( + Token &Tmp, SmallVectorImpl *DiscardedToks) { SourceRange Res; - - LexUnexpandedToken(Tmp); + auto ReadNextTok = [&]() { + LexUnexpandedToken(Tmp); + if (DiscardedToks && Tmp.isNot(tok::eod)) + DiscardedToks->push_back(Tmp); + }; + ReadNextTok(); Res.setBegin(Tmp.getLocation()); while (Tmp.isNot(tok::eod)) { assert(Tmp.isNot(tok::eof) && "EOF seen while discarding directive tokens"); - LexUnexpandedToken(Tmp); + ReadNextTok(); } Res.setEnd(Tmp.getLocation()); return Res; @@ -439,21 +444,27 @@ void Preprocessor::ReadMacroName(Token &MacroNameTok, MacroUse isDefineUndef, /// true, then we consider macros that expand to zero tokens as being ok. /// /// Returns the location of the end of the directive. -SourceLocation Preprocessor::CheckEndOfDirective(StringRef DirType, - bool EnableMacros) { +SourceLocation +Preprocessor::CheckEndOfDirective(StringRef DirType, bool EnableMacros, + SmallVectorImpl *ExtraToks) { Token Tmp; + auto ReadNextTok = [this, ExtraToks, &Tmp](auto &&LexFn) { + std::invoke(LexFn, this, Tmp); + if (ExtraToks && Tmp.isNot(tok::eod)) + ExtraToks->push_back(Tmp); + }; // Lex unexpanded tokens for most directives: macros might expand to zero // tokens, causing us to miss diagnosing invalid lines. Some directives (like // #line) allow empty macros. if (EnableMacros) - Lex(Tmp); + ReadNextTok(&Preprocessor::Lex); else - LexUnexpandedToken(Tmp); + ReadNextTok(&Preprocessor::LexUnexpandedToken); // There should be no tokens after the directive, but we allow them as an // extension. while (Tmp.is(tok::comment)) // Skip comments in -C mode. - LexUnexpandedToken(Tmp); + ReadNextTok(&Preprocessor::LexUnexpandedToken); if (Tmp.is(tok::eod)) return Tmp.getLocation(); @@ -4104,9 +4115,11 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { Lex(Tok); [[fallthrough]]; case tok::identifier: { - if (LexModuleNameContinue(Tok, UseLoc, Path)) { + if (LexModuleNameContinue(Tok, UseLoc, DirToks, Path)) { if (Tok.isNot(tok::eod)) - CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); + EnterModuleSuffixTokenStream(DirToks); return; } @@ -4118,7 +4131,7 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { FlatName += ":"; } - FlatName += ModuleNameLoc::stringFromModuleIdPath(Path); + FlatName += ModuleLoader::getFlatNameFromPath(Path); SourceLocation StartLoc = IsPartition ? UseLoc : Path[0].getLoc(); IdentifierLoc FlatNameLoc(StartLoc, getIdentifierInfo(FlatName)); @@ -4128,42 +4141,37 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { ModuleIdPath(FlatNameLoc), /*Imported=*/nullptr); } - DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); - DirToks.push_back(Tok); break; } default: - Diag(ImportTok, diag::err_pp_expected_module_name_or_header_name); + DirToks.push_back(Tok); break; } // Consume the pp-import-suffix and expand any macros in it now, if we're not // at the semicolon already. if (!DirToks.back().isOneOf(tok::semi, tok::eod)) - CollectPpImportSuffix(DirToks); - - // This is not a pp-import after all. - if (DirToks.back().isNot(tok::semi)) { - Diag(DirToks.back(), diag::err_pp_expected_semi_after_module_or_import) - << /*IsImport*/ true - << FixItHint::CreateInsertion(DirToks.back().getLocation(), - tok::getPunctuatorSpelling(tok::semi)); - return; - } + CollectPPImportSuffix(DirToks); if (DirToks.back().isNot(tok::eod)) CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); else DirToks.pop_back(); - // C++2a [cpp.module]p1: - // The ';' preprocessing-token terminating a pp-import shall not have - // been produced by macro replacement. - SourceLocation SemiLoc = DirToks.back().getLocation(); - if (SemiLoc.isMacroID()) - Diag(SemiLoc, diag::err_header_import_semi_in_macro); + // This is not a pp-import after all. + if (DirToks.back().isNot(tok::semi)) { + EnterModuleSuffixTokenStream(DirToks); + return; + } if (ImportingHeader) { + // C++2a [cpp.module]p1: + // The ';' preprocessing-token terminating a pp-import shall not have + // been produced by macro replacement. + SourceLocation SemiLoc = DirToks.back().getLocation(); + if (SemiLoc.isMacroID()) + Diag(SemiLoc, diag::err_header_import_semi_in_macro); + auto Action = HandleHeaderIncludeOrImport( /*HashLoc*/ SourceLocation(), ImportTok, Tok, SemiLoc); switch (Action.Kind) { @@ -4239,11 +4247,10 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { DirToks.push_back(Tok); LexUnexpandedToken(Tok); if (Tok.isNot(tok::kw_private)) { - Diag(DirToks.back().getLocation(), diag::err_pp_module_expected_ident) - << /*IsImport=*/false - << FixItHint::CreateReplacement( - {Tok.getLocation(), Tok.getEndLoc()}, - tok::getKeywordSpelling(tok::kw_private)); + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); + EnterModuleSuffixTokenStream(DirToks); return; } DirToks.push_back(Tok); @@ -4254,57 +4261,46 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { // normal text. // // P3034R1 Module Declarations Shouldn’t be Macros. - if (LexModuleNameContinue(Tok, UseLoc, Path, + if (LexModuleNameContinue(Tok, UseLoc, DirToks, Path, /*AllowMacroExpansion=*/false)) { if (Tok.isNot(tok::eod)) - CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); + EnterModuleSuffixTokenStream(DirToks); return; } - DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); - // C++20 [cpp.module]p // The pp-tokens, if any, of a pp-module shall be of the form: // pp-module-name pp-module-partition[opt] pp-tokens[opt] if (Tok.is(tok::colon)) { - DirToks.push_back(Tok); LexUnexpandedToken(Tok); - if (LexModuleNameContinue(Tok, UseLoc, Partition)) { + if (LexModuleNameContinue(Tok, UseLoc, DirToks, Partition)) { if (Tok.isNot(tok::eod)) CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + EnterModuleSuffixTokenStream(DirToks); return; } - DirToks.push_back(ModuleNameLoc::CreateAnnotToken(*this, Partition)); } - DirToks.push_back(Tok); break; } default: + DirToks.push_back(Tok); break; - ; } // Consume the pp-import-suffix and expand any macros in it now, if we're not // at the semicolon already. SourceLocation End = DirToks.back().getLocation(); if (!DirToks.back().isOneOf(tok::semi, tok::eod)) { - CollectPpImportSuffix(DirToks); + CollectPPImportSuffix(DirToks); End = DirToks.back().getLocation(); } - // This is not a pp-import after all. - if (DirToks.back().isNot(tok::semi)) { - Diag(DirToks.back(), diag::err_pp_expected_semi_after_module_or_import) - << /*IsImport*/ false - << FixItHint::CreateInsertion(DirToks.back().getLocation(), - tok::getPunctuatorSpelling(tok::semi)); - return; - } - if (DirToks.back().isNot(tok::eod)) - End = CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + End = CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); else End = DirToks.pop_back_val().getLocation(); - EnterModuleSuffixTokenStream(DirToks); } diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index b04018bfcae6f..95eaa633d6d9f 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -891,13 +891,6 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { return true; } -void Preprocessor::ModuleDeclSeq::handleModuleName(ModuleNameLoc *Path) { - if (isModuleCandidate() && Path) - Name += Path->str(); - else if (!isNamedModule()) - reset(); -} - void Preprocessor::Lex(Token &Result) { ++LexLevel; @@ -961,6 +954,9 @@ void Preprocessor::Lex(Token &Result) { case tok::colon: ModuleDeclState.handleColon(); break; + case tok::period: + ModuleDeclState.handlePeriod(); + break; case tok::kw_import: if (StdCXXImportSeqState.atTopLevel()) { TrackGMFState.handleImport(StdCXXImportSeqState.afterTopLevelSeq()); @@ -975,9 +971,8 @@ void Preprocessor::Lex(Token &Result) { ModuleDeclState.handleModule(); } break; - case tok::annot_module_name: - ModuleDeclState.handleModuleName( - Result.getAnnotationValueAs()); + case tok::identifier: + ModuleDeclState.handleIdentifier(Result.getIdentifierInfo()); if (ModuleDeclState.isModuleCandidate()) break; [[fallthrough]]; @@ -1120,28 +1115,11 @@ bool Preprocessor::LexHeaderName(Token &FilenameTok, bool AllowMacroExpansion) { return false; } -ModuleNameLoc *ModuleNameLoc::Create(Preprocessor &PP, ModuleIdPath Path) { - assert(!Path.empty() && "expect at least one identifier in a module name"); - void *Mem = PP.getPreprocessorAllocator().Allocate( - totalSizeToAlloc(Path.size()), alignof(ModuleNameLoc)); - return new (Mem) ModuleNameLoc(Path); -} - -Token ModuleNameLoc::CreateAnnotToken(Preprocessor &PP, ModuleIdPath Path) { - auto *NameLoc = Create(PP, Path); - Token ModuleNameTok; - ModuleNameTok.startToken(); - ModuleNameTok.setKind(tok::annot_module_name); - ModuleNameTok.setAnnotationRange(NameLoc->getRange()); - ModuleNameTok.setAnnotationValue(static_cast(NameLoc)); - return ModuleNameTok; -} - // We represent the primary and partition names as 'Paths' which are sections // of the hierarchical access path for a clang module. However for C++20 // the periods in a name are just another character, and we will need to // flatten them into a string. -std::string ModuleNameLoc::stringFromModuleIdPath(ModuleIdPath Path) { +std::string ModuleLoader::getFlatNameFromPath(ModuleIdPath Path) { std::string Name; if (Path.empty()) return Name; @@ -1155,31 +1133,22 @@ std::string ModuleNameLoc::stringFromModuleIdPath(ModuleIdPath Path) { return Name; } -std::string ModuleNameLoc::str() const { - return stringFromModuleIdPath(getModuleIdPath()); -} - -void ModuleNameLoc::print(llvm::raw_ostream &OS) const { OS << str(); } - bool Preprocessor::LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, + SmallVectorImpl &Suffix, SmallVectorImpl &Path, bool AllowMacroExpansion) { auto ConsumeToken = [&]() { - return AllowMacroExpansion ? Lex(Tok) : LexUnexpandedToken(Tok); + if (AllowMacroExpansion) + Lex(Tok); + else + LexUnexpandedToken(Tok); + Suffix.push_back(Tok); }; + Suffix.push_back(Tok); while (true) { - if (Tok.isNot(tok::identifier)) { - if (Tok.is(tok::code_completion)) { - CurLexer->cutOffLexing(); - Tok.setKind(tok::eof); - this->getCodeCompletionHandler()->CodeCompleteModuleImport(UseLoc, - Path); - } - Diag(Tok.getLocation(), diag::err_pp_expected_module_name) - << Path.empty(); + if (Tok.isNot(tok::identifier)) return true; - } // Record this part of the module path. Path.emplace_back(Tok.getLocation(), Tok.getIdentifierInfo()); @@ -1255,8 +1224,16 @@ bool Preprocessor::HandleModuleContextualKeyword( return false; } +bool Preprocessor::CollectPPImportSuffixAndEnterStream( + SmallVectorImpl &Toks, bool StopUntilEOD) { + CollectPPImportSuffix(Toks); + EnterModuleSuffixTokenStream(Toks); + return false; +} + /// Collect the tokens of a C++20 pp-import-suffix. -void Preprocessor::CollectPpImportSuffix(SmallVectorImpl &Toks) { +void Preprocessor::CollectPPImportSuffix(SmallVectorImpl &Toks, + bool StopUntilEOD) { // FIXME: For error recovery, consider recognizing attribute syntax here // and terminating / diagnosing a missing semicolon if we find anything // else? (Can we leave that to the parser?) @@ -1266,6 +1243,9 @@ void Preprocessor::CollectPpImportSuffix(SmallVectorImpl &Toks) { switch (Toks.back().getKind()) { case tok::semi: + if (!StopUntilEOD) + return; + [[fallthrough]]; case tok::eod: case tok::eof: return; @@ -1312,18 +1292,15 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) { SmallVector Suffix; SmallVector Path; Lex(Result); - if (LexModuleNameContinue(Result, ModuleImportLoc, Path)) - return false; - - Suffix.push_back(ModuleNameLoc::CreateAnnotToken(*this, Path)); - Suffix.push_back(Result); + if (LexModuleNameContinue(Result, ModuleImportLoc, Suffix, Path)) + return CollectPPImportSuffixAndEnterStream(Suffix); // Consume the pp-import-suffix and expand any macros in it now, if we're not // at the semicolon already. SourceLocation SemiLoc = Suffix.back().getLocation(); if (Suffix.back().isNot(tok::semi)) { if (Result.isNot(tok::eof)) - CollectPpImportSuffix(Suffix); + CollectPPImportSuffix(Suffix); if (Suffix.back().isNot(tok::semi)) { // This is not an import after all. EnterModuleSuffixTokenStream(Suffix); diff --git a/clang/lib/Lex/TokenConcatenation.cpp b/clang/lib/Lex/TokenConcatenation.cpp index f94caee24dc11..05f4203bd722b 100644 --- a/clang/lib/Lex/TokenConcatenation.cpp +++ b/clang/lib/Lex/TokenConcatenation.cpp @@ -161,8 +161,7 @@ bool TokenConcatenation::AvoidConcat(const Token &PrevPrevTok, const Token &PrevTok, const Token &Tok) const { // No space is required between header unit name in quote and semi. - if (PrevTok.isOneOf(tok::annot_header_unit, tok::annot_module_name) && - Tok.is(tok::semi)) + if (PrevTok.is(tok::annot_header_unit) && Tok.is(tok::semi)) return false; // Conservatively assume that every annotation token that has a printable @@ -198,12 +197,11 @@ bool TokenConcatenation::AvoidConcat(const Token &PrevPrevTok, if (Tok.isAnnotation()) { // Modules annotation can show up when generated automatically for includes. assert(Tok.isOneOf(tok::annot_module_include, tok::annot_module_begin, - tok::annot_module_end, tok::annot_embed, - tok::annot_module_name) && + tok::annot_module_end, tok::annot_embed) && "unexpected annotation in AvoidConcat"); ConcatInfo = 0; - if (Tok.isOneOf(tok::annot_embed, tok::annot_module_name)) + if (Tok.is(tok::annot_embed)) return true; } diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 9595cccd93a89..0156967186931 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -17,6 +17,7 @@ #include "clang/AST/DeclTemplate.h" #include "clang/Basic/DiagnosticParse.h" #include "clang/Basic/StackExhaustionHandler.h" +#include "clang/Basic/TokenKinds.h" #include "clang/Lex/ModuleLoader.h" #include "clang/Parse/RAIIObjectsForParser.h" #include "clang/Sema/DeclSpec.h" @@ -25,6 +26,7 @@ #include "clang/Sema/Scope.h" #include "clang/Sema/SemaCodeCompletion.h" #include "llvm/ADT/STLForwardCompat.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/TimeProfiler.h" using namespace clang; @@ -622,7 +624,25 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, goto module_decl; case tok::kw_import: goto import_decl; - + // Error recovery and recognize context-sensitive C++20 'export module' and + // 'export import' declarations. If the module/import directive is + // well-formed, it should be converted to a keyword in preprocessor, but not + // an identifier we saw here. + // + // FIXME: We should generate better diagnostic information here to explain + // why the module/import directive is ill-formed. + case tok::identifier: { + if (getLangOpts().CPlusPlusModules && + NextToken().isModuleContextualKeyword() && + GetLookAheadToken(2).isNot(tok::coloncolon)) { + if (NextToken().getIdentifierInfo()->isStr( + tok::getKeywordSpelling(tok::kw_module))) + goto module_decl; + else + goto import_decl; + } + break; + } default: break; } @@ -690,7 +710,25 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, Actions.ActOnEndOfTranslationUnit(); //else don't tell Sema that we ended parsing: more input might come. return true; - + case tok::identifier: + // C++20 [basic.link]p3: + // A token sequence beginning with 'export[opt] module' or + // 'export[opt] import' and not immediately followed by '::' + // is never interpreted as the declaration of a top-level-declaration. + // + // Error recovery and recognize context-sensitive C++20 'export module' and + // 'export import' declarations. If the module/import directive is + // well-formed, it should be converted to a keyword in preprocessor, but not + // an identifier we saw here. + if (getLangOpts().CPlusPlusModules && Tok.isModuleContextualKeyword() && + NextToken().isNot(tok::coloncolon)) { + if (Tok.getIdentifierInfo()->isStr( + tok::getKeywordSpelling(tok::kw_module))) + goto module_decl; + else + goto import_decl; + } + break; default: break; } @@ -883,7 +921,8 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs, case tok::kw_import: { Sema::ModuleImportState IS = Sema::ModuleImportState::NotACXX20Module; if (getLangOpts().CPlusPlusModules) { - Diag(Tok, diag::err_unexpected_module_import_decl) << /*IsImport*/ true; + Diag(Tok, diag::err_unexpected_module_or_import_decl) + << /*IsImport*/ true; SkipUntil(tok::semi); return nullptr; } @@ -977,7 +1016,7 @@ Parser::ParseExternalDeclaration(ParsedAttributes &Attrs, return nullptr; case tok::kw_module: - Diag(Tok, diag::err_unexpected_module_import_decl) << /*IsImport*/ false; + Diag(Tok, diag::err_unexpected_module_or_import_decl) << /*IsImport*/ false; SkipUntil(tok::semi); return nullptr; @@ -2319,10 +2358,16 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { ? Sema::ModuleDeclKind::Interface : Sema::ModuleDeclKind::Implementation; - assert( - (Tok.is(tok::kw_module) || - (Tok.is(tok::identifier) && Tok.getIdentifierInfo() == Ident_module)) && - "not a module declaration"); + assert((Tok.is(tok::kw_module) || + (Tok.is(tok::identifier) && + Tok.getIdentifierInfo()->isStr( + tok::getKeywordSpelling(tok::kw_module)))) && + "not a module declaration"); + + if (getLangOpts().CPlusPlusModules && Tok.is(tok::identifier)) + Diag(StartLoc, diag::err_invalid_module_or_import_directive) + << /*IsImport=*/false; + SourceLocation ModuleLoc = ConsumeToken(); // Attributes appear after the module name, not before. @@ -2363,20 +2408,20 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { return Actions.ActOnPrivateModuleFragmentDecl(ModuleLoc, PrivateLoc); } - ModuleNameLoc *Path = nullptr; - if (Tok.isNot(tok::annot_module_name)) + SmallVector Path; + if (ParseModuleName(ModuleLoc, Path, /*IsImport*/ false)) return nullptr; - Path = Tok.getAnnotationValueAs(); - ConsumeAnnotationToken(); // Parse the optional module-partition. - ModuleNameLoc *Partition = nullptr; + SmallVector Partition; if (Tok.is(tok::colon)) { - ConsumeToken(); - if (Tok.isNot(tok::annot_module_name)) + SourceLocation ColonLoc = ConsumeToken(); + if (!getLangOpts().CPlusPlusModules) + Diag(ColonLoc, diag::err_unsupported_module_partition) + << SourceRange(ColonLoc, Partition.back().getLoc()); + // Recover by ignoring the partition name. + else if (ParseModuleName(ModuleLoc, Partition, /*IsImport*/ false)) return nullptr; - Partition = Tok.getAnnotationValueAs(); - ConsumeAnnotationToken(); } // We don't support any module attributes yet; just parse them and diagnose. @@ -2387,12 +2432,12 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) { /*DiagnoseEmptyAttrs=*/false, /*WarnOnUnknownAttrs=*/true); - ExpectAndConsumeSemi(diag::err_module_expected_semi); + ExpectAndConsumeSemi(diag::err_expected_semi_after_module_or_import, + tok::getKeywordSpelling(tok::kw_module)); - return Actions.ActOnModuleDecl( - StartLoc, ModuleLoc, MDK, Path->getModuleIdPath(), - Partition ? Partition->getModuleIdPath() : ModuleIdPath{}, ImportState, - Introducer.hasSeenNoTrivialPPDirective()); + return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, Partition, + ImportState, + Introducer.hasSeenNoTrivialPPDirective()); } Decl *Parser::ParseModuleImport(SourceLocation AtLoc, @@ -2406,10 +2451,16 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, : Tok.isObjCAtKeyword(tok::objc_import)) && "Improper start to module import"); bool IsObjCAtImport = Tok.isObjCAtKeyword(tok::objc_import); + if (getLangOpts().CPlusPlusModules && !IsObjCAtImport && + Tok.is(tok::identifier)) { + Diag(StartLoc, diag::err_invalid_module_or_import_directive) + << /*IsImport=*/true; + } + SourceLocation ImportLoc = ConsumeToken(); // For C++20 modules, we can have "name" or ":Partition name" as valid input. - ModuleNameLoc *Path = nullptr; + SmallVector Path; bool IsPartition = false; Module *HeaderUnit = nullptr; if (Tok.is(tok::header_name)) { @@ -2422,17 +2473,18 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, HeaderUnit = reinterpret_cast(Tok.getAnnotationValue()); ConsumeAnnotationToken(); } else if (Tok.is(tok::colon)) { - ConsumeToken(); - if (Tok.isNot(tok::annot_module_name)) + SourceLocation ColonLoc = ConsumeToken(); + if (!getLangOpts().CPlusPlusModules) + Diag(ColonLoc, diag::err_unsupported_module_partition) + << SourceRange(ColonLoc, Path.back().getLoc()); + // Recover by leaving partition empty. + else if (ParseModuleName(ColonLoc, Path, /*IsImport=*/true)) return nullptr; - IsPartition = true; - Path = Tok.getAnnotationValueAs(); - ConsumeAnnotationToken(); + else + IsPartition = true; } else { - if (Tok.isNot(tok::annot_module_name)) + if (ParseModuleName(ImportLoc, Path, /*IsImport=*/true)) return nullptr; - Path = Tok.getAnnotationValueAs(); - ConsumeAnnotationToken(); } ParsedAttributes Attrs(AttrFactory); @@ -2491,7 +2543,12 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, SeenError = false; break; } - ExpectAndConsumeSemi(diag::err_module_expected_semi); + + if (getLangOpts().CPlusPlusModules) + ExpectAndConsumeSemi(diag::err_expected_semi_after_module_or_import, + tok::getKeywordSpelling(tok::kw_import)); + else + ExpectAndConsumeSemi(diag::err_module_expected_semi); if (SeenError) return nullptr; @@ -2500,9 +2557,9 @@ Decl *Parser::ParseModuleImport(SourceLocation AtLoc, if (HeaderUnit) Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, HeaderUnit); - else if (Path) - Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, - Path->getModuleIdPath(), IsPartition); + else if (!Path.empty()) + Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path, + IsPartition); if (Import.isInvalid()) return nullptr; @@ -2531,7 +2588,7 @@ bool Parser::ParseModuleName(SourceLocation UseLoc, return true; } - Diag(Tok, diag::err_module_expected_ident) << IsImport; + Diag(Tok, diag::err_module_expected_ident) << Path.empty(); SkipUntil(tok::semi); return true; } diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index e06371ed56300..1940f3eb6e746 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -357,10 +357,10 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, // Flatten the dots in a module name. Unlike Clang's hierarchical module map // modules, the dots here are just another character that can appear in a // module name. - std::string ModuleName = ModuleNameLoc::stringFromModuleIdPath(Path); + std::string ModuleName = ModuleLoader::getFlatNameFromPath(Path); if (IsPartition) { ModuleName += ":"; - ModuleName += ModuleNameLoc::stringFromModuleIdPath(Partition); + ModuleName += ModuleLoader::getFlatNameFromPath(Partition); } // If a module name was explicitly specified on the command line, it must be // correct. @@ -581,12 +581,12 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, // otherwise, the name of the importing named module. ModuleName = NamedMod->getPrimaryModuleInterfaceName().str(); ModuleName += ":"; - ModuleName += ModuleNameLoc::stringFromModuleIdPath(Path); + ModuleName += ModuleLoader::getFlatNameFromPath(Path); ModuleNameLoc = IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); } else if (getLangOpts().CPlusPlusModules) { - ModuleName = ModuleNameLoc::stringFromModuleIdPath(Path); + ModuleName = ModuleLoader::getFlatNameFromPath(Path); ModuleNameLoc = IdentifierLoc(Path[0].getLoc(), PP.getIdentifierInfo(ModuleName)); Path = ModuleIdPath(ModuleNameLoc); diff --git a/clang/test/CXX/basic/basic.link/p3.cpp b/clang/test/CXX/basic/basic.link/p3.cpp index 5661738455a62..f389b07d39883 100644 --- a/clang/test/CXX/basic/basic.link/p3.cpp +++ b/clang/test/CXX/basic/basic.link/p3.cpp @@ -45,7 +45,7 @@ constexpr int n = 123; export module m; // #1 -import x = {}; // expected-error {{expected ';' after module name}} +import x = {}; // expected-error {{'import' directive must end with a ';' on the same line}} // expected-error@-1 {{module 'x' not found}} //--- ImportError2.cpp diff --git a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp index 4bdcc9e5f278e..10254cd064de0 100644 --- a/clang/test/CXX/module/basic/basic.link/module-declaration.cpp +++ b/clang/test/CXX/module/basic/basic.link/module-declaration.cpp @@ -46,7 +46,7 @@ export module z; export module x; //--- invalid_module_name.cppm -export module z elderberry; // expected-error {{expected ';'}} \ +export module z elderberry; // expected-error {{'module' directive must end with a ';' on the same line}} \ // expected-error {{a type specifier is required}} //--- empty_attribute.cppm diff --git a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm index 53fd3ea29eccb..c4842d2642f41 100644 --- a/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm +++ b/clang/test/CXX/module/dcl.dcl/dcl.module/dcl.module.import/p1.cppm @@ -45,8 +45,7 @@ import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg: import x.y; import x.; // expected-error {{expected identifier after '.' in module name}} -import .x; // expected-error {{unknown type name 'import'}} \ - // expected-error {{cannot use dot operator on a type}} +import .x; // expected-error-re {{the import directive is ill-formed{{.*}}}} expected-error {{expected module name}} import blarg; // expected-error {{module 'blarg' not found}} @@ -64,8 +63,7 @@ import x [[blarg::noreturn]]; // expected-warning-re {{unknown attribute 'blarg: import x.y; import x.; // expected-error {{expected identifier after '.' in module name}} -import .x; // expected-error {{unknown type name 'import'}} \ - // expected-error {{cannot use dot operator on a type}} +import .x; // expected-error-re {{the import directive is ill-formed{{.*}}}} expected-error {{expected module name}} import blarg; // expected-error {{module 'blarg' not found}} diff --git a/clang/test/Modules/pr121066.cpp b/clang/test/Modules/pr121066.cpp index 849488e938d50..33bedd22ac487 100644 --- a/clang/test/Modules/pr121066.cpp +++ b/clang/test/Modules/pr121066.cpp @@ -1,3 +1,6 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only %s -verify +// This import directive is ill-formed, it's missing an ';' after +// module name, but we try to recovery from error and import the module. import mod // expected-error {{'import' directive must end with a ';' on the same line}} + // expected-error@-1 {{module 'mod' not found}} From 14ba684320993c3c7895ef6454794ac4b24aa63d Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Sun, 31 Aug 2025 16:00:29 +0800 Subject: [PATCH 20/22] Add test for pp-module and pp-import Signed-off-by: Wang, Yihan --- .../clang/Basic/DiagnosticParseKinds.td | 10 ++- clang/test/CXX/module/cpp.pre/p1.cpp | 75 +++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 clang/test/CXX/module/cpp.pre/p1.cpp diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index d579fbdc80828..529e948912330 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1777,12 +1777,14 @@ def ext_bit_int : Extension< let CategoryName = "Modules Issue" in { def err_invalid_module_or_import_directive : Error< "the %select{module|import}0 directive is ill-formed, " - "%select{module contextual keyword must be followed by an identifier " + "%select{module contextual keyword must be immediately " + "followed on the same line by an identifier, " "or a ';' after being at the start of a line, or preceded by " "an export keyword at the start of a line|" - "import contextual keyword must be followed by an identifier, " - "'<', '\"', or ':', but not '::', after being at the start of a " - "line or preceded by an export at the start of the line}0">; + "import contextual keyword must be immediately followed " + "on the same line by an identifier, '<', '\"', or ':', but not '::', " + "after being at the start of a line or preceded by an export at " + "the start of the line}0">; def err_unexpected_module_or_import_decl : Error< "%select{module|import}0 declaration can only appear at the top level">; def err_module_expected_ident : Error< diff --git a/clang/test/CXX/module/cpp.pre/p1.cpp b/clang/test/CXX/module/cpp.pre/p1.cpp new file mode 100644 index 0000000000000..a3928f3753e16 --- /dev/null +++ b/clang/test/CXX/module/cpp.pre/p1.cpp @@ -0,0 +1,75 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 %t/hash.cpp -fsyntax-only -verify +// RUN: %clang_cc1 -std=c++20 %t/module.cpp -fsyntax-only -verify + +// RUN: %clang_cc1 -std=c++20 %t/rightpad.cppm -emit-module-interface -o %t/rightpad.pcm +// RUN: %clang_cc1 -std=c++20 %t/M_part.cppm -emit-module-interface -o %t/M_part.pcm +// RUN: %clang_cc1 -std=c++20 -xc++-system-header %t/string -emit-header-unit -o %t/string.pcm +// RUN: %clang_cc1 -std=c++20 -xc++-user-header %t/squee -emit-header-unit -o %t/squee.pcm +// RUN: %clang_cc1 -std=c++20 %t/import.cpp -isystem %t \ +// RUN: -fmodule-file=rightpad=%t/rightpad.pcm \ +// RUN: -fmodule-file=M:part=%t/M_part.pcm \ +// RUN: -fmodule-file=%t/string.pcm \ +// RUN: -fmodule-file=%t/squee.pcm \ +// RUN: -fsyntax-only -verify + +// RUN: %clang_cc1 -std=c++20 %t/module_decl_not_in_same_line.cpp -fsyntax-only -verify +// RUN: %clang_cc1 -std=c++20 %t/foo.cppm -emit-module-interface -o %t/foo.pcm +// RUN: %clang_cc1 -std=c++20 %t/import_decl_not_in_same_line.cpp -fmodule-file=foo=%t/foo.pcm -fsyntax-only -verify +// RUN: %clang_cc1 -std=c++20 %t/not_import.cpp -fsyntax-only -verify + +//--- hash.cpp +// expected-no-diagnostics +# // preprocessing directive + +//--- module.cpp +// expected-no-diagnostics +module ; // preprocessing directive +export module leftpad; // preprocessing directive + +//--- string +#ifndef STRING_H +#define STRING_H +#endif // STRING_H + +//--- squee +#ifndef SQUEE_H +#define SQUEE_H +#endif + +//--- rightpad.cppm +export module rightpad; + +//--- M_part.cppm +export module M:part; + +//--- import.cpp +export module M; +import ; // expected-warning {{the implementation of header units is in an experimental phase}} +export import "squee"; // expected-warning {{the implementation of header units is in an experimental phase}} +import rightpad; // preprocessing directive +import :part; // preprocessing directive + +//--- module_decl_not_in_same_line.cpp +module // expected-error {{the module directive is ill-formed, module contextual keyword must be immediately followed on the same line by an identifier, or a ';' after being at the start of a line, or preceded by an export keyword at the start of a line}} +;export module M; // expected-error {{the module directive is ill-formed, module contextual keyword must be immediately followed on the same line by an identifier, or a ';' after being at the start of a line, or preceded by an export keyword at the start of a line}} + +//--- foo.cppm +export module foo; + +//--- import_decl_not_in_same_line.cpp +export module M; +export // expected-error {{the import directive is ill-formed, import contextual keyword must be immediately followed on the same line by an identifier, '<', '"', or ':', but not '::', after being at the start of a line or preceded by an export at the start of the line}} +import +foo; + +export // expected-error {{the import directive is ill-formed, import contextual keyword must be immediately followed on the same line by an identifier, '<', '"', or ':', but not '::', after being at the start of a line or preceded by an export at the start of the line}} +import foo; + +//--- not_import.cpp +export module M; +import :: // expected-error {{use of undeclared identifier 'import'}} +import -> // expected-error {{cannot use arrow operator on a type}} From 5dc3c9a9735568df3e990695412a26a725a2e3ac Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Tue, 2 Sep 2025 01:16:17 +0800 Subject: [PATCH 21/22] Refine isModuleContextualKeyword and isImportingCXXNamedModules, remove unecessary changes Signed-off-by: Wang, Yihan --- .../AnnotateFunctions/AnnotateFunctions.cpp | 2 +- clang/include/clang/Lex/Preprocessor.h | 10 ++-- clang/include/clang/Lex/Token.h | 6 +-- clang/lib/Basic/IdentifierTable.cpp | 2 +- clang/lib/Lex/Lexer.cpp | 13 +++-- clang/lib/Lex/PPDirectives.cpp | 49 ++++++++++--------- clang/lib/Lex/Preprocessor.cpp | 11 ++--- clang/lib/Lex/TokenLexer.cpp | 2 +- clang/lib/Parse/Parser.cpp | 6 +-- clang/lib/Sema/SemaModule.cpp | 2 - .../DependencyScanning/ModuleDepCollector.cpp | 3 +- 11 files changed, 55 insertions(+), 51 deletions(-) diff --git a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp index 22a3eb97f938b..d872020c2d8a3 100644 --- a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp +++ b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp @@ -65,7 +65,7 @@ class PragmaAnnotateHandler : public PragmaHandler { Token Tok; PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; if (HandledDecl) { DiagnosticsEngine &D = PP.getDiagnostics(); diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 47c62ae9c36d5..362a5c429a26a 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1812,7 +1812,7 @@ class Preprocessor { /// Callback invoked when the lexer sees one of export, import or module token /// at the start of a line. /// - /// This consumes the import, module directive, modifies the + /// This consumes the import/module directive, modifies the /// lexer/preprocessor state, and advances the lexer(s) so that the next token /// read is the correct one. bool HandleModuleContextualKeyword(Token &Result, @@ -2537,11 +2537,15 @@ class Preprocessor { /// If we're importing a standard C++20 Named Modules. bool isImportingCXXNamedModules() const { - return getLangOpts().CPlusPlusModules && ImportingCXXNamedModules; + assert(getLangOpts().CPlusPlusModules && + "Import C++ named modules are only valid for C++20 modules"); + return ImportingCXXNamedModules; } bool isDeclaringCXXNamedModules() const { - return getLangOpts().CPlusPlusModules && DeclaringCXXNamedModules; + assert(getLangOpts().CPlusPlusModules && + "Declare C++ named modules are only valid for C++20 modules"); + return DeclaringCXXNamedModules; } /// Allocate a new MacroInfo object with the provided SourceLocation. diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h index e50b5438042c7..ae9ab99cf00b1 100644 --- a/clang/include/clang/Lex/Token.h +++ b/clang/include/clang/Lex/Token.h @@ -233,9 +233,6 @@ class Token { PtrData = const_cast(Ptr); } - template T getAnnotationValueAs() const { - return static_cast(getAnnotationValue()); - } void *getAnnotationValue() const { assert(isAnnotation() && "Used AnnotVal on non-annotation token"); return PtrData; @@ -296,7 +293,8 @@ class Token { /// Return true if we have an C++20 Modules contextual keyword(export, import /// or module). - bool isModuleContextualKeyword(bool AllowExport = true) const; + bool isModuleContextualKeyword(const LangOptions &LangOpts, + bool AllowExport = true) const; bool isSimpleTypeSpecifier(const LangOptions &LangOpts) const; diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 5f6d34bd3b7ec..3a4a3d379e021 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -349,7 +349,7 @@ void IdentifierTable::AddKeywords(const LangOptions &LangOpts) { if (LangOpts.IEEE128) AddKeyword("__ieee128", tok::kw___float128, KEYALL, LangOpts, *this); - // Add the 'import' and 'module' contextual keyword. + // Add the 'import' and 'module' contextual keywords. get("import").setModulesImport(true); get("module").setModulesDeclaration(true); } diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index a4d9440447429..4e550b672d245 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -75,7 +75,10 @@ tok::ObjCKeywordKind Token::getObjCKeywordID() const { /// Return true if we have an C++20 Modules contextual keyword(export, import /// or module). -bool Token::isModuleContextualKeyword(bool AllowExport) const { +bool Token::isModuleContextualKeyword(const LangOptions &LangOpts, + bool AllowExport) const { + if (!LangOpts.CPlusPlusModules) + return false; if (AllowExport && is(tok::kw_export)) return true; if (isOneOf(tok::kw_import, tok::kw_module)) @@ -4037,9 +4040,9 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // Notify MIOpt that we read a non-whitespace/non-comment token. MIOpt.ReadToken(); bool returnedToken = LexIdentifierContinue(Result, CurPtr); - if (returnedToken && Result.isModuleContextualKeyword() && - LangOpts.CPlusPlusModules && !LexingRawMode && !Is_PragmaLexer && - !ParsingPreprocessorDirective && PP && + if (returnedToken && Result.isModuleContextualKeyword(LangOpts) && + !LexingRawMode && !Is_PragmaLexer && !ParsingPreprocessorDirective && + PP && PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine)) goto HandleDirective; return returnedToken; @@ -4616,7 +4619,7 @@ bool Lexer::LexDependencyDirectiveToken(Token &Result) { Result.setRawIdentifierData(TokPtr); if (!isLexingRawMode()) { const IdentifierInfo *II = PP->LookUpIdentifierInfo(Result); - if (Result.isModuleContextualKeyword() && + if (Result.isModuleContextualKeyword(LangOpts) && PP->HandleModuleContextualKeyword(Result, Result.isAtStartOfLine())) { PP->HandleDirective(Result); return false; diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index ab239fc01ac1a..cd553181563a5 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -1260,16 +1260,13 @@ void Preprocessor::HandleDirective(Token &Result) { // pp-directive. bool ReadAnyTokensBeforeDirective =CurPPLexer->MIOpt.getHasReadAnyTokensVal(); - // Save the '#' token in case we need to return it later. - Token SavedHash = Result; - - bool IsCXX20ImportOrModuleDirective = - getLangOpts().CPlusPlusModules && - Result.isModuleContextualKeyword(/*AllowExport=*/false); + // Save the directive-introducing token('#' and import/module in C++20) in + // case we need to return it later. + Token Introducer = Result; // Read the next token, the directive flavor. This isn't expanded due to // C99 6.10.3p8. - if (!IsCXX20ImportOrModuleDirective) + if (Introducer.is(tok::hash)) LexUnexpandedToken(Result); // C99 6.10.3p11: Is this preprocessor directive in macro invocation? e.g.: @@ -1291,7 +1288,9 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp_embed: case tok::pp_module: Diag(Result, diag::err_embedded_directive) - << IsCXX20ImportOrModuleDirective << II->getName(); + << Introducer.isModuleContextualKeyword(getLangOpts(), + /*AllowExport=*/false) + << II->getName(); Diag(*ArgMacro, diag::note_macro_expansion_here) << ArgMacro->getIdentifierInfo(); DiscardUntilEndOfDirective(); @@ -1308,7 +1307,8 @@ void Preprocessor::HandleDirective(Token &Result) { ResetMacroExpansionHelper helper(this); if (SkippingUntilPCHThroughHeader || SkippingUntilPragmaHdrStop) - return HandleSkippedDirectiveWhileUsingPCH(Result, SavedHash.getLocation()); + return HandleSkippedDirectiveWhileUsingPCH(Result, + Introducer.getLocation()); switch (Result.getKind()) { case tok::eod: @@ -1328,7 +1328,7 @@ void Preprocessor::HandleDirective(Token &Result) { // directive. However do permit it in the predefines file, as we use line // markers to mark the builtin macros as being in a system header. if (getLangOpts().AsmPreprocessor && - SourceMgr.getFileID(SavedHash.getLocation()) != getPredefinesFileID()) + SourceMgr.getFileID(Introducer.getLocation()) != getPredefinesFileID()) break; return HandleDigitDirective(Result); default: @@ -1340,30 +1340,32 @@ void Preprocessor::HandleDirective(Token &Result) { default: break; // C99 6.10.1 - Conditional Inclusion. case tok::pp_if: - return HandleIfDirective(Result, SavedHash, ReadAnyTokensBeforeDirective); + return HandleIfDirective(Result, Introducer, + ReadAnyTokensBeforeDirective); case tok::pp_ifdef: - return HandleIfdefDirective(Result, SavedHash, false, + return HandleIfdefDirective(Result, Introducer, false, true /*not valid for miopt*/); case tok::pp_ifndef: - return HandleIfdefDirective(Result, SavedHash, true, + return HandleIfdefDirective(Result, Introducer, true, ReadAnyTokensBeforeDirective); case tok::pp_elif: case tok::pp_elifdef: case tok::pp_elifndef: - return HandleElifFamilyDirective(Result, SavedHash, II->getPPKeywordID()); + return HandleElifFamilyDirective(Result, Introducer, + II->getPPKeywordID()); case tok::pp_else: - return HandleElseDirective(Result, SavedHash); + return HandleElseDirective(Result, Introducer); case tok::pp_endif: return HandleEndifDirective(Result); // C99 6.10.2 - Source File Inclusion. case tok::pp_include: // Handle #include. - return HandleIncludeDirective(SavedHash.getLocation(), Result); + return HandleIncludeDirective(Introducer.getLocation(), Result); case tok::pp___include_macros: // Handle -imacros. - return HandleIncludeMacrosDirective(SavedHash.getLocation(), Result); + return HandleIncludeMacrosDirective(Introducer.getLocation(), Result); // C99 6.10.3 - Macro Replacement. case tok::pp_define: @@ -1381,16 +1383,17 @@ void Preprocessor::HandleDirective(Token &Result) { // C99 6.10.6 - Pragma Directive. case tok::pp_pragma: - return HandlePragmaDirective({PIK_HashPragma, SavedHash.getLocation()}); + return HandlePragmaDirective({PIK_HashPragma, Introducer.getLocation()}); case tok::pp_module: return HandleCXXModuleDirective(Result); // GNU Extensions. case tok::pp_import: - if (IsCXX20ImportOrModuleDirective) + if (Introducer.isModuleContextualKeyword(getLangOpts(), + /*AllowExport=*/false)) return HandleCXXImportDirective(Result); - return HandleImportDirective(SavedHash.getLocation(), Result); + return HandleImportDirective(Introducer.getLocation(), Result); case tok::pp_include_next: - return HandleIncludeNextDirective(SavedHash.getLocation(), Result); + return HandleIncludeNextDirective(Introducer.getLocation(), Result); case tok::pp_warning: if (LangOpts.CPlusPlus) @@ -1409,7 +1412,7 @@ void Preprocessor::HandleDirective(Token &Result) { case tok::pp_sccs: return HandleIdentSCCSDirective(Result); case tok::pp_embed: - return HandleEmbedDirective(SavedHash.getLocation(), Result, + return HandleEmbedDirective(Introducer.getLocation(), Result, getCurrentFileLexer() ? *getCurrentFileLexer()->getFileEntry() : static_cast(nullptr)); @@ -1440,7 +1443,7 @@ void Preprocessor::HandleDirective(Token &Result) { if (getLangOpts().AsmPreprocessor) { auto Toks = std::make_unique(2); // Return the # and the token after it. - Toks[0] = SavedHash; + Toks[0] = Introducer; Toks[1] = Result; // If the second token is a hashhash token, then we need to translate it to diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 95eaa633d6d9f..7d5c9cbfc33b5 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1174,7 +1174,7 @@ bool Preprocessor::LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, /// Otherwise the token is treated as an identifier. bool Preprocessor::HandleModuleContextualKeyword( Token &Result, bool TokAtPhysicalStartOfLine) { - if (!getLangOpts().CPlusPlusModules || !Result.isModuleContextualKeyword()) + if (!Result.isModuleContextualKeyword(getLangOpts())) return false; if (Result.is(tok::kw_export)) { @@ -1188,9 +1188,8 @@ bool Preprocessor::HandleModuleContextualKeyword( if (!LastTokenWasExportKeyword.isAtPhysicalStartOfLine()) return false; // [cpp.pre]/1.4 - // export // not a preprocessing directive - // import foo; // preprocessing directive (ill-formed at phase - // 7) + // export // not a preprocessing directive + // import foo; // preprocessing directive (ill-formed at phase7) if (TokAtPhysicalStartOfLine) return false; } else if (!TokAtPhysicalStartOfLine) @@ -1266,7 +1265,7 @@ void Preprocessor::EnterModuleSuffixTokenStream(ArrayRef Toks) { /*DisableMacroExpansion*/ true, /*IsReinject*/ false); } -// Lex a token following the 'import' contextual keyword. +/// Lex a token following the 'import' contextual keyword. /// /// pp-import: [C++20] /// import header-name pp-import-suffix[opt] ; @@ -1290,7 +1289,7 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) { recomputeCurLexerKind(); SmallVector Suffix; - SmallVector Path; + SmallVector Path; Lex(Result); if (LexModuleNameContinue(Result, ModuleImportLoc, Suffix, Path)) return CollectPPImportSuffixAndEnterStream(Suffix); diff --git a/clang/lib/Lex/TokenLexer.cpp b/clang/lib/Lex/TokenLexer.cpp index 676fbe6f98f44..958c8cf42c1f1 100644 --- a/clang/lib/Lex/TokenLexer.cpp +++ b/clang/lib/Lex/TokenLexer.cpp @@ -701,7 +701,7 @@ bool TokenLexer::Lex(Token &Tok) { // Handle recursive expansion! if (!Tok.isAnnotation() && Tok.getIdentifierInfo() != nullptr && (!PP.getLangOpts().CPlusPlusModules || - !Tok.isModuleContextualKeyword())) { + !Tok.isModuleContextualKeyword(PP.getLangOpts()))) { // Change the kind of this identifier to the appropriate token kind, e.g. // turning "for" into a keyword. IdentifierInfo *II = Tok.getIdentifierInfo(); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 0156967186931..e0cc6bc7792ff 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -26,7 +26,6 @@ #include "clang/Sema/Scope.h" #include "clang/Sema/SemaCodeCompletion.h" #include "llvm/ADT/STLForwardCompat.h" -#include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/TimeProfiler.h" using namespace clang; @@ -632,8 +631,7 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, // FIXME: We should generate better diagnostic information here to explain // why the module/import directive is ill-formed. case tok::identifier: { - if (getLangOpts().CPlusPlusModules && - NextToken().isModuleContextualKeyword() && + if (NextToken().isModuleContextualKeyword(getLangOpts()) && GetLookAheadToken(2).isNot(tok::coloncolon)) { if (NextToken().getIdentifierInfo()->isStr( tok::getKeywordSpelling(tok::kw_module))) @@ -720,7 +718,7 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, // 'export import' declarations. If the module/import directive is // well-formed, it should be converted to a keyword in preprocessor, but not // an identifier we saw here. - if (getLangOpts().CPlusPlusModules && Tok.isModuleContextualKeyword() && + if (Tok.isModuleContextualKeyword(getLangOpts()) && NextToken().isNot(tok::coloncolon)) { if (Tok.getIdentifierInfo()->isStr( tok::getKeywordSpelling(tok::kw_module))) diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 1940f3eb6e746..529521e7185a4 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -14,9 +14,7 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/DynamicRecursiveASTVisitor.h" -#include "clang/Basic/SourceLocation.h" #include "clang/Lex/HeaderSearch.h" -#include "clang/Lex/ModuleLoader.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/ParsedAttr.h" #include "clang/Sema/SemaInternal.h" diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 9a3939c98ba12..23d89e88279ac 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -631,7 +631,8 @@ void ModuleDepCollectorPP::InclusionDirective( void ModuleDepCollectorPP::moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) { - if (MDC.ScanInstance.getPreprocessor().isImportingCXXNamedModules()) { + auto &PP = MDC.ScanInstance.getPreprocessor(); + if (PP.getLangOpts().CPlusPlusModules && PP.isImportingCXXNamedModules()) { P1689ModuleInfo RequiredModule; RequiredModule.ModuleName = Path[0].getIdentifierInfo()->getName().str(); RequiredModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule; From 8a77b45d0b4b737c3fc71cfba8275ee2a4704195 Mon Sep 17 00:00:00 2001 From: "Wang, Yihan" Date: Wed, 10 Sep 2025 01:41:02 +0800 Subject: [PATCH 22/22] [ObjC] Handle @import directive as a pp-directive Signed-off-by: Wang, Yihan --- .../AnnotateFunctions/AnnotateFunctions.cpp | 2 +- .../include/clang/Basic/DiagnosticLexKinds.td | 5 +- clang/include/clang/Lex/Preprocessor.h | 29 +---- clang/include/clang/Lex/Token.h | 8 +- clang/lib/Lex/DependencyDirectivesScanner.cpp | 19 ++- clang/lib/Lex/Lexer.cpp | 46 ++++--- clang/lib/Lex/PPDirectives.cpp | 105 +++++++++++++--- clang/lib/Lex/PPLexerChange.cpp | 13 +- clang/lib/Lex/Pragma.cpp | 23 ++-- clang/lib/Lex/Preprocessor.cpp | 114 ++++-------------- clang/lib/Lex/TokenLexer.cpp | 3 +- clang/test/Modules/no-stale-modtime.m | 3 +- clang/test/Modules/pr62359.cppm | 2 +- .../Lex/DependencyDirectivesScannerTest.cpp | 2 +- 14 files changed, 182 insertions(+), 192 deletions(-) diff --git a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp index d872020c2d8a3..22a3eb97f938b 100644 --- a/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp +++ b/clang/examples/AnnotateFunctions/AnnotateFunctions.cpp @@ -65,7 +65,7 @@ class PragmaAnnotateHandler : public PragmaHandler { Token Tok; PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; if (HandledDecl) { DiagnosticsEngine &D = PP.getDiagnostics(); diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index f5930f1badcbb..c8f04e408ad82 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -465,10 +465,7 @@ def err_pp_embed_device_file : Error< "device files are not yet supported by '#embed' directive">; def ext_pp_extra_tokens_at_eol : ExtWarn< - "extra tokens at end of #%0 directive">, InGroup; -def ext_pp_extra_tokens_at_module_directive_eol - : ExtWarn<"extra tokens at end of '%0' directive">, - InGroup; + "extra tokens at end of %0 directive">, InGroup; def ext_pp_comma_expr : Extension<"comma operator in operand of #if">; def ext_pp_bad_vaargs_use : Extension< diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 362a5c429a26a..44bf97c994093 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -137,24 +137,6 @@ struct CXXStandardLibraryVersionInfo { std::uint64_t Version; }; -class ExportContextualKeywordInfo { - Token ExportTok; - bool AtPhysicalStartOfLine = false; - -public: - ExportContextualKeywordInfo() = default; - ExportContextualKeywordInfo(const Token &Tok, bool AtPhysicalStartOfLine) - : ExportTok(Tok), AtPhysicalStartOfLine(AtPhysicalStartOfLine) {} - - bool isValid() const { return ExportTok.is(tok::kw_export); } - bool isAtPhysicalStartOfLine() const { return AtPhysicalStartOfLine; } - Token getExportTok() const { return ExportTok; } - void reset() { - ExportTok.startToken(); - AtPhysicalStartOfLine = false; - } -}; - /// Engages in a tight little dance with the lexer to efficiently /// preprocess tokens. /// @@ -378,7 +360,7 @@ class Preprocessor { bool DeclaringCXXNamedModules = false; /// Whether the last token we lexed was an 'export' keyword. - ExportContextualKeywordInfo LastTokenWasExportKeyword; + std::optional ModuleLikeDirectiveIntroducer; /// First pp-token source location in current translation unit. SourceLocation FirstPPTokenLoc; @@ -1808,6 +1790,7 @@ class Preprocessor { void EnterModuleSuffixTokenStream(ArrayRef Toks); void HandleCXXImportDirective(Token Import); void HandleCXXModuleDirective(Token Module); + void HandleObjCAtImportDirective(Token &ImportTok); /// Callback invoked when the lexer sees one of export, import or module token /// at the start of a line. @@ -1815,8 +1798,7 @@ class Preprocessor { /// This consumes the import/module directive, modifies the /// lexer/preprocessor state, and advances the lexer(s) so that the next token /// read is the correct one. - bool HandleModuleContextualKeyword(Token &Result, - bool TokAtPhysicalStartOfLine); + bool HandleModuleContextualKeyword(Token &Result); /// Get the start location of the first pp-token in main file. SourceLocation getMainFileFirstPPTokenLoc() const { @@ -1825,7 +1807,6 @@ class Preprocessor { return FirstPPTokenLoc; } - bool LexAfterModuleImport(Token &Result); void CollectPPImportSuffix(SmallVectorImpl &Toks, bool StopUntilEOD = false); bool CollectPPImportSuffixAndEnterStream(SmallVectorImpl &Toks, @@ -2356,6 +2337,7 @@ class Preprocessor { template bool isNextPPTokenOneOf(Ts... Ks) { static_assert(sizeof...(Ts) > 0, "requires at least one tok::TokenKind specified"); + // Do some quick tests for rejection cases. std::optional Val; if (CurLexer) @@ -3175,9 +3157,6 @@ class Preprocessor { static bool CLK_DependencyDirectivesLexer(Preprocessor &P, Token &Result) { return P.CurLexer->LexDependencyDirectiveToken(Result); } - static bool CLK_LexAfterModuleImport(Preprocessor &P, Token &Result) { - return P.LexAfterModuleImport(Result); - } }; /// Abstract base class that describes a handler that will receive diff --git a/clang/include/clang/Lex/Token.h b/clang/include/clang/Lex/Token.h index ae9ab99cf00b1..91785bd14510d 100644 --- a/clang/include/clang/Lex/Token.h +++ b/clang/include/clang/Lex/Token.h @@ -90,8 +90,10 @@ class Token { // re-added, e.g. via EnterTokenStream. Annotation // tokens are *not* reinjected. HasSeenNoTrivialPPDirective = - 0x1000, // Whether we've seen any 'no-trivial' pp-directives before + 0x1000, // Whether we've seen any 'no-trivial' pp-directives before// // current position. + PhysicalStartOfLine = + 0x2000, // At the physical start of line or only after whitespace. }; tok::TokenKind getKind() const { return Kind; } @@ -277,6 +279,10 @@ class Token { /// bool isAtStartOfLine() const { return getFlag(StartOfLine); } + /// isAtPhysicalStartOfLine - Return true if this token is at the physical + /// start of a line. + bool isAtPhysicalStartOfLine() const { return getFlag(PhysicalStartOfLine); } + /// Return true if this token has whitespace before it. /// bool hasLeadingSpace() const { return getFlag(LeadingSpace); } diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index 64a2d17451d1d..85ffc87017619 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -579,15 +579,12 @@ bool Scanner::lexModuleDirectiveBody(DirectiveKind Kind, const char *&First, return false; } + const auto &Tok = lexToken(First, End); pushDirective(Kind); - skipWhitespace(First, End); - if (First == End) + if (Tok.is(tok::eof) || Tok.is(tok::eod)) return false; - if (!isVerticalWhitespace(*First)) - return reportError( - DirectiveLoc, diag::err_dep_source_scanner_unexpected_tokens_at_import); - skipNewline(First, End); - return false; + return reportError(DirectiveLoc, + diag::err_dep_source_scanner_unexpected_tokens_at_import); } dependency_directives_scan::Token &Scanner::lexToken(const char *&First, @@ -931,10 +928,6 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { CurDirToks.clear(); }); - // FIXME: Shoule we handle @import as a preprocessing directive? - if (*First == '@') - return lexAt(First, End); - if (*First == '_') { if (isNextIdentifierOrSkipLine("_Pragma", First, End)) return lex_Pragma(First, End); @@ -947,6 +940,10 @@ bool Scanner::lexPPLine(const char *&First, const char *const End) { auto ScEx2 = make_scope_exit( [&]() { TheLexer.setParsingPreprocessorDirective(false); }); + // FIXME: Shoule we handle @import as a preprocessing directive? + if (*First == '@') + return lexAt(First, End); + // Handle module directives for C++20 modules. if (*First == 'i' || *First == 'e' || *First == 'm') return lexModule(First, End); diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 4e550b672d245..4ad2dbd63c830 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -77,16 +77,13 @@ tok::ObjCKeywordKind Token::getObjCKeywordID() const { /// or module). bool Token::isModuleContextualKeyword(const LangOptions &LangOpts, bool AllowExport) const { - if (!LangOpts.CPlusPlusModules) + if (!LangOpts.CPlusPlusModules || isAnnotation()) return false; if (AllowExport && is(tok::kw_export)) return true; - if (isOneOf(tok::kw_import, tok::kw_module)) - return true; - if (isNot(tok::identifier)) - return false; - const auto *II = getIdentifierInfo(); - return II->isModulesImport() || II->isModulesDeclaration(); + if (const auto *II = getIdentifierInfo()) + return II->isModulesImport() || II->isModulesDeclaration(); + return false; } /// Determine whether the token kind starts a simple-type-specifier. @@ -3758,6 +3755,9 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { assert(!Result.needsCleaning() && "Result needs cleaning"); assert(!Result.hasPtrData() && "Result has not been reset"); + if (TokAtPhysicalStartOfLine) + Result.setFlag(Token::PhysicalStartOfLine); + // CurPtr - Cache BufferPtr in an automatic variable. const char *CurPtr = BufferPtr; @@ -4040,10 +4040,9 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { // Notify MIOpt that we read a non-whitespace/non-comment token. MIOpt.ReadToken(); bool returnedToken = LexIdentifierContinue(Result, CurPtr); - if (returnedToken && Result.isModuleContextualKeyword(LangOpts) && - !LexingRawMode && !Is_PragmaLexer && !ParsingPreprocessorDirective && - PP && - PP->HandleModuleContextualKeyword(Result, TokAtPhysicalStartOfLine)) + if (returnedToken && Result.isNot(tok::eof) && !LexingRawMode && + !Is_PragmaLexer && !ParsingPreprocessorDirective && PP && + PP->HandleModuleContextualKeyword(Result)) goto HandleDirective; return returnedToken; } @@ -4454,9 +4453,14 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { case '@': // Objective C support. - if (CurPtr[-1] == '@' && LangOpts.ObjC) + if (CurPtr[-1] == '@' && LangOpts.ObjC) { + if (TokAtPhysicalStartOfLine && !LexingRawMode && !Is_PragmaLexer) { + FormTokenWithChars(Result, CurPtr, tok::at); + (void)PP->HandleModuleContextualKeyword(Result); + return true; + } Kind = tok::at; - else + } else Kind = tok::unknown; break; @@ -4538,10 +4542,6 @@ bool Lexer::LexTokenInternal(Token &Result, bool TokAtPhysicalStartOfLine) { HandleDirective: PP->HandleDirective(Result); - if (PP->hadModuleLoaderFatalFailure()) - // With a fatal failure in the module loader, we abort parsing. - return true; - // We parsed the directive; lex a token with the new state. return false; @@ -4608,20 +4608,26 @@ bool Lexer::LexDependencyDirectiveToken(Token &Result) { const char *TokPtr = convertDependencyDirectiveToken(DDTok, Result); - if (Result.is(tok::hash) && Result.isAtStartOfLine()) { + if (Result.is(tok::hash) && Result.isAtStartOfLine() && !isLexingRawMode()) { PP->HandleDirective(Result); if (PP->hadModuleLoaderFatalFailure()) // With a fatal failure in the module loader, we abort parsing. return true; return false; } + if (Result.is(tok::at) && Result.isAtStartOfLine() && !isLexingRawMode()) { + (void)PP->HandleModuleContextualKeyword(Result); + return false; + } if (Result.is(tok::raw_identifier)) { Result.setRawIdentifierData(TokPtr); if (!isLexingRawMode()) { const IdentifierInfo *II = PP->LookUpIdentifierInfo(Result); - if (Result.isModuleContextualKeyword(LangOpts) && - PP->HandleModuleContextualKeyword(Result, Result.isAtStartOfLine())) { + if (PP->HandleModuleContextualKeyword(Result)) { PP->HandleDirective(Result); + if (PP->hadModuleLoaderFatalFailure()) + // With a fatal failure in the module loader, we abort parsing. + return true; return false; } if (II->isHandleIdentifierCase()) diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index cd553181563a5..de80f28f1e9f5 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -478,13 +478,15 @@ Preprocessor::CheckEndOfDirective(StringRef DirType, bool EnableMacros, !CurTokenLexer) Hint = FixItHint::CreateInsertion(Tmp.getLocation(),"//"); - unsigned DiagID = diag::ext_pp_extra_tokens_at_eol; - // C++20 import or module directive has no '#' prefix. if (getLangOpts().CPlusPlusModules && (DirType == "import" || DirType == "module")) - DiagID = diag::ext_pp_extra_tokens_at_module_directive_eol; - - Diag(Tmp, DiagID) << DirType << Hint; + Diag(Tmp, diag::ext_pp_extra_tokens_at_eol) << DirType << Hint; + else if (IsAtImport && DirType == "import") + Diag(Tmp, diag::ext_pp_extra_tokens_at_eol) + << llvm::Twine("@").concat(DirType).str() << Hint; + else + Diag(Tmp, diag::ext_pp_extra_tokens_at_eol) + << llvm::Twine("#").concat(DirType).str() << Hint; return DiscardUntilEndOfDirective().getEnd(); } @@ -1388,10 +1390,18 @@ void Preprocessor::HandleDirective(Token &Result) { return HandleCXXModuleDirective(Result); // GNU Extensions. case tok::pp_import: - if (Introducer.isModuleContextualKeyword(getLangOpts(), - /*AllowExport=*/false)) + switch (Introducer.getKind()) { + case tok::kw_import: + if (ModuleLikeDirectiveIntroducer && + ModuleLikeDirectiveIntroducer->is(tok::at)) + return HandleObjCAtImportDirective(Result); return HandleCXXImportDirective(Result); - return HandleImportDirective(Introducer.getLocation(), Result); + case tok::hash: + return HandleImportDirective(Introducer.getLocation(), Result); + default: + llvm_unreachable("Not a valid import directive"); + } + break; case tok::pp_include_next: return HandleIncludeNextDirective(Introducer.getLocation(), Result); @@ -4084,14 +4094,80 @@ void Preprocessor::HandleEmbedDirective(SourceLocation HashLoc, Token &EmbedTok, HandleEmbedDirectiveImpl(HashLoc, *Params, BinaryContents, FilenameToGo); } +/// Lex a token following the 'import' contextual keyword. +/// +/// [ObjC] @ import module-name ; +/// module-name: +/// module-name-qualifier[opt] identifier +/// +/// module-name-qualifier +/// module-name-qualifier[opt] identifier . +/// +/// We respond to a pp-import by importing macros from the named module. +void Preprocessor::HandleObjCAtImportDirective(Token &ImportTok) { + assert(ModuleLikeDirectiveIntroducer && + ModuleLikeDirectiveIntroducer->is(tok::at) && + "@ token must set during pervious lexing"); + ModuleLikeDirectiveIntroducer.reset(); + ModuleImportLoc = ImportTok.getLocation(); + SmallVector DirToks{ImportTok}; + SmallVector Path; + Token Tok; + Lex(Tok); + if (LexModuleNameContinue(Tok, ModuleImportLoc, DirToks, Path)) { + if (Tok.isNot(tok::eod)) + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); + EnterModuleSuffixTokenStream(DirToks); + return; + } + + // Consume the pp-import-suffix and expand any macros in it now, if we're not + // at the semicolon already. + if (!DirToks.back().isOneOf(tok::semi, tok::eod)) + CollectPPImportSuffix(DirToks); + + if (DirToks.back().isNot(tok::eod)) + CheckEndOfDirective(ImportTok.getIdentifierInfo()->getName()); + else + DirToks.pop_back(); + + // This is not a pp-import after all. + if (DirToks.back().isNot(tok::semi)) { + EnterModuleSuffixTokenStream(DirToks); + return; + } + + SourceLocation SemiLoc = DirToks.back().getLocation(); + Module *Imported = nullptr; + if (getLangOpts().Modules) { + Imported = TheModuleLoader.loadModule(ImportTok.getLocation(), Path, + Module::Hidden, + /*IsInclusionDirective=*/false); + if (Imported) + makeModuleVisible(Imported, SemiLoc); + + // We hit an error processing the import. Bail out. + if (hadModuleLoaderFatalFailure()) { + // With a fatal failure in the module loader, we abort parsing. + assert(CurLexer && "#include but no current lexer set!"); + CurLexer->cutOffLexing(); + } + } + + if (Callbacks) + Callbacks->moduleImport(ModuleImportLoc, Path, Imported); + EnterModuleSuffixTokenStream(DirToks); +} + void Preprocessor::HandleCXXImportDirective(Token ImportTok) { assert(getLangOpts().CPlusPlusModules && ImportTok.is(tok::kw_import)); llvm::SaveAndRestore SaveImportingCXXModules( this->ImportingCXXNamedModules); ImportingCXXNamedModules = true; - if (LastTokenWasExportKeyword.isValid()) - LastTokenWasExportKeyword.reset(); + if (ModuleLikeDirectiveIntroducer) + ModuleLikeDirectiveIntroducer.reset(); Token Tok; if (LexHeaderName(Tok)) { @@ -4215,9 +4291,9 @@ void Preprocessor::HandleCXXImportDirective(Token ImportTok) { void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { assert(getLangOpts().CPlusPlusModules && ModuleTok.is(tok::kw_module)); Token Introducer = ModuleTok; - if (LastTokenWasExportKeyword.isValid()) { - Introducer = LastTokenWasExportKeyword.getExportTok(); - LastTokenWasExportKeyword.reset(); + if (ModuleLikeDirectiveIntroducer) { + Introducer = *ModuleLikeDirectiveIntroducer; + ModuleLikeDirectiveIntroducer.reset(); } SourceLocation StartLoc = Introducer.getLocation(); @@ -4280,7 +4356,8 @@ void Preprocessor::HandleCXXModuleDirective(Token ModuleTok) { LexUnexpandedToken(Tok); if (LexModuleNameContinue(Tok, UseLoc, DirToks, Partition)) { if (Tok.isNot(tok::eod)) - CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName()); + CheckEndOfDirective(ModuleTok.getIdentifierInfo()->getName(), + /*EnableMacros=*/false, &DirToks); EnterModuleSuffixTokenStream(DirToks); return; } diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp index d8f61c02a9837..8bd3749dd8ca2 100644 --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -115,10 +115,9 @@ void Preprocessor::EnterSourceFileWithLexer(Lexer *TheLexer, CurPPLexer = TheLexer; CurDirLookup = CurDir; CurLexerSubmodule = nullptr; - if (CurLexerCallback != CLK_LexAfterModuleImport) - CurLexerCallback = TheLexer->isDependencyDirectivesLexer() - ? CLK_DependencyDirectivesLexer - : CLK_Lexer; + CurLexerCallback = TheLexer->isDependencyDirectivesLexer() + ? CLK_DependencyDirectivesLexer + : CLK_Lexer; // Notify the client, if desired, that we are in a new source file. if (Callbacks && !CurLexer->Is_PragmaLexer) { @@ -154,8 +153,7 @@ void Preprocessor::EnterMacro(Token &Tok, SourceLocation ILEnd, PushIncludeMacroStack(); CurDirLookup = nullptr; CurTokenLexer = std::move(TokLexer); - if (CurLexerCallback != CLK_LexAfterModuleImport) - CurLexerCallback = CLK_TokenLexer; + CurLexerCallback = CLK_TokenLexer; } /// EnterTokenStream - Add a "macro" context to the top of the include stack, @@ -209,8 +207,7 @@ void Preprocessor::EnterTokenStream(const Token *Toks, unsigned NumToks, PushIncludeMacroStack(); CurDirLookup = nullptr; CurTokenLexer = std::move(TokLexer); - if (CurLexerCallback != CLK_LexAfterModuleImport) - CurLexerCallback = CLK_TokenLexer; + CurLexerCallback = CLK_TokenLexer; } /// Compute the relative path that names the given file relative to diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp index bba3c89bed38f..914974ef5f450 100644 --- a/clang/lib/Lex/Pragma.cpp +++ b/clang/lib/Lex/Pragma.cpp @@ -817,7 +817,7 @@ void Preprocessor::HandlePragmaModuleBuild(Token &Tok) { LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) { - Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; DiscardUntilEndOfDirective(); } @@ -898,7 +898,7 @@ void Preprocessor::HandlePragmaHdrstop(Token &Tok) { } if (Tok.isNot(tok::eod)) Diag(Tok.getLocation(), diag::ext_pp_extra_tokens_at_eol) - << "pragma hdrstop"; + << "#pragma hdrstop"; if (creatingPCHWithPragmaHdrStop() && SourceMgr.isInMainFile(Tok.getLocation())) { @@ -1236,7 +1236,7 @@ struct PragmaDebugHandler : public PragmaHandler { if (Tok.isNot(tok::eod)) { PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) - << "pragma clang __debug captured"; + << "#pragma clang __debug captured"; return; } @@ -1538,7 +1538,7 @@ struct PragmaWarningHandler : public PragmaHandler { PP.Lex(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma warning"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma warning"; } }; @@ -1602,7 +1602,8 @@ struct PragmaExecCharsetHandler : public PragmaHandler { PP.Lex(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma execution_character_set"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) + << "#pragma execution_character_set"; } }; @@ -1719,7 +1720,7 @@ struct PragmaModuleImportHandler : public PragmaHandler { return; if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; // If we have a non-empty module path, load the named module. Module *Imported = @@ -1755,7 +1756,7 @@ struct PragmaModuleBeginHandler : public PragmaHandler { return; if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; // We can only enter submodules of the current module. StringRef Current = PP.getLangOpts().CurrentModule; @@ -1814,7 +1815,7 @@ struct PragmaModuleEndHandler : public PragmaHandler { PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; Module *M = PP.LeaveSubmodule(/*ForPragma*/true); if (M) @@ -1848,7 +1849,7 @@ struct PragmaModuleLoadHandler : public PragmaHandler { return; if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; // Load the module, don't make it visible. PP.getModuleLoader().loadModule(Loc, ModuleName, Module::Hidden, @@ -1905,7 +1906,7 @@ struct PragmaARCCFCodeAuditedHandler : public PragmaHandler { // Verify that this is followed by EOD. PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; // The start location of the active audit. SourceLocation BeginLoc = PP.getPragmaARCCFCodeAuditedInfo().getLoc(); @@ -1960,7 +1961,7 @@ struct PragmaAssumeNonNullHandler : public PragmaHandler { // Verify that this is followed by EOD. PP.LexUnexpandedToken(Tok); if (Tok.isNot(tok::eod)) - PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "#pragma"; // The start location of the active audit. SourceLocation BeginLoc = PP.getPragmaAssumeNonNullLoc(); diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 7d5c9cbfc33b5..5436122da00e1 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -867,27 +867,6 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) { // like "#define TY typeof", "TY(1) x". if (II.isExtensionToken() && !DisableMacroExpansion) Diag(Identifier, diag::ext_token_used); - - // If this is the 'import' contextual keyword following an '@', note - // that the next token indicates a module name. - // - // Note that we do not treat 'import' as a contextual - // keyword when we're in a caching lexer, because caching lexers only get - // used in contexts where import declarations are disallowed. - // - // Likewise if this is the standard C++ import keyword. - if (((LastTokenWasAt && II.isModulesImport()) || - Identifier.is(tok::kw_import)) && - // FIXME: Can we just ignore DisableMacroExpansion here? - // https://github.com/llvm/llvm-project/pull/137665 disable - // macro expansion when current input file is preprocessed. - !InMacroArgs && - (!DisableMacroExpansion || MacroExpansionInDirectivesOverride) && - CurLexerCallback != CLK_CachingLexer) { - ModuleImportLoc = Identifier.getLocation(); - IsAtImport = true; - CurLexerCallback = CLK_LexAfterModuleImport; - } return true; } @@ -990,8 +969,8 @@ void Preprocessor::Lex(Token &Result) { } LastTokenWasAt = Result.is(tok::at); - if (Result.isNot(tok::kw_export)) - LastTokenWasExportKeyword.reset(); + if (!Result.isOneOf(tok::at, tok::kw_export)) + ModuleLikeDirectiveIntroducer.reset(); --LexLevel; @@ -1172,27 +1151,37 @@ bool Preprocessor::LexModuleNameContinue(Token &Tok, SourceLocation UseLoc, /// - <, ", or : (but not ::) pp tokens for 'import', or /// - ; for 'module' /// Otherwise the token is treated as an identifier. -bool Preprocessor::HandleModuleContextualKeyword( - Token &Result, bool TokAtPhysicalStartOfLine) { - if (!Result.isModuleContextualKeyword(getLangOpts())) +bool Preprocessor::HandleModuleContextualKeyword(Token &Result) { + if (!Result.isModuleContextualKeyword(getLangOpts()) && + Result.isNot(tok::at) && !Result.isObjCAtKeyword(tok::objc_import)) return false; - if (Result.is(tok::kw_export)) { - LastTokenWasExportKeyword = {Result, TokAtPhysicalStartOfLine}; + if (Result.isOneOf(tok::kw_export, tok::at)) { + ModuleLikeDirectiveIntroducer = Result; return false; } - if (LastTokenWasExportKeyword.isValid()) { + if (ModuleLikeDirectiveIntroducer) { // The export keyword was not at the start of line, it's not a // directive-introducing token. - if (!LastTokenWasExportKeyword.isAtPhysicalStartOfLine()) + if (!ModuleLikeDirectiveIntroducer->isAtPhysicalStartOfLine()) return false; // [cpp.pre]/1.4 // export // not a preprocessing directive // import foo; // preprocessing directive (ill-formed at phase7) - if (TokAtPhysicalStartOfLine) + if (Result.isAtPhysicalStartOfLine()) + return false; + + if (ModuleLikeDirectiveIntroducer->is(tok::at)) { + if (Result.isObjCAtKeyword(tok::objc_import)) { + Result.setKind(tok::kw_import); + ModuleImportLoc = Result.getLocation(); + IsAtImport = true; + return true; + } return false; - } else if (!TokAtPhysicalStartOfLine) + } + } else if (!Result.isAtPhysicalStartOfLine()) return false; bool SavedParsingPreprocessorDirective = @@ -1265,67 +1254,6 @@ void Preprocessor::EnterModuleSuffixTokenStream(ArrayRef Toks) { /*DisableMacroExpansion*/ true, /*IsReinject*/ false); } -/// Lex a token following the 'import' contextual keyword. -/// -/// pp-import: [C++20] -/// import header-name pp-import-suffix[opt] ; -/// import header-name-tokens pp-import-suffix[opt] ; -/// [ObjC] @ import module-name ; -/// [Clang] import module-name ; -/// -/// header-name-tokens: -/// string-literal -/// < [any sequence of preprocessing-tokens other than >] > -/// -/// module-name: -/// module-name-qualifier[opt] identifier -/// -/// module-name-qualifier -/// module-name-qualifier[opt] identifier . -/// -/// We respond to a pp-import by importing macros from the named module. -bool Preprocessor::LexAfterModuleImport(Token &Result) { - // Figure out what kind of lexer we actually have. - recomputeCurLexerKind(); - - SmallVector Suffix; - SmallVector Path; - Lex(Result); - if (LexModuleNameContinue(Result, ModuleImportLoc, Suffix, Path)) - return CollectPPImportSuffixAndEnterStream(Suffix); - - // Consume the pp-import-suffix and expand any macros in it now, if we're not - // at the semicolon already. - SourceLocation SemiLoc = Suffix.back().getLocation(); - if (Suffix.back().isNot(tok::semi)) { - if (Result.isNot(tok::eof)) - CollectPPImportSuffix(Suffix); - if (Suffix.back().isNot(tok::semi)) { - // This is not an import after all. - EnterModuleSuffixTokenStream(Suffix); - return false; - } - SemiLoc = Suffix.back().getLocation(); - } - - Module *Imported = nullptr; - if (getLangOpts().Modules) { - Imported = TheModuleLoader.loadModule(ModuleImportLoc, Path, Module::Hidden, - /*IsInclusionDirective=*/false); - if (Imported) - makeModuleVisible(Imported, SemiLoc); - } - - if (Callbacks) - Callbacks->moduleImport(ModuleImportLoc, Path, Imported); - - if (!Suffix.empty()) { - EnterModuleSuffixTokenStream(Suffix); - return false; - } - return true; -} - void Preprocessor::makeModuleVisible(Module *M, SourceLocation Loc, bool IncludeExports) { CurSubmoduleState->VisibleModules.setVisible( diff --git a/clang/lib/Lex/TokenLexer.cpp b/clang/lib/Lex/TokenLexer.cpp index 958c8cf42c1f1..9846ffc3a5f86 100644 --- a/clang/lib/Lex/TokenLexer.cpp +++ b/clang/lib/Lex/TokenLexer.cpp @@ -701,7 +701,8 @@ bool TokenLexer::Lex(Token &Tok) { // Handle recursive expansion! if (!Tok.isAnnotation() && Tok.getIdentifierInfo() != nullptr && (!PP.getLangOpts().CPlusPlusModules || - !Tok.isModuleContextualKeyword(PP.getLangOpts()))) { + !Tok.isModuleContextualKeyword(PP.getLangOpts())) && + (!PP.getLangOpts().ObjC || !Tok.isObjCAtKeyword(tok::objc_import))) { // Change the kind of this identifier to the appropriate token kind, e.g. // turning "for" into a keyword. IdentifierInfo *II = Tok.getIdentifierInfo(); diff --git a/clang/test/Modules/no-stale-modtime.m b/clang/test/Modules/no-stale-modtime.m index 92c18ac591add..6c62853db358e 100644 --- a/clang/test/Modules/no-stale-modtime.m +++ b/clang/test/Modules/no-stale-modtime.m @@ -4,7 +4,8 @@ // RUN: rm -rf %t // RUN: mkdir -p %t // This could be replaced by diamond_*, except we want to modify the top header -// RUN: echo '@import l; @import r;' > %t/b.h +// RUN: echo '@import l;' > %t/b.h +// RUN: echo '@import r;' >> %t/b.h // RUN: echo '@import t; // fromt l' > %t/l.h // RUN: echo '@import t; // fromt r' > %t/r.h diff --git a/clang/test/Modules/pr62359.cppm b/clang/test/Modules/pr62359.cppm index fab0b7d03d814..8d9bd0a8c1376 100644 --- a/clang/test/Modules/pr62359.cppm +++ b/clang/test/Modules/pr62359.cppm @@ -56,4 +56,4 @@ int use2() { } // CHECK: OpenMP{{.*}}differs in precompiled file '{{.*}}Hello.pcm' vs. current file -// CHECK: use of undeclared identifier 'pragma' +// CHECK: use of undeclared identifier 'hello' diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp index 1e1702c0c8d43..156de36b185ef 100644 --- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp +++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp @@ -639,7 +639,7 @@ TEST(MinimizeSourceToDependencyDirectivesTest, AtImport) { ASSERT_FALSE(minimizeSourceToDependencyDirectives(" @ import A;\n", Out)); EXPECT_STREQ("@import A;\n", Out.data()); - ASSERT_FALSE(minimizeSourceToDependencyDirectives("@import A\n;", Out)); + ASSERT_TRUE(minimizeSourceToDependencyDirectives("@import A\n;", Out)); EXPECT_STREQ("@import A;\n", Out.data()); ASSERT_FALSE(minimizeSourceToDependencyDirectives("@import A.B;\n", Out));