From 78d409d46bc61fa9254eaebfa3c764b418d50978 Mon Sep 17 00:00:00 2001 From: Googler Date: Mon, 28 Apr 2025 14:03:05 -0700 Subject: [PATCH] Implement Rust bindings for C++ `operator[]` overloads Instead of mapping directly to Rust's standard `std::ops::Index` and `std::ops::IndexMut` traits, this CL defines new Crubit-specific traits: `CcIndex` and `CcIndexMut`. This approach was chosen to address key interoperability challenges: 1. Return Types. C++ `operator[]` can return values, references, or proxy objects. Rust's `Index` traits are restrictive, requiring a reference (`&Self::Output`). The new traits use generic associated types (GATs) (`type Output<'a> where Self: 'a;`) to flexibly handle different return kinds and tie their lifetimes to the container. 2. `!Unpin` Containers. Many C++ types are not `Unpin` in Rust. `std::ops::IndexMut` takes `&mut self`, which is incompatible with `!Unpin` types that require `Pin<&mut Self>` for safe mutable access. `CcIndexMut::cc_index_mut` accepts `self: Pin<&mut Self>`. 3. `!Unpin` Items. The GAT-based `Output` type in `CcIndexMut` can return `Pin<&mut Item>` when necessary, preserving C++ safety invariants for non-movable items. Integration tests in `operators_index_test.rs` cover combinations of `Unpin` / `!Unpin` containers and items. PiperOrigin-RevId: 752439079 --- .../database/function_types.rs | 16 + .../generate_bindings/generate_function.rs | 298 +++++++++++++-- .../generate_function_test.rs | 53 +++ rs_bindings_from_cc/test/golden/BUILD | 1 + rs_bindings_from_cc/test/golden/operators.h | 65 ++++ .../test/golden/operators_rs_api.rs | 338 ++++++++++++++++++ .../test/golden/operators_rs_api_impl.cc | 59 +++ rs_bindings_from_cc/test/operators/BUILD | 22 ++ .../test/operators/operators_index.h | 81 +++++ .../test/operators/operators_index_test.rs | 69 ++++ support/operator.rs | 21 ++ 11 files changed, 999 insertions(+), 24 deletions(-) create mode 100644 rs_bindings_from_cc/test/operators/BUILD create mode 100644 rs_bindings_from_cc/test/operators/operators_index.h create mode 100644 rs_bindings_from_cc/test/operators/operators_index_test.rs diff --git a/rs_bindings_from_cc/generate_bindings/database/function_types.rs b/rs_bindings_from_cc/generate_bindings/database/function_types.rs index 8fb38ba7b..60090209d 100644 --- a/rs_bindings_from_cc/generate_bindings/database/function_types.rs +++ b/rs_bindings_from_cc/generate_bindings/database/function_types.rs @@ -55,6 +55,17 @@ pub enum TraitName { PartialOrd { param: Rc, }, + /// The trait for the const C++ operator[] overload. + CcIndex { + index_type: Rc, + output_type: Rc, + }, + /// The trait for the mutable C++ operator[] overload. + CcIndexMut { + index_type: Rc, + output_type: Rc, + }, + /// Any other trait, e.g. Eq. /// The operator::Delete trait. Delete, /// Any other trait, e.g. Eq. @@ -80,6 +91,8 @@ impl TraitName { TraitName::From { .. } => "From", TraitName::PartialEq { .. } => "PartialEq", TraitName::PartialOrd { .. } => "PartialOrd", + TraitName::CcIndex { .. } => "CcIndex", + TraitName::CcIndexMut { .. } => "CcIndexMut", TraitName::Delete => "::operator::Delete", TraitName::Other { name, .. } => name, } @@ -93,6 +106,9 @@ impl TraitName { Self::PartialEq { param, .. } | Self::PartialOrd { param } => { core::slice::from_ref(param) } + Self::CcIndex { index_type, .. } | Self::CcIndexMut { index_type, .. } => { + core::slice::from_ref(index_type) + } } } diff --git a/rs_bindings_from_cc/generate_bindings/generate_function.rs b/rs_bindings_from_cc/generate_bindings/generate_function.rs index 71cef7a24..50dac8222 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_function.rs +++ b/rs_bindings_from_cc/generate_bindings/generate_function.rs @@ -85,6 +85,23 @@ fn trait_name_to_token_stream_removing_trait_record( quote! {PartialOrd #formatted_params} } } + CcIndex { index_type, .. } | CcIndexMut { index_type, .. } => { + let trait_path = match trait_name { + CcIndex { .. } => quote! { ::operator::CcIndex }, + CcIndexMut { .. } => quote! { ::operator::CcIndexMut }, + _ => unreachable!(), + }; + if trait_record.is_some() && index_type.is_record(trait_record.unwrap()) { + quote! {#trait_path} + } else { + let formatted_params = format_generic_params_replacing_by_self( + db, + core::slice::from_ref(&**index_type), + trait_record, + ); + quote! {#trait_path #formatted_params} + } + } Other { name, params, .. } => { let name_as_token_stream = name.parse::().unwrap(); let formatted_params = @@ -316,6 +333,195 @@ fn api_func_shape_for_operator_eq( Ok((func_name, impl_kind)) } +/// TODO: b/242938276 - For now, just emit one impl per overload, either CcIndex or CcIndexMut. +/// Do not generate native core::ops::Index or core::ops::IndexMut impls yet. This means that Rust +/// callers must call .cc_index and .cc_index_mut rather than use bracket syntax. +/// +/// Other current limitations. +/// - Does not support C++ overloads that return items by value. +/// - Does not support C++ overloads that use multiple indices. +/// - Does not support C++ explicit object parameters, i.e. decltype(auto). +fn api_func_shape_for_operator_index( + db: &BindingsGenerator, + func: &Func, + param_types: &mut [RsTypeKind], + errors: &Errors, +) -> ErrorsOr<(Ident, ImplKind)> { + let CcTypeVariant::Pointer(pointee) = &func.return_type.variant else { + bail_to_errors!( + errors, + "operator[] should return a reference, found {:?}", + &func.return_type.variant + ) + }; + let return_val_is_const = pointee.pointee_type.is_const; + + let Some(instance_method_metadata) = &func.instance_method_metadata else { + panic!("cannot tell whether operator[] is const or not, shouldn't happen") + }; + let method_is_const = instance_method_metadata.is_const; + + let [container_type, index_type] = param_types else { + bail_to_errors!( + errors, + "Expected operator[] to have exactly two parameters. Found: {}.", + param_types.len(), + ); + }; + + let (container_record, _is_ref) = match container_type { + RsTypeKind::Reference { referent, .. } => { + let RsTypeKind::Record { record, .. } = &**referent else { + bail_to_errors!( + errors, + "Expected the 'this' parameter to refer to a record type, but found {:?}.", + pointee + ); + }; + (record.clone(), true) + } + _ => { + bail_to_errors!( + errors, + "Unexpected type for 'this' parameter of operator[]: {:?}.", + container_type + ); + } + }; + + let index_type_rc = Rc::new(index_type.clone()); + if return_val_is_const && method_is_const { + generate_cc_operator_index_nonmut_impls(db, func, container_record, index_type_rc, errors) + } else if !return_val_is_const && !method_is_const { + generate_cc_operator_index_mut_impls(db, func, container_record, index_type_rc, errors) + } else { + bail_to_errors!( + errors, + "operator[] must either:\n\ + (a) be a const method that returns a const reference, or,\n\ + (b) be a non-const method that returns a non-const reference.\n\ + Instead found a method: (which is const?)={}, and (whose return value is const?)={}", + method_is_const, + return_val_is_const + ) + } +} + +fn generate_cc_operator_index_nonmut_impls( + db: &BindingsGenerator, + func: &Func, + container_record: Rc, + index_type: Rc, + errors: &Errors, +) -> ErrorsOr<(Ident, ImplKind)> { + let func_name = make_rs_ident("cc_index"); + let output_pointee_cc_type = match &func.return_type.variant { + CcTypeVariant::Pointer(pointer_data) => { + if !matches!(pointer_data.kind, PointerTypeKind::LValueRef) { + errors.add(anyhow!( + "operator[] must return an lvalue reference (e.g. const T&), but found {:?}", + pointer_data.kind + )); + } + if !pointer_data.pointee_type.is_const { + errors.add(anyhow!("operator[] must return a const value")); + } + + (*pointer_data.pointee_type).clone() + } + + other_variant => { + bail_to_errors!( + errors, + "operator[] should return a reference (values are not yet supported), found {:?}", + other_variant + ) + } + }; + + let output_type: Rc = match db.rs_type_kind(output_pointee_cc_type) { + Ok(rs_kind) => Rc::new(rs_kind), + Err(err) => { + bail_to_errors!( + errors, + "In the return value of operator[], could not convert C++ pointee to a Rust equivalent: {}", + err + ) + } + }; + + let impl_kind = ImplKind::Trait { + record: container_record, + trait_name: TraitName::CcIndex { index_type, output_type }, + impl_for: ImplFor::T, + trait_generic_params: Rc::new([]), + format_first_param_as_self: true, + drop_return: false, + associated_return_type: Some(make_rs_ident("Output")), + force_const_reference_params: false, + always_public: false, + }; + Ok((func_name, impl_kind)) +} + +fn generate_cc_operator_index_mut_impls( + db: &BindingsGenerator, + func: &Func, + container_record: Rc, + index_type: Rc, + errors: &Errors, +) -> ErrorsOr<(Ident, ImplKind)> { + let func_name = make_rs_ident("cc_index_mut"); + + let output_pointee_cc_type = match &func.return_type.variant { + CcTypeVariant::Pointer(pointer_data) => { + if !matches!(pointer_data.kind, PointerTypeKind::LValueRef) { + errors.add(anyhow!( + "operator[] must return an lvalue reference (e.g. const T&), but found {:?}", + pointer_data.kind + )); + } + if pointer_data.pointee_type.is_const { + errors.add(anyhow!("(mutable) operator[] must return a non-const value")); + } + + (*pointer_data.pointee_type).clone() + } + + other_variant => { + bail_to_errors!( + errors, + "(mutable) operator[] should return a reference (values are not yet supported), found {:?}", + other_variant + ) + } + }; + + let output_type: Rc = match db.rs_type_kind(output_pointee_cc_type) { + Ok(rs_kind) => Rc::new(rs_kind), + Err(err) => { + bail_to_errors!( + errors, + "In the return value of operator[], could not convert C++ pointee to a Rust equivalent: {}", + err + ) + } + }; + + let impl_kind = ImplKind::Trait { + record: container_record, + trait_name: TraitName::CcIndexMut { index_type, output_type }, + impl_for: ImplFor::T, + trait_generic_params: Rc::new([]), + format_first_param_as_self: true, + drop_return: false, + associated_return_type: Some(make_rs_ident("Output")), + force_const_reference_params: false, + always_public: false, + }; + Ok((func_name, impl_kind)) +} + fn api_func_shape_for_operator_lt( db: &BindingsGenerator, func: &Func, @@ -567,6 +773,7 @@ fn api_func_shape_for_operator( "+" if param_types.len() == 1 => { api_func_shape_for_operator_unary_plus(db, ¶m_types[0], errors).ok() } + "[]" => api_func_shape_for_operator_index(db, func, param_types, errors).ok(), _ => { let Some(op_metadata) = OPERATOR_METADATA.by_cc_name_and_params.get(&(&op.name, param_types.len())) @@ -1638,7 +1845,15 @@ pub fn generate_function( } let function_return_type = match &impl_kind { - ImplKind::Trait { associated_return_type: Some(ident), .. } => quote! {Self::#ident}, + ImplKind::Trait { trait_name, associated_return_type: Some(ident), .. } => { + if matches!(trait_name, TraitName::CcIndex { .. } | TraitName::CcIndexMut { .. }) { + let gat_lifetime = + lifetimes.first().map(|l| quote! {#l}).unwrap_or_else(|| quote! {'_}); + quote! {Self::#ident<#gat_lifetime>} + } else { + quote! {Self::#ident} + } + } _ => quoted_return_type.clone(), }; let arrow = if !function_return_type.is_empty() { @@ -1730,13 +1945,21 @@ pub fn generate_function( }; let mut extra_body = if let Some(name) = associated_return_type { - let quoted_return_type = if quoted_return_type.is_empty() { - quote! {()} + if let TraitName::CcIndex { .. } | TraitName::CcIndexMut { .. } = trait_name { + let gat_lifetime = + lifetimes.first().map(|l| quote! {#l}).unwrap_or_else(|| quote! {'a}); + quote! { + type #name<#gat_lifetime> = #quoted_return_type; + } } else { - quoted_return_type - }; - quote! { - type #name = #quoted_return_type; + let quoted_return_type = if quoted_return_type.is_empty() { + quote! {()} + } else { + quoted_return_type + }; + quote! { + type #name = #quoted_return_type; + } } } else if let TraitName::PartialOrd { param } = &trait_name { let quoted_param_or_self = match impl_for { @@ -2141,7 +2364,13 @@ fn function_signature( }; } } - Some(TraitName::Other { .. } | TraitName::Delete) | None => {} + Some( + TraitName::CcIndex { .. } + | TraitName::CcIndexMut { .. } + | TraitName::Other { .. } + | TraitName::Delete, + ) + | None => {} } let return_type_fragment = if matches!( @@ -2188,25 +2417,46 @@ fn function_signature( match impl_kind { ImplKind::None { .. } => unreachable!(), ImplKind::Struct { .. } | ImplKind::Trait { impl_for: ImplFor::T, .. } => { - // In the ImplFor::T reference style (which is implied for ImplKind::Struct) the - // impl block is for `T`. The `self` parameter has a type determined by the - // first parameter (typically a reference of some kind) and can be passed to a - // thunk via the expression `self`. - if first_api_param.is_c_abi_compatible_by_value() { - let rs_snippet = first_api_param.format_as_self_param()?; - thunk_args[0] = if derived_record.is_some() { - quote! { oops::Upcast::<_>::upcast(self) } - } else { - quote! { self } + let is_cc_index_mut_method = matches!( + impl_kind, + ImplKind::Trait { trait_name: TraitName::CcIndexMut { .. }, .. } + ); + if is_cc_index_mut_method { + // Always use Pin<&mut Self> for CcIndexMut::cc_index_mut's receiver. + match first_api_param { + RsTypeKind::Reference { lifetime, referent, .. } => { + let lifetime_token = lifetime.format_for_reference(); + api_params[0] = + quote! { self: ::core::pin::Pin< & #lifetime_token mut Self > }; + if referent.is_unpin() { + thunk_args[0] = quote! { self.get_unchecked_mut() }; + } else { + thunk_args[0] = quote! { self }; + } + } + _ => unreachable!(), }; - api_params[0] = rs_snippet.tokens; - *features |= rs_snippet.features; } else { - api_params[0] = quote! { mut self }; - if derived_record.is_some() { - thunk_args[0] = quote! { oops::Upcast::<_>::upcast(&mut self) }; + // In the ImplFor::T reference style (which is implied for ImplKind::Struct) the + // impl block is for `T`. The `self` parameter has a type determined by the + // first parameter (typically a reference of some kind) and can be passed to a + // thunk via the expression `self`. + if first_api_param.is_c_abi_compatible_by_value() { + let rs_snippet = first_api_param.format_as_self_param()?; + thunk_args[0] = if derived_record.is_some() { + quote! { oops::Upcast::<_>::upcast(self) } + } else { + quote! { self } + }; + api_params[0] = rs_snippet.tokens; + *features |= rs_snippet.features; } else { - thunk_args[0] = quote! { &mut self }; + api_params[0] = quote! { mut self }; + if derived_record.is_some() { + thunk_args[0] = quote! { oops::Upcast::<_>::upcast(&mut self) }; + } else { + thunk_args[0] = quote! { &mut self }; + } } } } diff --git a/rs_bindings_from_cc/generate_bindings/generate_function_test.rs b/rs_bindings_from_cc/generate_bindings/generate_function_test.rs index e4756e389..d5905fca6 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_function_test.rs +++ b/rs_bindings_from_cc/generate_bindings/generate_function_test.rs @@ -488,6 +488,59 @@ fn test_impl_default_non_trivial_struct() -> Result<()> { assert_rs_not_matches!(rs_api, quote! {impl Default}); Ok(()) } +#[gtest] +fn test_impl_cc_index_for_member_function() -> Result<()> { + let ir = ir_from_cc( + r#"#pragma clang lifetime_elision + struct SomeStruct final { + inline const int& operator[](unsigned int index) const { + return items[index]; + } + int items[10]; + };"#, + )?; + let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api; + assert_rs_matches!( + rs_api, + quote! { + impl ::operator::CcIndex<::ffi_11::c_uint> for SomeStruct { + type Output<'a> = &'a ::ffi_11::c_int; + #[inline(always)] + fn cc_index<'a>(&'a self, index: ::ffi_11::c_uint) -> Self::Output<'a> { + unsafe { crate::detail::__rust_thunk___ZNK10SomeStructixEj(self, index) } + } + } + } + ); + Ok(()) +} + +#[gtest] +fn test_impl_cc_index_mut_for_member_function() -> Result<()> { + let ir = ir_from_cc( + r#"#pragma clang lifetime_elision + struct SomeStruct final { + inline int& operator[](unsigned int index) { + return items[index]; + } + int items[10]; + };"#, + )?; + let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api; + assert_rs_matches!( + rs_api, + quote! { + impl ::operator::CcIndexMut<::ffi_11::c_uint> for SomeStruct { + type Output<'a> = &'a mut ::ffi_11::c_int; + #[inline(always)] + fn cc_index_mut<'a>(self: ::core::pin::Pin<&'a mut Self>, index: ::ffi_11::c_uint) -> Self::Output<'a> { + unsafe { crate::detail::__rust_thunk___ZN10SomeStructixEj(self.get_unchecked_mut(), index) } + } + } + } + ); + Ok(()) +} #[gtest] fn test_impl_eq_for_member_function() -> Result<()> { diff --git a/rs_bindings_from_cc/test/golden/BUILD b/rs_bindings_from_cc/test/golden/BUILD index e1ab72dde..59d493090 100644 --- a/rs_bindings_from_cc/test/golden/BUILD +++ b/rs_bindings_from_cc/test/golden/BUILD @@ -31,6 +31,7 @@ TESTS = [name[:-2] for name in glob( # basenames of tests (e.g. "types") # other Bazel targets (e.g. "//foo/bar:baz") DEPS = { + "operators": ["//rs_bindings_from_cc/test/operators:operators_index"], "user_of_unsupported": ["unsupported"], "user_of_imported_type": ["trivial_type"], "user_of_base_class": ["inheritance"], diff --git a/rs_bindings_from_cc/test/golden/operators.h b/rs_bindings_from_cc/test/golden/operators.h index ecc8d4441..2a6c65865 100644 --- a/rs_bindings_from_cc/test/golden/operators.h +++ b/rs_bindings_from_cc/test/golden/operators.h @@ -133,4 +133,69 @@ struct ManyOperators final { ManyOperators& operator>>=(const ManyOperators& rhs); }; +struct ItemUnpin final { + int value = 0; +}; + +struct ItemNonUnpin final { + int value = 0; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ItemNonUnpin() {}; +}; + +class ContainerUnpinItemUnpin final { + public: + ContainerUnpinItemUnpin() = default; + const ItemUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemUnpin items_[10]; +}; + +class ContainerUnpinItemNonUnpin final { + public: + explicit ContainerUnpinItemNonUnpin(ItemNonUnpin* items) : items_(items) {} + + const ItemNonUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemNonUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemNonUnpin* items_; +}; + +class ContainerNonUnpinItemUnpin final { + public: + ContainerNonUnpinItemUnpin() = default; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ContainerNonUnpinItemUnpin() {}; + + const ItemUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemUnpin items_[10]; +}; + +class ContainerNonUnpinItemNonUnpin final { + public: + ContainerNonUnpinItemNonUnpin() = default; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ContainerNonUnpinItemNonUnpin() {}; + + const ItemNonUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemNonUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemNonUnpin items_[10]; +}; + #endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_OPERATORS_H_ diff --git a/rs_bindings_from_cc/test/golden/operators_rs_api.rs b/rs_bindings_from_cc/test/golden/operators_rs_api.rs index 22c7a8420..515cf54a1 100644 --- a/rs_bindings_from_cc/test/golden/operators_rs_api.rs +++ b/rs_bindings_from_cc/test/golden/operators_rs_api.rs @@ -1028,6 +1028,292 @@ impl<'a> ::core::ops::Not for &'a crate::ManyOperators { // Unsupported return type: references are not yet supported // Unsupported parameter #1 (rhs): references are not yet supported +#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] +#[repr(C)] +///CRUBIT_ANNOTATE: cpp_type=ItemUnpin +pub struct ItemUnpin { + pub value: ::ffi_11::c_int, +} +impl !Send for ItemUnpin {} +impl !Sync for ItemUnpin {} +unsafe impl ::cxx::ExternType for ItemUnpin { + type Id = ::cxx::type_id!("ItemUnpin"); + type Kind = ::cxx::kind::Trivial; +} + +impl Default for ItemUnpin { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN9ItemUnpinC1Ev(&raw mut tmp as *mut _); + tmp.assume_init() + } + } +} + +// error: constructor `ItemUnpin::ItemUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: constructor `ItemUnpin::ItemUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ItemUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ItemUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +#[::ctor::recursively_pinned(PinnedDrop)] +#[repr(C)] +///CRUBIT_ANNOTATE: cpp_type=ItemNonUnpin +pub struct ItemNonUnpin { + pub value: ::ffi_11::c_int, +} +impl !Send for ItemNonUnpin {} +impl !Sync for ItemNonUnpin {} +unsafe impl ::cxx::ExternType for ItemNonUnpin { + type Id = ::cxx::type_id!("ItemNonUnpin"); + type Kind = ::cxx::kind::Opaque; +} + +impl ::ctor::CtorNew<()> for ItemNonUnpin { + type CtorType = ::ctor::Ctor![Self]; + type Error = ::ctor::Infallible; + #[inline(always)] + fn ctor_new(args: ()) -> Self::CtorType { + let () = args; + unsafe { + ::ctor::FnCtor::new(move |dest: *mut Self| { + crate::detail::__rust_thunk___ZN12ItemNonUnpinC1Ev( + dest as *mut ::core::ffi::c_void, + ); + }) + } + } +} + +// error: constructor `ItemNonUnpin::ItemNonUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ItemNonUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +impl ::ctor::PinnedDrop for ItemNonUnpin { + #[inline(always)] + unsafe fn pinned_drop<'a>(self: ::core::pin::Pin<&'a mut Self>) { + crate::detail::__rust_thunk___ZN12ItemNonUnpinD1Ev(self) + } +} + +#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] +#[repr(C, align(4))] +///CRUBIT_ANNOTATE: cpp_type=ContainerUnpinItemUnpin +pub struct ContainerUnpinItemUnpin { + __non_field_data: [::core::mem::MaybeUninit; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) items_: [::core::mem::MaybeUninit; 40], +} +impl !Send for ContainerUnpinItemUnpin {} +impl !Sync for ContainerUnpinItemUnpin {} +unsafe impl ::cxx::ExternType for ContainerUnpinItemUnpin { + type Id = ::cxx::type_id!("ContainerUnpinItemUnpin"); + type Kind = ::cxx::kind::Trivial; +} + +// error: constructor `ContainerUnpinItemUnpin::ContainerUnpinItemUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: constructor `ContainerUnpinItemUnpin::ContainerUnpinItemUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerUnpinItemUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerUnpinItemUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +impl Default for ContainerUnpinItemUnpin { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN23ContainerUnpinItemUnpinC1Ev(&raw mut tmp as *mut _); + tmp.assume_init() + } + } +} + +// error: function `ContainerUnpinItemUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +// error: function `ContainerUnpinItemUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] +#[repr(C, align(8))] +///CRUBIT_ANNOTATE: cpp_type=ContainerUnpinItemNonUnpin +pub struct ContainerUnpinItemNonUnpin { + __non_field_data: [::core::mem::MaybeUninit; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) items_: [::core::mem::MaybeUninit; 8], +} +impl !Send for ContainerUnpinItemNonUnpin {} +impl !Sync for ContainerUnpinItemNonUnpin {} +unsafe impl ::cxx::ExternType for ContainerUnpinItemNonUnpin { + type Id = ::cxx::type_id!("ContainerUnpinItemNonUnpin"); + type Kind = ::cxx::kind::Trivial; +} + +// error: constructor `ContainerUnpinItemNonUnpin::ContainerUnpinItemNonUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: constructor `ContainerUnpinItemNonUnpin::ContainerUnpinItemNonUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerUnpinItemNonUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerUnpinItemNonUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +#[diagnostic::on_unimplemented( + message = "binding generation for function failed\nConstructors cannot be `unsafe`, but this constructor accepts:\n `items`: raw pointer" +)] +pub trait BindingFailedFor_ZN26ContainerUnpinItemNonUnpinC1EP12ItemNonUnpin {} +impl<'error> From<*mut crate::ItemNonUnpin> for ContainerUnpinItemNonUnpin +where + &'error (): BindingFailedFor_ZN26ContainerUnpinItemNonUnpinC1EP12ItemNonUnpin, +{ + #[inline(always)] + fn from(args: *mut crate::ItemNonUnpin) -> Self { + #![allow(unused_variables)] + unreachable!( + "This impl can never be instantiated. \ + If this message appears at runtime, please report a crubit.rs-bug." + ) + } +} + +// error: function `ContainerUnpinItemNonUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +// error: function `ContainerUnpinItemNonUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +#[::ctor::recursively_pinned(PinnedDrop)] +#[repr(C, align(4))] +///CRUBIT_ANNOTATE: cpp_type=ContainerNonUnpinItemUnpin +pub struct ContainerNonUnpinItemUnpin { + __non_field_data: [::core::cell::Cell<::core::mem::MaybeUninit>; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) items_: [::core::cell::Cell<::core::mem::MaybeUninit>; 40], +} +impl !Send for ContainerNonUnpinItemUnpin {} +impl !Sync for ContainerNonUnpinItemUnpin {} +unsafe impl ::cxx::ExternType for ContainerNonUnpinItemUnpin { + type Id = ::cxx::type_id!("ContainerNonUnpinItemUnpin"); + type Kind = ::cxx::kind::Opaque; +} + +// error: constructor `ContainerNonUnpinItemUnpin::ContainerNonUnpinItemUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerNonUnpinItemUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +impl ::ctor::CtorNew<()> for ContainerNonUnpinItemUnpin { + type CtorType = ::ctor::Ctor![Self]; + type Error = ::ctor::Infallible; + #[inline(always)] + fn ctor_new(args: ()) -> Self::CtorType { + let () = args; + unsafe { + ::ctor::FnCtor::new(move |dest: *mut Self| { + crate::detail::__rust_thunk___ZN26ContainerNonUnpinItemUnpinC1Ev( + dest as *mut ::core::ffi::c_void, + ); + }) + } + } +} + +impl ::ctor::PinnedDrop for ContainerNonUnpinItemUnpin { + #[inline(always)] + unsafe fn pinned_drop<'a>(self: ::core::pin::Pin<&'a mut Self>) { + crate::detail::__rust_thunk___ZN26ContainerNonUnpinItemUnpinD1Ev(self) + } +} + +// error: function `ContainerNonUnpinItemUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +// error: function `ContainerNonUnpinItemUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +#[::ctor::recursively_pinned(PinnedDrop)] +#[repr(C, align(4))] +///CRUBIT_ANNOTATE: cpp_type=ContainerNonUnpinItemNonUnpin +pub struct ContainerNonUnpinItemNonUnpin { + __non_field_data: [::core::cell::Cell<::core::mem::MaybeUninit>; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) items_: [::core::cell::Cell<::core::mem::MaybeUninit>; 40], +} +impl !Send for ContainerNonUnpinItemNonUnpin {} +impl !Sync for ContainerNonUnpinItemNonUnpin {} +unsafe impl ::cxx::ExternType for ContainerNonUnpinItemNonUnpin { + type Id = ::cxx::type_id!("ContainerNonUnpinItemNonUnpin"); + type Kind = ::cxx::kind::Opaque; +} + +// error: constructor `ContainerNonUnpinItemNonUnpin::ContainerNonUnpinItemNonUnpin` could not be bound +// Unsupported parameter #1 (__param_0): references are not yet supported + +// error: function `ContainerNonUnpinItemNonUnpin::operator=` could not be bound +// Unsupported return type: references are not yet supported +// Unsupported parameter #1 (__param_0): references are not yet supported + +impl ::ctor::CtorNew<()> for ContainerNonUnpinItemNonUnpin { + type CtorType = ::ctor::Ctor![Self]; + type Error = ::ctor::Infallible; + #[inline(always)] + fn ctor_new(args: ()) -> Self::CtorType { + let () = args; + unsafe { + ::ctor::FnCtor::new(move |dest: *mut Self| { + crate::detail::__rust_thunk___ZN29ContainerNonUnpinItemNonUnpinC1Ev( + dest as *mut ::core::ffi::c_void, + ); + }) + } + } +} + +impl ::ctor::PinnedDrop for ContainerNonUnpinItemNonUnpin { + #[inline(always)] + unsafe fn pinned_drop<'a>(self: ::core::pin::Pin<&'a mut Self>) { + crate::detail::__rust_thunk___ZN29ContainerNonUnpinItemNonUnpinD1Ev(self) + } +} + +// error: function `ContainerNonUnpinItemNonUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + +// error: function `ContainerNonUnpinItemNonUnpin::operator[]` could not be bound +// Unsupported return type: references are not yet supported + mod detail { #[allow(unused_imports)] use super::*; @@ -1123,6 +1409,26 @@ mod detail { __return: *mut ::core::ffi::c_void, __this: &'a crate::ManyOperators, ); + pub(crate) unsafe fn __rust_thunk___ZN9ItemUnpinC1Ev(__this: *mut ::core::ffi::c_void); + pub(crate) unsafe fn __rust_thunk___ZN12ItemNonUnpinC1Ev(__this: *mut ::core::ffi::c_void); + pub(crate) unsafe fn __rust_thunk___ZN12ItemNonUnpinD1Ev<'a>( + __this: ::core::pin::Pin<&'a mut crate::ItemNonUnpin>, + ); + pub(crate) unsafe fn __rust_thunk___ZN23ContainerUnpinItemUnpinC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZN26ContainerNonUnpinItemUnpinC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZN26ContainerNonUnpinItemUnpinD1Ev<'a>( + __this: ::core::pin::Pin<&'a mut crate::ContainerNonUnpinItemUnpin>, + ); + pub(crate) unsafe fn __rust_thunk___ZN29ContainerNonUnpinItemNonUnpinC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZN29ContainerNonUnpinItemNonUnpinD1Ev<'a>( + __this: ::core::pin::Pin<&'a mut crate::ContainerNonUnpinItemNonUnpin>, + ); } } @@ -1226,4 +1532,36 @@ const _: () = { assert!(::core::mem::align_of::() == 1); static_assertions::assert_impl_all!(crate::ManyOperators: Copy,Clone); static_assertions::assert_not_impl_any!(crate::ManyOperators: Drop); + + assert!(::core::mem::size_of::() == 4); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::ItemUnpin: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::ItemUnpin: Drop); + assert!(::core::mem::offset_of!(crate::ItemUnpin, value) == 0); + assert!(::core::mem::size_of::() == 4); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::ItemNonUnpin: Drop); + static_assertions::assert_not_impl_any!(crate::ItemNonUnpin: Copy); + assert!(::core::mem::offset_of!(crate::ItemNonUnpin, value) == 0); + static_assertions::assert_impl_all!(::ffi_11::c_int: Copy); + assert!(::core::mem::size_of::() == 40); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::ContainerUnpinItemUnpin: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::ContainerUnpinItemUnpin: Drop); + assert!(::core::mem::offset_of!(crate::ContainerUnpinItemUnpin, items_) == 0); + assert!(::core::mem::size_of::() == 8); + assert!(::core::mem::align_of::() == 8); + static_assertions::assert_impl_all!(crate::ContainerUnpinItemNonUnpin: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::ContainerUnpinItemNonUnpin: Drop); + assert!(::core::mem::offset_of!(crate::ContainerUnpinItemNonUnpin, items_) == 0); + assert!(::core::mem::size_of::() == 40); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::ContainerNonUnpinItemUnpin: Drop); + static_assertions::assert_not_impl_any!(crate::ContainerNonUnpinItemUnpin: Copy); + assert!(::core::mem::offset_of!(crate::ContainerNonUnpinItemUnpin, items_) == 0); + assert!(::core::mem::size_of::() == 40); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::ContainerNonUnpinItemNonUnpin: Drop); + static_assertions::assert_not_impl_any!(crate::ContainerNonUnpinItemNonUnpin: Copy); + assert!(::core::mem::offset_of!(crate::ContainerNonUnpinItemNonUnpin, items_) == 0); }; diff --git a/rs_bindings_from_cc/test/golden/operators_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/operators_rs_api_impl.cc index 57cd68935..9f58a32bc 100644 --- a/rs_bindings_from_cc/test/golden/operators_rs_api_impl.cc +++ b/rs_bindings_from_cc/test/golden/operators_rs_api_impl.cc @@ -203,4 +203,63 @@ extern "C" void __rust_thunk___ZNK13ManyOperatorsntEv( new (__return) auto(__this->operator!()); } +static_assert(CRUBIT_SIZEOF(struct ItemUnpin) == 4); +static_assert(alignof(struct ItemUnpin) == 4); +static_assert(CRUBIT_OFFSET_OF(value, struct ItemUnpin) == 0); + +extern "C" void __rust_thunk___ZN9ItemUnpinC1Ev(struct ItemUnpin* __this) { + crubit::construct_at(__this); +} + +static_assert(CRUBIT_SIZEOF(struct ItemNonUnpin) == 4); +static_assert(alignof(struct ItemNonUnpin) == 4); +static_assert(CRUBIT_OFFSET_OF(value, struct ItemNonUnpin) == 0); + +extern "C" void __rust_thunk___ZN12ItemNonUnpinC1Ev( + struct ItemNonUnpin* __this) { + crubit::construct_at(__this); +} + +extern "C" void __rust_thunk___ZN12ItemNonUnpinD1Ev( + struct ItemNonUnpin* __this) { + std::destroy_at(__this); +} + +static_assert(CRUBIT_SIZEOF(class ContainerUnpinItemUnpin) == 40); +static_assert(alignof(class ContainerUnpinItemUnpin) == 4); + +extern "C" void __rust_thunk___ZN23ContainerUnpinItemUnpinC1Ev( + class ContainerUnpinItemUnpin* __this) { + crubit::construct_at(__this); +} + +static_assert(CRUBIT_SIZEOF(class ContainerUnpinItemNonUnpin) == 8); +static_assert(alignof(class ContainerUnpinItemNonUnpin) == 8); + +static_assert(CRUBIT_SIZEOF(class ContainerNonUnpinItemUnpin) == 40); +static_assert(alignof(class ContainerNonUnpinItemUnpin) == 4); + +extern "C" void __rust_thunk___ZN26ContainerNonUnpinItemUnpinC1Ev( + class ContainerNonUnpinItemUnpin* __this) { + crubit::construct_at(__this); +} + +extern "C" void __rust_thunk___ZN26ContainerNonUnpinItemUnpinD1Ev( + class ContainerNonUnpinItemUnpin* __this) { + std::destroy_at(__this); +} + +static_assert(CRUBIT_SIZEOF(class ContainerNonUnpinItemNonUnpin) == 40); +static_assert(alignof(class ContainerNonUnpinItemNonUnpin) == 4); + +extern "C" void __rust_thunk___ZN29ContainerNonUnpinItemNonUnpinC1Ev( + class ContainerNonUnpinItemNonUnpin* __this) { + crubit::construct_at(__this); +} + +extern "C" void __rust_thunk___ZN29ContainerNonUnpinItemNonUnpinD1Ev( + class ContainerNonUnpinItemNonUnpin* __this) { + std::destroy_at(__this); +} + #pragma clang diagnostic pop diff --git a/rs_bindings_from_cc/test/operators/BUILD b/rs_bindings_from_cc/test/operators/BUILD new file mode 100644 index 000000000..90cbf0230 --- /dev/null +++ b/rs_bindings_from_cc/test/operators/BUILD @@ -0,0 +1,22 @@ +load("//common:crubit_wrapper_macros_oss.bzl", "crubit_rust_test") +load("//rs_bindings_from_cc/test:test_bindings.bzl", "crubit_test_cc_library") + +package(default_visibility = ["//:__subpackages__"]) + +crubit_test_cc_library( + name = "operators_index", + hdrs = ["operators_index.h"], + aspect_hints = ["//features:experimental"], +) + +crubit_rust_test( + name = "operators_index_test", + srcs = ["operators_index_test.rs"], + cc_deps = [ + ":operators_index", + ], + deps = [ + "//support:ctor", + "//support:operator", + ], +) diff --git a/rs_bindings_from_cc/test/operators/operators_index.h b/rs_bindings_from_cc/test/operators/operators_index.h new file mode 100644 index 000000000..e0d630838 --- /dev/null +++ b/rs_bindings_from_cc/test/operators/operators_index.h @@ -0,0 +1,81 @@ +// 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_OPERATORS_OPERATORS_INDEX_H_ +#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_OPERATORS_OPERATORS_INDEX_H_ + +#pragma clang lifetime_elision + +namespace crubit::test { + +struct ItemUnpin final { + int value = 0; +}; + +struct ItemNonUnpin final { + int value = 0; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ItemNonUnpin() {}; +}; + +class ContainerUnpinItemUnpin final { + public: + ContainerUnpinItemUnpin() = default; + const ItemUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemUnpin items_[10]; +}; + +class ContainerUnpinItemNonUnpin final { + public: + ContainerUnpinItemNonUnpin() : items_(items_storage_) {} + explicit ContainerUnpinItemNonUnpin(ItemNonUnpin* items) : items_(items) {} + + const ItemNonUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemNonUnpin& operator[](unsigned int index) { return items_[index]; } + + public: + ItemNonUnpin items_storage_[10]; + ItemNonUnpin* items_; +}; + +class ContainerNonUnpinItemUnpin final { + public: + ContainerNonUnpinItemUnpin() = default; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ContainerNonUnpinItemUnpin() {}; + + const ItemUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemUnpin items_[10]; +}; + +class ContainerNonUnpinItemNonUnpin final { + public: + ContainerNonUnpinItemNonUnpin() = default; + // NOLINTNEXTLINE(modernize-use-equals-default) + ~ContainerNonUnpinItemNonUnpin() {}; + + const ItemNonUnpin& operator[](unsigned int index) const { + return items_[index]; + } + ItemNonUnpin& operator[](unsigned int index) { return items_[index]; } + + private: + ItemNonUnpin items_[10]; +}; + +} // namespace crubit::test + +#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_OPERATORS_OPERATORS_INDEX_H_ diff --git a/rs_bindings_from_cc/test/operators/operators_index_test.rs b/rs_bindings_from_cc/test/operators/operators_index_test.rs new file mode 100644 index 000000000..eea6dd18f --- /dev/null +++ b/rs_bindings_from_cc/test/operators/operators_index_test.rs @@ -0,0 +1,69 @@ +// 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 + +use ctor::{emplace, CtorNew}; +use operator::{CcIndex, CcIndexMut}; +use operators_index::crubit::test::*; +use std::os::raw::c_uint; +use std::pin::Pin; + +#[gtest] +fn test_container_unpin_item_unpin_indexing() { + let mut c = ContainerUnpinItemUnpin::default(); + let index: c_uint = 0; + + let item_const = c.cc_index(index); + assert_eq!(item_const.value, 0); + + { + let item_mut = Pin::new(&mut c).cc_index_mut(index); + item_mut.value = 10; + } + assert_eq!(c.cc_index(index).value, 10); +} + +#[gtest] +fn test_container_unpin_item_non_unpin_indexing() { + let mut c = emplace!(ContainerUnpinItemNonUnpin::ctor_new(())); + let index: c_uint = 0; + + assert_eq!(c.cc_index(index).value, 0); + + unsafe { + let item_mut = c.as_mut().cc_index_mut(index); + let item_mut_ref: &mut ItemNonUnpin = Pin::get_unchecked_mut(item_mut); + item_mut_ref.value = 20; + } + + assert_eq!(c.cc_index(index).value, 20); +} + +#[gtest] +fn test_container_non_unpin_item_unpin_indexing() { + let mut c = emplace!(ContainerNonUnpinItemUnpin::ctor_new(())); + let index: c_uint = 0; + + assert_eq!(c.cc_index(index).value, 0); + + let item_mut = c.as_mut().cc_index_mut(index); + item_mut.value = 30; + + assert_eq!(c.cc_index(index).value, 30); +} + +#[gtest] +fn test_container_non_unpin_item_non_unpin_indexing() { + let mut c = emplace!(ContainerNonUnpinItemNonUnpin::ctor_new(())); + let index: c_uint = 0; + + assert_eq!(c.cc_index(index).value, 0); + + unsafe { + let item_mut = c.as_mut().cc_index_mut(index); + let item_mut_ref: &mut ItemNonUnpin = Pin::get_unchecked_mut(item_mut); + item_mut_ref.value = 40; + } + + assert_eq!(c.cc_index(index).value, 40); +} diff --git a/support/operator.rs b/support/operator.rs index 881eccdaf..380af09de 100644 --- a/support/operator.rs +++ b/support/operator.rs @@ -2,6 +2,27 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +use core::pin::Pin; + +/// Trait for the const version of the C++ `operator[]` overload. +/// +/// This trait uses Generic Associated Types (GATs) to handle lifetimes, +/// necessary because the output lifetime is tied to `self`. +pub trait CcIndex { + type Output<'a> + where + Self: 'a; + fn cc_index(&self, k: Key) -> Self::Output<'_>; +} + +/// Trait for the mutable version of the C++ `operator[]`. +pub trait CcIndexMut { + type Output<'a> + where + Self: 'a; + fn cc_index_mut(self: Pin<&mut Self>, k: Key) -> Self::Output<'_>; +} + /// A trait for types that must be deleted, if heap-allocated, using C++ `delete`. /// /// In particular, this is used for types with virtual destructors or overloaded