Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 165 additions & 61 deletions rs_bindings_from_cc/importers/existing_rust_type.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,15 @@
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Casting.h"

namespace crubit {
namespace {

// Gets the crubit_internal_rust_type attribute for `decl`.
// `decl` must not be null.
absl::StatusOr<std::optional<absl::string_view>> GetRustTypeAttribute(
const clang::Decl* decl) {
CRUBIT_ASSIGN_OR_RETURN(
std::optional<AnnotateArgs> args,
GetAnnotateAttrArgs(*decl, "crubit_internal_rust_type"));
if (!args.has_value()) return std::nullopt;
if (args->size() != 1 && args->size() != 2) {
// A second argument is allowed for forwards compatibility with b/483408552.
return absl::InvalidArgumentError(
"The `crubit_internal_rust_type` attribute requires a single "
"string literal "
"argument, the Rust type.");
}
return GetExprAsStringLiteral(*args->front(), decl->getASTContext());
}

// Gets the crubit_internal_same_abi attribute for `decl`.
// If the attribute is specified, returns true. If it's unspecified, returns
// false. If the attribute is malformed, returns a bad status.
Expand Down Expand Up @@ -89,38 +73,166 @@ std::string_view ArgKindToString(clang::TemplateArgument::ArgKind kind) {
}
}

// Gathers all instantiated template arguments for `decl` (if any) and converts
// them to `CcType`s.
//
// `decl` must not be null.
absl::StatusOr<std::vector<TemplateArg>> GetTemplateArgs(
ImportContext& ictx, const clang::Decl* decl) {
const auto* specialization_decl =
llvm::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(decl);
if (!specialization_decl) {
return std::vector<TemplateArg>();
// Returns the ClassTemplateSpecializationDecl of `crubit::{name}` if `type` is
// an instantiation of it, or nullptr otherwise.
const clang::ClassTemplateSpecializationDecl*
GetCrubitClassTemplateSpecializationDecl(clang::QualType type,
absl::string_view name) {
const auto* record_type = type->getAs<clang::RecordType>();
if (record_type == nullptr) {
return nullptr;
}
const clang::RecordDecl* record_decl = record_type->getDecl();
if (absl::string_view(record_decl->getName()) != name) {
return nullptr;
}
if (!record_decl->getDeclContext()->isNamespace() ||
llvm::cast<clang::NamespaceDecl>(record_decl->getDeclContext())
->getName() != "crubit") {
return nullptr;
}
return llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>(record_decl);
}

std::vector<TemplateArg> result;
result.reserve(specialization_decl->getTemplateArgs().size());
for (const auto& arg : specialization_decl->getTemplateArgs().asArray()) {
switch (arg.getKind()) {
case clang::TemplateArgument::Type:
// TODO(b/454627672): is specialization_decl the right decl to check for
// assumed_lifetimes?
result.push_back(TemplateArg(ictx.ConvertQualType(
arg.getAsType(), /*lifetimes=*/nullptr, /*nullable=*/true,
ictx.AreAssumedLifetimesEnabledForTarget(
ictx.GetOwningTarget(specialization_decl)))));
break;
default:
struct CrubitInternalRustType {
std::string format_string;
std::vector<TemplateArg> format_args;
};

// Gets the crubit_internal_rust_type attribute for `decl`.
absl::StatusOr<std::optional<CrubitInternalRustType>>
GetCrubitInternalRustTypeAttr(ImportContext& ictx, const clang::Decl& decl) {
CRUBIT_ASSIGN_OR_RETURN(
std::optional<AnnotateArgs> opt_args,
GetAnnotateAttrArgs(decl, "crubit_internal_rust_type"));
if (!opt_args.has_value()) return std::nullopt;
const AnnotateArgs& args = *opt_args;
if (args.empty()) {
return absl::InvalidArgumentError(
"crubit.rs-bug: The `CRUBIT_INTERNAL_RUST_TYPE` attribute is malformed."
"Crubit expects the annotation to expand to the form "
"`[[clang::annotate\"crubit_internal_rust_type\", \"RustType\", "
"crubit::crubit_internal_rust_type_args<T1, T2, ...>())]]`, but "
"instead only found `[[clang::annotate\"crubit_internal_rust_type\", "
"\"RustType\")]]`");
}
CRUBIT_ASSIGN_OR_RETURN(
absl::string_view format_string,
GetExprAsStringLiteral(*args.front(), decl.getASTContext()));

if (args.size() < 2) {
return CrubitInternalRustType{.format_string = std::string(format_string)};
}
if (args.size() > 2) {
return absl::InvalidArgumentError(
"crubit.rs-bug: The `CRUBIT_INTERNAL_RUST_TYPE` attribute is malformed."
"Crubit expects the annotation to expand to the form "
"`[[clang::annotate\"crubit_internal_rust_type\", \"RustType\", "
"crubit::crubit_internal_rust_type_args<T1, T2, ...>())]]`, but "
"instead found `[[clang::annotate\"crubit_internal_rust_type\", "
"\"RustType\", /* more than 1 trailing argument */)]]`");
}

const clang::ClassTemplateSpecializationDecl* spec =
GetCrubitClassTemplateSpecializationDecl(
args[1]->getType(), "crubit_internal_rust_type_args");
if (spec == nullptr) {
return absl::InvalidArgumentError(
"crubit.rs-bug: The `CRUBIT_INTERNAL_RUST_TYPE` attribute is malformed."
"Crubit expects the annotation to expand to the form "
"`[[clang::annotate\"crubit_internal_rust_type\", \"RustType\", "
"crubit::crubit_internal_rust_type_args<T1, T2, ...>())]]`, but "
"instead found `[[clang::annotate\"crubit_internal_rust_type\", "
"\"RustType\", /* something other than "
"`crubit::crubit_internal_rust_type_args<...>()` */)]]`");
}

// In `crubit::crubit_internal_rust_type_args<A, B, C>`, there's only one
// template argument: a Pack. To get A, B, and C, we need to call
// `getPackAsArray()` on that template argument.
if (spec->getTemplateArgs().size() != 1) {
return absl::InvalidArgumentError(absl::StrCat(
"crubit.rs-bug: The `CRUBIT_INTERNAL_RUST_TYPE` attribute is malformed."
"Crubit expects the annotation to expand to the form "
"`[[clang::annotate\"crubit_internal_rust_type\", \"RustType\", "
"crubit::crubit_internal_rust_type_args<T1, T2, ...>())]]`, but "
"instead found `[[clang::annotate\"crubit_internal_rust_type\", "
"\"RustType\", crubit::crubit_internal_rust_type_args<...>())]]`, "
"where the inner `<...>` has ",
spec->getTemplateArgs().size(),
" template arguments instead of a single pack argument."));
}

const clang::TemplateArgument& spec_template_arg =
spec->getTemplateArgs().get(0);
if (spec_template_arg.getKind() != clang::TemplateArgument::Pack) {
return absl::InvalidArgumentError(absl::StrCat(
"crubit.rs-bug: The `CRUBIT_INTERNAL_RUST_TYPE` attribute is malformed."
"Crubit expects the annotation to expand to the form "
"`[[clang::annotate\"crubit_internal_rust_type\", \"RustType\", "
"crubit::crubit_internal_rust_type_args<T1, T2, ...>())]]`, but "
"instead found `[[clang::annotate\"crubit_internal_rust_type\", "
"\"RustType\", crubit::crubit_internal_rust_type_args<...>())]]`, "
"where the inner `<...>` has a single argument of kind ",
ArgKindToString(spec_template_arg.getKind()),
" instead of a Pack argument."));
}

llvm::ArrayRef<clang::TemplateArgument> pack =
spec_template_arg.getPackAsArray();

std::vector<TemplateArg> format_args;
format_args.reserve(pack.size());
for (const auto& arg : pack) {
if (arg.getKind() != clang::TemplateArgument::Type) {
return absl::InvalidArgumentError(absl::StrCat(
"The template arguments of `CRUBIT_INTERNAL_RUST_TYPE` must be "
"types, found ",
ArgKindToString(arg.getKind()),
". For const generics, use `crubit::const_generic<N>`."));
}
clang::QualType type = arg.getAsType();

if (const auto* const_generic =
GetCrubitClassTemplateSpecializationDecl(type, "const_generic")) {
// The user wrote `crubit::const_generic<N>`, so we need to extract the
// value of N from this.

// Ensure that there is exactly one template argument (anything else
// should be impossible).
if (const_generic->getTemplateArgs().size() != 1) {
return absl::InvalidArgumentError(
absl::StrCat("Unsupported template argument kind: ",
ArgKindToString(arg.getKind())));
"crubit.rs-bug: `crubit::const_generic` must have exactly one "
"template argument.");
}

const clang::TemplateArgument& const_generic_arg =
const_generic->getTemplateArgs().get(0);

if (const_generic_arg.getKind() != clang::TemplateArgument::Integral) {
return absl::InvalidArgumentError(absl::StrCat(
"`crubit::const_generic` template argument must be an integral "
"constant, found: ",
ArgKindToString(const_generic_arg.getKind())));
}
format_args.push_back(
const_generic_arg.getIntegralType()->isBooleanType()
? TemplateArg(const_generic_arg.getAsIntegral().getBoolValue())
: TemplateArg(const_generic_arg.getAsIntegral().getExtValue()));
} else {
// TODO(b/454627672): is specialization_decl the right decl to check
// for assumed_lifetimes?
format_args.push_back(TemplateArg(
ictx.ConvertQualType(type, /*lifetimes=*/nullptr, /*nullable=*/true,
ictx.AreAssumedLifetimesEnabledForTarget(
ictx.GetOwningTarget(spec)))));
}
}

return result;
return CrubitInternalRustType{
.format_string = std::string(format_string),
.format_args = std::move(format_args),
};
}

// Gathers all template parameter names for `decl` (if any).
Expand Down Expand Up @@ -149,22 +261,23 @@ std::optional<std::vector<std::string>> GetTemplateParameterNames(

std::optional<IR::Item> ExistingRustTypeImporter::Import(
clang::TypeDecl* type_decl) {
absl::StatusOr<std::optional<absl::string_view>> rust_type =
GetRustTypeAttribute(type_decl);
if (!rust_type.ok()) {
absl::StatusOr<std::optional<CrubitInternalRustType>> status_or_opt_attr =
GetCrubitInternalRustTypeAttr(ictx_, *type_decl);
if (!status_or_opt_attr.ok()) {
return ictx_.HardError(
*type_decl,
// Failure here indicates that there was an incorrect attempt to use the
// `crubit_internal_rust_type` attribute. This attribute should never
// result in the generation of a Rust type, so we use the unnameable
// kind.
FormattedError::PrefixedStrCat(
"Invalid crubit_internal_rust_type attribute",
rust_type.status().message()));
"Invalid CRUBIT_INTERNAL_RUST_TYPE attribute",
std::move(status_or_opt_attr).status().message()));
}
if (!rust_type->has_value()) {
if (!status_or_opt_attr->has_value()) {
return std::nullopt;
}
const auto [format_string, format_args] = **std::move(status_or_opt_attr);
absl::StatusOr<bool> is_same_abi = GetIsSameAbiAttribute(type_decl);
if (!is_same_abi.ok()) {
return ictx_.HardError(*type_decl,
Expand All @@ -173,8 +286,6 @@ std::optional<IR::Item> ExistingRustTypeImporter::Import(
is_same_abi.status().message()));
}

auto rs_name = std::string(**rust_type);

clang::ASTContext& context = type_decl->getASTContext();
clang::QualType cc_qualtype = context.getTypeDeclType(type_decl);
const clang::Type* cpp_type = cc_qualtype.getTypePtr();
Expand All @@ -185,13 +296,6 @@ std::optional<IR::Item> ExistingRustTypeImporter::Import(
policy.SuppressTagKeyword = true;
std::string cc_name = cc_qualtype.getAsString(policy);

absl::StatusOr<std::vector<TemplateArg>> templace_args =
GetTemplateArgs(ictx_, type_decl);
if (!templace_args.ok()) {
return ictx_.ImportUnsupportedItem(
*type_decl, std::nullopt,
FormattedError::FromStatus(std::move(templace_args).status()));
}
std::optional<std::vector<std::string>> type_parameter_names =
GetTemplateParameterNames(ictx_, type_decl);

Expand All @@ -205,10 +309,10 @@ std::optional<IR::Item> ExistingRustTypeImporter::Import(
};
}
return ExistingRustType{
.rs_name = std::move(rs_name),
.rs_name = std::move(format_string),
.cc_name = std::move(cc_name),
.unique_name = ictx_.GetUniqueName(*type_decl),
.template_args = *std::move(templace_args),
.template_args = std::move(format_args),
.template_arg_names =
type_parameter_names.value_or(std::vector<std::string>()),
.owning_target = ictx_.GetOwningTarget(type_decl),
Expand Down
66 changes: 66 additions & 0 deletions rs_bindings_from_cc/ir_from_cc_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,72 @@ fn test_crubit_internal_rust_type_annotation_with_template_args() {
);
}

#[gtest]
fn test_crubit_internal_rust_type_annotation_with_const_generic() {
let ir = ir_from_cc(
r#"
namespace crubit {
template <typename...>
struct crubit_internal_rust_type_args {};
template <auto>
struct const_generic {};
}

struct [[clang::annotate("crubit_internal_rust_type", "MyType",
crubit::crubit_internal_rust_type_args<crubit::const_generic<true>, int>())]] MyType {};
"#,
)
.unwrap();

assert_ir_matches!(
ir,
quote! {
ExistingRustType {
rs_name: "MyType",
cc_name: "MyType", ...
template_args: [
Bool(true),
Type(CcType {
variant: Primitive(Int), ...
}),
], ...
}
}
);
}

#[gtest]
fn test_crubit_internal_rust_type_annotation_with_templates() {
let ir = ir_from_cc(
r#"
namespace crubit {
template <typename...>
struct crubit_internal_rust_type_args {};
}

template <typename T>
struct [[clang::annotate("crubit_internal_rust_type", "MyType",
crubit::crubit_internal_rust_type_args<T>())]] MyType {};

MyType<int> Instantiate();
"#,
)
.unwrap();

assert_ir_matches!(
ir,
quote! {
ExistingRustType {
rs_name: "MyType",
cc_name: "MyType<int>", ...
template_args: [Type(CcType {
variant: Primitive(Int), ...
})], ...
}
}
);
}

#[gtest]
fn test_struct_with_unsafe_annotation() {
let ir = ir_from_cc(
Expand Down
8 changes: 0 additions & 8 deletions rs_bindings_from_cc/test/golden/crubit_internal_rust_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,4 @@ struct ExistingRustTypeFieldTypes final {
TooFewArgs error;
};

template <typename T, bool B, int I>
struct [[clang::annotate("crubit_internal_rust_type", "RustPtr")]]
CppPtr final {
T* ptr;
};

CppPtr<int, true, 123> InstantiatedCppPtr();

#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_CRUBIT_INTERNAL_RS_TYPE_H_
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ impl Default for ExistingRustTypeFieldTypes {
// //rs_bindings_from_cc/test/golden:crubit_internal_rust_type_cc needs [//features:experimental] for ExistingRustTypeFieldTypes::operator= (return type: references are not supported)
// //rs_bindings_from_cc/test/golden:crubit_internal_rust_type_cc needs [//features:experimental] for ExistingRustTypeFieldTypes::operator= (the type of __param_0 (parameter #1): references are not supported)

// Error while generating bindings for class 'CppPtr':
// Class templates are not supported yet

// Error while generating bindings for function 'InstantiatedCppPtr':
// Return type is not supported: Unsupported type 'CppPtr<int, true, 123>': Failed to create bindings for template specialization type CppPtr<int, true, 123>: Unsupported template argument kind: Integral

// Error while generating bindings for struct 'CppPtr':
// Unsupported template argument kind: Integral

mod detail {
#[allow(unused_imports)]
use super::*;
Expand Down
Loading