Skip to content

[clang][modules] Separate parsing of modulemaps #119740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticLexKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,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, "
Expand Down
24 changes: 24 additions & 0 deletions clang/include/clang/Basic/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 1 addition & 23 deletions clang/include/clang/Lex/ModuleMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
162 changes: 162 additions & 0 deletions clang/include/clang/Lex/ModuleMapFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//===- 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 {

struct ExportDecl;

/// All declarations that can appear in a `module` declaration.
using Decl =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is very close to clang::Decl though don't know if it causes any problems in practice. I'm mostly concerned if an engineer starts looking into this code and they see Decl it can confuse them. Don't know if the name AnyDecl is better. Don't. have a strong opinion on this as I don't have any evidence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was that having it in a namespace would clarify as it would only be used unqualified in the modulemap code.

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>;
Comment on lines +33 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sizes of these types have large variance (UmbrellaDirDecl is 24B, ModuleDecl is 128B). I think it would make sense to either work on shrinking these or store them out-of-line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also fine as a follow-up.)


struct RequiresFeature {
StringRef Feature;
SourceLocation Location;
bool RequiredState = true; /// False if preceded by '!'.
};

struct RequiresDecl {
SourceLocation Location;
std::vector<RequiresFeature> Features;
};

struct HeaderDecl {
StringRef Path;
SourceLocation Location;
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 {
StringRef Path;
SourceLocation Location;
};

struct ModuleDecl {
ModuleId Id;
SourceLocation Location; /// Points to the first keyword in the decl.
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 {
ModuleId Id;
SourceLocation Location;
bool Wildcard; /// True if the last element of the ModuleId is '*'.
};

struct ExportAsDecl {
SourceLocation Location;
ModuleId Id;
};

struct ExternModuleDecl {
SourceLocation Location;
ModuleId Id;
StringRef Path;
};

struct UseDecl {
SourceLocation Location;
ModuleId Id;
};

struct LinkDecl {
StringRef Library;
SourceLocation Location;
LLVM_PREFERRED_TYPE(bool)
unsigned Framework : 1;
};

struct ConfigMacrosDecl {
std::vector<StringRef> Macros;
SourceLocation Location;
LLVM_PREFERRED_TYPE(bool)
unsigned Exhaustive : 1;
};

struct ConflictDecl {
SourceLocation Location;
ModuleId Id;
StringRef Message;
};

using TopLevelDecl = std::variant<ModuleDecl, ExternModuleDecl>;

/// Represents the parsed form of a module map file.
///
/// This holds many reference types (StringRef, SourceLocation, etc.) whose
/// lifetimes are bound by the SourceManager and FileManager used.
struct ModuleMapFile {
/// Beginning of the file, used for moduleMapFileRead callback.
SourceLocation Start;
std::vector<TopLevelDecl> Decls;

void dump(llvm::raw_ostream &out) const;
};

/// Parse a module map file into an in memory representation.
///
/// \param ID a valid local FileID.
/// \param Dir the directory in which this module map was found.
/// \param SM the SourceManager for \a ID.
/// \param Diags where to send the diagnostics.
/// \param IsSystem was this module map found in a system search path.
/// \param Offset optional offset into the buffer associated with \a ID. This is
/// used for handling `#pragma clang module build`. Set to the end
/// of the module map on return.
///
/// \returns The parsed ModuleMapFile if successful, std::nullopt otherwise.
std::optional<ModuleMapFile>
parseModuleMap(FileID ID, clang::DirectoryEntryRef Dir, SourceManager &SM,
DiagnosticsEngine &Diags, bool IsSystem, unsigned *Offset);

} // namespace modulemap
} // namespace clang

#endif
1 change: 1 addition & 0 deletions clang/lib/Lex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_clang_library(clangLex
MacroArgs.cpp
MacroInfo.cpp
ModuleMap.cpp
ModuleMapFile.cpp
PPCaching.cpp
PPCallbacks.cpp
PPConditionalDirectiveRecord.cpp
Expand Down
Loading
Loading