https://github.com/Bigcheese created https://github.com/llvm/llvm-project/pull/119740
This separates out parsing of modulemaps from updating the `clang::ModuleMap` information. Currently this has no effect other than slightly changing diagnostics. Upcoming changes will use this to allow searching for modules without fully processing modulemaps. This creates a new `modulemap` namespace because there are too many things called ModuleMap* right now that mean different things. I'd like to clean this up, but I'm not sure yet what I want to call everything. This also drops the `SourceLocation` from `moduleMapFileRead`. This is never used in tree, and in future patches I plan to make the modulemap parser use a different `SourceManager` so that we can share modulemap parsing between `CompilerInstance`s. This will make the `SourceLocation` meaningless. >From 12f19b16945b66e75a32cade1f7cb7aac8424b12 Mon Sep 17 00:00:00 2001 From: Michael Spencer <bigchees...@gmail.com> Date: Thu, 5 Dec 2024 14:53:50 -0800 Subject: [PATCH] [clang][modules] Separate parsing of modulemaps This separates out parsing of modulemaps from updating the `clang::ModuleMap` information. Currently this has no effect other than slightly changing diagnostics. Upcoming changes will use this to allow searching for modules without fully processing modulemaps. --- .../include/clang/Basic/DiagnosticLexKinds.td | 2 + clang/include/clang/Basic/Module.h | 24 + clang/include/clang/Lex/ModuleMap.h | 24 +- clang/include/clang/Lex/ModuleMapFile.h | 142 ++ clang/lib/Lex/CMakeLists.txt | 1 + clang/lib/Lex/ModuleMap.cpp | 1400 +++-------------- clang/lib/Lex/ModuleMapFile.cpp | 1248 +++++++++++++++ .../Modules/Inputs/export_as_test.modulemap | 4 + clang/test/Modules/diagnostics.modulemap | 21 +- clang/test/Modules/export_as_test.c | 5 +- llvm/include/llvm/ADT/STLExtras.h | 9 + 11 files changed, 1634 insertions(+), 1246 deletions(-) create mode 100644 clang/include/clang/Lex/ModuleMapFile.h create mode 100644 clang/lib/Lex/ModuleMapFile.cpp diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 959376b0847216..e5869619e69d35 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -914,6 +914,8 @@ def warn_mmap_redundant_export_as : Warning< InGroup<PrivateModule>; def err_mmap_submodule_export_as : Error< "only top-level modules can be re-exported as public">; +def err_mmap_qualified_export_as : Error< + "a module can only be re-exported as another top-level module">; def warn_quoted_include_in_framework_header : Warning< "double-quoted include \"%0\" in framework header, " diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index dd384c1d76c5fd..ef6e2ac95d0819 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -100,6 +100,30 @@ struct ASTFileSignature : std::array<uint8_t, 20> { } }; +/// The set of attributes that can be attached to a module. +struct ModuleAttributes { + /// Whether this is a system module. + LLVM_PREFERRED_TYPE(bool) + unsigned IsSystem : 1; + + /// Whether this is an extern "C" module. + LLVM_PREFERRED_TYPE(bool) + unsigned IsExternC : 1; + + /// Whether this is an exhaustive set of configuration macros. + LLVM_PREFERRED_TYPE(bool) + unsigned IsExhaustive : 1; + + /// Whether files in this module can only include non-modular headers + /// and headers from used modules. + LLVM_PREFERRED_TYPE(bool) + unsigned NoUndeclaredIncludes : 1; + + ModuleAttributes() + : IsSystem(false), IsExternC(false), IsExhaustive(false), + NoUndeclaredIncludes(false) {} +}; + /// Required to construct a Module. /// /// This tag type is only constructible by ModuleMap, guaranteeing it ownership diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index 53e9e0ec83ddb1..9de1b3b546c115 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -232,29 +232,7 @@ class ModuleMap { llvm::DenseMap<Module *, unsigned> ModuleScopeIDs; - /// The set of attributes that can be attached to a module. - struct Attributes { - /// Whether this is a system module. - LLVM_PREFERRED_TYPE(bool) - unsigned IsSystem : 1; - - /// Whether this is an extern "C" module. - LLVM_PREFERRED_TYPE(bool) - unsigned IsExternC : 1; - - /// Whether this is an exhaustive set of configuration macros. - LLVM_PREFERRED_TYPE(bool) - unsigned IsExhaustive : 1; - - /// Whether files in this module can only include non-modular headers - /// and headers from used modules. - LLVM_PREFERRED_TYPE(bool) - unsigned NoUndeclaredIncludes : 1; - - Attributes() - : IsSystem(false), IsExternC(false), IsExhaustive(false), - NoUndeclaredIncludes(false) {} - }; + using Attributes = ModuleAttributes; /// A directory for which framework modules can be inferred. struct InferredDirectory { diff --git a/clang/include/clang/Lex/ModuleMapFile.h b/clang/include/clang/Lex/ModuleMapFile.h new file mode 100644 index 00000000000000..8b79897876ad61 --- /dev/null +++ b/clang/include/clang/Lex/ModuleMapFile.h @@ -0,0 +1,142 @@ +//===- ModuleMapFile.h - Parsing and representation -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LEX_MODULEMAPFILE_H +#define LLVM_CLANG_LEX_MODULEMAPFILE_H + +#include "clang/Basic/LLVM.h" +// TODO: Consider moving ModuleId to another header, parsing a modulemap file is +// intended to not depend on anything about the clang::Module class. +#include "clang/Basic/Module.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/StringRef.h" + +#include <optional> +#include <variant> + +namespace clang { + +class DiagnosticsEngine; +class SourceManager; + +namespace modulemap { + +using Decl = + std::variant<struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl, + struct ModuleDecl, struct ExcludeDecl, struct ExportDecl, + struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl, + struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl>; + +struct RequiresFeature { + SourceLocation Location; + StringRef Feature; + bool RequiredState = true; +}; + +struct RequiresDecl { + SourceLocation Location; + std::vector<RequiresFeature> Features; +}; + +struct HeaderDecl { + SourceLocation Location; + StringRef Path; + SourceLocation PathLoc; + std::optional<int64_t> Size; + std::optional<int64_t> MTime; + LLVM_PREFERRED_TYPE(bool) + unsigned Private : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Textual : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Umbrella : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Excluded : 1; +}; + +struct UmbrellaDirDecl { + SourceLocation Location; + StringRef Path; +}; + +struct ModuleDecl { + SourceLocation Location; /// Points to the first keyword in the decl. + ModuleId Id; + ModuleAttributes Attrs; + std::vector<Decl> Decls; + + LLVM_PREFERRED_TYPE(bool) + unsigned Explicit : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Framework : 1; +}; + +struct ExcludeDecl { + SourceLocation Location; + StringRef Module; +}; + +struct ExportDecl { + SourceLocation Location; + ModuleId Id; + bool Wildcard; +}; + +struct ExportAsDecl { + SourceLocation Location; + ModuleId Id; +}; + +struct ExternModuleDecl { + SourceLocation Location; + ModuleId Id; + StringRef Path; +}; + +struct UseDecl { + SourceLocation Location; + ModuleId Id; +}; + +struct LinkDecl { + SourceLocation Location; + StringRef Library; + LLVM_PREFERRED_TYPE(bool) + unsigned Framework : 1; +}; + +struct ConfigMacrosDecl { + SourceLocation Location; + std::vector<StringRef> Macros; + LLVM_PREFERRED_TYPE(bool) + unsigned Exhaustive : 1; +}; + +struct ConflictDecl { + SourceLocation Location; + ModuleId Id; + StringRef Message; +}; + +using TopLevelDecl = + std::variant<ModuleDecl, ExternModuleDecl>; + +struct ModuleMapFile { + std::vector<TopLevelDecl> Decls; +}; + +std::optional<ModuleMapFile> parseModuleMap(FileEntryRef File, + SourceManager &SM, + DiagnosticsEngine &Diags, + bool IsSystem, unsigned *Offset); +void dumpModuleMapFile(ModuleMapFile &MMF, llvm::raw_ostream &out); + +} // namespace modulemap +} // namespace clang + +#endif diff --git a/clang/lib/Lex/CMakeLists.txt b/clang/lib/Lex/CMakeLists.txt index 766336b89a2382..5a049a1d84fd0d 100644 --- a/clang/lib/Lex/CMakeLists.txt +++ b/clang/lib/Lex/CMakeLists.txt @@ -15,6 +15,7 @@ add_clang_library(clangLex MacroArgs.cpp MacroInfo.cpp ModuleMap.cpp + ModuleMapFile.cpp PPCaching.cpp PPCallbacks.cpp PPConditionalDirectiveRecord.cpp diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index ccf94f6345ff28..11b38db9eb6526 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -24,9 +24,7 @@ #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/LexDiagnostic.h" -#include "clang/Lex/Lexer.h" -#include "clang/Lex/LiteralSupport.h" -#include "clang/Lex/Token.h" +#include "clang/Lex/ModuleMapFile.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" @@ -34,7 +32,6 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" -#include "llvm/Support/Allocator.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" @@ -1461,81 +1458,10 @@ bool ModuleMap::resolveConflicts(Module *Mod, bool Complain) { //----------------------------------------------------------------------------// namespace clang { - - /// A token in a module map file. - struct MMToken { - enum TokenKind { - Comma, - ConfigMacros, - Conflict, - EndOfFile, - HeaderKeyword, - Identifier, - Exclaim, - ExcludeKeyword, - ExplicitKeyword, - ExportKeyword, - ExportAsKeyword, - ExternKeyword, - FrameworkKeyword, - LinkKeyword, - ModuleKeyword, - Period, - PrivateKeyword, - UmbrellaKeyword, - UseKeyword, - RequiresKeyword, - Star, - StringLiteral, - IntegerLiteral, - TextualKeyword, - LBrace, - RBrace, - LSquare, - RSquare - } Kind; - - SourceLocation::UIntTy Location; - unsigned StringLength; - union { - // If Kind != IntegerLiteral. - const char *StringData; - - // If Kind == IntegerLiteral. - uint64_t IntegerValue; - }; - - void clear() { - Kind = EndOfFile; - Location = 0; - StringLength = 0; - StringData = nullptr; - } - - bool is(TokenKind K) const { return Kind == K; } - - SourceLocation getLocation() const { - return SourceLocation::getFromRawEncoding(Location); - } - - uint64_t getInteger() const { - return Kind == IntegerLiteral ? IntegerValue : 0; - } - - StringRef getString() const { - return Kind == IntegerLiteral ? StringRef() - : StringRef(StringData, StringLength); - } - }; - class ModuleMapParser { - Lexer &L; + modulemap::ModuleMapFile &MMF; SourceManager &SourceMgr; - /// Default target information, used only for string literal - /// parsing. - const TargetInfo *Target; - DiagnosticsEngine &Diags; ModuleMap ⤅ @@ -1555,13 +1481,6 @@ namespace clang { /// Whether an error occurred. bool HadError = false; - /// Stores string data for the various string literals referenced - /// during parsing. - llvm::BumpPtrAllocator StringData; - - /// The current token. - MMToken Tok; - /// The active module. Module *ActiveModule = nullptr; @@ -1575,305 +1494,45 @@ namespace clang { /// 'textual' to match the original intent. llvm::SmallPtrSet<Module *, 2> UsesRequiresExcludedHack; - /// Consume the current token and return its location. - SourceLocation consumeToken(); - - /// Skip tokens until we reach the a token with the given kind - /// (or the end of the file). - void skipUntil(MMToken::TokenKind K); - - bool parseModuleId(ModuleId &Id); - void parseModuleDecl(); - void parseExternModuleDecl(); - void parseRequiresDecl(); - void parseHeaderDecl(MMToken::TokenKind, SourceLocation LeadingLoc); - void parseUmbrellaDirDecl(SourceLocation UmbrellaLoc); - void parseExportDecl(); - void parseExportAsDecl(); - void parseUseDecl(); - void parseLinkDecl(); - void parseConfigMacros(); - void parseConflict(); - void parseInferredModuleDecl(bool Framework, bool Explicit); + void handleModuleDecl(const modulemap::ModuleDecl &MD); + void handleExternModuleDecl(const modulemap::ExternModuleDecl &EMD); + void handleRequiresDecl(const modulemap::RequiresDecl &RD); + void handleHeaderDecl(const modulemap::HeaderDecl &HD); + void handleUmbrellaDirDecl(const modulemap::UmbrellaDirDecl &UDD); + void handleExportDecl(const modulemap::ExportDecl &ED); + void handleExportAsDecl(const modulemap::ExportAsDecl &EAD); + void handleUseDecl(const modulemap::UseDecl &UD); + void handleLinkDecl(const modulemap::LinkDecl &LD); + void handleConfigMacros(const modulemap::ConfigMacrosDecl &CMD); + void handleConflict(const modulemap::ConflictDecl &CD); + void handleInferredModuleDecl(const modulemap::ModuleDecl &MD); /// Private modules are canonicalized as Foo_Private. Clang provides extra /// module map search logic to find the appropriate private module when PCH /// is used with implicit module maps. Warn when private modules are written /// in other ways (FooPrivate and Foo.Private), providing notes and fixits. - void diagnosePrivateModules(SourceLocation ExplicitLoc, - SourceLocation FrameworkLoc); + void diagnosePrivateModules(SourceLocation StartLoc); using Attributes = ModuleMap::Attributes; - bool parseOptionalAttributes(Attributes &Attrs); - public: - ModuleMapParser(Lexer &L, SourceManager &SourceMgr, - const TargetInfo *Target, DiagnosticsEngine &Diags, + ModuleMapParser(modulemap::ModuleMapFile &MMF, + SourceManager &SourceMgr, DiagnosticsEngine &Diags, ModuleMap &Map, FileID ModuleMapFID, DirectoryEntryRef Directory, bool IsSystem) - : L(L), SourceMgr(SourceMgr), Target(Target), Diags(Diags), Map(Map), - ModuleMapFID(ModuleMapFID), Directory(Directory), IsSystem(IsSystem) { - Tok.clear(); - consumeToken(); - } + : MMF(MMF), SourceMgr(SourceMgr), Diags(Diags), Map(Map), + ModuleMapFID(ModuleMapFID), Directory(Directory), IsSystem(IsSystem) {} bool parseModuleMapFile(); - - bool terminatedByDirective() { return false; } - SourceLocation getLocation() { return Tok.getLocation(); } }; } // namespace clang -SourceLocation ModuleMapParser::consumeToken() { - SourceLocation Result = Tok.getLocation(); - -retry: - Tok.clear(); - Token LToken; - L.LexFromRawLexer(LToken); - Tok.Location = LToken.getLocation().getRawEncoding(); - switch (LToken.getKind()) { - case tok::raw_identifier: { - StringRef RI = LToken.getRawIdentifier(); - Tok.StringData = RI.data(); - Tok.StringLength = RI.size(); - Tok.Kind = llvm::StringSwitch<MMToken::TokenKind>(RI) - .Case("config_macros", MMToken::ConfigMacros) - .Case("conflict", MMToken::Conflict) - .Case("exclude", MMToken::ExcludeKeyword) - .Case("explicit", MMToken::ExplicitKeyword) - .Case("export", MMToken::ExportKeyword) - .Case("export_as", MMToken::ExportAsKeyword) - .Case("extern", MMToken::ExternKeyword) - .Case("framework", MMToken::FrameworkKeyword) - .Case("header", MMToken::HeaderKeyword) - .Case("link", MMToken::LinkKeyword) - .Case("module", MMToken::ModuleKeyword) - .Case("private", MMToken::PrivateKeyword) - .Case("requires", MMToken::RequiresKeyword) - .Case("textual", MMToken::TextualKeyword) - .Case("umbrella", MMToken::UmbrellaKeyword) - .Case("use", MMToken::UseKeyword) - .Default(MMToken::Identifier); - break; - } - - case tok::comma: - Tok.Kind = MMToken::Comma; - break; - - case tok::eof: - Tok.Kind = MMToken::EndOfFile; - break; - - case tok::l_brace: - Tok.Kind = MMToken::LBrace; - break; - - case tok::l_square: - Tok.Kind = MMToken::LSquare; - break; - - case tok::period: - Tok.Kind = MMToken::Period; - break; - - case tok::r_brace: - Tok.Kind = MMToken::RBrace; - break; - - case tok::r_square: - Tok.Kind = MMToken::RSquare; - break; - - case tok::star: - Tok.Kind = MMToken::Star; - break; - - case tok::exclaim: - Tok.Kind = MMToken::Exclaim; - break; - - case tok::string_literal: { - if (LToken.hasUDSuffix()) { - Diags.Report(LToken.getLocation(), diag::err_invalid_string_udl); - HadError = true; - goto retry; - } - - // Parse the string literal. - LangOptions LangOpts; - StringLiteralParser StringLiteral(LToken, SourceMgr, LangOpts, *Target); - if (StringLiteral.hadError) - goto retry; - - // Copy the string literal into our string data allocator. - unsigned Length = StringLiteral.GetStringLength(); - char *Saved = StringData.Allocate<char>(Length + 1); - memcpy(Saved, StringLiteral.GetString().data(), Length); - Saved[Length] = 0; - - // Form the token. - Tok.Kind = MMToken::StringLiteral; - Tok.StringData = Saved; - Tok.StringLength = Length; - break; - } - - case tok::numeric_constant: { - // We don't support any suffixes or other complications. - SmallString<32> SpellingBuffer; - SpellingBuffer.resize(LToken.getLength() + 1); - const char *Start = SpellingBuffer.data(); - unsigned Length = - Lexer::getSpelling(LToken, Start, SourceMgr, Map.LangOpts); - uint64_t Value; - if (StringRef(Start, Length).getAsInteger(0, Value)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token); - HadError = true; - goto retry; - } - - Tok.Kind = MMToken::IntegerLiteral; - Tok.IntegerValue = Value; - break; - } - - case tok::comment: - goto retry; - - case tok::hash: - // A module map can be terminated prematurely by - // #pragma clang module contents - // When building the module, we'll treat the rest of the file as the - // contents of the module. - { - auto NextIsIdent = [&](StringRef Str) -> bool { - L.LexFromRawLexer(LToken); - return !LToken.isAtStartOfLine() && LToken.is(tok::raw_identifier) && - LToken.getRawIdentifier() == Str; - }; - if (NextIsIdent("pragma") && NextIsIdent("clang") && - NextIsIdent("module") && NextIsIdent("contents")) { - Tok.Kind = MMToken::EndOfFile; - break; - } - } - [[fallthrough]]; - - default: - Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token); - HadError = true; - goto retry; - } - - return Result; -} - -void ModuleMapParser::skipUntil(MMToken::TokenKind K) { - unsigned braceDepth = 0; - unsigned squareDepth = 0; - do { - switch (Tok.Kind) { - case MMToken::EndOfFile: - return; - - case MMToken::LBrace: - if (Tok.is(K) && braceDepth == 0 && squareDepth == 0) - return; - - ++braceDepth; - break; - - case MMToken::LSquare: - if (Tok.is(K) && braceDepth == 0 && squareDepth == 0) - return; - - ++squareDepth; - break; - - case MMToken::RBrace: - if (braceDepth > 0) - --braceDepth; - else if (Tok.is(K)) - return; - break; - - case MMToken::RSquare: - if (squareDepth > 0) - --squareDepth; - else if (Tok.is(K)) - return; - break; - - default: - if (braceDepth == 0 && squareDepth == 0 && Tok.is(K)) - return; - break; - } - - consumeToken(); - } while (true); -} - -/// Parse a module-id. -/// -/// module-id: -/// identifier -/// identifier '.' module-id -/// -/// \returns true if an error occurred, false otherwise. -bool ModuleMapParser::parseModuleId(ModuleId &Id) { - Id.clear(); - do { - if (Tok.is(MMToken::Identifier) || Tok.is(MMToken::StringLiteral)) { - Id.push_back( - std::make_pair(std::string(Tok.getString()), Tok.getLocation())); - consumeToken(); - } else { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module_name); - return true; - } - - if (!Tok.is(MMToken::Period)) - break; - - consumeToken(); - } while (true); - - return false; -} - -namespace { - - /// Enumerates the known attributes. - enum AttributeKind { - /// An unknown attribute. - AT_unknown, - - /// The 'system' attribute. - AT_system, - - /// The 'extern_c' attribute. - AT_extern_c, - - /// The 'exhaustive' attribute. - AT_exhaustive, - - /// The 'no_undeclared_includes' attribute. - AT_no_undeclared_includes - }; - -} // namespace - /// Private modules are canonicalized as Foo_Private. Clang provides extra /// module map search logic to find the appropriate private module when PCH /// is used with implicit module maps. Warn when private modules are written /// in other ways (FooPrivate and Foo.Private), providing notes and fixits. -void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc, - SourceLocation FrameworkLoc) { +void ModuleMapParser::diagnosePrivateModules(SourceLocation StartLoc) { auto GenNoteAndFixIt = [&](StringRef BadName, StringRef Canonical, const Module *M, SourceRange ReplLoc) { auto D = Diags.Report(ActiveModule->DefinitionLoc, @@ -1902,12 +1561,10 @@ void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc, << FullName; SourceLocation FixItInitBegin = CurrModuleDeclLoc; - if (FrameworkLoc.isValid()) - FixItInitBegin = FrameworkLoc; - if (ExplicitLoc.isValid()) - FixItInitBegin = ExplicitLoc; + if (StartLoc.isValid()) + FixItInitBegin = StartLoc; - if (FrameworkLoc.isValid() || ActiveModule->Parent->IsFramework) + if (ActiveModule->Parent->IsFramework) FixedPrivModDecl.append("framework "); FixedPrivModDecl.append("module "); FixedPrivModDecl.append(Canonical); @@ -1929,103 +1586,28 @@ void ModuleMapParser::diagnosePrivateModules(SourceLocation ExplicitLoc, } } -/// Parse a module declaration. -/// -/// module-declaration: -/// 'extern' 'module' module-id string-literal -/// 'explicit'[opt] 'framework'[opt] 'module' module-id attributes[opt] -/// { module-member* } -/// -/// module-member: -/// requires-declaration -/// header-declaration -/// submodule-declaration -/// export-declaration -/// export-as-declaration -/// link-declaration -/// -/// submodule-declaration: -/// module-declaration -/// inferred-submodule-declaration -void ModuleMapParser::parseModuleDecl() { - assert(Tok.is(MMToken::ExplicitKeyword) || Tok.is(MMToken::ModuleKeyword) || - Tok.is(MMToken::FrameworkKeyword) || Tok.is(MMToken::ExternKeyword)); - if (Tok.is(MMToken::ExternKeyword)) { - parseExternModuleDecl(); - return; - } - - // Parse 'explicit' or 'framework' keyword, if present. - SourceLocation ExplicitLoc; - SourceLocation FrameworkLoc; - bool Explicit = false; - bool Framework = false; - - // Parse 'explicit' keyword, if present. - if (Tok.is(MMToken::ExplicitKeyword)) { - ExplicitLoc = consumeToken(); - Explicit = true; - } - - // Parse 'framework' keyword, if present. - if (Tok.is(MMToken::FrameworkKeyword)) { - FrameworkLoc = consumeToken(); - Framework = true; - } - - // Parse 'module' keyword. - if (!Tok.is(MMToken::ModuleKeyword)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); - consumeToken(); - HadError = true; - return; - } - CurrModuleDeclLoc = consumeToken(); // 'module' keyword - - // If we have a wildcard for the module name, this is an inferred submodule. - // Parse it. - if (Tok.is(MMToken::Star)) - return parseInferredModuleDecl(Framework, Explicit); +void ModuleMapParser::handleModuleDecl(const modulemap::ModuleDecl &MD) { + if (MD.Id.front().first == "*") + return handleInferredModuleDecl(MD); - // Parse the module name. - ModuleId Id; - if (parseModuleId(Id)) { - HadError = true; - return; - } - - if (ActiveModule) { - if (Id.size() > 1) { - Diags.Report(Id.front().second, diag::err_mmap_nested_submodule_id) - << SourceRange(Id.front().second, Id.back().second); - - HadError = true; - return; - } - } else if (Id.size() == 1 && Explicit) { - // Top-level modules can't be explicit. - Diags.Report(ExplicitLoc, diag::err_mmap_explicit_top_level); - Explicit = false; - ExplicitLoc = SourceLocation(); - HadError = true; - } + CurrModuleDeclLoc = MD.Location; Module *PreviousActiveModule = ActiveModule; - if (Id.size() > 1) { + if (MD.Id.size() > 1) { // This module map defines a submodule. Go find the module of which it // is a submodule. ActiveModule = nullptr; const Module *TopLevelModule = nullptr; - for (unsigned I = 0, N = Id.size() - 1; I != N; ++I) { - if (Module *Next = Map.lookupModuleQualified(Id[I].first, ActiveModule)) { + for (unsigned I = 0, N = MD.Id.size() - 1; I != N; ++I) { + if (Module *Next = Map.lookupModuleQualified(MD.Id[I].first, ActiveModule)) { if (I == 0) TopLevelModule = Next; ActiveModule = Next; continue; } - Diags.Report(Id[I].second, diag::err_mmap_missing_parent_module) - << Id[I].first << (ActiveModule != nullptr) + Diags.Report(MD.Id[I].second, diag::err_mmap_missing_parent_module) + << MD.Id[I].first << (ActiveModule != nullptr) << (ActiveModule ? ActiveModule->getTopLevelModule()->getFullModuleName() : ""); @@ -2043,22 +1625,8 @@ void ModuleMapParser::parseModuleDecl() { } } - StringRef ModuleName = Id.back().first; - SourceLocation ModuleNameLoc = Id.back().second; - - // Parse the optional attribute list. - Attributes Attrs; - if (parseOptionalAttributes(Attrs)) - return; - - // Parse the opening brace. - if (!Tok.is(MMToken::LBrace)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace) - << ModuleName; - HadError = true; - return; - } - SourceLocation LBraceLoc = consumeToken(); + StringRef ModuleName = MD.Id.back().first; + SourceLocation ModuleNameLoc = MD.Id.back().second; // Determine whether this (sub)module has already been defined. Module *ShadowingModule = nullptr; @@ -2085,7 +1653,7 @@ void ModuleMapParser::parseModuleDecl() { // that \c Existing is part of a framework iff the redefinition of FW // we have just skipped had it too. Once we do that, stop checking // the local framework qualifier and only rely on \c Existing. - bool PartOfFramework = Framework || Existing->isPartOfFramework(); + bool PartOfFramework = MD.Framework || Existing->isPartOfFramework(); // - If we're building a (preprocessed) module and we've just loaded the // module map file from which it was created. bool ParsedAsMainInput = @@ -2096,14 +1664,6 @@ void ModuleMapParser::parseModuleDecl() { if (LoadedFromASTFile || Inferred || PartOfFramework || ParsedAsMainInput) { ActiveModule = PreviousActiveModule; // Skip the module definition. - skipUntil(MMToken::RBrace); - if (Tok.is(MMToken::RBrace)) - consumeToken(); - else { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); - Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); - HadError = true; - } return; } @@ -2114,12 +1674,6 @@ void ModuleMapParser::parseModuleDecl() { Diags.Report(ModuleNameLoc, diag::err_mmap_module_redefinition) << ModuleName; Diags.Report(Existing->DefinitionLoc, diag::note_mmap_prev_definition); - - // Skip the module definition. - skipUntil(MMToken::RBrace); - if (Tok.is(MMToken::RBrace)) - consumeToken(); - HadError = true; return; } @@ -2128,18 +1682,18 @@ void ModuleMapParser::parseModuleDecl() { // Start defining this module. if (ShadowingModule) { ActiveModule = - Map.createShadowedModule(ModuleName, Framework, ShadowingModule); + Map.createShadowedModule(ModuleName, MD.Framework, ShadowingModule); } else { ActiveModule = Map.findOrCreateModuleFirst(ModuleName, ActiveModule, - Framework, Explicit); + MD.Framework, MD.Explicit); } ActiveModule->DefinitionLoc = ModuleNameLoc; - if (Attrs.IsSystem || IsSystem) + if (MD.Attrs.IsSystem || IsSystem) ActiveModule->IsSystem = true; - if (Attrs.IsExternC) + if (MD.Attrs.IsExternC) ActiveModule->IsExternC = true; - if (Attrs.NoUndeclaredIncludes) + if (MD.Attrs.NoUndeclaredIncludes) ActiveModule->NoUndeclaredIncludes = true; ActiveModule->Directory = Directory; @@ -2161,89 +1715,34 @@ void ModuleMapParser::parseModuleDecl() { !Diags.isIgnored(diag::warn_mmap_mismatched_private_module_name, StartLoc) && ActiveModule->ModuleMapIsPrivate) - diagnosePrivateModules(ExplicitLoc, FrameworkLoc); - - bool Done = false; - do { - switch (Tok.Kind) { - case MMToken::EndOfFile: - case MMToken::RBrace: - Done = true; - break; - - case MMToken::ConfigMacros: - parseConfigMacros(); - break; - - case MMToken::Conflict: - parseConflict(); - break; - - case MMToken::ExplicitKeyword: - case MMToken::ExternKeyword: - case MMToken::FrameworkKeyword: - case MMToken::ModuleKeyword: - parseModuleDecl(); - break; - - case MMToken::ExportKeyword: - parseExportDecl(); - break; - - case MMToken::ExportAsKeyword: - parseExportAsDecl(); - break; - - case MMToken::UseKeyword: - parseUseDecl(); - break; - - case MMToken::RequiresKeyword: - parseRequiresDecl(); - break; - - case MMToken::TextualKeyword: - parseHeaderDecl(MMToken::TextualKeyword, consumeToken()); - break; - - case MMToken::UmbrellaKeyword: { - SourceLocation UmbrellaLoc = consumeToken(); - if (Tok.is(MMToken::HeaderKeyword)) - parseHeaderDecl(MMToken::UmbrellaKeyword, UmbrellaLoc); - else - parseUmbrellaDirDecl(UmbrellaLoc); - break; - } - - case MMToken::ExcludeKeyword: - parseHeaderDecl(MMToken::ExcludeKeyword, consumeToken()); - break; - - case MMToken::PrivateKeyword: - parseHeaderDecl(MMToken::PrivateKeyword, consumeToken()); - break; - - case MMToken::HeaderKeyword: - parseHeaderDecl(MMToken::HeaderKeyword, consumeToken()); - break; - - case MMToken::LinkKeyword: - parseLinkDecl(); - break; - - default: - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_member); - consumeToken(); - break; - } - } while (!Done); - - if (Tok.is(MMToken::RBrace)) - consumeToken(); - else { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); - Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); - HadError = true; + diagnosePrivateModules(MD.Location); + + for (const modulemap::Decl &Decl : MD.Decls) { + std::visit( + llvm::overloaded{ + [&](const modulemap::RequiresDecl &RD) { handleRequiresDecl(RD); }, + [&](const modulemap::HeaderDecl &HD) { handleHeaderDecl(HD); }, + [&](const modulemap::UmbrellaDirDecl &UDD) { + handleUmbrellaDirDecl(UDD); + }, + [&](const modulemap::ModuleDecl &MD) { handleModuleDecl(MD); }, + [&](const modulemap::ExportDecl &ED) { handleExportDecl(ED); }, + [&](const modulemap::ExportAsDecl &EAD) { + handleExportAsDecl(EAD); + }, + [&](const modulemap::ExternModuleDecl &EMD) { + handleExternModuleDecl(EMD); + }, + [&](const modulemap::UseDecl &UD) { handleUseDecl(UD); }, + [&](const modulemap::LinkDecl &LD) { handleLinkDecl(LD); }, + [&](const modulemap::ConfigMacrosDecl &CMD) { + handleConfigMacros(CMD); + }, + [&](const modulemap::ConflictDecl &CD) { handleConflict(CD); }, + [&](const modulemap::ExcludeDecl &ED) { + Diags.Report(ED.Location, diag::err_mmap_expected_member); + }}, + Decl); } // If the active module is a top-level framework, and there are no link @@ -2265,44 +1764,13 @@ void ModuleMapParser::parseModuleDecl() { ActiveModule = PreviousActiveModule; } -/// Parse an extern module declaration. -/// -/// extern module-declaration: -/// 'extern' 'module' module-id string-literal -void ModuleMapParser::parseExternModuleDecl() { - assert(Tok.is(MMToken::ExternKeyword)); - SourceLocation ExternLoc = consumeToken(); // 'extern' keyword - - // Parse 'module' keyword. - if (!Tok.is(MMToken::ModuleKeyword)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); - consumeToken(); - HadError = true; - return; - } - consumeToken(); // 'module' keyword - - // Parse the module name. - ModuleId Id; - if (parseModuleId(Id)) { - HadError = true; - return; - } - - // Parse the referenced module map file name. - if (!Tok.is(MMToken::StringLiteral)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_mmap_file); - HadError = true; - return; - } - std::string FileName = std::string(Tok.getString()); - consumeToken(); // filename - - StringRef FileNameRef = FileName; +void ModuleMapParser::handleExternModuleDecl( + const modulemap::ExternModuleDecl &EMD) { + StringRef FileNameRef = EMD.Path; SmallString<128> ModuleMapFileName; if (llvm::sys::path::is_relative(FileNameRef)) { ModuleMapFileName += Directory.getName(); - llvm::sys::path::append(ModuleMapFileName, FileName); + llvm::sys::path::append(ModuleMapFileName, EMD.Path); FileNameRef = ModuleMapFileName; } if (auto File = SourceMgr.getFileManager().getOptionalFileRef(FileNameRef)) @@ -2311,7 +1779,7 @@ void ModuleMapParser::parseExternModuleDecl() { Map.HeaderInfo.getHeaderSearchOpts().ModuleMapFileHomeIsCwd ? Directory : File->getDir(), - FileID(), nullptr, ExternLoc); + FileID(), nullptr, EMD.Location); } /// Whether to add the requirement \p Feature to the module \p M. @@ -2343,88 +1811,36 @@ static bool shouldAddRequirement(Module *M, StringRef Feature, return true; } -/// Parse a requires declaration. -/// -/// requires-declaration: -/// 'requires' feature-list -/// -/// feature-list: -/// feature ',' feature-list -/// feature -/// -/// feature: -/// '!'[opt] identifier -void ModuleMapParser::parseRequiresDecl() { - assert(Tok.is(MMToken::RequiresKeyword)); - - // Parse 'requires' keyword. - consumeToken(); - - // Parse the feature-list. - do { - bool RequiredState = true; - if (Tok.is(MMToken::Exclaim)) { - RequiredState = false; - consumeToken(); - } - if (!Tok.is(MMToken::Identifier)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_feature); - HadError = true; - return; - } - - // Consume the feature name. - std::string Feature = std::string(Tok.getString()); - consumeToken(); +void ModuleMapParser::handleRequiresDecl(const modulemap::RequiresDecl &RD) { + for (const modulemap::RequiresFeature &RF : RD.Features) { bool IsRequiresExcludedHack = false; bool ShouldAddRequirement = - shouldAddRequirement(ActiveModule, Feature, IsRequiresExcludedHack); + shouldAddRequirement(ActiveModule, RF.Feature, IsRequiresExcludedHack); if (IsRequiresExcludedHack) UsesRequiresExcludedHack.insert(ActiveModule); if (ShouldAddRequirement) { // Add this feature. - ActiveModule->addRequirement(Feature, RequiredState, Map.LangOpts, + ActiveModule->addRequirement(RF.Feature, RF.RequiredState, Map.LangOpts, *Map.Target); } - - if (!Tok.is(MMToken::Comma)) - break; - - // Consume the comma. - consumeToken(); - } while (true); + } } -/// Parse a header declaration. -/// -/// header-declaration: -/// 'textual'[opt] 'header' string-literal -/// 'private' 'textual'[opt] 'header' string-literal -/// 'exclude' 'header' string-literal -/// 'umbrella' 'header' string-literal -/// -/// FIXME: Support 'private textual header'. -void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken, - SourceLocation LeadingLoc) { +void ModuleMapParser::handleHeaderDecl(const modulemap::HeaderDecl &HD) { // We've already consumed the first token. ModuleMap::ModuleHeaderRole Role = ModuleMap::NormalHeader; - if (LeadingToken == MMToken::PrivateKeyword) { + if (HD.Private) { Role = ModuleMap::PrivateHeader; - // 'private' may optionally be followed by 'textual'. - if (Tok.is(MMToken::TextualKeyword)) { - LeadingToken = Tok.Kind; - consumeToken(); - } - } else if (LeadingToken == MMToken::ExcludeKeyword) { + } else if (HD.Excluded) { Role = ModuleMap::ExcludedHeader; } - if (LeadingToken == MMToken::TextualKeyword) + if (HD.Textual) Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader); if (UsesRequiresExcludedHack.count(ActiveModule)) { @@ -2433,28 +1849,10 @@ void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken, Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader); } - if (LeadingToken != MMToken::HeaderKeyword) { - if (!Tok.is(MMToken::HeaderKeyword)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) - << (LeadingToken == MMToken::PrivateKeyword ? "private" : - LeadingToken == MMToken::ExcludeKeyword ? "exclude" : - LeadingToken == MMToken::TextualKeyword ? "textual" : "umbrella"); - return; - } - consumeToken(); - } - - // Parse the header name. - if (!Tok.is(MMToken::StringLiteral)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) - << "header"; - HadError = true; - return; - } Module::UnresolvedHeaderDirective Header; - Header.FileName = std::string(Tok.getString()); - Header.FileNameLoc = consumeToken(); - Header.IsUmbrella = LeadingToken == MMToken::UmbrellaKeyword; + Header.FileName = HD.Path; + Header.FileNameLoc = HD.PathLoc; + Header.IsUmbrella = HD.Umbrella; Header.Kind = Map.headerRoleToKind(Role); // Check whether we already have an umbrella. @@ -2466,60 +1864,10 @@ void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken, return; } - // If we were given stat information, parse it so we can skip looking for - // the file. - if (Tok.is(MMToken::LBrace)) { - SourceLocation LBraceLoc = consumeToken(); - - while (!Tok.is(MMToken::RBrace) && !Tok.is(MMToken::EndOfFile)) { - enum Attribute { Size, ModTime, Unknown }; - StringRef Str = Tok.getString(); - SourceLocation Loc = consumeToken(); - switch (llvm::StringSwitch<Attribute>(Str) - .Case("size", Size) - .Case("mtime", ModTime) - .Default(Unknown)) { - case Size: - if (Header.Size) - Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str; - if (!Tok.is(MMToken::IntegerLiteral)) { - Diags.Report(Tok.getLocation(), - diag::err_mmap_invalid_header_attribute_value) << Str; - skipUntil(MMToken::RBrace); - break; - } - Header.Size = Tok.getInteger(); - consumeToken(); - break; - - case ModTime: - if (Header.ModTime) - Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str; - if (!Tok.is(MMToken::IntegerLiteral)) { - Diags.Report(Tok.getLocation(), - diag::err_mmap_invalid_header_attribute_value) << Str; - skipUntil(MMToken::RBrace); - break; - } - Header.ModTime = Tok.getInteger(); - consumeToken(); - break; - - case Unknown: - Diags.Report(Loc, diag::err_mmap_expected_header_attribute); - skipUntil(MMToken::RBrace); - break; - } - } - - if (Tok.is(MMToken::RBrace)) - consumeToken(); - else { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); - Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); - HadError = true; - } - } + if (HD.Size) + Header.Size = HD.Size; + if (HD.MTime) + Header.ModTime = HD.MTime; bool NeedsFramework = false; // Don't add headers to the builtin modules if the builtin headers belong to @@ -2541,26 +1889,13 @@ static bool compareModuleHeaders(const Module::Header &A, return A.NameAsWritten < B.NameAsWritten; } -/// Parse an umbrella directory declaration. -/// -/// umbrella-dir-declaration: -/// umbrella string-literal -void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { - // Parse the directory name. - if (!Tok.is(MMToken::StringLiteral)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) - << "umbrella"; - HadError = true; - return; - } - - std::string DirName = std::string(Tok.getString()); +void ModuleMapParser::handleUmbrellaDirDecl(const modulemap::UmbrellaDirDecl &UDD) { + std::string DirName = std::string(UDD.Path); std::string DirNameAsWritten = DirName; - SourceLocation DirNameLoc = consumeToken(); // Check whether we already have an umbrella. if (!std::holds_alternative<std::monostate>(ActiveModule->Umbrella)) { - Diags.Report(DirNameLoc, diag::err_mmap_umbrella_clash) + Diags.Report(UDD.Location, diag::err_mmap_umbrella_clash) << ActiveModule->getFullModuleName(); HadError = true; return; @@ -2578,7 +1913,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { } if (!Dir) { - Diags.Report(DirNameLoc, diag::warn_mmap_umbrella_dir_not_found) + Diags.Report(UDD.Location, diag::warn_mmap_umbrella_dir_not_found) << DirName; return; } @@ -2609,7 +1944,7 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { } if (Module *OwningModule = Map.UmbrellaDirs[*Dir]) { - Diags.Report(UmbrellaLoc, diag::err_mmap_umbrella_clash) + Diags.Report(UDD.Location, diag::err_mmap_umbrella_clash) << OwningModule->getFullModuleName(); HadError = true; return; @@ -2619,520 +1954,170 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) { Map.setUmbrellaDirAsWritten(ActiveModule, *Dir, DirNameAsWritten, DirName); } -/// Parse a module export declaration. -/// -/// export-declaration: -/// 'export' wildcard-module-id -/// -/// wildcard-module-id: -/// identifier -/// '*' -/// identifier '.' wildcard-module-id -void ModuleMapParser::parseExportDecl() { - assert(Tok.is(MMToken::ExportKeyword)); - SourceLocation ExportLoc = consumeToken(); - - // Parse the module-id with an optional wildcard at the end. - ModuleId ParsedModuleId; - bool Wildcard = false; - do { - // FIXME: Support string-literal module names here. - if (Tok.is(MMToken::Identifier)) { - ParsedModuleId.push_back( - std::make_pair(std::string(Tok.getString()), Tok.getLocation())); - consumeToken(); - - if (Tok.is(MMToken::Period)) { - consumeToken(); - continue; - } - - break; - } - - if(Tok.is(MMToken::Star)) { - Wildcard = true; - consumeToken(); - break; - } - - Diags.Report(Tok.getLocation(), diag::err_mmap_module_id); - HadError = true; - return; - } while (true); - +void ModuleMapParser::handleExportDecl(const modulemap::ExportDecl &ED) { Module::UnresolvedExportDecl Unresolved = { - ExportLoc, ParsedModuleId, Wildcard + ED.Location, ED.Id, ED.Wildcard }; ActiveModule->UnresolvedExports.push_back(Unresolved); } -/// Parse a module export_as declaration. -/// -/// export-as-declaration: -/// 'export_as' identifier -void ModuleMapParser::parseExportAsDecl() { - assert(Tok.is(MMToken::ExportAsKeyword)); - consumeToken(); - - if (!Tok.is(MMToken::Identifier)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_module_id); - HadError = true; - return; - } - - if (ActiveModule->Parent) { - Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as); - consumeToken(); - return; - } +void ModuleMapParser::handleExportAsDecl(const modulemap::ExportAsDecl &EAD) { + auto ModName = EAD.Id.front(); if (!ActiveModule->ExportAsModule.empty()) { - if (ActiveModule->ExportAsModule == Tok.getString()) { - Diags.Report(Tok.getLocation(), diag::warn_mmap_redundant_export_as) - << ActiveModule->Name << Tok.getString(); + if (ActiveModule->ExportAsModule == ModName.first) { + Diags.Report(ModName.second, diag::warn_mmap_redundant_export_as) + << ActiveModule->Name << ModName.first; } else { - Diags.Report(Tok.getLocation(), diag::err_mmap_conflicting_export_as) + Diags.Report(ModName.second, diag::err_mmap_conflicting_export_as) << ActiveModule->Name << ActiveModule->ExportAsModule - << Tok.getString(); + << ModName.first; } } - ActiveModule->ExportAsModule = std::string(Tok.getString()); + ActiveModule->ExportAsModule = ModName.first; Map.addLinkAsDependency(ActiveModule); - - consumeToken(); } -/// Parse a module use declaration. -/// -/// use-declaration: -/// 'use' wildcard-module-id -void ModuleMapParser::parseUseDecl() { - assert(Tok.is(MMToken::UseKeyword)); - auto KWLoc = consumeToken(); - // Parse the module-id. - ModuleId ParsedModuleId; - parseModuleId(ParsedModuleId); - +void ModuleMapParser::handleUseDecl(const modulemap::UseDecl &UD) { if (ActiveModule->Parent) - Diags.Report(KWLoc, diag::err_mmap_use_decl_submodule); + Diags.Report(UD.Location, diag::err_mmap_use_decl_submodule); else - ActiveModule->UnresolvedDirectUses.push_back(ParsedModuleId); + ActiveModule->UnresolvedDirectUses.push_back(UD.Id); } -/// Parse a link declaration. -/// -/// module-declaration: -/// 'link' 'framework'[opt] string-literal -void ModuleMapParser::parseLinkDecl() { - assert(Tok.is(MMToken::LinkKeyword)); - SourceLocation LinkLoc = consumeToken(); - - // Parse the optional 'framework' keyword. - bool IsFramework = false; - if (Tok.is(MMToken::FrameworkKeyword)) { - consumeToken(); - IsFramework = true; - } - - // Parse the library name - if (!Tok.is(MMToken::StringLiteral)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_library_name) - << IsFramework << SourceRange(LinkLoc); - HadError = true; - return; - } - - std::string LibraryName = std::string(Tok.getString()); - consumeToken(); - ActiveModule->LinkLibraries.push_back(Module::LinkLibrary(LibraryName, - IsFramework)); +void ModuleMapParser::handleLinkDecl(const modulemap::LinkDecl &LD) { + ActiveModule->LinkLibraries.push_back( + Module::LinkLibrary(std::string{LD.Library}, LD.Framework)); } -/// Parse a configuration macro declaration. -/// -/// module-declaration: -/// 'config_macros' attributes[opt] config-macro-list? -/// -/// config-macro-list: -/// identifier (',' identifier)? -void ModuleMapParser::parseConfigMacros() { - assert(Tok.is(MMToken::ConfigMacros)); - SourceLocation ConfigMacrosLoc = consumeToken(); - - // Only top-level modules can have configuration macros. +void ModuleMapParser::handleConfigMacros( + const modulemap::ConfigMacrosDecl &CMD) { if (ActiveModule->Parent) { - Diags.Report(ConfigMacrosLoc, diag::err_mmap_config_macro_submodule); - } - - // Parse the optional attributes. - Attributes Attrs; - if (parseOptionalAttributes(Attrs)) - return; - - if (Attrs.IsExhaustive && !ActiveModule->Parent) { - ActiveModule->ConfigMacrosExhaustive = true; - } - - // If we don't have an identifier, we're done. - // FIXME: Support macros with the same name as a keyword here. - if (!Tok.is(MMToken::Identifier)) + Diags.Report(CMD.Location, diag::err_mmap_config_macro_submodule); return; - - // Consume the first identifier. - if (!ActiveModule->Parent) { - ActiveModule->ConfigMacros.push_back(Tok.getString().str()); } - consumeToken(); - - do { - // If there's a comma, consume it. - if (!Tok.is(MMToken::Comma)) - break; - consumeToken(); - - // We expect to see a macro name here. - // FIXME: Support macros with the same name as a keyword here. - if (!Tok.is(MMToken::Identifier)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_config_macro); - break; - } - - // Consume the macro name. - if (!ActiveModule->Parent) { - ActiveModule->ConfigMacros.push_back(Tok.getString().str()); - } - consumeToken(); - } while (true); -} - -/// Format a module-id into a string. -static std::string formatModuleId(const ModuleId &Id) { - std::string result; - { - llvm::raw_string_ostream OS(result); - for (unsigned I = 0, N = Id.size(); I != N; ++I) { - if (I) - OS << "."; - OS << Id[I].first; - } + // TODO: Is this really the behavior we want for multiple config_macros + // declarations? If any of them are exhaustive then all of them are. + if (CMD.Exhaustive) { + ActiveModule->ConfigMacrosExhaustive = true; } - - return result; + ActiveModule->ConfigMacros.insert(ActiveModule->ConfigMacros.end(), + CMD.Macros.begin(), CMD.Macros.end()); } -/// Parse a conflict declaration. -/// -/// module-declaration: -/// 'conflict' module-id ',' string-literal -void ModuleMapParser::parseConflict() { - assert(Tok.is(MMToken::Conflict)); - SourceLocation ConflictLoc = consumeToken(); +void ModuleMapParser::handleConflict(const modulemap::ConflictDecl &CD) { Module::UnresolvedConflict Conflict; - // Parse the module-id. - if (parseModuleId(Conflict.Id)) - return; - - // Parse the ','. - if (!Tok.is(MMToken::Comma)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_comma) - << SourceRange(ConflictLoc); - return; - } - consumeToken(); - - // Parse the message. - if (!Tok.is(MMToken::StringLiteral)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_message) - << formatModuleId(Conflict.Id); - return; - } - Conflict.Message = Tok.getString().str(); - consumeToken(); + Conflict.Id = CD.Id; + Conflict.Message = CD.Message; - // Add this unresolved conflict. ActiveModule->UnresolvedConflicts.push_back(Conflict); } -/// Parse an inferred module declaration (wildcard modules). -/// -/// module-declaration: -/// 'explicit'[opt] 'framework'[opt] 'module' * attributes[opt] -/// { inferred-module-member* } -/// -/// inferred-module-member: -/// 'export' '*' -/// 'exclude' identifier -void ModuleMapParser::parseInferredModuleDecl(bool Framework, bool Explicit) { - assert(Tok.is(MMToken::Star)); - SourceLocation StarLoc = consumeToken(); - bool Failed = false; +void ModuleMapParser::handleInferredModuleDecl(const modulemap::ModuleDecl &MD) { + SourceLocation StarLoc = MD.Id.front().second; // Inferred modules must be submodules. - if (!ActiveModule && !Framework) { + if (!ActiveModule && !MD.Framework) { Diags.Report(StarLoc, diag::err_mmap_top_level_inferred_submodule); - Failed = true; + return; } if (ActiveModule) { // Inferred modules must have umbrella directories. - if (!Failed && ActiveModule->IsAvailable && + if (ActiveModule->IsAvailable && !ActiveModule->getEffectiveUmbrellaDir()) { Diags.Report(StarLoc, diag::err_mmap_inferred_no_umbrella); - Failed = true; + return; } // Check for redefinition of an inferred module. - if (!Failed && ActiveModule->InferSubmodules) { + if (ActiveModule->InferSubmodules) { Diags.Report(StarLoc, diag::err_mmap_inferred_redef); if (ActiveModule->InferredSubmoduleLoc.isValid()) Diags.Report(ActiveModule->InferredSubmoduleLoc, diag::note_mmap_prev_definition); - Failed = true; + return; } // Check for the 'framework' keyword, which is not permitted here. - if (Framework) { + if (MD.Framework) { Diags.Report(StarLoc, diag::err_mmap_inferred_framework_submodule); - Framework = false; + return; } - } else if (Explicit) { + } else if (MD.Explicit) { Diags.Report(StarLoc, diag::err_mmap_explicit_inferred_framework); - Explicit = false; - } - - // If there were any problems with this inferred submodule, skip its body. - if (Failed) { - if (Tok.is(MMToken::LBrace)) { - consumeToken(); - skipUntil(MMToken::RBrace); - if (Tok.is(MMToken::RBrace)) - consumeToken(); - } - HadError = true; return; } - // Parse optional attributes. - Attributes Attrs; - if (parseOptionalAttributes(Attrs)) - return; - if (ActiveModule) { // Note that we have an inferred submodule. ActiveModule->InferSubmodules = true; ActiveModule->InferredSubmoduleLoc = StarLoc; - ActiveModule->InferExplicitSubmodules = Explicit; + ActiveModule->InferExplicitSubmodules = MD.Explicit; } else { // We'll be inferring framework modules for this directory. Map.InferredDirectories[Directory].InferModules = true; - Map.InferredDirectories[Directory].Attrs = Attrs; + Map.InferredDirectories[Directory].Attrs = MD.Attrs; Map.InferredDirectories[Directory].ModuleMapFID = ModuleMapFID; // FIXME: Handle the 'framework' keyword. } - // Parse the opening brace. - if (!Tok.is(MMToken::LBrace)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace_wildcard); - HadError = true; - return; - } - SourceLocation LBraceLoc = consumeToken(); - - // Parse the body of the inferred submodule. - bool Done = false; - do { - switch (Tok.Kind) { - case MMToken::EndOfFile: - case MMToken::RBrace: - Done = true; - break; - - case MMToken::ExcludeKeyword: - if (ActiveModule) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member) - << (ActiveModule != nullptr); - consumeToken(); - break; - } - - consumeToken(); - // FIXME: Support string-literal module names here. - if (!Tok.is(MMToken::Identifier)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_missing_exclude_name); - break; - } - - Map.InferredDirectories[Directory].ExcludedModules.push_back( - std::string(Tok.getString())); - consumeToken(); - break; - - case MMToken::ExportKeyword: - if (!ActiveModule) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member) - << (ActiveModule != nullptr); - consumeToken(); - break; - } - - consumeToken(); - if (Tok.is(MMToken::Star)) - ActiveModule->InferExportWildcard = true; - else - Diags.Report(Tok.getLocation(), - diag::err_mmap_expected_export_wildcard); - consumeToken(); - break; - - case MMToken::ExplicitKeyword: - case MMToken::ModuleKeyword: - case MMToken::HeaderKeyword: - case MMToken::PrivateKeyword: - case MMToken::UmbrellaKeyword: - default: - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_inferred_member) - << (ActiveModule != nullptr); - consumeToken(); - break; - } - } while (!Done); - - if (Tok.is(MMToken::RBrace)) - consumeToken(); - else { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); - Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); - HadError = true; + for (const modulemap::Decl &Decl : MD.Decls) { + std::visit( + llvm::overloaded{ + [&](const auto &Other) { + Diags.Report(Other.Location, + diag::err_mmap_expected_inferred_member) + << (ActiveModule != nullptr); + }, + [&](const modulemap::ExcludeDecl &ED) { + // Only inferred frameworks can have exclude decls + if (ActiveModule) { + Diags.Report(ED.Location, + diag::err_mmap_expected_inferred_member) + << (ActiveModule != nullptr); + HadError = true; + return; + } + Map.InferredDirectories[Directory].ExcludedModules.emplace_back( + ED.Module); + }, + [&](const modulemap::ExportDecl &ED) { + // Only inferred submodules can have export decls + if (!ActiveModule) { + Diags.Report(ED.Location, + diag::err_mmap_expected_inferred_member) + << (ActiveModule != nullptr); + HadError = true; + return; + } + + if (ED.Wildcard && ED.Id.size() == 0) + ActiveModule->InferExportWildcard = true; + else + Diags.Report(ED.Id.front().second, + diag::err_mmap_expected_export_wildcard); + }}, + Decl); } } -/// Parse optional attributes. -/// -/// attributes: -/// attribute attributes -/// attribute -/// -/// attribute: -/// [ identifier ] -/// -/// \param Attrs Will be filled in with the parsed attributes. -/// -/// \returns true if an error occurred, false otherwise. -bool ModuleMapParser::parseOptionalAttributes(Attributes &Attrs) { - bool HadError = false; - - while (Tok.is(MMToken::LSquare)) { - // Consume the '['. - SourceLocation LSquareLoc = consumeToken(); - - // Check whether we have an attribute name here. - if (!Tok.is(MMToken::Identifier)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_attribute); - skipUntil(MMToken::RSquare); - if (Tok.is(MMToken::RSquare)) - consumeToken(); - HadError = true; - } - - // Decode the attribute name. - AttributeKind Attribute - = llvm::StringSwitch<AttributeKind>(Tok.getString()) - .Case("exhaustive", AT_exhaustive) - .Case("extern_c", AT_extern_c) - .Case("no_undeclared_includes", AT_no_undeclared_includes) - .Case("system", AT_system) - .Default(AT_unknown); - switch (Attribute) { - case AT_unknown: - Diags.Report(Tok.getLocation(), diag::warn_mmap_unknown_attribute) - << Tok.getString(); - break; - - case AT_system: - Attrs.IsSystem = true; - break; - - case AT_extern_c: - Attrs.IsExternC = true; - break; - - case AT_exhaustive: - Attrs.IsExhaustive = true; - break; - - case AT_no_undeclared_includes: - Attrs.NoUndeclaredIncludes = true; - break; - } - consumeToken(); - - // Consume the ']'. - if (!Tok.is(MMToken::RSquare)) { - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rsquare); - Diags.Report(LSquareLoc, diag::note_mmap_lsquare_match); - skipUntil(MMToken::RSquare); - HadError = true; - } - - if (Tok.is(MMToken::RSquare)) - consumeToken(); +bool ModuleMapParser::parseModuleMapFile() { + for (const auto &Decl : MMF.Decls) { + std::visit(llvm::overloaded{[&](const modulemap::ModuleDecl &MD) { + handleModuleDecl(MD); + }, + [&](const modulemap::ExternModuleDecl &EMD) { + handleExternModuleDecl(EMD); + }}, + Decl); } - return HadError; } -/// Parse a module map file. -/// -/// module-map-file: -/// module-declaration* -bool ModuleMapParser::parseModuleMapFile() { - do { - switch (Tok.Kind) { - case MMToken::EndOfFile: - return HadError; - - case MMToken::ExplicitKeyword: - case MMToken::ExternKeyword: - case MMToken::ModuleKeyword: - case MMToken::FrameworkKeyword: - parseModuleDecl(); - break; - - case MMToken::Comma: - case MMToken::ConfigMacros: - case MMToken::Conflict: - case MMToken::Exclaim: - case MMToken::ExcludeKeyword: - case MMToken::ExportKeyword: - case MMToken::ExportAsKeyword: - case MMToken::HeaderKeyword: - case MMToken::Identifier: - case MMToken::LBrace: - case MMToken::LinkKeyword: - case MMToken::LSquare: - case MMToken::Period: - case MMToken::PrivateKeyword: - case MMToken::RBrace: - case MMToken::RSquare: - case MMToken::RequiresKeyword: - case MMToken::Star: - case MMToken::StringLiteral: - case MMToken::IntegerLiteral: - case MMToken::TextualKeyword: - case MMToken::UmbrellaKeyword: - case MMToken::UseKeyword: - Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); - HadError = true; - consumeToken(); - break; - } - } while (true); -} - bool ModuleMap::parseModuleMapFile(FileEntryRef File, bool IsSystem, DirectoryEntryRef Dir, FileID ID, unsigned *Offset, @@ -3157,25 +2142,18 @@ bool ModuleMap::parseModuleMapFile(FileEntryRef File, bool IsSystem, assert((!Offset || *Offset <= Buffer->getBufferSize()) && "invalid buffer offset"); - // Parse this module map file. - Lexer L(SourceMgr.getLocForStartOfFile(ID), MMapLangOpts, - Buffer->getBufferStart(), - Buffer->getBufferStart() + (Offset ? *Offset : 0), - Buffer->getBufferEnd()); - SourceLocation Start = L.getSourceLocation(); - ModuleMapParser Parser(L, SourceMgr, Target, Diags, *this, ID, Dir, IsSystem); - bool Result = Parser.parseModuleMapFile(); - ParsedModuleMap[File] = Result; - - if (Offset) { - auto Loc = SourceMgr.getDecomposedLoc(Parser.getLocation()); - assert(Loc.first == ID && "stopped in a different file?"); - *Offset = Loc.second; + std::optional<modulemap::ModuleMapFile> MMF = + modulemap::parseModuleMap(File, SourceMgr, Diags, IsSystem, Offset); + bool Result = false; + if (MMF) { + ModuleMapParser Parser(*MMF, SourceMgr, Diags, *this, ID, Dir, IsSystem); + Result = Parser.parseModuleMapFile(); } + ParsedModuleMap[File] = Result; // Notify callbacks that we parsed it. for (const auto &Cb : Callbacks) - Cb->moduleMapFileRead(Start, File, IsSystem); + Cb->moduleMapFileRead(SourceLocation(), File, IsSystem); return Result; } diff --git a/clang/lib/Lex/ModuleMapFile.cpp b/clang/lib/Lex/ModuleMapFile.cpp new file mode 100644 index 00000000000000..23e6dd80e28973 --- /dev/null +++ b/clang/lib/Lex/ModuleMapFile.cpp @@ -0,0 +1,1248 @@ +//===- ModuleMapFile.cpp - ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file handles parsing of modulemap files into a simple AST. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Lex/ModuleMapFile.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/Module.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/LexDiagnostic.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/ModuleMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Format.h" +#include <optional> + +using namespace clang; +using namespace modulemap; + +namespace { +struct MMToken { + enum TokenKind { + Comma, + ConfigMacros, + Conflict, + EndOfFile, + HeaderKeyword, + Identifier, + Exclaim, + ExcludeKeyword, + ExplicitKeyword, + ExportKeyword, + ExportAsKeyword, + ExternKeyword, + FrameworkKeyword, + LinkKeyword, + ModuleKeyword, + Period, + PrivateKeyword, + UmbrellaKeyword, + UseKeyword, + RequiresKeyword, + Star, + StringLiteral, + IntegerLiteral, + TextualKeyword, + LBrace, + RBrace, + LSquare, + RSquare + } Kind; + + SourceLocation::UIntTy Location; + unsigned StringLength; + union { + // If Kind != IntegerLiteral. + const char *StringData; + + // If Kind == IntegerLiteral. + uint64_t IntegerValue; + }; + + void clear() { + Kind = EndOfFile; + Location = 0; + StringLength = 0; + StringData = nullptr; + } + + bool is(TokenKind K) const { return Kind == K; } + + SourceLocation getLocation() const { + return SourceLocation::getFromRawEncoding(Location); + } + + uint64_t getInteger() const { + return Kind == IntegerLiteral ? IntegerValue : 0; + } + + StringRef getString() const { + return Kind == IntegerLiteral ? StringRef() + : StringRef(StringData, StringLength); + } +}; + +struct ModuleMapParser { + // External context + Lexer &L; + DiagnosticsEngine &Diags; + + /// The current module map file. + FileEntryRef MMFile; + + /// Parsed representation of the module map file + ModuleMapFile MMF{}; + + /// Temporarily store string data during parsing + llvm::BumpPtrAllocator StringData{}; + + bool HadError = false; + + /// The current token. + MMToken Tok{}; + + bool parseTopLevelDecls(); + std::optional<ModuleDecl> parseModuleDecl(bool TopLevel); + std::optional<ExternModuleDecl> parseExternModuleDecl(); + std::optional<ConfigMacrosDecl> parseConfigMacrosDecl(); + std::optional<ConflictDecl> parseConflictDecl(); + std::optional<ExportDecl> parseExportDecl(); + std::optional<ExportAsDecl> parseExportAsDecl(); + std::optional<UseDecl> parseUseDecl(); + std::optional<RequiresDecl> parseRequiresDecl(); + std::optional<HeaderDecl> parseHeaderDecl(MMToken::TokenKind LeadingToken, + SourceLocation LeadingLoc); + std::optional<ExcludeDecl> parseExcludeDecl(clang::SourceLocation LeadingLoc); + std::optional<UmbrellaDirDecl> + parseUmbrellaDirDecl(SourceLocation UmbrellaLoc); + std::optional<LinkDecl> parseLinkDecl(); + + SourceLocation consumeToken(); + void skipUntil(MMToken::TokenKind K); + bool parseModuleId(ModuleId &Id); + bool parseOptionalAttributes(ModuleAttributes &Attrs); + + SourceLocation getLocation() const { return Tok.getLocation(); }; +}; + +std::string formatModuleId(const ModuleId &Id) { + std::string result; + { + llvm::raw_string_ostream OS(result); + + for (unsigned I = 0, N = Id.size(); I != N; ++I) { + if (I) + OS << "."; + OS << Id[I].first; + } + } + + return result; +} +} // end anonymous namespace + +std::optional<ModuleMapFile> modulemap::parseModuleMap(FileEntryRef File, + SourceManager &SM, + DiagnosticsEngine &Diags, + bool IsSystem, + unsigned *Offset) { + auto FileCharacter = + IsSystem ? SrcMgr::C_System_ModuleMap : SrcMgr::C_User_ModuleMap; + FileID ID = SM.getOrCreateFileID(File, FileCharacter); + std::optional<llvm::MemoryBufferRef> Buffer = SM.getBufferOrNone(ID); + LangOptions LOpts; + LOpts.LangStd = clang::LangStandard::lang_c99; + Lexer L(SM.getLocForStartOfFile(ID), LOpts, Buffer->getBufferStart(), + Buffer->getBufferStart() + (Offset ? *Offset : 0), + Buffer->getBufferEnd()); + + ModuleMapParser Parser{L, Diags, File}; + bool Failed = Parser.parseTopLevelDecls(); + + if (Offset) { + auto Loc = SM.getDecomposedLoc(Parser.getLocation()); + assert(Loc.first == ID && "stopped in a different file?"); + *Offset = Loc.second; + } + + if (Failed) + return std::nullopt; + return std::move(Parser.MMF); +} + +bool ModuleMapParser::parseTopLevelDecls() { + Tok.clear(); + consumeToken(); + do { + switch (Tok.Kind) { + case MMToken::EndOfFile: + return HadError; + case MMToken::ExternKeyword: { + std::optional<ExternModuleDecl> EMD = parseExternModuleDecl(); + if (EMD) + MMF.Decls.push_back(std::move(*EMD)); + break; + } + case MMToken::ExplicitKeyword: + case MMToken::ModuleKeyword: + case MMToken::FrameworkKeyword: { + std::optional<ModuleDecl> MD = parseModuleDecl(true); + if (MD) + MMF.Decls.push_back(std::move(*MD)); + break; + } + case MMToken::Comma: + case MMToken::ConfigMacros: + case MMToken::Conflict: + case MMToken::Exclaim: + case MMToken::ExcludeKeyword: + case MMToken::ExportKeyword: + case MMToken::ExportAsKeyword: + case MMToken::HeaderKeyword: + case MMToken::Identifier: + case MMToken::LBrace: + case MMToken::LinkKeyword: + case MMToken::LSquare: + case MMToken::Period: + case MMToken::PrivateKeyword: + case MMToken::RBrace: + case MMToken::RSquare: + case MMToken::RequiresKeyword: + case MMToken::Star: + case MMToken::StringLiteral: + case MMToken::IntegerLiteral: + case MMToken::TextualKeyword: + case MMToken::UmbrellaKeyword: + case MMToken::UseKeyword: + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); + HadError = true; + consumeToken(); + break; + } + } while (true); +} + +/// Parse a module declaration. +/// +/// module-declaration: +/// 'extern' 'module' module-id string-literal +/// 'explicit'[opt] 'framework'[opt] 'module' module-id attributes[opt] +/// { module-member* } +/// +/// module-member: +/// requires-declaration +/// header-declaration +/// submodule-declaration +/// export-declaration +/// export-as-declaration +/// link-declaration +/// +/// submodule-declaration: +/// module-declaration +/// inferred-submodule-declaration +std::optional<ModuleDecl> ModuleMapParser::parseModuleDecl(bool TopLevel) { + assert(Tok.is(MMToken::ExplicitKeyword) || Tok.is(MMToken::ModuleKeyword) || + Tok.is(MMToken::FrameworkKeyword)); + + ModuleDecl MDecl; + + SourceLocation ExplicitLoc; + MDecl.Explicit = false; + MDecl.Framework = false; + + // Parse 'explicit' keyword, if present. + if (Tok.is(MMToken::ExplicitKeyword)) { + MDecl.Location = ExplicitLoc = consumeToken(); + MDecl.Explicit = true; + } + + // Parse 'framework' keyword, if present. + if (Tok.is(MMToken::FrameworkKeyword)) { + SourceLocation FrameworkLoc = consumeToken(); + if (!MDecl.Location.isValid()) + MDecl.Location = FrameworkLoc; + MDecl.Framework = true; + } + + // Parse 'module' keyword. + if (!Tok.is(MMToken::ModuleKeyword)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); + consumeToken(); + HadError = true; + return std::nullopt; + } + SourceLocation ModuleLoc = consumeToken(); + if (!MDecl.Location.isValid()) + MDecl.Location = ModuleLoc; // 'module' keyword + + // If we have a wildcard for the module name, this is an inferred submodule. + // We treat it as a normal module at this point. + if (Tok.is(MMToken::Star)) { + SourceLocation StarLoc = consumeToken(); + MDecl.Id.push_back({"*", StarLoc}); + if (TopLevel && !MDecl.Framework) { + Diags.Report(StarLoc, diag::err_mmap_top_level_inferred_submodule); + HadError = true; + return std::nullopt; + } + } else { + // Parse the module name. + if (parseModuleId(MDecl.Id)) { + HadError = true; + return std::nullopt; + } + if (!TopLevel) { + if (MDecl.Id.size() > 1) { + Diags.Report(MDecl.Id.front().second, + diag::err_mmap_nested_submodule_id) + << SourceRange(MDecl.Id.front().second, MDecl.Id.back().second); + + HadError = true; + } + } else if (MDecl.Id.size() == 1 && MDecl.Explicit) { + // Top-level modules can't be explicit. + Diags.Report(ExplicitLoc, diag::err_mmap_explicit_top_level); + MDecl.Explicit = false; + HadError = true; + } + } + + // Parse the optional attribute list. + if (parseOptionalAttributes(MDecl.Attrs)) + return std::nullopt; + + // Parse the opening brace. + if (!Tok.is(MMToken::LBrace)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_lbrace) + << MDecl.Id.back().first; + HadError = true; + return std::nullopt; + } + SourceLocation LBraceLoc = consumeToken(); + + bool Done = false; + do { + std::optional<Decl> SubDecl; + switch (Tok.Kind) { + case MMToken::EndOfFile: + case MMToken::RBrace: + Done = true; + break; + + case MMToken::ConfigMacros: + // Only top-level modules can have configuration macros. + if (!TopLevel) + Diags.Report(Tok.getLocation(), diag::err_mmap_config_macro_submodule); + SubDecl = parseConfigMacrosDecl(); + break; + + case MMToken::Conflict: + SubDecl = parseConflictDecl(); + break; + + case MMToken::ExternKeyword: + SubDecl = parseExternModuleDecl(); + break; + + case MMToken::ExplicitKeyword: + case MMToken::FrameworkKeyword: + case MMToken::ModuleKeyword: + SubDecl = parseModuleDecl(false); + break; + + case MMToken::ExportKeyword: + SubDecl = parseExportDecl(); + break; + + case MMToken::ExportAsKeyword: + if (!TopLevel) { + Diags.Report(Tok.getLocation(), diag::err_mmap_submodule_export_as); + parseExportAsDecl(); + } else + SubDecl = parseExportAsDecl(); + break; + + case MMToken::UseKeyword: + SubDecl = parseUseDecl(); + break; + + case MMToken::RequiresKeyword: + SubDecl = parseRequiresDecl(); + break; + + case MMToken::TextualKeyword: + SubDecl = parseHeaderDecl(MMToken::TextualKeyword, consumeToken()); + break; + + case MMToken::UmbrellaKeyword: { + SourceLocation UmbrellaLoc = consumeToken(); + if (Tok.is(MMToken::HeaderKeyword)) + SubDecl = parseHeaderDecl(MMToken::UmbrellaKeyword, UmbrellaLoc); + else + SubDecl = parseUmbrellaDirDecl(UmbrellaLoc); + break; + } + + case MMToken::ExcludeKeyword: { + SourceLocation ExcludeLoc = consumeToken(); + if (Tok.is(MMToken::HeaderKeyword)) + SubDecl = parseHeaderDecl(MMToken::ExcludeKeyword, ExcludeLoc); + else + SubDecl = parseExcludeDecl(ExcludeLoc); + break; + } + + case MMToken::PrivateKeyword: + SubDecl = parseHeaderDecl(MMToken::PrivateKeyword, consumeToken()); + break; + + case MMToken::HeaderKeyword: + SubDecl = parseHeaderDecl(MMToken::HeaderKeyword, consumeToken()); + break; + + case MMToken::LinkKeyword: + SubDecl = parseLinkDecl(); + break; + + default: + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_member); + consumeToken(); + break; + } + if (SubDecl) + MDecl.Decls.push_back(std::move(*SubDecl)); + } while (!Done); + + if (Tok.is(MMToken::RBrace)) + consumeToken(); + else { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); + Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); + HadError = true; + } + return std::move(MDecl); +} + +std::optional<ExternModuleDecl> ModuleMapParser::parseExternModuleDecl() { + assert(Tok.is(MMToken::ExternKeyword)); + ExternModuleDecl EMD; + EMD.Location = consumeToken(); // 'extern' keyword + + // Parse 'module' keyword. + if (!Tok.is(MMToken::ModuleKeyword)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module); + consumeToken(); + HadError = true; + return std::nullopt; + } + consumeToken(); // 'module' keyword + + // Parse the module name. + if (parseModuleId(EMD.Id)) { + HadError = true; + return std::nullopt; + } + + // Parse the referenced module map file name. + if (!Tok.is(MMToken::StringLiteral)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_mmap_file); + HadError = true; + return std::nullopt; + } + EMD.Path = Tok.getString(); + consumeToken(); // filename + + return std::move(EMD); +} + +/// Parse a configuration macro declaration. +/// +/// module-declaration: +/// 'config_macros' attributes[opt] config-macro-list? +/// +/// config-macro-list: +/// identifier (',' identifier)? +std::optional<ConfigMacrosDecl> ModuleMapParser::parseConfigMacrosDecl() { + assert(Tok.is(MMToken::ConfigMacros)); + ConfigMacrosDecl CMDecl; + CMDecl.Location = consumeToken(); + + // Parse the optional attributes. + ModuleAttributes Attrs; + if (parseOptionalAttributes(Attrs)) + return std::nullopt; + + CMDecl.Exhaustive = Attrs.IsExhaustive; + + // If we don't have an identifier, we're done. + // FIXME: Support macros with the same name as a keyword here. + if (!Tok.is(MMToken::Identifier)) + return std::nullopt; + + // Consume the first identifier. + CMDecl.Macros.push_back(Tok.getString()); + consumeToken(); + + do { + // If there's a comma, consume it. + if (!Tok.is(MMToken::Comma)) + break; + consumeToken(); + + // We expect to see a macro name here. + // FIXME: Support macros with the same name as a keyword here. + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_config_macro); + return std::nullopt; + } + + // Consume the macro name. + CMDecl.Macros.push_back(Tok.getString()); + consumeToken(); + } while (true); + return std::move(CMDecl); +} + +/// Parse a conflict declaration. +/// +/// module-declaration: +/// 'conflict' module-id ',' string-literal +std::optional<ConflictDecl> ModuleMapParser::parseConflictDecl() { + assert(Tok.is(MMToken::Conflict)); + ConflictDecl CD; + CD.Location = consumeToken(); + + // Parse the module-id. + if (parseModuleId(CD.Id)) + return std::nullopt; + + // Parse the ','. + if (!Tok.is(MMToken::Comma)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_comma) + << SourceRange(CD.Location); + return std::nullopt; + } + consumeToken(); + + // Parse the message. + if (!Tok.is(MMToken::StringLiteral)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_conflicts_message) + << formatModuleId(CD.Id); + return std::nullopt; + } + CD.Message = Tok.getString(); + consumeToken(); + return std::move(CD); +} + +/// Parse a module export declaration. +/// +/// export-declaration: +/// 'export' wildcard-module-id +/// +/// wildcard-module-id: +/// identifier +/// '*' +/// identifier '.' wildcard-module-id +std::optional<ExportDecl> ModuleMapParser::parseExportDecl() { + assert(Tok.is(MMToken::ExportKeyword)); + ExportDecl ED; + ED.Location = consumeToken(); + + // Parse the module-id with an optional wildcard at the end. + ED.Wildcard = false; + do { + // FIXME: Support string-literal module names here. + if (Tok.is(MMToken::Identifier)) { + ED.Id.push_back( + std::make_pair(std::string(Tok.getString()), Tok.getLocation())); + consumeToken(); + + if (Tok.is(MMToken::Period)) { + consumeToken(); + continue; + } + + break; + } + + if (Tok.is(MMToken::Star)) { + ED.Wildcard = true; + consumeToken(); + break; + } + + Diags.Report(Tok.getLocation(), diag::err_mmap_module_id); + HadError = true; + return std::nullopt; + } while (true); + + return std::move(ED); +} + +/// Parse a module export_as declaration. +/// +/// export-as-declaration: +/// 'export_as' identifier +std::optional<ExportAsDecl> ModuleMapParser::parseExportAsDecl() { + assert(Tok.is(MMToken::ExportAsKeyword)); + ExportAsDecl EAD; + EAD.Location = consumeToken(); + + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_module_id); + HadError = true; + return std::nullopt; + } + + if (parseModuleId(EAD.Id)) + return std::nullopt; + if (EAD.Id.size() > 1) + Diags.Report(EAD.Id[1].second, diag::err_mmap_qualified_export_as); + return std::move(EAD); +} + +/// Parse a module use declaration. +/// +/// use-declaration: +/// 'use' wildcard-module-id +std::optional<UseDecl> ModuleMapParser::parseUseDecl() { + assert(Tok.is(MMToken::UseKeyword)); + UseDecl UD; + UD.Location = consumeToken(); + if (parseModuleId(UD.Id)) + return std::nullopt; + return std::move(UD); +} + +/// Parse a requires declaration. +/// +/// requires-declaration: +/// 'requires' feature-list +/// +/// feature-list: +/// feature ',' feature-list +/// feature +/// +/// feature: +/// '!'[opt] identifier +std::optional<RequiresDecl> ModuleMapParser::parseRequiresDecl() { + assert(Tok.is(MMToken::RequiresKeyword)); + RequiresDecl RD; + RD.Location = consumeToken(); + + // Parse the feature-list. + do { + bool RequiredState = true; + if (Tok.is(MMToken::Exclaim)) { + RequiredState = false; + consumeToken(); + } + + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_feature); + HadError = true; + return std::nullopt; + } + + // Consume the feature name. + RequiresFeature RF; + RF.Feature = Tok.getString(); + RF.Location = consumeToken(); + RF.RequiredState = RequiredState; + + RD.Features.push_back(std::move(RF)); + + if (!Tok.is(MMToken::Comma)) + break; + + // Consume the comma. + consumeToken(); + } while (true); + return std::move(RD); +} + +/// Parse a header declaration. +/// +/// header-declaration: +/// 'textual'[opt] 'header' string-literal +/// 'private' 'textual'[opt] 'header' string-literal +/// 'exclude' 'header' string-literal +/// 'umbrella' 'header' string-literal +std::optional<HeaderDecl> +ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken, + clang::SourceLocation LeadingLoc) { + HeaderDecl HD; + HD.Private = false; + HD.Excluded = false; + HD.Textual = false; + // We've already consumed the first token. + HD.Location = LeadingLoc; + + if (LeadingToken == MMToken::PrivateKeyword) { + HD.Private = true; + // 'private' may optionally be followed by 'textual'. + if (Tok.is(MMToken::TextualKeyword)) { + HD.Textual = true; + LeadingToken = Tok.Kind; + consumeToken(); + } + } else if (LeadingToken == MMToken::ExcludeKeyword) + HD.Excluded = true; + else if (LeadingToken == MMToken::TextualKeyword) + HD.Textual = true; + + if (LeadingToken != MMToken::HeaderKeyword) { + if (!Tok.is(MMToken::HeaderKeyword)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) + << (LeadingToken == MMToken::PrivateKeyword ? "private" + : LeadingToken == MMToken::ExcludeKeyword ? "exclude" + : LeadingToken == MMToken::TextualKeyword ? "textual" + : "umbrella"); + return std::nullopt; + } + consumeToken(); + } + + // Parse the header name. + if (!Tok.is(MMToken::StringLiteral)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) << "header"; + HadError = true; + return std::nullopt; + } + HD.Path = Tok.getString(); + HD.PathLoc = consumeToken(); + HD.Umbrella = LeadingToken == MMToken::UmbrellaKeyword; + + // If we were given stat information, parse it so we can skip looking for + // the file. + if (Tok.is(MMToken::LBrace)) { + SourceLocation LBraceLoc = consumeToken(); + + while (!Tok.is(MMToken::RBrace) && !Tok.is(MMToken::EndOfFile)) { + enum Attribute { Size, ModTime, Unknown }; + StringRef Str = Tok.getString(); + SourceLocation Loc = consumeToken(); + switch (llvm::StringSwitch<Attribute>(Str) + .Case("size", Size) + .Case("mtime", ModTime) + .Default(Unknown)) { + case Size: + if (HD.Size) + Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str; + if (!Tok.is(MMToken::IntegerLiteral)) { + Diags.Report(Tok.getLocation(), + diag::err_mmap_invalid_header_attribute_value) + << Str; + skipUntil(MMToken::RBrace); + break; + } + HD.Size = Tok.getInteger(); + consumeToken(); + break; + + case ModTime: + if (HD.MTime) + Diags.Report(Loc, diag::err_mmap_duplicate_header_attribute) << Str; + if (!Tok.is(MMToken::IntegerLiteral)) { + Diags.Report(Tok.getLocation(), + diag::err_mmap_invalid_header_attribute_value) + << Str; + skipUntil(MMToken::RBrace); + break; + } + HD.MTime = Tok.getInteger(); + consumeToken(); + break; + + case Unknown: + Diags.Report(Loc, diag::err_mmap_expected_header_attribute); + skipUntil(MMToken::RBrace); + break; + } + } + + if (Tok.is(MMToken::RBrace)) + consumeToken(); + else { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rbrace); + Diags.Report(LBraceLoc, diag::note_mmap_lbrace_match); + HadError = true; + } + } + return std::move(HD); +} + +/// Parse an exclude declaration. +/// +/// exclude-declaration: +/// 'exclude' identifier +std::optional<ExcludeDecl> +ModuleMapParser::parseExcludeDecl(clang::SourceLocation LeadingLoc) { + // FIXME: Support string-literal module names here. + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_missing_exclude_name); + HadError = true; + return std::nullopt; + } + + ExcludeDecl ED; + ED.Location = LeadingLoc; + ED.Module = Tok.getString(); + consumeToken(); + return std::move(ED); +} + +/// Parse an umbrella directory declaration. +/// +/// umbrella-dir-declaration: +/// umbrella string-literal +std::optional<UmbrellaDirDecl> +ModuleMapParser::parseUmbrellaDirDecl(clang::SourceLocation UmbrellaLoc) { + UmbrellaDirDecl UDD; + UDD.Location = UmbrellaLoc; + // Parse the directory name. + if (!Tok.is(MMToken::StringLiteral)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header) + << "umbrella"; + HadError = true; + return std::nullopt; + } + + UDD.Path = Tok.getString(); + consumeToken(); + return std::move(UDD); +} + +/// Parse a link declaration. +/// +/// module-declaration: +/// 'link' 'framework'[opt] string-literal +std::optional<LinkDecl> ModuleMapParser::parseLinkDecl() { + assert(Tok.is(MMToken::LinkKeyword)); + LinkDecl LD; + LD.Location = consumeToken(); + + // Parse the optional 'framework' keyword. + LD.Framework = false; + if (Tok.is(MMToken::FrameworkKeyword)) { + consumeToken(); + LD.Framework = true; + } + + // Parse the library name + if (!Tok.is(MMToken::StringLiteral)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_library_name) + << LD.Framework << SourceRange(LD.Location); + HadError = true; + return std::nullopt; + } + + LD.Library = Tok.getString(); + consumeToken(); + return std::move(LD); +} + +SourceLocation ModuleMapParser::consumeToken() { + SourceLocation Result = Tok.getLocation(); + +retry: + Tok.clear(); + Token LToken; + L.LexFromRawLexer(LToken); + Tok.Location = LToken.getLocation().getRawEncoding(); + switch (LToken.getKind()) { + case tok::raw_identifier: { + StringRef RI = LToken.getRawIdentifier(); + Tok.StringData = RI.data(); + Tok.StringLength = RI.size(); + Tok.Kind = llvm::StringSwitch<MMToken::TokenKind>(RI) + .Case("config_macros", MMToken::ConfigMacros) + .Case("conflict", MMToken::Conflict) + .Case("exclude", MMToken::ExcludeKeyword) + .Case("explicit", MMToken::ExplicitKeyword) + .Case("export", MMToken::ExportKeyword) + .Case("export_as", MMToken::ExportAsKeyword) + .Case("extern", MMToken::ExternKeyword) + .Case("framework", MMToken::FrameworkKeyword) + .Case("header", MMToken::HeaderKeyword) + .Case("link", MMToken::LinkKeyword) + .Case("module", MMToken::ModuleKeyword) + .Case("private", MMToken::PrivateKeyword) + .Case("requires", MMToken::RequiresKeyword) + .Case("textual", MMToken::TextualKeyword) + .Case("umbrella", MMToken::UmbrellaKeyword) + .Case("use", MMToken::UseKeyword) + .Default(MMToken::Identifier); + break; + } + + case tok::comma: + Tok.Kind = MMToken::Comma; + break; + + case tok::eof: + Tok.Kind = MMToken::EndOfFile; + break; + + case tok::l_brace: + Tok.Kind = MMToken::LBrace; + break; + + case tok::l_square: + Tok.Kind = MMToken::LSquare; + break; + + case tok::period: + Tok.Kind = MMToken::Period; + break; + + case tok::r_brace: + Tok.Kind = MMToken::RBrace; + break; + + case tok::r_square: + Tok.Kind = MMToken::RSquare; + break; + + case tok::star: + Tok.Kind = MMToken::Star; + break; + + case tok::exclaim: + Tok.Kind = MMToken::Exclaim; + break; + + case tok::string_literal: { + if (LToken.hasUDSuffix()) { + Diags.Report(LToken.getLocation(), diag::err_invalid_string_udl); + HadError = true; + goto retry; + } + + // Form the token. + Tok.Kind = MMToken::StringLiteral; + Tok.StringData = LToken.getLiteralData() + 1; + Tok.StringLength = LToken.getLength() - 2; + break; + } + + case tok::numeric_constant: { + // We don't support any suffixes or other complications. + uint64_t Value; + if (StringRef(LToken.getLiteralData(), LToken.getLength()) + .getAsInteger(0, Value)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token); + HadError = true; + goto retry; + } + + Tok.Kind = MMToken::IntegerLiteral; + Tok.IntegerValue = Value; + break; + } + + case tok::comment: + goto retry; + + case tok::hash: + // A module map can be terminated prematurely by + // #pragma clang module contents + // When building the module, we'll treat the rest of the file as the + // contents of the module. + { + auto NextIsIdent = [&](StringRef Str) -> bool { + L.LexFromRawLexer(LToken); + return !LToken.isAtStartOfLine() && LToken.is(tok::raw_identifier) && + LToken.getRawIdentifier() == Str; + }; + if (NextIsIdent("pragma") && NextIsIdent("clang") && + NextIsIdent("module") && NextIsIdent("contents")) { + Tok.Kind = MMToken::EndOfFile; + break; + } + } + [[fallthrough]]; + + default: + Diags.Report(Tok.getLocation(), diag::err_mmap_unknown_token); + HadError = true; + goto retry; + } + + return Result; +} + +void ModuleMapParser::skipUntil(MMToken::TokenKind K) { + unsigned braceDepth = 0; + unsigned squareDepth = 0; + do { + switch (Tok.Kind) { + case MMToken::EndOfFile: + return; + + case MMToken::LBrace: + if (Tok.is(K) && braceDepth == 0 && squareDepth == 0) + return; + + ++braceDepth; + break; + + case MMToken::LSquare: + if (Tok.is(K) && braceDepth == 0 && squareDepth == 0) + return; + + ++squareDepth; + break; + + case MMToken::RBrace: + if (braceDepth > 0) + --braceDepth; + else if (Tok.is(K)) + return; + break; + + case MMToken::RSquare: + if (squareDepth > 0) + --squareDepth; + else if (Tok.is(K)) + return; + break; + + default: + if (braceDepth == 0 && squareDepth == 0 && Tok.is(K)) + return; + break; + } + + consumeToken(); + } while (true); +} + +/// Parse a module-id. +/// +/// module-id: +/// identifier +/// identifier '.' module-id +/// +/// \returns true if an error occurred, false otherwise. +bool ModuleMapParser::parseModuleId(ModuleId &Id) { + Id.clear(); + do { + if (Tok.is(MMToken::Identifier) || Tok.is(MMToken::StringLiteral)) { + Id.push_back( + std::make_pair(std::string(Tok.getString()), Tok.getLocation())); + consumeToken(); + } else { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_module_name); + return true; + } + + if (!Tok.is(MMToken::Period)) + break; + + consumeToken(); + } while (true); + + return false; +} + +/// Parse optional attributes. +/// +/// attributes: +/// attribute attributes +/// attribute +/// +/// attribute: +/// [ identifier ] +/// +/// \param Attrs Will be filled in with the parsed attributes. +/// +/// \returns true if an error occurred, false otherwise. +bool ModuleMapParser::parseOptionalAttributes(ModuleAttributes &Attrs) { + bool Error = false; + + while (Tok.is(MMToken::LSquare)) { + // Consume the '['. + SourceLocation LSquareLoc = consumeToken(); + + // Check whether we have an attribute name here. + if (!Tok.is(MMToken::Identifier)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_attribute); + skipUntil(MMToken::RSquare); + if (Tok.is(MMToken::RSquare)) + consumeToken(); + Error = true; + } + + /// Enumerates the known attributes. + enum AttributeKind { + /// An unknown attribute. + AT_unknown, + + /// The 'system' attribute. + AT_system, + + /// The 'extern_c' attribute. + AT_extern_c, + + /// The 'exhaustive' attribute. + AT_exhaustive, + + /// The 'no_undeclared_includes' attribute. + AT_no_undeclared_includes + }; + + // Decode the attribute name. + AttributeKind Attribute = + llvm::StringSwitch<AttributeKind>(Tok.getString()) + .Case("exhaustive", AT_exhaustive) + .Case("extern_c", AT_extern_c) + .Case("no_undeclared_includes", AT_no_undeclared_includes) + .Case("system", AT_system) + .Default(AT_unknown); + switch (Attribute) { + case AT_unknown: + Diags.Report(Tok.getLocation(), diag::warn_mmap_unknown_attribute) + << Tok.getString(); + break; + + case AT_system: + Attrs.IsSystem = true; + break; + + case AT_extern_c: + Attrs.IsExternC = true; + break; + + case AT_exhaustive: + Attrs.IsExhaustive = true; + break; + + case AT_no_undeclared_includes: + Attrs.NoUndeclaredIncludes = true; + break; + } + consumeToken(); + + // Consume the ']'. + if (!Tok.is(MMToken::RSquare)) { + Diags.Report(Tok.getLocation(), diag::err_mmap_expected_rsquare); + Diags.Report(LSquareLoc, diag::note_mmap_lsquare_match); + skipUntil(MMToken::RSquare); + Error = true; + } + + if (Tok.is(MMToken::RSquare)) + consumeToken(); + } + + if (Error) + HadError = true; + + return Error; +} + +static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out, int depth); + +static void dumpExternModule(const ExternModuleDecl &EMD, + llvm::raw_ostream &out, int depth) { + out.indent(depth * 2); + out << "extern module " << formatModuleId(EMD.Id) << " \"" << EMD.Path + << "\"\n"; +} + +static void dumpDecls(ArrayRef<Decl> Decls, llvm::raw_ostream &out, int depth) { + for (const auto &Decl : Decls) { + std::visit(llvm::overloaded{ + [&](const RequiresDecl &RD) { + out.indent(depth * 2); + out << "requires\n"; + }, + [&](const HeaderDecl &HD) { + out.indent(depth * 2); + if (HD.Private) + out << "private "; + if (HD.Textual) + out << "textual "; + if (HD.Excluded) + out << "excluded "; + if (HD.Umbrella) + out << "umbrella "; + out << "header \"" << HD.Path << "\"\n"; + }, + [&](const UmbrellaDirDecl &UDD) { + out.indent(depth * 2); + out << "umbrella\n"; + }, + [&](const ModuleDecl &MD) { dumpModule(MD, out, depth); }, + [&](const ExcludeDecl &ED) { + out.indent(depth * 2); + out << "exclude " << ED.Module << "\n"; + }, + [&](const ExportDecl &ED) { + out.indent(depth * 2); + out << "export " + << (ED.Wildcard ? "*" : formatModuleId(ED.Id)) << "\n"; + }, + [&](const ExportAsDecl &EAD) { + out.indent(depth * 2); + out << "export as\n"; + }, + [&](const ExternModuleDecl &EMD) { + dumpExternModule(EMD, out, depth); + }, + [&](const UseDecl &UD) { + out.indent(depth * 2); + out << "use\n"; + }, + [&](const LinkDecl &LD) { + out.indent(depth * 2); + out << "link\n"; + }, + [&](const ConfigMacrosDecl &CMD) { + out.indent(depth * 2); + out << "config_macros "; + if (CMD.Exhaustive) + out << "[exhaustive] "; + for (auto Macro : CMD.Macros) { + out << Macro << " "; + } + out << "\n"; + }, + [&](const ConflictDecl &CD) { + out.indent(depth * 2); + out << "conflicts\n"; + }}, + Decl); + } +} + +static void dumpModule(const ModuleDecl &MD, llvm::raw_ostream &out, + int depth) { + out.indent(depth * 2); + out << "module " << formatModuleId(MD.Id) << "\n"; + dumpDecls(MD.Decls, out, depth + 1); +} + +void modulemap::dumpModuleMapFile(ModuleMapFile &MMF, llvm::raw_ostream &out) { + for (const auto &Decl : MMF.Decls) { + std::visit( + llvm::overloaded{[&](const ModuleDecl &MD) { dumpModule(MD, out, 0); }, + [&](const ExternModuleDecl &EMD) { + dumpExternModule(EMD, out, 0); + }}, + Decl); + } +} diff --git a/clang/test/Modules/Inputs/export_as_test.modulemap b/clang/test/Modules/Inputs/export_as_test.modulemap index 4aaec4194ef5af..bbe60924046e98 100644 --- a/clang/test/Modules/Inputs/export_as_test.modulemap +++ b/clang/test/Modules/Inputs/export_as_test.modulemap @@ -7,3 +7,7 @@ module PrivateFoo { export_as Wibble } } + +module B { + export_as C.B +} diff --git a/clang/test/Modules/diagnostics.modulemap b/clang/test/Modules/diagnostics.modulemap index c12fef50c38ed7..4f893ac897034e 100644 --- a/clang/test/Modules/diagnostics.modulemap +++ b/clang/test/Modules/diagnostics.modulemap @@ -1,36 +1,39 @@ // RUN: rm -rf %t -// RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/diagnostics-aux.modulemap -fmodule-map-file=%s -fsyntax-only -x c++ /dev/null 2>&1 | FileCheck %s --implicit-check-not error: +// RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/diagnostics-aux.modulemap -fmodule-map-file=%s -fsyntax-only -x c++ /dev/null 2>&1 | FileCheck %s // CHECK: In file included from {{.*}}diagnostics-aux.modulemap:3: // CHECK: diagnostics-aux-2.modulemap:2:3: error: expected // PR22299: Ensure we can produce diagnostics for duplicate modules from -fmodule-map-file=. // -// CHECK: diagnostics.modulemap:[[@LINE+2]]:8: error: redefinition of module 'foo' -// CHECK: diagnostics-aux.modulemap:1:8: note: previously defined here +// CHECK-DAG: diagnostics.modulemap:[[@LINE+2]]:8: error: redefinition of module 'foo' +// CHECK-DAG: diagnostics-aux.modulemap:1:8: note: previously defined here module foo {} //* Check that we accept BCPL comments properly, not just as an extension. */ module bad_use { - // CHECK: diagnostics.modulemap:[[@LINE+1]]:22: error: use declarations are only allowed in top-level modules + // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:22: error: use declarations are only allowed in top-level modules module submodule { use foo } } module header_attr { - // CHECK: diagnostics.modulemap:[[@LINE+1]]:20: error: expected a header attribute name + // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:20: error: expected a header attribute name header "foo.h" { x } - // CHECK: diagnostics.modulemap:[[@LINE+1]]:27: error: header attribute 'size' specified multiple times + // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:27: error: header attribute 'size' specified multiple times header "bar.h" { size 1 size 2 } - // CHECK: diagnostics.modulemap:[[@LINE+1]]:25: error: expected integer literal as value for header attribute 'size' + // CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:25: error: expected integer literal as value for header attribute 'size' header "baz.h" { size "30 kilobytes" } header "quux.h" { size 1 mtime 2 } header "no_attrs.h" {} } -// CHECK: diagnostics.modulemap:[[@LINE+1]]:8: error: no module named 'unknown' found, parent module must be defined before the submodule +// CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:8: error: no module named 'unknown' found, parent module must be defined before the submodule module unknown.submodule {} module known_top_level {} -// CHECK: diagnostics.modulemap:[[@LINE+1]]:24: error: no module named 'unknown' in 'known_top_level', parent module must be defined before the submodule +// CHECK-DAG: diagnostics.modulemap:[[@LINE+1]]:24: error: no module named 'unknown' in 'known_top_level', parent module must be defined before the submodule module known_top_level.unknown.submodule {} + +// Check that there were no other errors emitted. +// CHECK: 8 errors generated diff --git a/clang/test/Modules/export_as_test.c b/clang/test/Modules/export_as_test.c index a73d6bfc460d6e..a98dcafd4e6692 100644 --- a/clang/test/Modules/export_as_test.c +++ b/clang/test/Modules/export_as_test.c @@ -2,8 +2,7 @@ // RUN: not %clang_cc1 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%S/Inputs/export_as_test.modulemap %s 2> %t.err // RUN: FileCheck %s < %t.err +// CHECK: export_as_test.modulemap:7:5: error: only top-level modules can be re-exported as public +// CHECK: export_as_test.modulemap:12:15: error: a module can only be re-exported as another top-level module // CHECK: export_as_test.modulemap:3:13: error: conflicting re-export of module 'PrivateFoo' as 'Foo' or 'Bar' // CHECK: export_as_test.modulemap:4:13: warning: module 'PrivateFoo' already re-exported as 'Bar' -// CHECK: export_as_test.modulemap:7:15: error: only top-level modules can be re-exported as public - - diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h index ace5f60b572d75..1e9765058fea7d 100644 --- a/llvm/include/llvm/ADT/STLExtras.h +++ b/llvm/include/llvm/ADT/STLExtras.h @@ -2596,6 +2596,15 @@ template <typename T> using has_sizeof = decltype(sizeof(T)); template <typename T> constexpr bool is_incomplete_v = !is_detected<detail::has_sizeof, T>::value; +//===----------------------------------------------------------------------===// +// Extra additions to <variant> +//===----------------------------------------------------------------------===// + +template <class... Ts> struct overloaded : Ts... { + using Ts::operator()...; +}; +template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>; + } // end namespace llvm namespace std { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits