Split `Importer` into individual classes for each decl type.
This is one step towards a more query-based importer architecture, by
encapsulating the individual import methods and extracting an interface for
their interaction with the importer.
The extracted interfaces of the `DeclImporter` and `ImportContext` are not meant
to be permanent. They merely extract the status quo and provide a basis for
future improvements.
I also plan on moving individual `DeclImporter`s into their own files. Maybe
also the `Invocation` and the `ImportContext`. I haven't done that in this CL to
make review easier.
PiperOrigin-RevId: 443642590
diff --git a/rs_bindings_from_cc/ast_consumer.h b/rs_bindings_from_cc/ast_consumer.h
index 581ad23..3e6b1a7 100644
--- a/rs_bindings_from_cc/ast_consumer.h
+++ b/rs_bindings_from_cc/ast_consumer.h
@@ -17,14 +17,14 @@
class AstConsumer : public clang::ASTConsumer {
public:
explicit AstConsumer(clang::CompilerInstance& instance,
- Importer::Invocation& invocation)
+ Invocation& invocation)
: instance_(instance), invocation_(invocation) {}
void HandleTranslationUnit(clang::ASTContext& context) override;
private:
clang::CompilerInstance& instance_;
- Importer::Invocation& invocation_;
+ Invocation& invocation_;
}; // class AstConsumer
} // namespace crubit
diff --git a/rs_bindings_from_cc/frontend_action.h b/rs_bindings_from_cc/frontend_action.h
index 2ffca73..db3fa17 100644
--- a/rs_bindings_from_cc/frontend_action.h
+++ b/rs_bindings_from_cc/frontend_action.h
@@ -18,14 +18,13 @@
// (`IR`) into the invocation object.
class FrontendAction : public clang::ASTFrontendAction {
public:
- explicit FrontendAction(Importer::Invocation& invocation)
- : invocation_(invocation) {}
+ explicit FrontendAction(Invocation& invocation) : invocation_(invocation) {}
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance& instance, llvm::StringRef) override;
private:
- Importer::Invocation& invocation_;
+ Invocation& invocation_;
};
} // namespace crubit
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index cdee63b..97216c9 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -62,11 +62,13 @@
constexpr absl::string_view kTypeStatusPayloadUrl =
"type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type";
+}
+
// A mapping of C++ standard types to their equivalent Rust types.
// To produce more idiomatic results, these types receive special handling
// instead of using the generic type mapping mechanism.
-std::optional<absl::string_view> MapKnownCcTypeToRsType(
- absl::string_view cc_type) {
+std::optional<absl::string_view> TypeMapper::MapKnownCcTypeToRsType(
+ absl::string_view cc_type) const {
static const auto* const kWellKnownTypes =
new absl::flat_hash_map<absl::string_view, absl::string_view>({
{"ptrdiff_t", "isize"},
@@ -106,6 +108,8 @@
return it->second;
}
+namespace {
+
ItemId GenerateItemId(const clang::Decl* decl) {
return ItemId(reinterpret_cast<uintptr_t>(decl->getCanonicalDecl()));
}
@@ -398,80 +402,71 @@
}
std::optional<IR::Item> Importer::ImportDecl(clang::Decl* decl) {
- if (auto* namespace_decl = clang::dyn_cast<clang::NamespaceDecl>(decl)) {
- return ImportNamespace(namespace_decl);
- } else if (auto* function_decl = clang::dyn_cast<clang::FunctionDecl>(decl)) {
- return ImportFunction(function_decl);
- } else if (auto* function_template_decl =
- clang::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
- return ImportFunction(function_template_decl->getTemplatedDecl(),
- function_template_decl);
- } else if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
- auto result = ImportRecord(record_decl);
+ std::optional<IR::Item> result;
+ for (auto& importer : decl_importers_) {
+ if (importer->CanImport(decl)) {
+ result = importer->ImportDecl(decl);
+ }
+ }
+
+ if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
// TODO(forster): Should we even visit the nested decl if we couldn't
// import the parent? For now we have tests that check that we generate
// error messages for those decls, so we're visiting.
ImportDeclsFromDeclContext(record_decl);
- return result;
- } else if (auto* enum_decl = clang::dyn_cast<clang::EnumDecl>(decl)) {
- return ImportEnum(enum_decl);
- } else if (auto* typedef_name_decl =
- clang::dyn_cast<clang::TypedefNameDecl>(decl)) {
- return ImportTypedefName(typedef_name_decl);
- } else if (clang::isa<clang::ClassTemplateDecl>(decl)) {
- return ImportUnsupportedItem(decl, "Class templates are not supported yet");
- } else {
- return std::nullopt;
}
+
+ return result;
}
-std::optional<IR::Item> Importer::ImportNamespace(
+std::optional<IR::Item> NamespaceDeclImporter::Import(
clang::NamespaceDecl* namespace_decl) {
- if (!IsFromCurrentTarget(namespace_decl)) return std::nullopt;
+ if (!ictx_.IsFromCurrentTarget(namespace_decl)) return std::nullopt;
// TODO(rosica) In order to fully enable namespaces we first need to ensure
// that each decl Item contains information on its namespace parents.
if (!absl::StrContains(namespace_decl->getQualifiedNameAsString(),
"test_namespace_bindings")) {
- return ImportUnsupportedItem(namespace_decl,
- "Namespaces are not supported yet");
+ return ictx_.ImportUnsupportedItem(namespace_decl,
+ "Namespaces are not supported yet");
}
if (namespace_decl->isInline()) {
- return ImportUnsupportedItem(namespace_decl,
- "Inline namespaces are not supported yet");
+ return ictx_.ImportUnsupportedItem(
+ namespace_decl, "Inline namespaces are not supported yet");
}
if (namespace_decl->isAnonymousNamespace()) {
- return ImportUnsupportedItem(namespace_decl,
- "Anonymous namespaces are not supported yet");
+ return ictx_.ImportUnsupportedItem(
+ namespace_decl, "Anonymous namespaces are not supported yet");
}
- ImportDeclsFromDeclContext(namespace_decl);
- auto identifier = GetTranslatedIdentifier(namespace_decl);
+ ictx_.ImportDeclsFromDeclContext(namespace_decl);
+ auto identifier = ictx_.GetTranslatedIdentifier(namespace_decl);
CRUBIT_CHECK(identifier.has_value());
- auto item_ids = GetItemIdsInSourceOrder(namespace_decl);
+ auto item_ids = ictx_.GetItemIdsInSourceOrder(namespace_decl);
return Namespace{
.name = *identifier,
.id = GenerateItemId(namespace_decl),
- .owning_target = GetOwningTarget(namespace_decl),
+ .owning_target = ictx_.GetOwningTarget(namespace_decl),
.child_item_ids = std::move(item_ids),
};
}
-std::optional<IR::Item> Importer::ImportFunction(
- clang::FunctionDecl* function_decl,
+std::optional<IR::Item> FunctionTemplateDeclImporter::Import(
clang::FunctionTemplateDecl* function_template_decl) {
- if (!IsFromCurrentTarget(function_decl)) return std::nullopt;
+ return ictx_.ImportUnsupportedItem(
+ function_template_decl, "Function templates are not supported yet");
+}
+
+std::optional<IR::Item> FunctionDeclImporter::Import(
+ clang::FunctionDecl* function_decl) {
+ if (!ictx_.IsFromCurrentTarget(function_decl)) return std::nullopt;
if (function_decl->isDeleted()) return std::nullopt;
- if (function_decl->isTemplated()) {
- return ImportUnsupportedItem(function_template_decl,
- "Function templates are not supported yet");
- }
clang::tidy::lifetimes::LifetimeSymbolTable lifetime_symbol_table;
llvm::Expected<clang::tidy::lifetimes::FunctionLifetimes> lifetimes =
clang::tidy::lifetimes::GetLifetimeAnnotations(
- function_decl, *invocation_.lifetime_context_,
+ function_decl, *ictx_.invocation_.lifetime_context_,
&lifetime_symbol_table);
std::vector<FuncParam> params;
@@ -482,8 +477,9 @@
};
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
- if (!type_mapper_.Contains(method_decl->getParent())) {
- return ImportUnsupportedItem(function_decl, "Couldn't import the parent");
+ if (!ictx_.type_mapper_.Contains(method_decl->getParent())) {
+ return ictx_.ImportUnsupportedItem(function_decl,
+ "Couldn't import the parent");
}
// non-static member functions receive an implicit `this` parameter.
@@ -492,9 +488,9 @@
if (lifetimes) {
this_lifetimes = lifetimes->GetThisLifetimes();
}
- auto param_type = type_mapper_.ConvertQualType(method_decl->getThisType(),
- this_lifetimes,
- /*nullable=*/false);
+ auto param_type = ictx_.type_mapper_.ConvertQualType(
+ method_decl->getThisType(), this_lifetimes,
+ /*nullable=*/false);
if (!param_type.ok()) {
add_error(absl::StrCat("`this` parameter is not supported: ",
param_type.status().message()));
@@ -515,7 +511,7 @@
param_lifetimes = lifetimes->GetParamLifetimes(i);
}
auto param_type =
- type_mapper_.ConvertQualType(param->getType(), param_lifetimes);
+ ictx_.type_mapper_.ConvertQualType(param->getType(), param_lifetimes);
if (!param_type.ok()) {
add_error(absl::Substitute("Parameter #$0 is not supported: $1", i,
param_type.status().message()));
@@ -538,7 +534,7 @@
}
}
- std::optional<Identifier> param_name = GetTranslatedIdentifier(param);
+ std::optional<Identifier> param_name = ictx_.GetTranslatedIdentifier(param);
CRUBIT_CHECK(param_name.has_value()); // No known failure cases.
params.push_back({*param_type, *std::move(param_name)});
}
@@ -564,7 +560,7 @@
return_lifetimes = lifetimes->GetReturnLifetimes();
}
- auto return_type = type_mapper_.ConvertQualType(
+ auto return_type = ictx_.type_mapper_.ConvertQualType(
function_decl->getReturnType(), return_lifetimes);
if (!return_type.ok()) {
add_error(absl::StrCat("Return type is not supported: ",
@@ -635,14 +631,14 @@
}
if (!errors.empty()) {
- return ImportUnsupportedItem(function_decl, errors);
+ return ictx_.ImportUnsupportedItem(function_decl, errors);
}
bool has_c_calling_convention =
function_decl->getType()->getAs<clang::FunctionType>()->getCallConv() ==
clang::CC_C;
std::optional<UnqualifiedIdentifier> translated_name =
- GetTranslatedName(function_decl);
+ ictx_.GetTranslatedName(function_decl);
// Silence ClangTidy, checked above: calling `add_error` if
// `!return_type.ok()` and returning early if `!errors.empty()`.
@@ -651,19 +647,17 @@
if (translated_name.has_value()) {
return Func{
.name = *translated_name,
- .owning_target = GetOwningTarget(function_decl),
- .doc_comment = GetComment(function_decl),
- .mangled_name = GetMangledName(function_decl),
+ .owning_target = ictx_.GetOwningTarget(function_decl),
+ .doc_comment = ictx_.GetComment(function_decl),
+ .mangled_name = ictx_.GetMangledName(function_decl),
.return_type = *return_type,
.params = std::move(params),
.lifetime_params = std::move(lifetime_params),
.is_inline = function_decl->isInlined(),
.member_func_metadata = std::move(member_func_metadata),
.has_c_calling_convention = has_c_calling_convention,
- .source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
- .id = function_template_decl == nullptr
- ? GenerateItemId(function_decl)
- : GenerateItemId(function_template_decl),
+ .source_loc = ictx_.ConvertSourceLocation(function_decl->getBeginLoc()),
+ .id = GenerateItemId(function_decl),
};
}
return std::nullopt;
@@ -704,7 +698,13 @@
return invocation_.target_ == GetOwningTarget(decl);
}
-std::optional<IR::Item> Importer::ImportRecord(
+std::optional<IR::Item> ClassTemplateDeclImporter::Import(
+ clang::ClassTemplateDecl* class_template_decl) {
+ return ictx_.ImportUnsupportedItem(class_template_decl,
+ "Class templates are not supported yet");
+}
+
+std::optional<IR::Item> CXXRecordDeclImporter::Import(
clang::CXXRecordDecl* record_decl) {
const clang::DeclContext* decl_context = record_decl->getDeclContext();
if (decl_context->isFunctionOrMethod()) {
@@ -714,14 +714,15 @@
return std::nullopt;
}
if (decl_context->isRecord()) {
- return ImportUnsupportedItem(record_decl,
- "Nested classes are not supported yet");
+ return ictx_.ImportUnsupportedItem(record_decl,
+ "Nested classes are not supported yet");
}
if (record_decl->isInvalidDecl()) {
return std::nullopt;
}
- std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
+ std::optional<Identifier> record_name =
+ ictx_.GetTranslatedIdentifier(record_decl);
if (!record_name.has_value()) {
return std::nullopt;
}
@@ -730,23 +731,25 @@
record_decl = complete;
} else {
CRUBIT_CHECK(!record_decl->isCompleteDefinition());
- type_mapper_.Insert(record_decl);
- return IncompleteRecord{.cc_name = std::string(record_name->Ident()),
- .id = GenerateItemId(record_decl),
- .owning_target = GetOwningTarget(record_decl)};
+ ictx_.type_mapper_.Insert(record_decl);
+ return IncompleteRecord{
+ .cc_name = std::string(record_name->Ident()),
+ .id = GenerateItemId(record_decl),
+ .owning_target = ictx_.GetOwningTarget(record_decl)};
}
// To compute the memory layout of the record, it needs to be a concrete type,
// not a template.
if (record_decl->getDescribedClassTemplate() ||
clang::isa<clang::ClassTemplateSpecializationDecl>(record_decl)) {
- return ImportUnsupportedItem(record_decl,
- "Class templates are not supported yet");
+ return ictx_.ImportUnsupportedItem(record_decl,
+ "Class templates are not supported yet");
}
- sema_.ForceDeclarationOfImplicitMembers(record_decl);
+ ictx_.sema_.ForceDeclarationOfImplicitMembers(record_decl);
- const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
+ const clang::ASTRecordLayout& layout =
+ ictx_.ctx_.getASTRecordLayout(record_decl);
llvm::Optional<size_t> base_size;
bool override_alignment = record_decl->hasAttr<clang::AlignedAttr>();
@@ -765,7 +768,7 @@
absl::StatusOr<std::vector<Field>> fields = ImportFields(record_decl);
if (!fields.ok()) {
- return ImportUnsupportedItem(record_decl, fields.status().ToString());
+ return ictx_.ImportUnsupportedItem(record_decl, fields.status().ToString());
}
for (const Field& field : *fields) {
@@ -775,15 +778,15 @@
}
}
- type_mapper_.Insert(record_decl);
+ ictx_.type_mapper_.Insert(record_decl);
- auto item_ids = GetItemIdsInSourceOrder(record_decl);
+ auto item_ids = ictx_.GetItemIdsInSourceOrder(record_decl);
return Record{
.rs_name = std::string(record_name->Ident()),
.cc_name = std::string(record_name->Ident()),
.id = GenerateItemId(record_decl),
- .owning_target = GetOwningTarget(record_decl),
- .doc_comment = GetComment(record_decl),
+ .owning_target = ictx_.GetOwningTarget(record_decl),
+ .doc_comment = ictx_.GetComment(record_decl),
.unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
.fields = *std::move(fields),
.size = layout.getSize().getQuantity(),
@@ -800,15 +803,16 @@
.child_item_ids = std::move(item_ids)};
}
-std::optional<IR::Item> Importer::ImportEnum(clang::EnumDecl* enum_decl) {
- std::optional<Identifier> enum_name = GetTranslatedIdentifier(enum_decl);
+std::optional<IR::Item> EnumDeclImporter::Import(clang::EnumDecl* enum_decl) {
+ std::optional<Identifier> enum_name =
+ ictx_.GetTranslatedIdentifier(enum_decl);
if (!enum_name.has_value()) {
// TODO(b/208945197): This corresponds to an unnamed enum declaration like
// `enum { kFoo = 1 }`, which only exists to provide constants into the
// surrounding scope and doesn't actually introduce an enum namespace. It
// seems like it should probably be handled with other constants.
- return ImportUnsupportedItem(enum_decl,
- "Unnamed enums are not supported yet");
+ return ictx_.ImportUnsupportedItem(enum_decl,
+ "Unnamed enums are not supported yet");
}
clang::QualType cc_type = enum_decl->getIntegerType();
@@ -818,15 +822,15 @@
// with no fixed underlying type." The same page implies that this can't
// occur in C++ nor in standard C, but clang supports enums like this
// in C "as an extension".
- return ImportUnsupportedItem(
+ return ictx_.ImportUnsupportedItem(
enum_decl,
"Forward declared enums without type specifiers are not supported");
}
std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
absl::StatusOr<MappedType> type =
- type_mapper_.ConvertQualType(cc_type, no_lifetimes);
+ ictx_.type_mapper_.ConvertQualType(cc_type, no_lifetimes);
if (!type.ok()) {
- return ImportUnsupportedItem(enum_decl, type.status().ToString());
+ return ictx_.ImportUnsupportedItem(enum_decl, type.status().ToString());
}
std::vector<Enumerator> enumerators;
@@ -834,10 +838,10 @@
enum_decl->enumerators().end()));
for (clang::EnumConstantDecl* enumerator : enum_decl->enumerators()) {
std::optional<Identifier> enumerator_name =
- GetTranslatedIdentifier(enumerator);
+ ictx_.GetTranslatedIdentifier(enumerator);
if (!enumerator_name.has_value()) {
// It's not clear that this case is possible
- return ImportUnsupportedItem(
+ return ictx_.ImportUnsupportedItem(
enum_decl, "importing enum failed: missing enumerator name");
}
@@ -850,7 +854,7 @@
return Enum{
.identifier = *enum_name,
.id = GenerateItemId(enum_decl),
- .owning_target = GetOwningTarget(enum_decl),
+ .owning_target = ictx_.GetOwningTarget(enum_decl),
.underlying_type = *std::move(type),
.enumerators = enumerators,
};
@@ -874,7 +878,7 @@
return ImportUnsupportedItem(decl, absl::StrJoin(errors, "\n\n"));
}
-std::optional<IR::Item> Importer::ImportTypedefName(
+std::optional<IR::Item> crubit::TypedefNameDeclImporter::Import(
clang::TypedefNameDecl* typedef_name_decl) {
const clang::DeclContext* decl_context = typedef_name_decl->getDeclContext();
if (decl_context) {
@@ -882,7 +886,7 @@
return std::nullopt;
}
if (decl_context->isRecord()) {
- return ImportUnsupportedItem(
+ return ictx_.ImportUnsupportedItem(
typedef_name_decl,
"Typedefs nested in classes are not supported yet");
}
@@ -890,25 +894,27 @@
clang::QualType type =
typedef_name_decl->getASTContext().getTypedefType(typedef_name_decl);
- if (MapKnownCcTypeToRsType(type.getAsString()).has_value()) {
+ if (ictx_.type_mapper_.MapKnownCcTypeToRsType(type.getAsString())
+ .has_value()) {
return std::nullopt;
}
std::optional<Identifier> identifier =
- GetTranslatedIdentifier(typedef_name_decl);
+ ictx_.GetTranslatedIdentifier(typedef_name_decl);
CRUBIT_CHECK(identifier.has_value()); // This should never happen.
std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
- absl::StatusOr<MappedType> underlying_type = type_mapper_.ConvertQualType(
- typedef_name_decl->getUnderlyingType(), no_lifetimes);
+ absl::StatusOr<MappedType> underlying_type =
+ ictx_.type_mapper_.ConvertQualType(typedef_name_decl->getUnderlyingType(),
+ no_lifetimes);
if (underlying_type.ok()) {
- type_mapper_.Insert(typedef_name_decl);
+ ictx_.type_mapper_.Insert(typedef_name_decl);
return TypeAlias{.identifier = *identifier,
.id = GenerateItemId(typedef_name_decl),
- .owning_target = GetOwningTarget(typedef_name_decl),
- .doc_comment = GetComment(typedef_name_decl),
+ .owning_target = ictx_.GetOwningTarget(typedef_name_decl),
+ .doc_comment = ictx_.GetComment(typedef_name_decl),
.underlying_type = *underlying_type};
} else {
- return ImportUnsupportedItem(
+ return ictx_.ImportUnsupportedItem(
typedef_name_decl, std::string(underlying_type.status().message()));
}
}
@@ -957,7 +963,7 @@
.column = sm.getSpellingColumnNumber(loc)};
}
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertTypeDecl(
+absl::StatusOr<MappedType> TypeMapper::ConvertTypeDecl(
const clang::TypeDecl* decl) const {
if (!known_type_decls_.contains(decl)) {
return absl::NotFoundError(absl::Substitute(
@@ -968,7 +974,7 @@
return MappedType::WithDeclId(decl_id);
}
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertType(
+absl::StatusOr<MappedType> TypeMapper::ConvertType(
const clang::Type* type,
std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
bool nullable) const {
@@ -1082,7 +1088,7 @@
"Unsupported clang::Type class '", type->getTypeClassName(), "'"));
}
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertQualType(
+absl::StatusOr<MappedType> TypeMapper::ConvertQualType(
clang::QualType qual_type,
std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
bool nullable) const {
@@ -1106,17 +1112,18 @@
return type;
}
-absl::StatusOr<std::vector<Field>> Importer::ImportFields(
+absl::StatusOr<std::vector<Field>> CXXRecordDeclImporter::ImportFields(
clang::CXXRecordDecl* record_decl) {
// Provisionally assume that we know this RecordDecl so that we'll be able
// to import fields whose type contains the record itself.
- TypeMapper temp_import_mapper(type_mapper_);
+ TypeMapper temp_import_mapper(ictx_.type_mapper_);
temp_import_mapper.Insert(record_decl);
clang::AccessSpecifier default_access =
record_decl->isClass() ? clang::AS_private : clang::AS_public;
std::vector<Field> fields;
- const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
+ const clang::ASTRecordLayout& layout =
+ ictx_.ctx_.getASTRecordLayout(record_decl);
for (const clang::FieldDecl* field_decl : record_decl->fields()) {
std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
auto type =
@@ -1131,7 +1138,8 @@
access = default_access;
}
- std::optional<Identifier> field_name = GetTranslatedIdentifier(field_decl);
+ std::optional<Identifier> field_name =
+ ictx_.GetTranslatedIdentifier(field_decl);
if (!field_name.has_value()) {
return absl::UnimplementedError(
absl::Substitute("Cannot translate name for field '$0'",
@@ -1139,7 +1147,7 @@
}
fields.push_back(
{.identifier = *std::move(field_name),
- .doc_comment = GetComment(field_decl),
+ .doc_comment = ictx_.GetComment(field_decl),
.type = *type,
.access = TranslateAccessSpecifier(access),
.offset = layout.getFieldOffset(field_decl->getFieldIndex()),
@@ -1237,7 +1245,7 @@
}
}
-std::vector<BaseClass> Importer::GetUnambiguousPublicBases(
+std::vector<BaseClass> CXXRecordDeclImporter::GetUnambiguousPublicBases(
const clang::CXXRecordDecl& record_decl) const {
// This function is unfortunate: the only way to correctly get information
// about the bases is lookupInBases. It runs a complex O(N^3) algorithm for
@@ -1282,13 +1290,13 @@
const clang::CXXBaseSpecifier& base_specifier =
*path[path.size() - 1].Base;
const clang::QualType& base = base_specifier.getType();
- if (paths.isAmbiguous(ctx_.getCanonicalType(base))) {
+ if (paths.isAmbiguous(ictx_.ctx_.getCanonicalType(base))) {
continue;
}
clang::CXXRecordDecl* base_record_decl =
CRUBIT_DIE_IF_NULL(base_specifier.getType()->getAsCXXRecordDecl());
- if (!type_mapper_.ConvertTypeDecl(base_record_decl).status().ok()) {
+ if (!ictx_.type_mapper_.ConvertTypeDecl(base_record_decl).status().ok()) {
continue;
}
@@ -1299,7 +1307,7 @@
break;
}
*offset +=
- {ctx_.getASTRecordLayout(base_path_element.Class)
+ {ictx_.ctx_.getASTRecordLayout(base_path_element.Class)
.getBaseClassOffset(CRUBIT_DIE_IF_NULL(
base_path_element.Base->getType()->getAsCXXRecordDecl()))
.getQuantity()};
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index 50aa64e..c2824c8 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -33,154 +33,133 @@
namespace crubit {
-// Iterates over the AST created from the invocation's entry headers and
-// creates an intermediate representation of the import (`IR`) into the
-// invocation object.
-class Importer {
+// Top-level parameters as well as return value of an importer invocation.
+class Invocation {
public:
- // Top-level parameters as well as return value of an importer invocation.
- class Invocation {
- public:
- Invocation(BazelLabel target, absl::Span<const HeaderName> entry_headers,
- const absl::flat_hash_map<const HeaderName, const BazelLabel>&
- header_targets)
- : target_(target),
- entry_headers_(entry_headers),
- lifetime_context_(
- std::make_shared<
- clang::tidy::lifetimes::LifetimeAnnotationContext>()),
- header_targets_(header_targets) {
- // Caller should verify that the inputs are non-empty.
- CRUBIT_CHECK(!entry_headers_.empty());
- CRUBIT_CHECK(!header_targets_.empty());
+ Invocation(BazelLabel target, absl::Span<const HeaderName> entry_headers,
+ const absl::flat_hash_map<const HeaderName, const BazelLabel>&
+ header_targets)
+ : target_(target),
+ entry_headers_(entry_headers),
+ lifetime_context_(std::make_shared<
+ clang::tidy::lifetimes::LifetimeAnnotationContext>()),
+ header_targets_(header_targets) {
+ // Caller should verify that the inputs are non-empty.
+ CRUBIT_CHECK(!entry_headers_.empty());
+ CRUBIT_CHECK(!header_targets_.empty());
- ir_.used_headers.insert(ir_.used_headers.end(), entry_headers_.begin(),
- entry_headers.end());
- ir_.current_target = target_;
- }
+ ir_.used_headers.insert(ir_.used_headers.end(), entry_headers_.begin(),
+ entry_headers.end());
+ ir_.current_target = target_;
+ }
- // Returns the target of a header, if any.
- std::optional<BazelLabel> header_target(const HeaderName header) const {
- auto it = header_targets_.find(header);
- return (it != header_targets_.end()) ? std::optional(it->second)
- : std::nullopt;
- }
+ // Returns the target of a header, if any.
+ std::optional<BazelLabel> header_target(const HeaderName header) const {
+ auto it = header_targets_.find(header);
+ return (it != header_targets_.end()) ? std::optional(it->second)
+ : std::nullopt;
+ }
- // The main target from which we are importing.
- const BazelLabel target_;
+ // The main target from which we are importing.
+ const BazelLabel target_;
- // The headers from which the import starts (a collection of
- // paths in the format suitable for a google3-relative quote include).
- const absl::Span<const HeaderName> entry_headers_;
+ // The headers from which the import starts (a collection of
+ // paths in the format suitable for a google3-relative quote include).
+ const absl::Span<const HeaderName> entry_headers_;
- const std::shared_ptr<clang::tidy::lifetimes::LifetimeAnnotationContext>
- lifetime_context_;
+ const std::shared_ptr<clang::tidy::lifetimes::LifetimeAnnotationContext>
+ lifetime_context_;
- // The main output of the import process
- IR ir_;
-
- private:
- const absl::flat_hash_map<const HeaderName, const BazelLabel>&
- header_targets_;
- };
-
- // The currently known canonical type decls that we know how to map into
- // Rust.
- class TypeMapper {
- public:
- TypeMapper(const clang::ASTContext* ctx) : ctx_(ctx) {}
-
- TypeMapper(const TypeMapper& other) = default;
- TypeMapper& operator=(const TypeMapper& other) = default;
-
- // Converts the Clang type `qual_type` into an equivalent `MappedType`.
- // Lifetimes for the type can optionally be specified using `lifetimes`.
- // If `qual_type` is a pointer type, `nullable` specifies whether the
- // pointer can be null.
- // TODO(b/209390498): Currently, we're able to specify nullability only for
- // top-level pointers. Extend this so that we can specify nullability for
- // all pointers contained in `qual_type`, in the same way that `lifetimes`
- // specifies lifetimes for all these pointers. Once this is done, make sure
- // that all callers pass in the appropriate information, derived from
- // nullability annotations.
- absl::StatusOr<MappedType> ConvertQualType(
- clang::QualType qual_type,
- std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
- bool nullable = true) const;
- absl::StatusOr<MappedType> ConvertType(
- const clang::Type* type,
- std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
- bool nullable) const;
- absl::StatusOr<MappedType> ConvertTypeDecl(
- const clang::TypeDecl* decl) const;
-
- bool Contains(const clang::TypeDecl* decl) const {
- return known_type_decls_.contains(
- clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
- }
-
- void Insert(const clang::TypeDecl* decl) {
- known_type_decls_.insert(
- clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
- }
-
- private:
- const clang::ASTContext* ctx_;
- absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
- };
-
- explicit Importer(Invocation& invocation, clang::ASTContext& ctx,
- clang::Sema& sema)
- : invocation_(invocation),
- ctx_(ctx),
- sema_(sema),
- mangler_(CRUBIT_DIE_IF_NULL(ctx_.createMangleContext())),
- type_mapper_(&ctx) {}
-
- // Import all visible declarations from a translation unit.
- void Import(clang::TranslationUnitDecl* decl);
+ // The main output of the import process
+ IR ir_;
private:
+ const absl::flat_hash_map<const HeaderName, const BazelLabel>&
+ header_targets_;
+};
+
+// The currently known canonical type decls that we know how to map into
+// Rust.
+class TypeMapper {
+ public:
+ TypeMapper(const clang::ASTContext* ctx) : ctx_(ctx) {}
+
+ TypeMapper(const TypeMapper& other) = default;
+ TypeMapper& operator=(const TypeMapper& other) = default;
+
+ // Converts the Clang type `qual_type` into an equivalent `MappedType`.
+ // Lifetimes for the type can optionally be specified using `lifetimes`.
+ // If `qual_type` is a pointer type, `nullable` specifies whether the
+ // pointer can be null.
+ // TODO(b/209390498): Currently, we're able to specify nullability only for
+ // top-level pointers. Extend this so that we can specify nullability for
+ // all pointers contained in `qual_type`, in the same way that `lifetimes`
+ // specifies lifetimes for all these pointers. Once this is done, make sure
+ // that all callers pass in the appropriate information, derived from
+ // nullability annotations.
+ absl::StatusOr<MappedType> ConvertQualType(
+ clang::QualType qual_type,
+ std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
+ bool nullable = true) const;
+ absl::StatusOr<MappedType> ConvertType(
+ const clang::Type* type,
+ std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
+ bool nullable) const;
+ absl::StatusOr<MappedType> ConvertTypeDecl(const clang::TypeDecl* decl) const;
+ std::optional<absl::string_view> MapKnownCcTypeToRsType(
+ absl::string_view cc_type) const;
+
+ bool Contains(const clang::TypeDecl* decl) const {
+ return known_type_decls_.contains(
+ clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
+ }
+
+ void Insert(const clang::TypeDecl* decl) {
+ known_type_decls_.insert(
+ clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
+ }
+
+ private:
+ const clang::ASTContext* ctx_;
+ absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
+};
+
+// Explicitly defined interface that defines how `DeclImporter`s are allowed to
+// interface with the global state of the importer.
+class ImportContext {
+ public:
+ ImportContext(Invocation& invocation, clang::ASTContext& ctx,
+ clang::Sema& sema)
+ : invocation_(invocation), ctx_(ctx), sema_(sema), type_mapper_(&ctx){};
+ virtual ~ImportContext(){};
+
// Imports all decls contained in a `DeclContext`.
- void ImportDeclsFromDeclContext(const clang::DeclContext* decl_context);
+ virtual void ImportDeclsFromDeclContext(
+ const clang::DeclContext* decl_context) = 0;
- // Returns the Item of a Decl, importing it first if necessary.
- std::optional<IR::Item> GetDeclItem(clang::Decl* decl);
+ // Imports an unsupported item with a single error message.
+ virtual IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+ std::string error) = 0;
- // Imports a decl and creates an IR item (or error messages).
- // Does not use or update the cache.
- std::optional<IR::Item> ImportDecl(clang::Decl* decl);
+ // Imports an unsupported item with multiple error messages.
+ virtual IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+ std::set<std::string> errors) = 0;
- // These functions import specific `Decl` subtypes. They use `LookupDecl` to
- // lookup dependencies. They don't use or update the cache themselves.
- std::optional<IR::Item> ImportNamespace(clang::NamespaceDecl* namespace_decl);
- std::optional<IR::Item> ImportFunction(
- clang::FunctionDecl* function_decl,
- clang::FunctionTemplateDecl* function_template_decl = nullptr);
- std::optional<IR::Item> ImportRecord(clang::CXXRecordDecl* record_decl);
- std::optional<IR::Item> ImportTypedefName(
- clang::TypedefNameDecl* typedef_name_decl);
- std::optional<IR::Item> ImportEnum(clang::EnumDecl* enum_decl);
-
- IR::Item ImportUnsupportedItem(const clang::Decl* decl, std::string error);
- IR::Item ImportUnsupportedItem(const clang::Decl* decl,
- std::set<std::string> errors);
-
- absl::StatusOr<std::vector<Field>> ImportFields(
- clang::CXXRecordDecl* record_decl);
- // Stores the comments of this target in source order.
- void ImportFreeComments();
// Returns the item ids of the children and comments of the given decl in
// source order. This method assumes that the children decls have already been
// imported.
- std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl);
+ virtual std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl) = 0;
- std::string GetMangledName(const clang::NamedDecl* named_decl) const;
- BazelLabel GetOwningTarget(const clang::Decl* decl) const;
+ // Mangles the name of a named decl.
+ virtual std::string GetMangledName(
+ const clang::NamedDecl* named_decl) const = 0;
+
+ // Returs the label of the target that contains a decl.
+ virtual BazelLabel GetOwningTarget(const clang::Decl* decl) const = 0;
// Checks if the given decl belongs to the current target. Does not look into
// other redeclarations of the decl.
- bool IsFromCurrentTarget(const clang::Decl* decl) const;
+ virtual bool IsFromCurrentTarget(const clang::Decl* decl) const = 0;
// Gets an IR UnqualifiedIdentifier for the named decl.
//
@@ -190,36 +169,184 @@
// a SpecialName.
//
// If the translated name is not yet implemented, this returns null.
- std::optional<UnqualifiedIdentifier> GetTranslatedName(
- const clang::NamedDecl* named_decl) const;
+ virtual std::optional<UnqualifiedIdentifier> GetTranslatedName(
+ const clang::NamedDecl* named_decl) const = 0;
// GetTranslatedName, but only for identifier names. This is the common case.
+ virtual std::optional<Identifier> GetTranslatedIdentifier(
+ const clang::NamedDecl* named_decl) const = 0;
+
+ // Gets the doc comment of the declaration.
+ virtual llvm::Optional<std::string> GetComment(
+ const clang::Decl* decl) const = 0;
+
+ // Converts a Clang source location to IR.
+ virtual SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const = 0;
+
+ Invocation& invocation_;
+ clang::ASTContext& ctx_;
+ clang::Sema& sema_;
+ TypeMapper type_mapper_;
+};
+
+// Interface for components that can import decls of a certain category.
+class DeclImporter {
+ public:
+ DeclImporter(ImportContext& ictx) : ictx_(ictx){};
+ virtual ~DeclImporter(){};
+
+ // Determines whether this importer is autoritative for a decl. This does not
+ // imply that the import will be succesful.
+ virtual bool CanImport(clang::Decl*) = 0;
+
+ // Returns an IR item for a decl, or `std::nullopt` if importing failed.
+ // This member function may only be called after `CanImport` returned `true`.
+ virtual std::optional<IR::Item> ImportDecl(clang::Decl*) = 0;
+
+ protected:
+ ImportContext& ictx_;
+};
+
+// Common implementation for defining `DeclImporter`s that determine their
+// applicability by the dynamic type of the decl.
+template <typename D>
+class DeclImporterBase : public DeclImporter {
+ public:
+ DeclImporterBase(ImportContext& context) : DeclImporter(context) {}
+
+ protected:
+ bool CanImport(clang::Decl* decl) { return clang::isa<D>(decl); }
+ std::optional<IR::Item> ImportDecl(clang::Decl* decl) {
+ return Import(clang::cast<D>(decl));
+ }
+ virtual std::optional<IR::Item> Import(D*);
+};
+
+// TODO(forster): Move those implementations into separate files.
+
+// A `DeclImporter` for `ClassTemplateDecl`s.
+class ClassTemplateDeclImporter
+ : public DeclImporterBase<clang::ClassTemplateDecl> {
+ public:
+ ClassTemplateDeclImporter(ImportContext& context)
+ : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::ClassTemplateDecl*);
+};
+
+// A `DeclImporter` for `CXXRecordDecl`s.
+class CXXRecordDeclImporter : public DeclImporterBase<clang::CXXRecordDecl> {
+ public:
+ CXXRecordDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::CXXRecordDecl*);
+
+ private:
+ absl::StatusOr<std::vector<Field>> ImportFields(clang::CXXRecordDecl*);
+ std::vector<BaseClass> GetUnambiguousPublicBases(
+ const clang::CXXRecordDecl& record_decl) const;
+};
+
+// A `DeclImporter` for `EnumDecl`s.
+class EnumDeclImporter : public DeclImporterBase<clang::EnumDecl> {
+ public:
+ EnumDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::EnumDecl*);
+};
+
+// A `DeclImporter` for `FunctionTemplateDecl`s.
+class FunctionTemplateDeclImporter
+ : public DeclImporterBase<clang::FunctionTemplateDecl> {
+ public:
+ FunctionTemplateDeclImporter(ImportContext& context)
+ : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::FunctionTemplateDecl*);
+};
+
+// A `DeclImporter` for `FunctionDecl`s.
+class FunctionDeclImporter : public DeclImporterBase<clang::FunctionDecl> {
+ public:
+ FunctionDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::FunctionDecl*);
+};
+
+// A `DeclImporter` for `NamespaceDecl`s.
+class NamespaceDeclImporter : public DeclImporterBase<clang::NamespaceDecl> {
+ public:
+ NamespaceDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::NamespaceDecl*);
+};
+
+// A `DeclImporter` for `TypedefNameDecl`s.
+class TypedefNameDeclImporter
+ : public DeclImporterBase<clang::TypedefNameDecl> {
+ public:
+ TypedefNameDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+ std::optional<IR::Item> Import(clang::TypedefNameDecl*);
+};
+
+// Iterates over the AST created from the invocation's entry headers and
+// creates an intermediate representation of the import (`IR`) into the
+// invocation object.
+class Importer : public ImportContext {
+ public:
+ explicit Importer(Invocation& invocation, clang::ASTContext& ctx,
+ clang::Sema& sema)
+ : ImportContext(invocation, ctx, sema),
+ mangler_(CRUBIT_DIE_IF_NULL(ctx_.createMangleContext())) {
+ decl_importers_.push_back(
+ std::make_unique<ClassTemplateDeclImporter>(*this));
+ decl_importers_.push_back(std::make_unique<CXXRecordDeclImporter>(*this));
+ decl_importers_.push_back(std::make_unique<EnumDeclImporter>(*this));
+ decl_importers_.push_back(std::make_unique<FunctionDeclImporter>(*this));
+ decl_importers_.push_back(
+ std::make_unique<FunctionTemplateDeclImporter>(*this));
+ decl_importers_.push_back(std::make_unique<NamespaceDeclImporter>(*this));
+ decl_importers_.push_back(std::make_unique<TypedefNameDeclImporter>(*this));
+ }
+
+ // Import all visible declarations from a translation unit.
+ void Import(clang::TranslationUnitDecl* decl);
+
+ protected:
+ // Implementation of `ImportContext`
+ void ImportDeclsFromDeclContext(
+ const clang::DeclContext* decl_context) override;
+ IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+ std::string error) override;
+ IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+ std::set<std::string> errors) override;
+ std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl) override;
+ std::string GetMangledName(const clang::NamedDecl* named_decl) const override;
+ BazelLabel GetOwningTarget(const clang::Decl* decl) const override;
+ bool IsFromCurrentTarget(const clang::Decl* decl) const override;
+ std::optional<UnqualifiedIdentifier> GetTranslatedName(
+ const clang::NamedDecl* named_decl) const override;
std::optional<Identifier> GetTranslatedIdentifier(
- const clang::NamedDecl* named_decl) const {
+ const clang::NamedDecl* named_decl) const override {
if (std::optional<UnqualifiedIdentifier> name =
GetTranslatedName(named_decl)) {
return std::move(*std::get_if<Identifier>(&*name));
}
return std::nullopt;
}
+ llvm::Optional<std::string> GetComment(
+ const clang::Decl* decl) const override;
+ SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const override;
- // Gets the doc comment of the declaration.
- llvm::Optional<std::string> GetComment(const clang::Decl* decl) const;
+ private:
+ // Returns the Item of a Decl, importing it first if necessary.
+ std::optional<IR::Item> GetDeclItem(clang::Decl* decl);
- SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const;
+ // Imports a decl and creates an IR item (or error messages).
+ // Does not use or update the cache.
+ std::optional<IR::Item> ImportDecl(clang::Decl* decl);
- std::vector<BaseClass> GetUnambiguousPublicBases(
- const clang::CXXRecordDecl& record_decl) const;
+ // Stores the comments of this target in source order.
+ void ImportFreeComments();
- Invocation& invocation_;
-
- clang::ASTContext& ctx_;
- clang::Sema& sema_;
-
+ std::vector<std::unique_ptr<DeclImporter>> decl_importers_;
std::unique_ptr<clang::MangleContext> mangler_;
absl::flat_hash_map<const clang::Decl*, std::optional<IR::Item>>
import_cache_;
- TypeMapper type_mapper_;
std::vector<const clang::RawComment*> comments_;
}; // class Importer
diff --git a/rs_bindings_from_cc/ir_from_cc.cc b/rs_bindings_from_cc/ir_from_cc.cc
index 1fd9166..45ff3be 100644
--- a/rs_bindings_from_cc/ir_from_cc.cc
+++ b/rs_bindings_from_cc/ir_from_cc.cc
@@ -70,8 +70,8 @@
"-fparse-all-comments"};
args_as_strings.insert(args_as_strings.end(), args.begin(), args.end());
- if (Importer::Invocation invocation(current_target, entrypoint_headers,
- headers_to_targets);
+ if (Invocation invocation(current_target, entrypoint_headers,
+ headers_to_targets);
clang::tooling::runToolOnCodeWithArgs(
std::make_unique<FrontendAction>(invocation),
virtual_input_file_content, args_as_strings, kVirtualInputPath,