diff --git a/rs_bindings_from_cc/importers/existing_rust_type.cc b/rs_bindings_from_cc/importers/existing_rust_type.cc index cb4681355..8c99e42b2 100644 --- a/rs_bindings_from_cc/importers/existing_rust_type.cc +++ b/rs_bindings_from_cc/importers/existing_rust_type.cc @@ -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> GetRustTypeAttribute( - const clang::Decl* decl) { - CRUBIT_ASSIGN_OR_RETURN( - std::optional 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. @@ -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> GetTemplateArgs( - ImportContext& ictx, const clang::Decl* decl) { - const auto* specialization_decl = - llvm::dyn_cast_or_null(decl); - if (!specialization_decl) { - return std::vector(); +// 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(); + 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(record_decl->getDeclContext()) + ->getName() != "crubit") { + return nullptr; + } + return llvm::dyn_cast(record_decl); +} - std::vector 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 format_args; +}; + +// Gets the crubit_internal_rust_type attribute for `decl`. +absl::StatusOr> +GetCrubitInternalRustTypeAttr(ImportContext& ictx, const clang::Decl& decl) { + CRUBIT_ASSIGN_OR_RETURN( + std::optional 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())]]`, 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())]]`, 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())]]`, 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`, 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())]]`, 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())]]`, 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 pack = + spec_template_arg.getPackAsArray(); + + std::vector 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`.")); + } + clang::QualType type = arg.getAsType(); + + if (const auto* const_generic = + GetCrubitClassTemplateSpecializationDecl(type, "const_generic")) { + // The user wrote `crubit::const_generic`, 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). @@ -149,9 +261,9 @@ std::optional> GetTemplateParameterNames( std::optional ExistingRustTypeImporter::Import( clang::TypeDecl* type_decl) { - absl::StatusOr> rust_type = - GetRustTypeAttribute(type_decl); - if (!rust_type.ok()) { + absl::StatusOr> 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 @@ -159,12 +271,13 @@ std::optional ExistingRustTypeImporter::Import( // 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 is_same_abi = GetIsSameAbiAttribute(type_decl); if (!is_same_abi.ok()) { return ictx_.HardError(*type_decl, @@ -173,8 +286,6 @@ std::optional 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(); @@ -185,13 +296,6 @@ std::optional ExistingRustTypeImporter::Import( policy.SuppressTagKeyword = true; std::string cc_name = cc_qualtype.getAsString(policy); - absl::StatusOr> 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> type_parameter_names = GetTemplateParameterNames(ictx_, type_decl); @@ -205,10 +309,10 @@ std::optional 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()), .owning_target = ictx_.GetOwningTarget(type_decl), diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs index 03626eb4b..794a08a21 100644 --- a/rs_bindings_from_cc/ir_from_cc_test.rs +++ b/rs_bindings_from_cc/ir_from_cc_test.rs @@ -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 + struct crubit_internal_rust_type_args {}; + template + struct const_generic {}; + } + + struct [[clang::annotate("crubit_internal_rust_type", "MyType", + crubit::crubit_internal_rust_type_args, 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 + struct crubit_internal_rust_type_args {}; + } + + template + struct [[clang::annotate("crubit_internal_rust_type", "MyType", + crubit::crubit_internal_rust_type_args())]] MyType {}; + + MyType Instantiate(); + "#, + ) + .unwrap(); + + assert_ir_matches!( + ir, + quote! { + ExistingRustType { + rs_name: "MyType", + cc_name: "MyType", ... + template_args: [Type(CcType { + variant: Primitive(Int), ... + })], ... + } + } + ); +} + #[gtest] fn test_struct_with_unsafe_annotation() { let ir = ir_from_cc( diff --git a/rs_bindings_from_cc/test/golden/crubit_internal_rust_type.h b/rs_bindings_from_cc/test/golden/crubit_internal_rust_type.h index 8d87ad214..f895459a3 100644 --- a/rs_bindings_from_cc/test/golden/crubit_internal_rust_type.h +++ b/rs_bindings_from_cc/test/golden/crubit_internal_rust_type.h @@ -59,12 +59,4 @@ struct ExistingRustTypeFieldTypes final { TooFewArgs error; }; -template -struct [[clang::annotate("crubit_internal_rust_type", "RustPtr")]] -CppPtr final { - T* ptr; -}; - -CppPtr InstantiatedCppPtr(); - #endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_CRUBIT_INTERNAL_RS_TYPE_H_ diff --git a/rs_bindings_from_cc/test/golden/crubit_internal_rust_type_rs_api.rs b/rs_bindings_from_cc/test/golden/crubit_internal_rust_type_rs_api.rs index d3d9b80f0..d73e12da4 100644 --- a/rs_bindings_from_cc/test/golden/crubit_internal_rust_type_rs_api.rs +++ b/rs_bindings_from_cc/test/golden/crubit_internal_rust_type_rs_api.rs @@ -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': Failed to create bindings for template specialization type CppPtr: Unsupported template argument kind: Integral - -// Error while generating bindings for struct 'CppPtr': -// Unsupported template argument kind: Integral - mod detail { #[allow(unused_imports)] use super::*; diff --git a/support/annotations_internal.h b/support/annotations_internal.h index bb6374ebf..930275c33 100644 --- a/support/annotations_internal.h +++ b/support/annotations_internal.h @@ -18,11 +18,19 @@ #define CRUBIT_INTERNAL_ANNOTATE_TYPE(...) #endif +namespace crubit { +template +struct crubit_internal_rust_type_args {}; + +template +struct const_generic {}; +} // namespace crubit + // Unsafe: disables bindings, and reinterprets all uses of this type as `t`. // // This attribute completely disables automated bindings for the type which it // appertains to. All uses of that type are replaced with uses of `t`, which -// must be a rust type which exists and is guaranteed to be available by that +// must be a Rust type which exists and is guaranteed to be available by that // name. // // This can be applied to a struct, class, or enum. @@ -51,8 +59,10 @@ // // SAFETY: // If the type is not layout-compatible with `t`, the behavior is undefined. -#define CRUBIT_INTERNAL_RUST_TYPE(t) \ - CRUBIT_INTERNAL_ANNOTATE("crubit_internal_rust_type", t) +#define CRUBIT_INTERNAL_RUST_TYPE(t, ...) \ + CRUBIT_INTERNAL_ANNOTATE( \ + "crubit_internal_rust_type", t, \ + crubit::crubit_internal_rust_type_args<__VA_ARGS__>()) // Unsafe: forces a type to be treated as C abi compatible with its rust // equivalent. diff --git a/support/rs_std/slice_ref.h b/support/rs_std/slice_ref.h index 7e979cb49..1d4507050 100644 --- a/support/rs_std/slice_ref.h +++ b/support/rs_std/slice_ref.h @@ -25,8 +25,8 @@ namespace rs_std { // `rust_builtin_type_abi_assumptions.md` documents the ABI compatibility of // these types. template -class CRUBIT_INTERNAL_RUST_TYPE("&[]") - ABSL_ATTRIBUTE_TRIVIAL_ABI SliceRef final { +class CRUBIT_INTERNAL_RUST_TYPE("&[]", T) ABSL_ATTRIBUTE_TRIVIAL_ABI + SliceRef final { public: // Creates a default `SliceRef` - one that represents an empty slice. // To mirror slices in Rust, the data pointer is not null.