Type aliases bound to fully-instantiated template
PiperOrigin-RevId: 449002160
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 7468975..db38b32 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -108,9 +108,24 @@
)
cc_library(
+ name = "ast_util",
+ srcs = ["ast_util.cc"],
+ hdrs = ["ast_util.h"],
+ visibility = ["//third_party/crubit:__subpackages__"],
+ deps = [
+ "//common:check",
+ "@llvm///clang:ast",
+ ],
+)
+
+cc_library(
name = "bazel_types",
+ srcs = ["bazel_types.cc"],
hdrs = ["bazel_types.h"],
- deps = ["//common:string_type"],
+ deps = [
+ "//common:string_type",
+ "@absl//strings",
+ ],
)
cc_library(
@@ -149,6 +164,7 @@
"//lifetime_annotations",
"@absl//container:flat_hash_map",
"@absl//status:statusor",
+ "@llvm///clang:ast",
],
)
@@ -189,6 +205,7 @@
"//common:check",
"//common:status_macros",
"//lifetime_annotations:type_lifetimes",
+ "//rs_bindings_from_cc:ast_util",
"//rs_bindings_from_cc/importers:class_template",
"//rs_bindings_from_cc/importers:cxx_record",
"//rs_bindings_from_cc/importers:enum",
diff --git a/rs_bindings_from_cc/ast_util.cc b/rs_bindings_from_cc/ast_util.cc
new file mode 100644
index 0000000..93fce47
--- /dev/null
+++ b/rs_bindings_from_cc/ast_util.cc
@@ -0,0 +1,29 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "rs_bindings_from_cc/ast_util.h"
+
+#include "common/check.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+
+namespace crubit {
+
+bool IsFullClassTemplateSpecializationOrChild(const clang::Decl* decl) {
+ if (clang::isa<clang::ClassTemplatePartialSpecializationDecl>(decl)) {
+ return false;
+ }
+ if (clang::isa<clang::ClassTemplateSpecializationDecl>(decl)) {
+ return true;
+ }
+
+ if (const auto* decl_context = decl->getDeclContext()) {
+ return IsFullClassTemplateSpecializationOrChild(
+ clang::dyn_cast<clang::Decl>(decl_context));
+ }
+
+ return false;
+}
+
+} // namespace crubit
diff --git a/rs_bindings_from_cc/ast_util.h b/rs_bindings_from_cc/ast_util.h
new file mode 100644
index 0000000..576c9b2
--- /dev/null
+++ b/rs_bindings_from_cc/ast_util.h
@@ -0,0 +1,19 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_AST_UTIL_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_AST_UTIL_H_
+
+#include "clang/AST/DeclCXX.h"
+
+namespace crubit {
+
+// Returns true if `decl` is either 1) a ClassTemplateSpecializationDecl (but
+// not ClassTemplatePartialSpecializationDecl) or 2) a decl (e.g. a member
+// function decl) nested inside a ClassTemplateSpecializationDecl.
+bool IsFullClassTemplateSpecializationOrChild(const clang::Decl* decl);
+
+} // namespace crubit
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_AST_UTIL_H_
diff --git a/rs_bindings_from_cc/bazel_types.cc b/rs_bindings_from_cc/bazel_types.cc
new file mode 100644
index 0000000..fa4cde8
--- /dev/null
+++ b/rs_bindings_from_cc/bazel_types.cc
@@ -0,0 +1,31 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "rs_bindings_from_cc/bazel_types.h"
+
+#include "absl/strings/ascii.h"
+
+namespace crubit {
+
+std::string ConvertToCcIdentifier(const BazelLabel& target) {
+ std::string result = target.value();
+
+ // TODO(b/222001243): The escaping below can arrive at the same result for 2
+ // distinct targets like //foo/bar:baz and //foo_bar:baz. In the long-term
+ // this should be fixed, or alternatively ConvertToCcIdentifier should be
+ // removed (the latter is the current plan of record - see also "Handling
+ // thunks" section in <internal link>).
+ for (char& c : result) {
+ if (!absl::ascii_isalnum(c)) {
+ c = '_';
+ }
+ }
+ if (!result.empty() && !absl::ascii_isalpha(result[0])) {
+ result[0] = '_';
+ }
+
+ return result;
+}
+
+} // namespace crubit
diff --git a/rs_bindings_from_cc/bazel_types.h b/rs_bindings_from_cc/bazel_types.h
index 1c87ae1..8d6d2fc 100644
--- a/rs_bindings_from_cc/bazel_types.h
+++ b/rs_bindings_from_cc/bazel_types.h
@@ -14,6 +14,10 @@
// Representation of a Bazel label (for example //foo/bar:baz).
CRUBIT_DEFINE_STRING_TYPE(BazelLabel);
+// Coverts the argument to a valid C++ identifier (e.g. replacing characters
+// like ":" or "/" with "_").
+std::string ConvertToCcIdentifier(const BazelLabel&);
+
} // namespace crubit
#endif // CRUBIT_RS_BINDINGS_FROM_CC_BAZEL_TYPES_H_
diff --git a/rs_bindings_from_cc/decl_importer.h b/rs_bindings_from_cc/decl_importer.h
index c1e8809..8508ccd 100644
--- a/rs_bindings_from_cc/decl_importer.h
+++ b/rs_bindings_from_cc/decl_importer.h
@@ -10,6 +10,7 @@
#include "lifetime_annotations/lifetime_annotations.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
+#include "clang/AST/DeclTemplate.h"
namespace crubit {
@@ -163,6 +164,11 @@
// Converts a Clang source location to IR.
virtual SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const = 0;
+ // Converts `type` into a MappedType, after first importing the Record behind
+ // the template instantiation.
+ virtual absl::StatusOr<MappedType> ConvertTemplateSpecializationType(
+ const clang::TemplateSpecializationType* type) = 0;
+
Invocation& invocation_;
clang::ASTContext& ctx_;
clang::Sema& sema_;
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index 9713fae..49c30cb 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -28,6 +28,7 @@
#include "common/check.h"
#include "common/status_macros.h"
#include "lifetime_annotations/type_lifetimes.h"
+#include "rs_bindings_from_cc/ast_util.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "clang/AST/ASTContext.h"
@@ -41,6 +42,7 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Specifiers.h"
+#include "clang/Sema/Sema.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Casting.h"
@@ -52,6 +54,17 @@
constexpr absl::string_view kTypeStatusPayloadUrl =
"type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type";
+// Checks if the return value from `GetDeclItem` indicates that the import was
+// successful.
+absl::Status CheckImportStatus(const std::optional<IR::Item>& item) {
+ if (!item.has_value()) {
+ return absl::InvalidArgumentError("The import has been skipped");
+ }
+ if (auto* unsupported = std::get_if<UnsupportedItem>(&*item)) {
+ return absl::InvalidArgumentError(unsupported->message);
+ }
+ return absl::OkStatus();
+}
}
// A mapping of C++ standard types to their equivalent Rust types.
@@ -322,6 +335,27 @@
return ordered_item_ids;
}
+std::vector<ItemId> Importer::GetOrderedItemIdsOfTemplateInstantiations()
+ const {
+ std::vector<SourceLocationComparator::OrderedItemId> items;
+ items.reserve(class_template_instantiations_for_current_target_.size());
+ for (const auto* decl : class_template_instantiations_for_current_target_) {
+ items.push_back(
+ {decl->getSourceRange(), GetDeclOrder(decl), GenerateItemId(decl)});
+ }
+
+ clang::SourceManager& sm = ctx_.getSourceManager();
+ auto compare_locations = SourceLocationComparator(sm);
+ llvm::sort(items, compare_locations);
+
+ std::vector<ItemId> ordered_item_ids;
+ ordered_item_ids.reserve(items.size());
+ for (const auto& ordered_item : items) {
+ ordered_item_ids.push_back(std::get<2>(ordered_item));
+ }
+ return ordered_item_ids;
+}
+
void Importer::ImportFreeComments() {
clang::SourceManager& sm = ctx_.getSourceManager();
for (const auto& header : invocation_.entry_headers_) {
@@ -369,6 +403,11 @@
}
invocation_.ir_.top_level_item_ids =
GetItemIdsInSourceOrder(translation_unit_decl);
+
+ // TODO(b/222001243): Consider placing the generated template instantiations
+ // into a separate namespace (maybe `crubit::instantiated_templates` ?).
+ llvm::copy(GetOrderedItemIdsOfTemplateInstantiations(),
+ std::back_inserter(invocation_.ir_.top_level_item_ids));
}
void Importer::ImportDeclsFromDeclContext(
@@ -408,6 +447,12 @@
}
BazelLabel Importer::GetOwningTarget(const clang::Decl* decl) const {
+ // Template instantiations need to be generated in the target that triggered
+ // the instantiation (not in the target where the template is defined).
+ if (IsFullClassTemplateSpecializationOrChild(decl)) {
+ return invocation_.target_;
+ }
+
clang::SourceManager& source_manager = ctx_.getSourceManager();
auto source_location = decl->getLocation();
@@ -504,6 +549,53 @@
.column = sm.getSpellingColumnNumber(loc)};
}
+absl::StatusOr<MappedType> Importer::ConvertTemplateSpecializationType(
+ const clang::TemplateSpecializationType* type) {
+ // Qualifiers are handled separately in TypeMapper::ConvertQualType().
+ std::string type_string = clang::QualType(type, 0).getAsString();
+
+ auto* specialization_decl =
+ clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(
+ type->getAsCXXRecordDecl());
+ if (!specialization_decl) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "Template specialization '$0' without an associated record decl "
+ "is not supported.",
+ type_string));
+ }
+
+ if (IsFromCurrentTarget(specialization_decl) &&
+ !specialization_decl->isExplicitSpecialization()) {
+ // Store implicit `specialization_decl`s so that they will get included in
+ // IR::top_level_item_ids.
+ class_template_instantiations_for_current_target_.insert(
+ specialization_decl);
+ }
+
+ // `Sema::isCompleteType` will try to instantiate the class template as a
+ // side-effect and we rely on this here. `decl->getDefinition()` can
+ // return nullptr before the call to sema and return its definition
+ // afterwards.
+ if (!sema_.isCompleteType(specialization_decl->getLocation(),
+ ctx_.getRecordType(specialization_decl))) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "'$0' template specialization is incomplete", type_string));
+ }
+
+ // TODO(lukasza): Limit specialization depth? (e.g. using
+ // `isSpecializationDepthGreaterThan` from earlier prototypes).
+
+ absl::Status import_status =
+ CheckImportStatus(GetDeclItem(specialization_decl));
+ if (!import_status.ok()) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "Failed to create bindings for template specialization type $0: $1",
+ type_string, import_status.message()));
+ }
+
+ return type_mapper_.ConvertTypeDecl(specialization_decl);
+}
+
absl::StatusOr<MappedType> TypeMapper::ConvertTypeDecl(
const clang::TypeDecl* decl) const {
if (!known_type_decls_.contains(decl)) {
@@ -623,6 +715,9 @@
} else if (const auto* typedef_type =
type->getAsAdjusted<clang::TypedefType>()) {
return ConvertTypeDecl(typedef_type->getDecl());
+ } else if (const auto* subst_type =
+ type->getAs<clang::SubstTemplateTypeParmType>()) {
+ return ConvertQualType(subst_type->getReplacementType(), lifetimes);
}
return absl::UnimplementedError(absl::StrCat(
@@ -654,6 +749,26 @@
}
std::string Importer::GetMangledName(const clang::NamedDecl* named_decl) const {
+ if (auto specialization_decl =
+ clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(named_decl)) {
+ // Itanium mangler produces valid Rust identifiers, use it to generate a
+ // name for this instantiation.
+ llvm::SmallString<128> storage;
+ llvm::raw_svector_ostream buffer(storage);
+ mangler_->mangleTypeName(ctx_.getRecordType(specialization_decl), buffer);
+
+ // The Itanium mangler does not provide a way to get the mangled
+ // representation of a type. Instead, we call mangleTypeName() that
+ // returns the name of the RTTI typeinfo symbol, and remove the _ZTS
+ // prefix. Then we prepend __CcTemplateInst to reduce chances of conflict
+ // with regular C and C++ structs.
+ constexpr llvm::StringRef kZtsPrefix = "_ZTS";
+ constexpr llvm::StringRef kCcTemplatePrefix = "__CcTemplateInst";
+ CRUBIT_CHECK(buffer.str().take_front(4) == kZtsPrefix);
+ return llvm::formatv("{0}{1}", kCcTemplatePrefix,
+ buffer.str().drop_front(kZtsPrefix.size()));
+ }
+
clang::GlobalDecl decl;
// There are only three named decl types that don't work with the GlobalDecl
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index aecf1a7..7509dec 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -73,8 +73,15 @@
llvm::Optional<std::string> GetComment(
const clang::Decl* decl) const override;
SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const override;
+ absl::StatusOr<MappedType> ConvertTemplateSpecializationType(
+ const clang::TemplateSpecializationType* type) override;
private:
+ // Returns the item ids of template instantiations that have been triggered
+ // from the current target. The returned items are in an arbitrary,
+ // deterministic/reproducible order.
+ std::vector<ItemId> GetOrderedItemIdsOfTemplateInstantiations() const;
+
// Returns the Item of a Decl, importing it first if necessary.
std::optional<IR::Item> GetDeclItem(clang::Decl* decl);
@@ -89,6 +96,8 @@
std::unique_ptr<clang::MangleContext> mangler_;
absl::flat_hash_map<const clang::Decl*, std::optional<IR::Item>>
import_cache_;
+ absl::flat_hash_set<const clang::ClassTemplateSpecializationDecl*>
+ class_template_instantiations_for_current_target_;
std::vector<const clang::RawComment*> comments_;
}; // class Importer
diff --git a/rs_bindings_from_cc/importers/BUILD b/rs_bindings_from_cc/importers/BUILD
index a7ae2b4..7391473 100644
--- a/rs_bindings_from_cc/importers/BUILD
+++ b/rs_bindings_from_cc/importers/BUILD
@@ -19,6 +19,7 @@
deps = [
"@absl//strings",
"//rs_bindings_from_cc:ast_convert",
+ "//rs_bindings_from_cc:ast_util",
"//rs_bindings_from_cc:decl_importer",
"@llvm///clang:ast",
"@llvm///clang:sema",
@@ -41,6 +42,7 @@
hdrs = ["function.h"],
deps = [
"@absl//strings",
+ "//rs_bindings_from_cc:ast_util",
"//rs_bindings_from_cc:decl_importer",
],
)
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index 5347ac1..024d643 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -13,6 +13,19 @@
namespace crubit {
+namespace {
+
+std::string GetClassTemplateSpecializationCcName(
+ const clang::ASTContext& ast_context,
+ const clang::ClassTemplateSpecializationDecl* specialization_decl) {
+ clang::PrintingPolicy policy(ast_context.getLangOpts());
+ policy.IncludeTagDefinition = false;
+ return clang::QualType(specialization_decl->getTypeForDecl(), 0)
+ .getAsString(policy);
+}
+
+} // namespace
+
std::optional<IR::Item> CXXRecordDeclImporter::Import(
clang::CXXRecordDecl* record_decl) {
const clang::DeclContext* decl_context = record_decl->getDeclContext();
@@ -26,14 +39,35 @@
return ictx_.ImportUnsupportedItem(record_decl,
"Nested classes are not supported yet");
}
+ if (clang::isa<clang::ClassTemplatePartialSpecializationDecl>(record_decl)) {
+ return ictx_.ImportUnsupportedItem(
+ record_decl, "Partially-specialized class templates are not supported");
+ }
if (record_decl->isInvalidDecl()) {
return std::nullopt;
}
- std::optional<Identifier> record_name =
- ictx_.GetTranslatedIdentifier(record_decl);
- if (!record_name.has_value()) {
- return std::nullopt;
+ std::string rs_name, cc_name;
+ llvm::Optional<std::string> doc_comment;
+ if (auto* specialization_decl =
+ clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(
+ record_decl)) {
+ rs_name = ictx_.GetMangledName(specialization_decl);
+ cc_name =
+ GetClassTemplateSpecializationCcName(ictx_.ctx_, specialization_decl);
+ doc_comment = ictx_.GetComment(specialization_decl);
+ if (!doc_comment.hasValue()) {
+ doc_comment =
+ ictx_.GetComment(specialization_decl->getSpecializedTemplate());
+ }
+ } else {
+ std::optional<Identifier> record_name =
+ ictx_.GetTranslatedIdentifier(record_decl);
+ if (!record_name.has_value()) {
+ return std::nullopt;
+ }
+ rs_name = cc_name = record_name->Ident();
+ doc_comment = ictx_.GetComment(record_decl);
}
if (clang::CXXRecordDecl* complete = record_decl->getDefinition()) {
@@ -42,20 +76,12 @@
CRUBIT_CHECK(!record_decl->isCompleteDefinition());
ictx_.type_mapper_.Insert(record_decl);
return IncompleteRecord{
- .cc_name = std::string(record_name->Ident()),
+ .cc_name = std::move(cc_name),
.id = GenerateItemId(record_decl),
.owning_target = ictx_.GetOwningTarget(record_decl),
.enclosing_namespace_id = GetEnclosingNamespaceId(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 ictx_.ImportUnsupportedItem(record_decl,
- "Class templates are not supported yet");
- }
-
ictx_.sema_.ForceDeclarationOfImplicitMembers(record_decl);
const clang::ASTRecordLayout& layout =
@@ -81,11 +107,11 @@
auto item_ids = ictx_.GetItemIdsInSourceOrder(record_decl);
return Record{
- .rs_name = std::string(record_name->Ident()),
- .cc_name = std::string(record_name->Ident()),
+ .rs_name = std::move(rs_name),
+ .cc_name = std::move(cc_name),
.id = GenerateItemId(record_decl),
.owning_target = ictx_.GetOwningTarget(record_decl),
- .doc_comment = ictx_.GetComment(record_decl),
+ .doc_comment = std::move(doc_comment),
.unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
.fields = *std::move(fields),
.size = layout.getSize().getQuantity(),
diff --git a/rs_bindings_from_cc/importers/function.cc b/rs_bindings_from_cc/importers/function.cc
index e09245b..1f1e363 100644
--- a/rs_bindings_from_cc/importers/function.cc
+++ b/rs_bindings_from_cc/importers/function.cc
@@ -5,6 +5,7 @@
#include "rs_bindings_from_cc/importers/function.h"
#include "absl/strings/substitute.h"
+#include "rs_bindings_from_cc/ast_util.h"
namespace crubit {
@@ -187,9 +188,34 @@
bool has_c_calling_convention =
function_decl->getType()->getAs<clang::FunctionType>()->getCallConv() ==
clang::CC_C;
+ bool is_member_or_descendant_of_class_template =
+ IsFullClassTemplateSpecializationOrChild(function_decl);
std::optional<UnqualifiedIdentifier> translated_name =
ictx_.GetTranslatedName(function_decl);
+ llvm::Optional<std::string> doc_comment = ictx_.GetComment(function_decl);
+ if (!doc_comment.hasValue() && is_member_or_descendant_of_class_template) {
+ // Despite `is_member_or_descendant_of_class_template` check above, we are
+ // not guaranteed that a `func_pattern` exists below. For example, it may
+ // be missing when `function_decl` is an implicitly defined constructor of a
+ // class template -- such decls are generated, not instantiated.
+ if (clang::FunctionDecl* func_pattern =
+ function_decl->getTemplateInstantiationPattern()) {
+ doc_comment = ictx_.GetComment(func_pattern);
+ }
+ }
+
+ std::string mangled_name = ictx_.GetMangledName(function_decl);
+ if (is_member_or_descendant_of_class_template) {
+ // TODO(b/222001243): Avoid calling `ConvertToCcIdentifier(target)` to
+ // distinguish multiple definitions of a template instantiation. Instead
+ // help the linker merge all the definitions into one, by defining the
+ // thunk via a function template - see "Handling thunks" section in
+ // <internal link>
+ mangled_name += '_';
+ mangled_name += ConvertToCcIdentifier(ictx_.GetOwningTarget(function_decl));
+ }
+
// Silence ClangTidy, checked above: calling `add_error` if
// `!return_type.ok()` and returning early if `!errors.empty()`.
CRUBIT_CHECK(return_type.ok());
@@ -198,14 +224,16 @@
return Func{
.name = *translated_name,
.owning_target = ictx_.GetOwningTarget(function_decl),
- .doc_comment = ictx_.GetComment(function_decl),
- .mangled_name = ictx_.GetMangledName(function_decl),
+ .doc_comment = std::move(doc_comment),
+ .mangled_name = std::move(mangled_name),
.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,
+ .is_member_or_descendant_of_class_template =
+ is_member_or_descendant_of_class_template,
.source_loc = ictx_.ConvertSourceLocation(function_decl->getBeginLoc()),
.id = GenerateItemId(function_decl),
.enclosing_namespace_id = GetEnclosingNamespaceId(function_decl),
diff --git a/rs_bindings_from_cc/importers/typedef_name.cc b/rs_bindings_from_cc/importers/typedef_name.cc
index ff27f16..e2b483a 100644
--- a/rs_bindings_from_cc/importers/typedef_name.cc
+++ b/rs_bindings_from_cc/importers/typedef_name.cc
@@ -32,10 +32,18 @@
std::optional<Identifier> identifier =
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 =
- ictx_.type_mapper_.ConvertQualType(typedef_name_decl->getUnderlyingType(),
- no_lifetimes);
+
+ absl::StatusOr<MappedType> underlying_type;
+ // TODO(b/228868369): Move this "if" into the generic TypeMapper::ConvertType.
+ // This will extend support for template instantiations outside type aliases.
+ if (const auto* tst_type = typedef_name_decl->getUnderlyingType()
+ ->getAs<clang::TemplateSpecializationType>()) {
+ underlying_type = ictx_.ConvertTemplateSpecializationType(tst_type);
+ } else {
+ std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
+ underlying_type = ictx_.type_mapper_.ConvertQualType(
+ typedef_name_decl->getUnderlyingType(), no_lifetimes);
+ }
if (underlying_type.ok()) {
ictx_.type_mapper_.Insert(typedef_name_decl);
return TypeAlias{
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 10ec1e9..7e2730e 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -291,6 +291,8 @@
{"is_inline", is_inline},
{"member_func_metadata", member_func_metadata},
{"has_c_calling_convention", has_c_calling_convention},
+ {"is_member_or_descendant_of_class_template",
+ is_member_or_descendant_of_class_template},
{"source_loc", source_loc},
{"id", id},
{"enclosing_namespace_id", enclosing_namespace_id},
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index b0c35e3..f4027e5 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -454,6 +454,7 @@
// If null, this is not a member function.
llvm::Optional<MemberFuncMetadata> member_func_metadata;
bool has_c_calling_convention = true;
+ bool is_member_or_descendant_of_class_template = false;
SourceLoc source_loc;
ItemId id;
llvm::Optional<ItemId> enclosing_namespace_id;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index e91e099..1209f0e 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -282,6 +282,7 @@
pub is_inline: bool,
pub member_func_metadata: Option<MemberFuncMetadata>,
pub has_c_calling_convention: bool,
+ pub is_member_or_descendant_of_class_template: bool,
pub source_loc: SourceLoc,
pub id: ItemId,
pub enclosing_namespace_id: Option<ItemId>,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 986c5fa..35b5db6 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -77,6 +77,7 @@
is_inline: false,
member_func_metadata: None,
has_c_calling_convention: true,
+ is_member_or_descendant_of_class_template: false,
source_loc: SourceLoc {
filename: "ir_from_cc_virtual_header.h",
line: 3,
@@ -137,14 +138,14 @@
#[test]
fn test_dont_import_record_nested_in_func() {
let ir = ir_from_cc("inline void f() { struct S{}; }").unwrap();
- assert_ir_not_matches!(ir, quote! { Record { identifier: "S" ... } });
+ assert_ir_not_matches!(ir, quote! { Record { ... "S" ... } });
}
#[test]
-fn test_dont_import_class_template_or_specialization() {
+fn test_dont_import_unused_class_template_or_specialization() {
let ir = ir_from_cc("template <class T> struct Template{}; template<> struct Template<int>{};")
.unwrap();
- assert_ir_not_matches!(ir, quote! { Record { identifier: "Template" ... } });
+ assert_ir_not_matches!(ir, quote! { Record { ... "Template" ... } });
}
#[test]
@@ -749,6 +750,697 @@
}
#[test]
+fn test_typedef_of_fully_instantiated_template() -> Result<()> {
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+
+ // Doc comment of MyStruct template.
+ template <typename T>
+ struct MyStruct {
+ // Doc comment of GetValue method.
+ const T& GetValue() const { return value; }
+
+ // Doc comment of `value` field.
+ T value;
+ };
+
+ // Doc comment of MyTypeAlias.
+ using MyTypeAlias = MyStruct<int>; "#,
+ )?;
+ // Instantiation of the struct template:
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record {
+ rs_name: "__CcTemplateInst8MyStructIiE", ...
+ cc_name: "MyStruct<int>", ...
+ owning_target: BazelLabel("//test:testing_target"), ...
+ doc_comment: Some("Doc comment of MyStruct template."), ...
+ fields: [Field {
+ identifier: Some("value"), ...
+ doc_comment: Some("Doc comment of `value` field."), ...
+ type_: MappedType {
+ rs_type: RsType { name: Some("i32"), ... },
+ cc_type: CcType { name: Some("int"), ... },
+ },
+ access: Public,
+ offset: 0, ...
+ }], ...
+ }
+ }
+ );
+ // Make sure the instantiation of the class template appears exactly once in the
+ // `top_level_item_ids`.
+ let record = ir.records().find(|r| r.cc_name == "MyStruct<int>").unwrap();
+ assert_eq!(1, ir.top_level_item_ids().filter(|&&id| id == record.id).count());
+ // Type alias for the class template specialization.
+ assert_ir_matches!(
+ ir,
+ quote! {
+ TypeAlias {
+ identifier: "MyTypeAlias", ...
+ owning_target: BazelLabel("//test:testing_target"), ...
+ doc_comment: Some("Doc comment of MyTypeAlias."), ...
+ underlying_type: MappedType {
+ rs_type: RsType {
+ name: None,
+ lifetime_args: [],
+ type_args: [],
+ decl_id: Some(ItemId(...)),
+ },
+ cc_type: CcType {
+ name: None,
+ is_const: false,
+ type_args: [],
+ decl_id: Some(ItemId(...)),
+ },
+ } ...
+ }
+ }
+ );
+ // Member function of the struct template instantiation:
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetValue",
+ owning_target: BazelLabel("//test:testing_target"),
+ mangled_name: "_ZNK8MyStructIiE8GetValueEv___test_testing_target", ...
+ doc_comment: Some("Doc comment of GetValue method."), ...
+ is_inline: true, ...
+ member_func_metadata: Some(MemberFuncMetadata {
+ record_id: ItemId(...),
+ instance_method_metadata: Some(InstanceMethodMetadata { ... }), ...
+ }), ...
+ }
+ }
+ );
+ // Implicitly defined assignment operator inside the struct template is
+ // represented in the AST slightly differently (not marked as instantiated)
+ // because it is generated by the compiler for the complete, instantiated type
+ // according to general rules.
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "operator=",
+ owning_target: BazelLabel("//test:testing_target"),
+ mangled_name: "_ZN8MyStructIiEaSERKS0____test_testing_target", ...
+ doc_comment: None, ...
+ }
+ }
+ );
+ Ok(())
+}
+
+#[test]
+fn test_typedef_for_explicit_template_specialization() -> Result<()> {
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+
+ template <typename T>
+ struct MyStruct final {};
+
+ // Doc comment for template specialization for T=int.
+ template<>
+ struct MyStruct<int> final {
+ // Doc comment of the GetValue method specialization for T=int.
+ const int& GetValue() const { return value * 42; }
+
+ // Doc comment of the `value` field specialization for T=int.
+ int value;
+ };
+
+ // Doc comment of MyTypeAlias.
+ using MyTypeAlias = MyStruct<int>; "#,
+ )?;
+ // Make sure the explicit specialization of the struct template appears exactly
+ // once in the `top_level_item_ids`.
+ let record = ir.records().find(|r| r.cc_name == "MyStruct<int>").unwrap();
+ assert_eq!(1, ir.top_level_item_ids().filter(|&&id| id == record.id).count());
+ // Instantiation of the struct template based on the specialization for T=int:
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record {
+ rs_name: "__CcTemplateInst8MyStructIiE", ...
+ cc_name: "MyStruct<int>", ...
+ owning_target: BazelLabel("//test:testing_target"), ...
+ doc_comment: Some("Doc comment for template specialization for T=int."), ...
+ fields: [Field {
+ identifier: Some("value"), ...
+ doc_comment: Some("Doc comment of the `value` field specialization for T=int."), ...
+ type_: MappedType {
+ rs_type: RsType { name: Some("i32"), ... },
+ cc_type: CcType { name: Some("int"), ... },
+ },
+ access: Public,
+ offset: 0, ...
+ }], ...
+ }
+ }
+ );
+ // Instance method inside the struct template:
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetValue",
+ owning_target: BazelLabel("//test:testing_target"),
+ mangled_name: "_ZNK8MyStructIiE8GetValueEv___test_testing_target", ...
+ doc_comment: Some("Doc comment of the GetValue method specialization for T=int."), ...
+ is_inline: true, ...
+ member_func_metadata: Some(MemberFuncMetadata {
+ record_id: ItemId(...),
+ instance_method_metadata: Some(InstanceMethodMetadata { ... }), ...
+ }), ...
+ }
+ }
+ );
+ Ok(())
+}
+
+#[test]
+fn test_multiple_typedefs_to_same_template() -> Result<()> {
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyStruct {
+ void MyMethod() {}
+ };
+ using MyIntAlias = MyStruct<int>;
+ using MyIntAlias = MyStruct<int>;
+ using MyIntAlias2 = MyStruct<int>;
+ using MyFloatAlias = MyStruct<float>;
+ "#,
+ )?;
+
+ // Verify that there is only 1 record for each specialization.
+ assert_eq!(1, ir.records().filter(|r| r.cc_name == "MyStruct<int>").count());
+ assert_eq!(1, ir.records().filter(|r| r.cc_name == "MyStruct<float>").count());
+ let functions = ir
+ .functions()
+ .filter(|f| f.name == UnqualifiedIdentifier::Identifier(ir_id("MyMethod")))
+ .collect_vec();
+
+ // Verify that there is only 1 function per instantiation.
+ assert_eq!(2, functions.len());
+ let rec_id1 = functions[0].member_func_metadata.as_ref().unwrap().record_id;
+ let rec_id2 = functions[1].member_func_metadata.as_ref().unwrap().record_id;
+ assert_ne!(rec_id1, rec_id2);
+ Ok(())
+}
+
+#[test]
+fn test_templates_inheritance() -> Result<()> {
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ class BaseTemplate {
+ protected:
+ BaseTemplate(T base_value) : base_value_(base_value) {}
+ const T& base_value() const { return base_value_; }
+ private:
+ T base_value_;
+ };
+
+ template <typename T>
+ class ClassTemplateDerivedFromClassTemplate : public BaseTemplate<T> {
+ public:
+ ClassTemplateDerivedFromClassTemplate(T base_value, T derived_value)
+ : BaseTemplate<T>(base_value), derived_value_(derived_value) {}
+ T combined_value() const {
+ return 1000 * BaseTemplate<T>::base_value() + derived_value_;
+ }
+ private:
+ T derived_value_;
+ };
+
+ using TypeAliasForClassTemplateDerivedFromClassTemplate =
+ ClassTemplateDerivedFromClassTemplate<int>;
+ "#,
+ )?;
+
+ // ClassTemplateDerivedFromClassTemplate is instantiated because of
+ // TypeAliasForClassTemplateDerivedFromClassTemplate..
+ assert_eq!(
+ 1,
+ ir.records()
+ .filter(|r| r.cc_name.contains("ClassTemplateDerivedFromClassTemplate"))
+ .count()
+ );
+
+ // BaseTemplate is *not* instantiated in the generated bindings/IR. The derived
+ // class's bindings work fine without the bindings for the base class (this
+ // is also true for non-templated base/derived classes).
+ assert_eq!(0, ir.records().filter(|r| r.cc_name.contains("BaseTemplate")).count());
+ Ok(())
+}
+
+#[test]
+fn test_aliased_class_template_instantiated_in_header() -> Result<()> {
+ // This aliased class template specialization is instantiated due to the code
+ // that is present in the header. We should not corrupt the AST by
+ // instantiating again.
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ const T& GetValue() { return field; }
+ T field;
+ };
+
+ inline void my_full_instantiation() {
+ MyTemplate<int> t;
+ t.field = 123;
+ t.field = t.GetValue() * 123;
+ }
+
+ using MyAlias = MyTemplate<int>; "#,
+ )?;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record {
+ rs_name: "__CcTemplateInst10MyTemplateIiE", ...
+ cc_name: "MyTemplate<int>", ...
+ fields: [Field { identifier: Some("field"), ... }], ...
+ }
+ }
+ );
+ assert_ir_matches!(ir, quote! { Func { name: "GetValue", ... } });
+ Ok(())
+}
+
+#[test]
+fn test_aliased_class_template_partially_instantiated_in_header() -> Result<()> {
+ // Similar to `test_aliased_class_template_instantiated_in_header`, but doesn't
+ // instantiate all members.
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ const T& GetValue() { return field; }
+ T field;
+ };
+
+ inline void my_instantiation() {
+ MyTemplate<int> t;
+ // Members of MyTemplate are not used/instantiated.
+ }
+
+ using MyAlias = MyTemplate<int>; "#,
+ )?;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record {
+ rs_name: "__CcTemplateInst10MyTemplateIiE", ...
+ cc_name: "MyTemplate<int>", ...
+ fields: [Field { identifier: Some("field"), ... }], ...
+ }
+ }
+ );
+ assert_ir_matches!(ir, quote! { Func { name: "GetValue", ... } });
+ Ok(())
+}
+
+#[test]
+fn test_no_instantiation_of_template_only_used_in_private_field() -> Result<()> {
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate { T field; };
+
+ class MyStruct {
+ private:
+ MyTemplate<int> private_field_;
+ }; "#,
+ )?;
+ // There should be no instantiated template, just because of the private field.
+ // To some extent this test is an early enforcement of the long-term plan for
+ // b/226580208 and <internal link>.
+ assert_ir_not_matches!(ir, quote! { "field" });
+ Ok(())
+}
+
+#[test]
+fn test_subst_template_type_parm_type_vs_const_when_non_const_template_param() -> Result<()> {
+ // This test (and
+ // `test_subst_template_type_parm_type_vs_const_when_const_template_param`)
+ // verifies that `importer.cc` preserves `const` qualifier attached *both* to
+ // QualType associated with:
+ // 1) SubstTemplateTypeParm (i.e. the template *argument* has `const`:
+ // `MyTemplate<const int>`) 2) TemplateTypeParmType used inside the template
+ // definition: `const T& GetConstRef()`
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ const T& GetConstRef() const { return value; }
+ T& GetRef() { return value; }
+ T value;
+ };
+
+ // Just like the other test_subst_template_type_parm_type_vs_const...
+ // test, but using non-*const* int template parameter.
+ using MyAlias = MyTemplate<int>; "#,
+ )?;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetConstRef", ...
+ return_type: MappedType {
+ rs_type: RsType {
+ name: Some("&"), ...
+ type_args: [RsType { name: Some("i32"), ... }], ...
+ },
+ cc_type: CcType {
+ name: Some("&"),
+ is_const: false,
+ type_args: [CcType {
+ name: Some("int"),
+ is_const: true, ...
+ }], ...
+ },
+ }, ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetRef", ...
+ return_type: MappedType {
+ rs_type: RsType {
+ name: Some("&mut"), ...
+ type_args: [RsType { name: Some("i32"), ... }], ...
+ },
+ cc_type: CcType {
+ name: Some("&"),
+ is_const: false,
+ type_args: [CcType {
+ name: Some("int"),
+ is_const: false, ...
+ }], ...
+ },
+ }, ...
+ }
+ }
+ );
+ Ok(())
+}
+
+#[test]
+fn test_subst_template_type_parm_type_vs_const_when_const_template_param() -> Result<()> {
+ // This test (and
+ // `test_subst_template_type_parm_type_vs_const_when_non_const_template_param`)
+ // verifies that `importer.cc` preserves `const` qualifier attached *both* to
+ // QualType associated with:
+ // 1) SubstTemplateTypeParm (i.e. the template *argument* has `const`:
+ // `MyTemplate<const int>`) 2) TemplateTypeParmType used inside the template
+ // definition: `const T& GetConstRef()`
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ const T& GetConstRef() const { return value; }
+ T& GetRef() { return value; }
+ T value;
+ };
+
+ // Just like the other test_subst_template_type_parm_type_vs_const...
+ // test, but using *const* int template parameter.
+ using MyAlias = MyTemplate<const int>; "#,
+ )?;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetConstRef", ...
+ return_type: MappedType {
+ rs_type: RsType {
+ name: Some("&"), ...
+ type_args: [RsType { name: Some("i32"), ... }], ...
+ },
+ cc_type: CcType {
+ name: Some("&"),
+ is_const: false,
+ type_args: [CcType {
+ name: Some("int"),
+ is_const: true, ...
+ }], ...
+ },
+ }, ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func {
+ name: "GetRef", ...
+ return_type: MappedType {
+ rs_type: RsType {
+ name: Some("&"), ...
+ type_args: [RsType { name: Some("i32"), ... }], ...
+ },
+ cc_type: CcType {
+ name: Some("&"),
+ is_const: false,
+ type_args: [CcType {
+ name: Some("int"),
+ is_const: true, ...
+ }], ...
+ },
+ }, ...
+ }
+ }
+ );
+ Ok(())
+}
+
+#[test]
+fn test_template_and_alias_are_both_in_dependency() -> Result<()> {
+ // See also the `test_template_in_dependency_and_alias_in_current_target` test.
+ let ir = {
+ let dependency_src = r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ T GetValue();
+ T field;
+ };
+ using MyAliasOfTemplate = MyTemplate<int>;
+ struct StructInDependency {}; "#;
+ let current_target_src = r#" #pragma clang lifetime_elision
+ /* no references to MyTemplate or MyAliasOfTemplate */
+ struct StructInCurrentTarget {}; "#;
+ ir_from_cc_dependency(current_target_src, dependency_src)?
+ };
+
+ // Just double-checking the test inputs VS target names.
+ let current_target = ir_testing::TESTING_TARGET;
+ let dependency = ir_testing::DEPENDENCY_TARGET;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "StructInCurrentTarget", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "StructInDependency", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+
+ // Type alias is only defined in `dependency`.
+ assert_ir_matches!(
+ ir,
+ quote! {
+ TypeAlias { ...
+ identifier: "MyAliasOfTemplate", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ TypeAlias { ...
+ identifier: "MyAliasOfTemplate", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ // The template should be instantiated in `dependency`, rather than in
+ // `current_target`.
+ // TODO(b/222001243): Fix which target contains the instantiations and then flip
+ // the test assertions below. Tentative fix: cl/438580040.
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "MyTemplate<int>", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "MyTemplate<int>", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ // The template instantiations in the `dependency` should only produce type
+ // information (e.g. TypeAlias, Record) and don't need to produce Func
+ // items.
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ Func { ...
+ name: "GetValue", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ // There should be nothing template-instantiation-related in the main test
+ // target. TODO(b/222001243): Fix which target contains the instantiations
+ // and then flip the test assertions below to `assert_ir_not_matches`.
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func { ...
+ name: "GetValue", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ Ok(())
+}
+
+#[test]
+fn test_template_in_dependency_and_alias_in_current_target() -> Result<()> {
+ // See also the `test_template_and_alias_are_both_in_dependency` test.
+ let ir = {
+ let dependency_src = r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ T GetValue();
+ T field;
+ };
+ struct StructInDependency{}; "#;
+ let current_target_src = r#" #pragma clang lifetime_elision
+ using MyAliasOfTemplate = MyTemplate<int>;
+ struct StructInCurrentTarget{}; "#;
+ ir_from_cc_dependency(current_target_src, dependency_src)?
+ };
+
+ // Just double-checking the test inputs VS target names.
+ let current_target = ir_testing::TESTING_TARGET;
+ let dependency = ir_testing::DEPENDENCY_TARGET;
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "StructInCurrentTarget", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "StructInDependency", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+
+ // Type alias is only defined in `current_target`
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ TypeAlias { ...
+ identifier: "MyAliasOfTemplate", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ TypeAlias { ...
+ identifier: "MyAliasOfTemplate", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ // The template should be instantiated in `current_target`, rather than in
+ // `dependency`.
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "MyTemplate<int>", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record { ...
+ cc_name: "MyTemplate<int>", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ // There should be nothing template-instantiation-related in the dependency
+ // (since there is no instantiation there).
+ assert_ir_not_matches!(
+ ir,
+ quote! {
+ Func { ...
+ name: "GetValue", ...
+ owning_target: BazelLabel(#dependency), ...
+ }
+ }
+ );
+ // The template instantiations in the current target should produce not only
+ // type information (e.g. TypeAlias, Record) but also Func items (for
+ // methods of the instantiated class template).
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Func { ...
+ name: "GetValue", ...
+ owning_target: BazelLabel(#current_target), ...
+ }
+ }
+ );
+
+ Ok(())
+}
+
+#[test]
fn test_well_known_types_check_namespaces() -> Result<()> {
// Check that we don't treat a type called `int32_t` in a user-defined
// namespace as if it was the standard type `int32_t`.
diff --git a/rs_bindings_from_cc/ir_testing.rs b/rs_bindings_from_cc/ir_testing.rs
index a207743..3549ab3 100644
--- a/rs_bindings_from_cc/ir_testing.rs
+++ b/rs_bindings_from_cc/ir_testing.rs
@@ -27,11 +27,15 @@
)
}
+/// Target of the dependency used by `ir_from_cc_dependency`.
+/// Needs to be kept in sync with `kDependencyTarget` in `json_from_cc.cc`.
+pub const DEPENDENCY_TARGET: &str = "//test:dependency";
+
/// Generates `IR` from a header that depends on another header.
///
/// `header_source` of the header will be updated to contain the `#include` line
/// for the header with `dependency_header_source`. The name of the dependency
-/// target is assumed to be `"//test:dependency"`.
+/// target is exposed as `DEPENDENCY_TARGET`.
pub fn ir_from_cc_dependency(header_source: &str, dependency_header_source: &str) -> Result<IR> {
const DEPENDENCY_HEADER_NAME: &str = "test/dependency_header.h";
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 4be69ee..9fac534 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -173,6 +173,12 @@
if func.is_inline {
return false;
}
+ // ## Member functions (or descendants) of class templates
+ //
+ // A thunk is required to force/guarantee template instantiation.
+ if func.is_member_or_descendant_of_class_template {
+ return false;
+ }
// ## Virtual functions
//
// When calling virtual `A::Method()`, it's not necessarily the case that we'll
@@ -2593,6 +2599,133 @@
}
#[test]
+ fn test_template_in_dependency_and_alias_in_current_target() -> Result<()> {
+ // See also the test with the same name in `ir_from_cc_test.rs`.
+ let ir = {
+ let dependency_src = r#" #pragma clang lifetime_elision
+ template <typename T>
+ struct MyTemplate {
+ T GetValue() { return field; }
+ T field;
+ }; "#;
+ let current_target_src = r#" #pragma clang lifetime_elision
+ using MyAliasOfTemplate = MyTemplate<int>; "#;
+ ir_from_cc_dependency(current_target_src, dependency_src)?
+ };
+
+ let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(&ir)?;
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ #[repr(C)]
+ pub struct __CcTemplateInst10MyTemplateIiE {
+ pub field: i32,
+ }
+ }
+ );
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ impl __CcTemplateInst10MyTemplateIiE {
+ #[inline(always)]
+ pub fn GetValue<'a>(self: ... Pin<&'a mut Self>) -> i32 { unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIiE8GetValueEv___test_testing_target(
+ self)
+ }}
+ }
+ }
+ );
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ pub type MyAliasOfTemplate = crate::__CcTemplateInst10MyTemplateIiE;
+ }
+ );
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ mod detail {
+ ...
+ extern "C" {
+ ...
+ pub(crate) fn
+ __rust_thunk___ZN10MyTemplateIiE8GetValueEv___test_testing_target<'a>(
+ __this: ... Pin<&'a mut crate::__CcTemplateInst10MyTemplateIiE>
+ ) -> i32;
+ ...
+ }
+ }
+ }
+ );
+ assert_cc_matches!(
+ rs_api_impl,
+ quote! {
+ extern "C" int __rust_thunk___ZN10MyTemplateIiE8GetValueEv___test_testing_target(
+ class MyTemplate<int>* __this) {
+ return __this->GetValue();
+ }
+ }
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_template_with_out_of_line_definition() -> Result<()> {
+ // See also an end-to-end test in the `test/templates/out_of_line_definition`
+ // directory.
+ let ir = ir_from_cc(
+ r#" #pragma clang lifetime_elision
+ template <typename T>
+ class MyTemplate final {
+ public:
+ static MyTemplate Create(T value);
+ const T& value() const;
+
+ private:
+ T value_;
+ };
+
+ using MyTypeAlias = MyTemplate<int>; "#,
+ )?;
+
+ let BindingsTokens { rs_api_impl, .. } = generate_bindings_tokens(&ir)?;
+
+ // Even though the member functions above are *not* defined inline (e.g.
+ // IR::Func::is_inline is false), they still need to have thunks generated for
+ // them (to force/guarantee that the class template and its members get
+ // instantiated). This is also covered in the following end-to-end
+ // tests:
+ // - test/templates/out_of_line_definition/ - without a thunk, the template
+ // won't be instantiated and Rust bindings won't be able to call the member
+ // function (there will be no instantiation of the member function in the C++
+ // object files)
+ // - test/templates/definition_in_cc/ - the instantiation happens in the .cc
+ // file and therefore the thunk is not *required* (but it doesn't hurt to have
+ // the thunk)
+ assert_cc_matches!(
+ rs_api_impl,
+ quote! {
+ extern "C" class MyTemplate<int>
+ __rust_thunk___ZN10MyTemplateIiE6CreateEi___test_testing_target(int value) {
+ return MyTemplate<int>::Create(std::forward<decltype(value)>(value));
+ }
+ }
+ );
+ assert_cc_matches!(
+ rs_api_impl,
+ quote! {
+ extern "C" int const&
+ __rust_thunk___ZNK10MyTemplateIiE5valueEv___test_testing_target(
+ const class MyTemplate<int>*__this) {
+ return __this->value();
+ }
+ }
+ );
+ Ok(())
+ }
+
+ #[test]
fn test_simple_struct() -> Result<()> {
let ir = ir_from_cc(&tokens_to_string(quote! {
struct SomeStruct final {
diff --git a/rs_bindings_from_cc/support/offsetof.h b/rs_bindings_from_cc/support/offsetof.h
index 58876a0..2bbd611 100644
--- a/rs_bindings_from_cc/support/offsetof.h
+++ b/rs_bindings_from_cc/support/offsetof.h
@@ -47,7 +47,7 @@
// CRUBIT_OFFSET_OF is a wrapper around the standard `offsetof` macro [1] that
// adds support for using a type name (i.e. `T...`) that contains commas (e.g.
-// `TemplateClassWithTwoTemplateParameters<int, int>`).
+// `ClassTemplateWithTwoTemplateParameters<int, int>`).
//
// CRUBIT_OFFSET_OF doesn't require wrapping the type name in an extra set of
// parens. This aspect is achieved by making CRUBIT_OFFSET_OF a variadic macro
diff --git a/rs_bindings_from_cc/test/golden/doc_comment.h b/rs_bindings_from_cc/test/golden/doc_comment.h
index fc7dcea..411903f 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment.h
+++ b/rs_bindings_from_cc/test/golden/doc_comment.h
@@ -71,4 +71,43 @@
/// A type alias
using MyTypeAlias = DocCommentSlashes;
+/// Class template.
+template <typename T>
+struct MyTemplate final {
+ /// A non-static member function.
+ const T& get_field_value() const { return value; }
+
+ /// Data member.
+ T value;
+};
+
+/// Class template specialization.
+template <>
+struct MyTemplate<float> final {
+ /// A non-static member function in a specialization.
+ const float& get_field_value() const { return value; }
+
+ /// Data member in a specialization.
+ float value;
+};
+
+/// Type alias to template instantiation.
+using MyInstantiation = MyTemplate<int>;
+
+/// Type alias to instantiation of a template specialization.
+using MySpecializedInstantiation = MyTemplate<float>;
+
+/// Class template with nested struct inside.
+template <typename T>
+struct OuterTemplate final {
+ /// Doc comment for the nested struct.
+ struct NestedStruct {
+ /// Data member in a nested struct.
+ T value;
+ };
+};
+
+/// Type alias to a struct nested in a template instantiation.
+using ConcreteNestedStruct = OuterTemplate<int>::NestedStruct;
+
#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_DOC_COMMENT_H_
diff --git a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
index 5def129..2d1b037 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
@@ -283,8 +283,136 @@
/// A type alias
pub type MyTypeAlias = crate::DocCommentSlashes;
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=75
+// Error while generating bindings for item 'MyTemplate':
+// Class templates are not supported yet
+
+/// Class template specialization.
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct __CcTemplateInst10MyTemplateIfE {
+ /// Data member in a specialization.
+ pub value: f32,
+}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("MyTemplate<float>"),
+ crate::__CcTemplateInst10MyTemplateIfE
+);
+
+impl Default for __CcTemplateInst10MyTemplateIfE {
+ #[inline(always)]
+ fn default() -> Self {
+ let mut tmp = crate::rust_std::mem::MaybeUninit::<Self>::zeroed();
+ unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIfEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(&mut tmp);
+ tmp.assume_init()
+ }
+ }
+}
+
+impl<'b> From<ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIfE>>
+ for __CcTemplateInst10MyTemplateIfE
+{
+ #[inline(always)]
+ fn from(__param_0: ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIfE>) -> Self {
+ let mut tmp = crate::rust_std::mem::MaybeUninit::<Self>::zeroed();
+ unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIfEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(&mut tmp,__param_0);
+ tmp.assume_init()
+ }
+ }
+}
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=86
+// Error while generating bindings for item 'MyTemplate<float>::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=86
+// Error while generating bindings for item 'MyTemplate<float>::operator=':
+// Bindings for this kind of operator are not supported
+
+impl __CcTemplateInst10MyTemplateIfE {
+ /// A non-static member function in a specialization.
+ #[inline(always)]
+ pub fn get_field_value<'a>(&'a self) -> &'a f32 {
+ unsafe {
+ crate::detail::__rust_thunk___ZNK10MyTemplateIfE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(self)
+ }
+ }
+}
+
+/// Type alias to template instantiation.
+pub type MyInstantiation = crate::__CcTemplateInst10MyTemplateIiE;
+
+/// Type alias to instantiation of a template specialization.
+pub type MySpecializedInstantiation = crate::__CcTemplateInst10MyTemplateIfE;
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=101
+// Error while generating bindings for item 'OuterTemplate':
+// Class templates are not supported yet
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=111
+// Error while generating bindings for item 'ConcreteNestedStruct':
+// Unsupported type 'OuterTemplate<int>::NestedStruct': No generated bindings found for 'NestedStruct'
+
// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_DOC_COMMENT_H_
+/// Class template.
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct __CcTemplateInst10MyTemplateIiE {
+ /// Data member.
+ pub value: i32,
+}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("MyTemplate<int>"),
+ crate::__CcTemplateInst10MyTemplateIiE
+);
+
+impl Default for __CcTemplateInst10MyTemplateIiE {
+ #[inline(always)]
+ fn default() -> Self {
+ let mut tmp = crate::rust_std::mem::MaybeUninit::<Self>::zeroed();
+ unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIiEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(&mut tmp);
+ tmp.assume_init()
+ }
+ }
+}
+
+impl<'b> From<ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIiE>>
+ for __CcTemplateInst10MyTemplateIiE
+{
+ #[inline(always)]
+ fn from(__param_0: ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIiE>) -> Self {
+ let mut tmp = crate::rust_std::mem::MaybeUninit::<Self>::zeroed();
+ unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIiEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(&mut tmp,__param_0);
+ tmp.assume_init()
+ }
+ }
+}
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=76
+// Error while generating bindings for item 'MyTemplate<int>::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=76
+// Error while generating bindings for item 'MyTemplate<int>::operator=':
+// Bindings for this kind of operator are not supported
+
+// A non-static member function.
+
+impl __CcTemplateInst10MyTemplateIiE {
+ /// A non-static member function.
+ #[inline(always)]
+ pub fn get_field_value<'a>(&'a self) -> &'a i32 {
+ unsafe {
+ crate::detail::__rust_thunk___ZNK10MyTemplateIiE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(self)
+ }
+ }
+}
+
mod detail {
#[allow(unused_imports)]
use super::*;
@@ -342,6 +470,48 @@
__param_0: ctor::RvalueReference<'b, crate::MultilineOneStar>,
);
pub(crate) fn __rust_thunk___Z3foov() -> i32;
+ pub(crate) fn __rust_thunk___ZN10MyTemplateIfEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ >(
+ __this: &'a mut crate::rust_std::mem::MaybeUninit<
+ crate::__CcTemplateInst10MyTemplateIfE,
+ >,
+ );
+ pub(crate) fn __rust_thunk___ZN10MyTemplateIfEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ 'b,
+ >(
+ __this: &'a mut crate::rust_std::mem::MaybeUninit<
+ crate::__CcTemplateInst10MyTemplateIfE,
+ >,
+ __param_0: ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIfE>,
+ );
+ pub(crate) fn __rust_thunk___ZNK10MyTemplateIfE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ >(
+ __this: &'a crate::__CcTemplateInst10MyTemplateIfE,
+ ) -> &'a f32;
+ pub(crate) fn __rust_thunk___ZN10MyTemplateIiEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ >(
+ __this: &'a mut crate::rust_std::mem::MaybeUninit<
+ crate::__CcTemplateInst10MyTemplateIiE,
+ >,
+ );
+ pub(crate) fn __rust_thunk___ZN10MyTemplateIiEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ 'b,
+ >(
+ __this: &'a mut crate::rust_std::mem::MaybeUninit<
+ crate::__CcTemplateInst10MyTemplateIiE,
+ >,
+ __param_0: ctor::RvalueReference<'b, crate::__CcTemplateInst10MyTemplateIiE>,
+ );
+ pub(crate) fn __rust_thunk___ZNK10MyTemplateIiE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc<
+ 'a,
+ >(
+ __this: &'a crate::__CcTemplateInst10MyTemplateIiE,
+ ) -> &'a i32;
}
}
@@ -414,3 +584,37 @@
};
const _: () =
assert!(memoffset_unstable_const::offset_of!(crate::MultilineOneStar, i) * 8 == 0usize);
+
+const _: () = assert!(rust_std::mem::size_of::<crate::__CcTemplateInst10MyTemplateIfE>() == 4usize);
+const _: () =
+ assert!(rust_std::mem::align_of::<crate::__CcTemplateInst10MyTemplateIfE>() == 4usize);
+const _: () = {
+ static_assertions::assert_impl_all!(crate::__CcTemplateInst10MyTemplateIfE: Clone);
+};
+const _: () = {
+ static_assertions::assert_impl_all!(crate::__CcTemplateInst10MyTemplateIfE: Copy);
+};
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::__CcTemplateInst10MyTemplateIfE: Drop);
+};
+const _: () = assert!(
+ memoffset_unstable_const::offset_of!(crate::__CcTemplateInst10MyTemplateIfE, value) * 8
+ == 0usize
+);
+
+const _: () = assert!(rust_std::mem::size_of::<crate::__CcTemplateInst10MyTemplateIiE>() == 4usize);
+const _: () =
+ assert!(rust_std::mem::align_of::<crate::__CcTemplateInst10MyTemplateIiE>() == 4usize);
+const _: () = {
+ static_assertions::assert_impl_all!(crate::__CcTemplateInst10MyTemplateIiE: Clone);
+};
+const _: () = {
+ static_assertions::assert_impl_all!(crate::__CcTemplateInst10MyTemplateIiE: Copy);
+};
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::__CcTemplateInst10MyTemplateIiE: Drop);
+};
+const _: () = assert!(
+ memoffset_unstable_const::offset_of!(crate::__CcTemplateInst10MyTemplateIiE, value) * 8
+ == 0usize
+);
diff --git a/rs_bindings_from_cc/test/golden/doc_comment_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/doc_comment_rs_api_impl.cc
index 3c7a4ca..44a54bd 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment_rs_api_impl.cc
+++ b/rs_bindings_from_cc/test/golden/doc_comment_rs_api_impl.cc
@@ -142,6 +142,76 @@
return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
}
extern "C" int __rust_thunk___Z3foov() { return foo(); }
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiEC1ERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this, const class MyTemplate<int>& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this, class MyTemplate<int>&& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiED1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this) {
+ std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" class MyTemplate<int>&
+__rust_thunk___ZN10MyTemplateIiEaSERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this, const class MyTemplate<int>& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+} extern "C" class MyTemplate<int>&
+__rust_thunk___ZN10MyTemplateIiEaSEOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<int>* __this, class MyTemplate<int>&& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+} extern "C" int const&
+__rust_thunk___ZNK10MyTemplateIiE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ const class MyTemplate<int>* __this) {
+ return __this->get_field_value();
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIfEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIfEC1ERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this, const class MyTemplate<float>& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIfEC1EOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this, class MyTemplate<float>&& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIfED1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this) {
+ std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" class MyTemplate<float>&
+__rust_thunk___ZN10MyTemplateIfEaSERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this, const class MyTemplate<float>& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+} extern "C" class MyTemplate<float>&
+__rust_thunk___ZN10MyTemplateIfEaSEOS0____third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ class MyTemplate<float>* __this, class MyTemplate<float>&& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+} extern "C" float const&
+__rust_thunk___ZNK10MyTemplateIfE15get_field_valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_doc_comment_cc(
+ const class MyTemplate<float>* __this) {
+ return __this->get_field_value();
+}
static_assert(sizeof(class DocCommentSlashes) == 4);
static_assert(alignof(class DocCommentSlashes) == 4);
@@ -163,4 +233,12 @@
static_assert(alignof(class MultilineOneStar) == 4);
static_assert(CRUBIT_OFFSET_OF(i, class MultilineOneStar) * 8 == 0);
+static_assert(sizeof(class MyTemplate<int>) == 4);
+static_assert(alignof(class MyTemplate<int>) == 4);
+static_assert(CRUBIT_OFFSET_OF(value, class MyTemplate<int>) * 8 == 0);
+
+static_assert(sizeof(class MyTemplate<float>) == 4);
+static_assert(alignof(class MyTemplate<float>) == 4);
+static_assert(CRUBIT_OFFSET_OF(value, class MyTemplate<float>) * 8 == 0);
+
#pragma clang diagnostic pop
diff --git a/rs_bindings_from_cc/test/golden/templates.h b/rs_bindings_from_cc/test/golden/templates.h
new file mode 100644
index 0000000..fae6253
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/templates.h
@@ -0,0 +1,34 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TEMPLATES_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TEMPLATES_H_
+
+template <typename T>
+class MyTemplate {
+ public:
+ static MyTemplate Create(T value) {
+ MyTemplate result;
+ result.value_ = value;
+ return result;
+ }
+
+ const T& value() const { return value_; }
+
+ private:
+ T value_;
+};
+
+using MyTypeAlias = MyTemplate<int>;
+using OtherTypeAliasInSameTarget = MyTemplate<int>;
+
+template <typename T1, typename T2>
+struct TemplateWithTwoParams {
+ T1 value1;
+ T2 value2;
+};
+
+using AliasToTemplateWithTwoParams = TemplateWithTwoParams<int, float>;
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TEMPLATES_H_
diff --git a/rs_bindings_from_cc/test/golden/templates_rs_api.rs b/rs_bindings_from_cc/test/golden/templates_rs_api.rs
new file mode 100644
index 0000000..33dc4ae
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/templates_rs_api.rs
@@ -0,0 +1,173 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Automatically @generated Rust bindings for C++ target
+// //rs_bindings_from_cc/test/golden:templates_cc
+#![rustfmt::skip]
+#![feature(const_ptr_offset_from, custom_inner_attributes, negative_impls)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![deny(warnings)]
+
+use ::std as rust_std;
+
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// rs_bindings_from_cc/test/golden/templates.h;l=8
+// Error while generating bindings for item 'MyTemplate':
+// Class templates are not supported yet
+
+pub type MyTypeAlias = crate::__CcTemplateInst10MyTemplateIiE;
+
+pub type OtherTypeAliasInSameTarget = crate::__CcTemplateInst10MyTemplateIiE;
+
+// rs_bindings_from_cc/test/golden/templates.h;l=26
+// Error while generating bindings for item 'TemplateWithTwoParams':
+// Class templates are not supported yet
+
+pub type AliasToTemplateWithTwoParams = crate::__CcTemplateInst21TemplateWithTwoParamsIifE;
+
+// THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TEMPLATES_H_
+
+#[repr(C)]
+pub struct __CcTemplateInst10MyTemplateIiE {
+ value_: i32,
+}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("MyTemplate<int>"),
+ crate::__CcTemplateInst10MyTemplateIiE
+);
+
+impl !Unpin for __CcTemplateInst10MyTemplateIiE {}
+
+// rs_bindings_from_cc/test/golden/templates.h;l=9
+// Error while generating bindings for item 'MyTemplate<int>::MyTemplate<int>':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=9
+// Error while generating bindings for item 'MyTemplate<int>::MyTemplate<int>':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=9
+// Error while generating bindings for item 'MyTemplate<int>::MyTemplate':
+// Parameter #0 is not supported: Unsupported type 'class MyTemplate<int> &&': Unsupported type: && without lifetime
+
+// rs_bindings_from_cc/test/golden/templates.h;l=9
+// Error while generating bindings for item 'MyTemplate<int>::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=9
+// Error while generating bindings for item 'MyTemplate<int>::operator=':
+// Parameter #0 is not supported: Unsupported type 'class MyTemplate<int> &&': Unsupported type: && without lifetime
+
+impl __CcTemplateInst10MyTemplateIiE {
+ #[inline(always)]
+ pub fn Create(value: i32) -> crate::__CcTemplateInst10MyTemplateIiE {
+ unsafe {
+ crate::detail::__rust_thunk___ZN10MyTemplateIiE6CreateEi___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(value)
+ }
+ }
+}
+
+impl __CcTemplateInst10MyTemplateIiE {
+ #[inline(always)]
+ pub unsafe fn value(__this: *const crate::__CcTemplateInst10MyTemplateIiE) -> *const i32 {
+ crate::detail::__rust_thunk___ZNK10MyTemplateIiE5valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(__this)
+ }
+}
+
+#[repr(C)]
+pub struct __CcTemplateInst21TemplateWithTwoParamsIifE {
+ pub value1: i32,
+ pub value2: f32,
+}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("TemplateWithTwoParams<int, float>"),
+ crate::__CcTemplateInst21TemplateWithTwoParamsIifE
+);
+
+impl !Unpin for __CcTemplateInst21TemplateWithTwoParamsIifE {}
+
+// rs_bindings_from_cc/test/golden/templates.h;l=27
+// Error while generating bindings for item 'TemplateWithTwoParams<int, float>::TemplateWithTwoParams<int, float>':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=27
+// Error while generating bindings for item 'TemplateWithTwoParams<int, float>::TemplateWithTwoParams<int, float>':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=27
+// Error while generating bindings for item 'TemplateWithTwoParams<int, float>::TemplateWithTwoParams':
+// Parameter #0 is not supported: Unsupported type 'struct TemplateWithTwoParams<int, float> &&': Unsupported type: && without lifetime
+
+// rs_bindings_from_cc/test/golden/templates.h;l=27
+// Error while generating bindings for item 'TemplateWithTwoParams<int, float>::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/templates.h;l=27
+// Error while generating bindings for item 'TemplateWithTwoParams<int, float>::operator=':
+// Parameter #0 is not supported: Unsupported type 'struct TemplateWithTwoParams<int, float> &&': Unsupported type: && without lifetime
+
+mod detail {
+ #[allow(unused_imports)]
+ use super::*;
+ extern "C" {
+ pub(crate) fn __rust_thunk___ZN10MyTemplateIiE6CreateEi___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ value: i32,
+ ) -> crate::__CcTemplateInst10MyTemplateIiE;
+ pub(crate) fn __rust_thunk___ZNK10MyTemplateIiE5valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ __this: *const crate::__CcTemplateInst10MyTemplateIiE,
+ ) -> *const i32;
+ }
+}
+
+const _: () = assert!(rust_std::mem::size_of::<Option<&i32>>() == rust_std::mem::size_of::<&i32>());
+
+const _: () = assert!(rust_std::mem::size_of::<crate::__CcTemplateInst10MyTemplateIiE>() == 4usize);
+const _: () =
+ assert!(rust_std::mem::align_of::<crate::__CcTemplateInst10MyTemplateIiE>() == 4usize);
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::__CcTemplateInst10MyTemplateIiE: Copy);
+};
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::__CcTemplateInst10MyTemplateIiE: Drop);
+};
+const _: () = assert!(
+ memoffset_unstable_const::offset_of!(crate::__CcTemplateInst10MyTemplateIiE, value_) * 8
+ == 0usize
+);
+
+const _: () = assert!(
+ rust_std::mem::size_of::<crate::__CcTemplateInst21TemplateWithTwoParamsIifE>() == 8usize
+);
+const _: () = assert!(
+ rust_std::mem::align_of::<crate::__CcTemplateInst21TemplateWithTwoParamsIifE>() == 4usize
+);
+const _: () = {
+ static_assertions::assert_not_impl_all!(
+ crate::__CcTemplateInst21TemplateWithTwoParamsIifE: Copy
+ );
+};
+const _: () = {
+ static_assertions::assert_not_impl_all!(
+ crate::__CcTemplateInst21TemplateWithTwoParamsIifE: Drop
+ );
+};
+const _: () = assert!(
+ memoffset_unstable_const::offset_of!(
+ crate::__CcTemplateInst21TemplateWithTwoParamsIifE,
+ value1
+ ) * 8
+ == 0usize
+);
+const _: () = assert!(
+ memoffset_unstable_const::offset_of!(
+ crate::__CcTemplateInst21TemplateWithTwoParamsIifE,
+ value2
+ ) * 8
+ == 32usize
+);
diff --git a/rs_bindings_from_cc/test/golden/templates_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/templates_rs_api_impl.cc
new file mode 100644
index 0000000..26f113e
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/templates_rs_api_impl.cc
@@ -0,0 +1,79 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <cstddef>
+#include <memory>
+
+#include "rs_bindings_from_cc/support/cxx20_backports.h"
+#include "rs_bindings_from_cc/support/offsetof.h"
+#include "rs_bindings_from_cc/test/golden/templates.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class MyTemplate<int>* __this) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiEC1ERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class MyTemplate<int>* __this, const class MyTemplate<int>& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN10MyTemplateIiED1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class MyTemplate<int>* __this) {
+ std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" class MyTemplate<int>&
+__rust_thunk___ZN10MyTemplateIiEaSERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class MyTemplate<int>* __this, const class MyTemplate<int>& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+} extern "C" class MyTemplate<int>
+__rust_thunk___ZN10MyTemplateIiE6CreateEi___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ int value) {
+ return MyTemplate<int>::Create(std::forward<decltype(value)>(value));
+} extern "C" int const&
+__rust_thunk___ZNK10MyTemplateIiE5valueEv___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ const class MyTemplate<int>* __this) {
+ return __this->value();
+}
+extern "C" void
+__rust_thunk___ZN21TemplateWithTwoParamsIifEC1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class TemplateWithTwoParams<int, float>* __this) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void
+__rust_thunk___ZN21TemplateWithTwoParamsIifEC1ERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class TemplateWithTwoParams<int, float>* __this,
+ const class TemplateWithTwoParams<int, float>& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void
+__rust_thunk___ZN21TemplateWithTwoParamsIifED1Ev___third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class TemplateWithTwoParams<int, float>* __this) {
+ std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" class TemplateWithTwoParams<int, float>&
+__rust_thunk___ZN21TemplateWithTwoParamsIifEaSERKS0____third_party_crubit_rs_bindings_from_cc_test_golden_templates_cc(
+ class TemplateWithTwoParams<int, float>* __this,
+ const class TemplateWithTwoParams<int, float>& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+}
+
+static_assert(sizeof(class MyTemplate<int>) == 4);
+static_assert(alignof(class MyTemplate<int>) == 4);
+
+static_assert(sizeof(class TemplateWithTwoParams<int, float>) == 8);
+static_assert(alignof(class TemplateWithTwoParams<int, float>) == 4);
+static_assert(
+ CRUBIT_OFFSET_OF(value1, class TemplateWithTwoParams<int, float>) * 8 == 0);
+static_assert(CRUBIT_OFFSET_OF(value2,
+ class TemplateWithTwoParams<int, float>) *
+ 8 ==
+ 32);
+
+#pragma clang diagnostic pop
diff --git a/rs_bindings_from_cc/test/templates/definition_in_cc/BUILD b/rs_bindings_from_cc/test/templates/definition_in_cc/BUILD
new file mode 100644
index 0000000..78b841d
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/definition_in_cc/BUILD
@@ -0,0 +1,17 @@
+"""End-to-end example of using type aliases to fully-instantiated templates."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "definition_in_cc",
+ srcs = ["definition_in_cc.cc"],
+ hdrs = ["definition_in_cc.h"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [":definition_in_cc"],
+)
diff --git a/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.cc b/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.cc
new file mode 100644
index 0000000..48853e3
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.cc
@@ -0,0 +1,20 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.h"
+
+// static
+template <typename T>
+MyTemplate<T> MyTemplate<T>::Create(T value) {
+ MyTemplate<T> result;
+ result.value_ = value;
+ return result;
+}
+
+template <typename T>
+const T& MyTemplate<T>::value() const {
+ return value_;
+}
+
+template class MyTemplate<int>;
diff --git a/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.h b/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.h
new file mode 100644
index 0000000..00bb591
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/definition_in_cc/definition_in_cc.h
@@ -0,0 +1,22 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_DEFINITION_IN_CC_DEFINITION_IN_CC_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_DEFINITION_IN_CC_DEFINITION_IN_CC_H_
+
+#pragma clang lifetime_elision
+
+template <typename T>
+class MyTemplate {
+ public:
+ static MyTemplate Create(T value);
+ const T& value() const;
+
+ private:
+ T value_;
+};
+
+using MyTypeAlias = MyTemplate<int>;
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_DEFINITION_IN_CC_DEFINITION_IN_CC_H_
diff --git a/rs_bindings_from_cc/test/templates/definition_in_cc/test.rs b/rs_bindings_from_cc/test/templates/definition_in_cc/test.rs
new file mode 100644
index 0000000..6ae0841
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/definition_in_cc/test.rs
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_member_function_of_class_template_defined_in_cc_file() {
+ let s = definition_in_cc::MyTypeAlias::Create(123);
+ assert_eq!(123, *s.value());
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/BUILD b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/BUILD
new file mode 100644
index 0000000..b9b456c
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/BUILD
@@ -0,0 +1,26 @@
+"""End-to-end example of using type aliases that refer to fully-instantiated
+templates in a different target (where the target with the header doesn't
+have this particular instantiation)."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "template_without_instantiation",
+ hdrs = ["template_without_instantiation.h"],
+)
+
+cc_library(
+ name = "type_alias_in_different_target",
+ hdrs = ["type_alias_in_different_target.h"],
+ deps = [":template_without_instantiation"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [
+ ":type_alias_in_different_target",
+ ],
+)
diff --git a/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/template_without_instantiation.h b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/template_without_instantiation.h
new file mode 100644
index 0000000..ba97a74
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/template_without_instantiation.h
@@ -0,0 +1,27 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TEMPLATE_WITHOUT_INSTANTIATION_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TEMPLATE_WITHOUT_INSTANTIATION_H_
+
+#pragma clang lifetime_elision
+
+// This template is not instantiated anywhere in this header file
+// (this is what the test scenario exercised here cares about).
+template <typename T>
+class MyTemplate {
+ public:
+ static MyTemplate Create(T value) {
+ MyTemplate result;
+ result.value_ = value;
+ return result;
+ }
+
+ const T& value() const { return value_; }
+
+ private:
+ T value_;
+};
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TEMPLATE_WITHOUT_INSTANTIATION_H_
diff --git a/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/test.rs b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/test.rs
new file mode 100644
index 0000000..f66a5ad
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/test.rs
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_alias_to_template_without_instantiation_in_different_target() {
+ let s = type_alias_in_different_target::TypeAliasInDifferentTarget::Create(321);
+ assert_eq!(321, *s.value());
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/type_alias_in_different_target.h b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/type_alias_in_different_target.h
new file mode 100644
index 0000000..af31f17
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/type_alias_in_different_target.h
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_
+
+#include "rs_bindings_from_cc/test/templates/no_instantiation_in_template_target/template_without_instantiation.h"
+
+using TypeAliasInDifferentTarget = MyTemplate<int>;
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NO_INSTANTIATION_IN_TEMPLATE_TARGET_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_
diff --git a/rs_bindings_from_cc/test/templates/non_type_template_params/BUILD b/rs_bindings_from_cc/test/templates/non_type_template_params/BUILD
new file mode 100644
index 0000000..f7a226b
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/non_type_template_params/BUILD
@@ -0,0 +1,16 @@
+"""End-to-end example of using type aliases to fully-instantiated templates."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "non_type_template_params",
+ hdrs = ["non_type_template_params.h"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [":non_type_template_params"],
+)
diff --git a/rs_bindings_from_cc/test/templates/non_type_template_params/non_type_template_params.h b/rs_bindings_from_cc/test/templates/non_type_template_params/non_type_template_params.h
new file mode 100644
index 0000000..67afd7e
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/non_type_template_params/non_type_template_params.h
@@ -0,0 +1,19 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef DEVTOOLS_RUST_CC_INTEROP_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NON_TYPE_TEMPLATE_PARAMS_NON_TYPE_TEMPLATE_PARAMS_H_
+#define DEVTOOLS_RUST_CC_INTEROP_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NON_TYPE_TEMPLATE_PARAMS_NON_TYPE_TEMPLATE_PARAMS_H_
+
+#pragma clang lifetime_elision
+
+template <int multiplier>
+class MyTemplate {
+ public:
+ static int Multiply(int value) { return value * multiplier; }
+};
+
+using MyMultiplierX100 = MyTemplate<100>;
+using MyMultiplierX1000 = MyTemplate<1000>;
+
+#endif // DEVTOOLS_RUST_CC_INTEROP_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_NON_TYPE_TEMPLATE_PARAMS_NON_TYPE_TEMPLATE_PARAMS_H_
diff --git a/rs_bindings_from_cc/test/templates/non_type_template_params/test.rs b/rs_bindings_from_cc/test/templates/non_type_template_params/test.rs
new file mode 100644
index 0000000..b5a3c90
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/non_type_template_params/test.rs
@@ -0,0 +1,14 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ use non_type_template_params::*;
+
+ #[test]
+ fn test_non_type_template_params() {
+ assert_eq!(123 * 100, MyMultiplierX100::Multiply(123));
+ assert_eq!(123 * 1000, MyMultiplierX1000::Multiply(123));
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/out_of_line_definition/BUILD b/rs_bindings_from_cc/test/templates/out_of_line_definition/BUILD
new file mode 100644
index 0000000..064826c
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/out_of_line_definition/BUILD
@@ -0,0 +1,16 @@
+"""End-to-end example of using type aliases to fully-instantiated templates."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "out_of_line_definition",
+ hdrs = ["out_of_line_definition.h"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [":out_of_line_definition"],
+)
diff --git a/rs_bindings_from_cc/test/templates/out_of_line_definition/out_of_line_definition.h b/rs_bindings_from_cc/test/templates/out_of_line_definition/out_of_line_definition.h
new file mode 100644
index 0000000..ab21f17
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/out_of_line_definition/out_of_line_definition.h
@@ -0,0 +1,34 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_OUT_OF_LINE_DEFINITION_OUT_OF_LINE_DEFINITION_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_OUT_OF_LINE_DEFINITION_OUT_OF_LINE_DEFINITION_H_
+
+#pragma clang lifetime_elision
+
+template <typename T>
+class MyTemplate {
+ public:
+ static MyTemplate Create(T value);
+ const T& value() const;
+
+ private:
+ T value_;
+};
+
+template <typename T>
+MyTemplate<T> MyTemplate<T>::Create(T value) {
+ MyTemplate<T> result;
+ result.value_ = value;
+ return result;
+}
+
+template <typename T>
+const T& MyTemplate<T>::value() const {
+ return value_;
+}
+
+using MyTypeAlias = MyTemplate<int>;
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_OUT_OF_LINE_DEFINITION_OUT_OF_LINE_DEFINITION_H_
diff --git a/rs_bindings_from_cc/test/templates/out_of_line_definition/test.rs b/rs_bindings_from_cc/test/templates/out_of_line_definition/test.rs
new file mode 100644
index 0000000..94589b9
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/out_of_line_definition/test.rs
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_member_function_of_class_template_defined_out_of_line_in_h_file() {
+ let s = out_of_line_definition::MyTypeAlias::Create(123);
+ assert_eq!(123, *s.value());
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/two_template_parameters/BUILD b/rs_bindings_from_cc/test/templates/two_template_parameters/BUILD
new file mode 100644
index 0000000..12f84f7
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/two_template_parameters/BUILD
@@ -0,0 +1,16 @@
+"""End-to-end example of using type aliases to fully-instantiated templates."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "two_template_parameters",
+ hdrs = ["two_template_parameters.h"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [":two_template_parameters"],
+)
diff --git a/rs_bindings_from_cc/test/templates/two_template_parameters/test.rs b/rs_bindings_from_cc/test/templates/two_template_parameters/test.rs
new file mode 100644
index 0000000..cddd66d
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/two_template_parameters/test.rs
@@ -0,0 +1,14 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_two_template_parameters() {
+ let s =
+ two_template_parameters::AliasToTemplateWithTwoParams { value1: 123, value2: 456.789 };
+ assert_eq!(123, s.value1);
+ assert_eq!(456.789, s.value2);
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/two_template_parameters/two_template_parameters.h b/rs_bindings_from_cc/test/templates/two_template_parameters/two_template_parameters.h
new file mode 100644
index 0000000..02e47e0
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/two_template_parameters/two_template_parameters.h
@@ -0,0 +1,18 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
+
+#pragma clang lifetime_elision
+
+template <typename T1, typename T2>
+struct TemplateWithTwoParams {
+ T1 value1;
+ T2 value2;
+};
+
+using AliasToTemplateWithTwoParams = TemplateWithTwoParams<int, float>;
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
diff --git a/rs_bindings_from_cc/test/templates/type_alias/BUILD b/rs_bindings_from_cc/test/templates/type_alias/BUILD
new file mode 100644
index 0000000..7b98ab9
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/type_alias/BUILD
@@ -0,0 +1,25 @@
+"""End-to-end example of using type aliases to fully-instantiated templates."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "type_alias",
+ hdrs = ["type_alias.h"],
+)
+
+cc_library(
+ name = "type_alias_in_different_target",
+ hdrs = ["type_alias_in_different_target.h"],
+ deps = [":type_alias"],
+)
+
+rust_test(
+ name = "main",
+ srcs = ["test.rs"],
+ cc_deps = [
+ ":type_alias",
+ ":type_alias_in_different_target",
+ ],
+)
diff --git a/rs_bindings_from_cc/test/templates/type_alias/test.rs b/rs_bindings_from_cc/test/templates/type_alias/test.rs
new file mode 100644
index 0000000..8137e26
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/type_alias/test.rs
@@ -0,0 +1,28 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_alias_to_template_instantiation() {
+ let s = type_alias::MyTypeAlias::Create(123);
+ assert_eq!(123, *s.value());
+ }
+
+ #[test]
+ fn test_aliases_in_same_target_are_compatible() {
+ let s: type_alias::MyTypeAlias = type_alias::MyTypeAlias::Create(456);
+ let s2: type_alias::OtherTypeAliasInSameTarget = s;
+ assert_eq!(456, *s2.value());
+ }
+
+ #[test]
+ fn test_alias_in_different_target_than_template() {
+ let s = type_alias_in_different_target::TypeAliasInDifferentTarget::Create(789);
+ assert_eq!(789, *s.value());
+
+ // TODO: Test cross-target bridging:
+ // let s2: type_alias::MyTypeAlias = s.bridge_into();
+ }
+}
diff --git a/rs_bindings_from_cc/test/templates/type_alias/type_alias.h b/rs_bindings_from_cc/test/templates/type_alias/type_alias.h
new file mode 100644
index 0000000..e55d96d
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/type_alias/type_alias.h
@@ -0,0 +1,28 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
+
+#pragma clang lifetime_elision
+
+template <typename T>
+class MyTemplate {
+ public:
+ static MyTemplate Create(T value) {
+ MyTemplate result;
+ result.value_ = value;
+ return result;
+ }
+
+ const T& value() const { return value_; }
+
+ private:
+ T value_;
+};
+
+using MyTypeAlias = MyTemplate<int>;
+using OtherTypeAliasInSameTarget = MyTemplate<int>;
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_TYPE_ALIAS_H_
diff --git a/rs_bindings_from_cc/test/templates/type_alias/type_alias_in_different_target.h b/rs_bindings_from_cc/test/templates/type_alias/type_alias_in_different_target.h
new file mode 100644
index 0000000..e8df930
--- /dev/null
+++ b/rs_bindings_from_cc/test/templates/type_alias/type_alias_in_different_target.h
@@ -0,0 +1,12 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_
+
+#include "rs_bindings_from_cc/test/templates/type_alias/type_alias.h"
+
+using TypeAliasInDifferentTarget = MyTemplate<int>;
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_TEMPLATES_TYPE_ALIAS_IN_DIFFERENT_TARGET_H_