llvm-project/clang/lib/Lex/ModuleMapFile.cpp
Michael Spencer 32fb8c5f5f
[clang][modules] Lazily load by name lookups in module maps (#132853)
Instead of eagerly populating the `clang::ModuleMap` when looking up a
module by name, this patch changes `HeaderSearch` to only load the
modules that are actually used.

This introduces `ModuleMap::findOrLoadModule` which will load modules
from parsed but not loaded module maps. This cannot be used anywhere
that the module loading code calls into as it can create infinite
recursion.

This currently just reparses module maps when looking up a module by
header. This is fine as redeclarations are allowed from the same file,
but future patches will also make looking up a module by header lazy.

This patch changes the shadow.m test to use explicitly built modules and
`#import`. This test and the shadow feature are very brittle and do not
work in general. The test relied on pcm files being left behind by prior
failing clang invocations that were then reused by the last invocation.
If you clean the cache then the last invocation will always fail. This
is because the input module map and the `-fmodule-map-file=` module map
are parsed in the same module scope, and `-fmodule-map-file=` is
forwarded to implicit module builds. That means you are guaranteed to
hit a module redeclaration error if the TU actually imports the module
it is trying to shadow.

This patch changes when we load A2's module map to after the `A` module
has been loaded, which sets the `IsFromModuleFile` bit on `A`. This
means that A2's `A` is skipped entirely instead of creating a shadow
module, and we get textual inclusion. It is possible to construct a case
where this would happen before this patch too.

An upcoming patch in this series will rework shadowing to work in the
general case, but that's only possible once header -> module lookup is
lazy too.
2025-05-06 16:40:01 -07:00

1244 lines
34 KiB
C++

//===- 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 ModuleMapFileParser {
// External context
Lexer &L;
DiagnosticsEngine &Diags;
/// Parsed representation of the module map file
ModuleMapFile MMF{};
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(FileID ID, clang::DirectoryEntryRef Dir,
SourceManager &SM, DiagnosticsEngine &Diags,
bool IsSystem, unsigned *Offset) {
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());
SourceLocation Start = L.getSourceLocation();
ModuleMapFileParser Parser{L, Diags};
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;
Parser.MMF.ID = ID;
Parser.MMF.Dir = Dir;
Parser.MMF.Start = Start;
Parser.MMF.IsSystem = IsSystem;
return std::move(Parser.MMF);
}
bool ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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> ModuleMapFileParser::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>
ModuleMapFileParser::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>
ModuleMapFileParser::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>
ModuleMapFileParser::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> ModuleMapFileParser::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 ModuleMapFileParser::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 ModuleMapFileParser::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 ModuleMapFileParser::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 ModuleMapFileParser::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::makeVisitor(
[&](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 ModuleMapFile::dump(llvm::raw_ostream &out) const {
for (const auto &Decl : Decls) {
std::visit(
llvm::makeVisitor([&](const ModuleDecl &MD) { dumpModule(MD, out, 0); },
[&](const ExternModuleDecl &EMD) {
dumpExternModule(EMD, out, 0);
}),
Decl);
}
}