blob: 68c340666c9fb75fea2370772651cc41e73ef634 [file]
// 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 arc_anyhow::Result;
use code_gen_utils::make_rs_ident;
use database::code_snippet::BindingsTokens;
use database::rs_snippet::{format_generic_params, Lifetime};
use generate_function_thunk::thunk_ident;
use googletest::prelude::{assert_that, contains_substring, gtest, OrFail as _};
use ir::{Func, Item, UnqualifiedIdentifier};
use ir_testing::{retrieve_func, with_lifetime_macros};
use multiplatform_ir_testing::{ir_from_cc, ir_from_cc_dependency};
use quote::quote;
use test_generators::generate_bindings_tokens_for_test;
use token_stream_matchers::{
assert_cc_matches, assert_cc_not_matches, assert_rs_matches, assert_rs_not_matches,
};
#[gtest]
fn test_simple_function() -> Result<()> {
let ir = ir_from_cc("int Add(int a, int b);")?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn Add(a: ::core::ffi::c_int, b: ::core::ffi::c_int) -> ::core::ffi::c_int {
unsafe { crate::detail::__rust_thunk___Z3Addii(a, b) }
}
}
);
assert_rs_matches!(
rs_api,
quote! {
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
#[link_name = "_Z3Addii"]
pub(crate) unsafe fn __rust_thunk___Z3Addii(a: ::core::ffi::c_int, b: ::core::ffi::c_int) -> ::core::ffi::c_int;
}
}
}
);
assert_cc_not_matches!(rs_api_impl, quote! {__rust_thunk___Z3Addii});
Ok(())
}
#[gtest]
fn test_inline_function() -> Result<()> {
let ir = ir_from_cc("inline int Add(int a, int b) { return a + b; }")?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn Add(a: ::core::ffi::c_int, b: ::core::ffi::c_int) -> ::core::ffi::c_int {
unsafe { crate::detail::__rust_thunk___Z3Addii(a, b) }
}
}
);
assert_rs_matches!(
rs_api,
quote! {
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___Z3Addii(a: ::core::ffi::c_int, b: ::core::ffi::c_int) -> ::core::ffi::c_int;
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" int __rust_thunk___Z3Addii(int a, int b) {
return Add(a, b);
}
}
);
Ok(())
}
#[gtest]
fn test_simple_function_with_types_from_other_target() -> Result<()> {
let ir = ir_from_cc_dependency(
"inline ReturnStruct DoSomething(ParamStruct param) { return ReturnStruct {}; }",
"struct ReturnStruct final {}; struct ParamStruct final {};",
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn DoSomething(mut param: dependency::ParamStruct) -> dependency::ReturnStruct {
unsafe {
let mut __return = ::core::mem::MaybeUninit::<dependency::ReturnStruct>::uninit();
crate::detail::__rust_thunk___Z11DoSomething11ParamStruct(
&raw mut __return as *mut ::core::ffi::c_void,
&mut param
);
__return.assume_init()
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___Z11DoSomething11ParamStruct(
__return: *mut ::core::ffi::c_void,
param: &mut dependency::ParamStruct
);
}
}}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z11DoSomething11ParamStruct(
struct ReturnStruct* __return, struct ParamStruct* param) {
new (__return) auto(DoSomething(std::move(*param)));
}
}
);
Ok(())
}
#[gtest]
fn test_ref_to_struct_in_thunk_impls() -> Result<()> {
let ir = ir_from_cc("struct S{}; inline void foo(S& s) {} ")?;
let rs_api_impl = generate_bindings_tokens_for_test(ir)?.rs_api_impl;
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z3fooR1S(struct S* s) {
foo(*s);
}
}
);
Ok(())
}
#[gtest]
fn test_const_ref_to_struct_in_thunk_impls() -> Result<()> {
let ir = ir_from_cc("struct S{}; inline void foo(const S& s) {} ")?;
let rs_api_impl = generate_bindings_tokens_for_test(ir)?.rs_api_impl;
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z3fooRK1S(struct S const * s) {
foo(*s);
}
}
);
Ok(())
}
#[gtest]
fn test_unsigned_int_in_thunk_impls() -> Result<()> {
let ir = ir_from_cc("inline void foo(unsigned int i) {} ")?;
let rs_api_impl = generate_bindings_tokens_for_test(ir)?.rs_api_impl;
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z3fooj(unsigned int i) {
foo(i);
}
}
);
Ok(())
}
#[gtest]
fn test_record_static_methods_qualify_call_in_thunk() -> Result<()> {
let ir = ir_from_cc(
r#"
struct SomeStruct {
static inline int some_func() { return 42; }
}; "#,
)?;
assert_cc_matches!(
generate_bindings_tokens_for_test(ir)?.rs_api_impl,
quote! {
extern "C" int __rust_thunk___ZN10SomeStruct9some_funcEv() {
return SomeStruct::some_func();
}
}
);
Ok(())
}
#[gtest]
fn test_record_instance_methods_deref_this_in_thunk() -> Result<()> {
let ir = ir_from_cc(
r#"
struct SomeStruct {
inline int some_func(int arg) const { return 42 + arg; }
}; "#,
)?;
assert_cc_matches!(
generate_bindings_tokens_for_test(ir)?.rs_api_impl,
quote! {
extern "C" int __rust_thunk___ZNK10SomeStruct9some_funcEi(
struct SomeStruct const * __this, int arg) {
return __this->some_func(arg);
}
}
);
Ok(())
}
#[gtest]
fn test_ptr_func() -> Result<()> {
let ir = ir_from_cc(r#" inline int* Deref(int*const* p) { return *p; } "#)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub unsafe fn Deref(p: *const *mut ::core::ffi::c_int) -> *mut ::core::ffi::c_int {
crate::detail::__rust_thunk___Z5DerefPKPi(p)
}
}
);
assert_rs_matches!(
rs_api,
quote! {
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___Z5DerefPKPi(p: *const *mut ::core::ffi::c_int) -> *mut ::core::ffi::c_int;
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" int* __rust_thunk___Z5DerefPKPi(int* const * p) {
return Deref(p);
}
}
);
Ok(())
}
#[gtest]
fn test_const_char_ptr_func() -> Result<()> {
// This is a regression test: We used to include the "const" in the name
// of the CcType, which caused a panic in the code generator
// ('"const char" is not a valid Ident').
// It's therefore important that f() is inline so that we need to
// generate a thunk for it (where we then process the CcType).
let ir = ir_from_cc(r#" inline void f(const signed char *str) {} "#)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub unsafe fn f(str: *const ::core::ffi::c_schar) {
crate::detail::__rust_thunk___Z1fPKa(str)
}
}
);
assert_rs_matches!(
rs_api,
quote! {
extern "C" {
pub(crate) unsafe fn __rust_thunk___Z1fPKa(str: *const ::core::ffi::c_schar);
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z1fPKa(signed char const * str){ f(str); }
}
);
Ok(())
}
#[gtest]
fn test_func_ptr_thunk() -> Result<()> {
// Using an `inline` keyword forces generation of a C++ thunk in
// `rs_api_impl` (i.e. exercises `format_cpp_type` and similar code).
let ir = ir_from_cc(
r#"
int multiply(int x, int y);
inline int (*inline_get_pointer_to_function())(int, int) {
return multiply;
}
"#,
)?;
let rs_api_impl = generate_bindings_tokens_for_test(ir)?.rs_api_impl;
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" crubit::type_identity_t<int(int , int)>*
__rust_thunk___Z30inline_get_pointer_to_functionv() {
return inline_get_pointer_to_function();
}
}
);
Ok(())
}
#[gtest]
fn test_doc_comment_func() -> Result<()> {
let ir = ir_from_cc(
"
// Doc Comment
// with two lines
int func();",
)?;
assert_rs_matches!(
generate_bindings_tokens_for_test(ir)?.rs_api,
// leading space is intentional so there is a space between /// and the text of the
// comment
quote! {
#[doc = " Doc Comment\n with two lines\n \n Generated from: ir_from_cc_virtual_header.h;l=6"]
#[inline(always)]
pub fn func
}
);
Ok(())
}
/// Trivial types (at least those that are mapped to Copy rust types) do not
/// get a Drop impl.
#[gtest]
fn test_impl_drop_trivial() -> Result<()> {
let ir = ir_from_cc(
r#"struct Trivial final {
~Trivial() = default;
int x;
};"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_not_matches!(rs_api, quote! {impl Drop});
assert_rs_not_matches!(rs_api, quote! {impl ::ctor::PinnedDrop});
assert_rs_matches!(rs_api, quote! {pub x: ::core::ffi::c_int});
assert_cc_not_matches!(rs_api_impl, quote! { std::destroy_at });
Ok(())
}
#[gtest]
fn test_impl_default_explicitly_defaulted_constructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct DefaultedConstructor final {
DefaultedConstructor() = default;
};"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl Default for DefaultedConstructor {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN20DefaultedConstructorC1Ev(&raw mut tmp as *mut _);
tmp.assume_init()
}
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___ZN20DefaultedConstructorC1Ev(
struct DefaultedConstructor* __this) {
crubit::construct_at(__this);
}
}
);
Ok(())
}
#[gtest]
fn test_impl_clone_that_propagates_lifetime() -> Result<()> {
// This test covers the case where a single lifetime applies to 1)
// the `__this` parameter and 2) other constructor parameters. For
// example, maybe the newly constructed object needs to have the
// same lifetime as the constructor's parameter. (This might require
// annotating the whole C++ struct with a lifetime, so maybe the
// example below is not fully realistic/accurate...).
let ir = ir_from_cc(&with_lifetime_macros(
r#"#pragma clang lifetime_elision
struct Foo final {
Foo(const int& $a i) $a;
};"#,
))?;
let ctor: &Func = ir
.items()
.filter_map(|item| match item {
Item::Func(func) => Some(&**func),
_ => None,
})
.find(|f| {
matches!(&f.rs_name, UnqualifiedIdentifier::Constructor)
&& f.params
.get(1)
.map(|p| p.identifier.identifier.as_ref() == "i")
.unwrap_or_default()
})
.unwrap();
{
// Double-check that the test scenario set up above uses the same lifetime
// for both of the constructor's parameters: `__this` and `i`.
assert_eq!(ctor.params.len(), 2);
let this_lifetime = ctor.params[0].type_.variant.as_pointer().unwrap().lifetime.unwrap();
let i_lifetime = ctor.params[1].type_.variant.as_pointer().unwrap().lifetime.unwrap();
assert_eq!(i_lifetime, this_lifetime);
}
// Before cl/423346348 the generated Rust code would incorrectly look
// like this (note the mismatched 'a and 'b lifetimes):
// fn from<'b>(i: &'a i32) -> Self
// After this CL, this scenario will result in an explicit error.
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl From});
assert_rs_matches!(rs_api, {
let txt = "Generated from: ir_from_cc_virtual_header.h;l=34\n\
Error while generating bindings for constructor 'Foo::Foo':\n\
The lifetime of `__this` is \
unexpectedly also used by another parameter: &'a::core::ffi::c_int";
quote! { __COMMENT__ #txt }
});
Ok(())
}
#[gtest]
fn test_impl_default_non_trivial_struct() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct NonTrivialStructWithConstructors final {
NonTrivialStructWithConstructors();
~NonTrivialStructWithConstructors(); // Non-trivial
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl Default});
Ok(())
}
#[gtest]
fn test_impl_from_for_1_arg_constructor() -> Result<()> {
for explicit_qualifier in ["", "explicit"] {
let ir = ir_from_cc(&format!(
r#"#pragma clang lifetime_elision
struct SomeStruct final {{
{explicit_qualifier} SomeStruct(int i); // implicit - no `explicit` keyword
}};"#,
))?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl From<::core::ffi::c_int> for SomeStruct {
#[inline(always)]
fn from(i: ::core::ffi::c_int) -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN10SomeStructC1Ei(&raw mut tmp as *mut _, i);
tmp.assume_init()
}
}
}
}
);
}
Ok(())
}
#[gtest]
fn test_impl_from_for_implicit_conversion_from_reference() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeOtherStruct final { int i; };
struct StructUnderTest final {
StructUnderTest(const SomeOtherStruct& other); // implicit - no `explicit` keyword
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
// This is a regression test for b/223800038: We want to ensure that the
// code says `impl<'b>` (instead of incorrectly declaring that lifetime
// in `fn from<'b>`).
assert_rs_matches!(
rs_api,
quote! {
impl<'b> From<&'b crate::SomeOtherStruct> for StructUnderTest {
#[inline(always)]
fn from(other: &'b crate::SomeOtherStruct) -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN15StructUnderTestC1ERK15SomeOtherStruct(
&raw mut tmp as *mut _, other);
tmp.assume_init()
}
}
}
},
);
Ok(())
}
#[gtest]
fn test_impl_eq_for_member_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator==(const SomeStruct& other) const {
return i == other.i;
}
int i;
};"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl PartialEq for SomeStruct {
#[inline(always)]
fn eq<'a, 'b>(&'a self, other: &'b Self) -> bool {
unsafe { crate::detail::__rust_thunk___ZNK10SomeStructeqERKS_(self, other) }
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" bool __rust_thunk___ZNK10SomeStructeqERKS_(
struct SomeStruct const * __this, struct SomeStruct const * other) {
return __this->operator==(*other);
}
}
);
Ok(())
}
#[gtest]
fn test_impl_eq_for_free_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
namespace ns {
struct SomeStruct final { int i; };
}
bool operator==(const ns::SomeStruct& lhs, const ns::SomeStruct& rhs) {
return lhs.i == rhs.i;
}
"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl PartialEq for crate::ns::SomeStruct {
#[inline(always)]
fn eq<'a, 'b>(&'a self, rhs: &'b Self) -> bool {
unsafe { crate::detail::__rust_thunk___ZeqRKN2ns10SomeStructES2_(self, rhs) }
}
}
}
);
Ok(())
}
#[gtest]
fn test_impl_eq_for_free_function_different_types() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final { int i; };
struct SomeOtherStruct final { int i; };
bool operator==(const SomeStruct& lhs, const SomeOtherStruct& rhs) {
return lhs.i == rhs.i;
}"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl PartialEq<crate::SomeOtherStruct> for crate::SomeStruct {
#[inline(always)]
fn eq<'a, 'b>(&'a self, rhs: &'b crate::SomeOtherStruct) -> bool {
unsafe { crate::detail::__rust_thunk___ZeqRK10SomeStructRK15SomeOtherStruct(self, rhs) }
}
}
}
);
Ok(())
}
#[gtest]
fn test_impl_eq_for_free_function_by_value() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final { int i; };
bool operator==(SomeStruct lhs, SomeStruct rhs) {
return lhs.i == rhs.i;
}"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl PartialEq for crate::SomeStruct {
#[inline(always)]
fn eq(&self, rhs: &Self) -> bool {
unsafe {
crate::detail::__rust_thunk___Zeq10SomeStructS_(&mut self.clone(), &mut rhs.clone())
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_impl_lt_for_member_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator==(const SomeStruct& other) const {
return i == other.i;
}
inline bool operator<(const SomeStruct& other) const {
return i < other.i;
}
int i;
};"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl PartialOrd for SomeStruct {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self == other {
return Some(core::cmp::Ordering::Equal);
}
if self < other {
return Some(core::cmp::Ordering::Less);
}
if other < self {
return Some(core::cmp::Ordering::Greater);
}
None
}
#[inline(always)]
fn lt<'a, 'b>(&'a self, other: &'b Self) -> bool {
unsafe { crate::detail::__rust_thunk___ZNK10SomeStructltERKS_(self, other) }
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" bool __rust_thunk___ZNK10SomeStructltERKS_(
struct SomeStruct const * __this, struct SomeStruct const * other) {
return __this->operator<(*other);
}
}
);
Ok(())
}
#[gtest]
fn test_impl_lt_for_free_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator==(const SomeStruct& other) const {
return i == other.i;
}
int i;
};
bool operator<(const SomeStruct& lhs, const SomeStruct& rhs) {
return lhs.i < rhs.i;
}"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl PartialOrd for crate::SomeStruct {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self == other {
return Some(core::cmp::Ordering::Equal);
}
if self < other {
return Some(core::cmp::Ordering::Less);
}
if other < self {
return Some(core::cmp::Ordering::Greater);
}
None
}
#[inline(always)]
fn lt<'a, 'b>(&'a self, rhs: &'b Self) -> bool {
unsafe { crate::detail::__rust_thunk___ZltRK10SomeStructS1_(self, rhs) }
}
}
}
);
Ok(())
}
#[gtest]
fn test_impl_lt_for_free_function_by_value() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final { int i; };
bool operator==(SomeStruct lhs, SomeStruct rhs) {
return lhs.i == rhs.i;
}
bool operator<(SomeStruct lhs, SomeStruct rhs) {
return lhs.i < rhs.i;
}"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl PartialOrd for crate::SomeStruct {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self == other {
return Some(core::cmp::Ordering::Equal);
}
if self < other {
return Some(core::cmp::Ordering::Less);
}
if other < self {
return Some(core::cmp::Ordering::Greater);
}
None
}
#[inline(always)]
fn lt(& self, rhs: &Self) -> bool {
unsafe { crate::detail::__rust_thunk___Zlt10SomeStructS_(
&mut self.clone(), &mut rhs.clone()) }
}
}
}
);
Ok(())
}
#[gtest]
fn test_assign() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct {
~SomeStruct();
SomeStruct& operator=(const SomeStruct& other);
};"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl<'b> ::ctor::Assign<&'b Self> for SomeStruct {
#[inline(always)]
fn assign<'a>(self: ::core::pin::Pin<&'a mut Self>, other: &'b Self) {
unsafe {
crate::detail::__rust_thunk___ZN10SomeStructaSERKS_(self, other);
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_assign_nonreference_other() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct {
~SomeStruct();
SomeStruct& operator=(int other);
};"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl<'b> ::ctor::Assign<&'b Self> for SomeStruct {
#[inline(always)]
fn assign<'a>(self: ::core::pin::Pin<&'a mut Self>, __param_0: &'b Self) {
unsafe {
crate::detail::__rust_thunk___ZN10SomeStructaSERKS_(self, __param_0);
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_assign_nonreference_return() -> Result<()> {
let ir = ir_from_cc(
r#"
#pragma clang lifetime_elision
struct SomeStruct {
~SomeStruct();
int operator=(const SomeStruct& other);
};"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl<'b> ::ctor::Assign<&'b Self> for SomeStruct {
#[inline(always)]
fn assign<'a>(self: ::core::pin::Pin<&'a mut Self>, other: &'b Self) {
unsafe {
crate::detail::__rust_thunk___ZN10SomeStructaSERKS_(self, other);
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_impl_eq_non_const_member_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
bool operator==(const SomeStruct& other) /* no `const` here */;
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl PartialEq});
Ok(())
}
#[gtest]
fn test_impl_lt_different_operands() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct1 final {
int i;
};
struct SomeStruct2 final {
inline bool operator==(const SomeStruct1& other) const {
return i == other.i;
}
inline bool operator<(const SomeStruct1& other) const {
return i < other.i;
};
int i;
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl PartialOrd});
Ok(())
}
#[gtest]
fn test_impl_lt_non_const_member_function() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator==(const SomeStruct& other) const {
return i == other.i;
}
int i;
bool operator<(const SomeStruct& other) /* no `const` here */;
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl PartialOrd});
Ok(())
}
#[gtest]
fn test_impl_lt_rhs_by_value() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator==(const SomeStruct& other) const {
return i == other.i;
}
int i;
bool operator<(SomeStruct other) const;
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl PartialOrd});
Ok(())
}
#[gtest]
fn test_impl_lt_missing_eq_impl() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
inline bool operator<(const SomeStruct& other) const {
return i < other.i;
}
int i;
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(rs_api, quote! {impl PartialOrd});
Ok(())
}
#[gtest]
fn test_thunk_ident_function() -> Result<()> {
let ir = ir_from_cc("inline int foo() { return 42; }")?;
let func = retrieve_func(&ir, "foo");
assert_eq!(thunk_ident(func), make_rs_ident("__rust_thunk___Z3foov"));
Ok(())
}
#[gtest]
fn test_thunk_ident_special_names() {
let ir = ir_from_cc("struct Class {};").unwrap();
let destructor = ir.get_functions_by_name(&UnqualifiedIdentifier::Destructor).next().unwrap();
assert_eq!(thunk_ident(destructor), make_rs_ident("__rust_thunk___ZN5ClassD1Ev"));
let default_constructor = ir
.get_functions_by_name(&UnqualifiedIdentifier::Constructor)
.find(|f| f.params.len() == 1)
.unwrap();
assert_eq!(thunk_ident(default_constructor), make_rs_ident("__rust_thunk___ZN5ClassC1Ev"));
}
#[gtest]
fn test_elided_lifetimes() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct S final {
int& f(int& i);
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
pub fn f<'a, 'b>(&'a mut self, i: &'b mut ::core::ffi::c_int) -> &'a mut ::core::ffi::c_int { ... }
}
);
assert_rs_matches!(
rs_api,
quote! {
pub(crate) unsafe fn __rust_thunk___ZN1S1fERi<'a, 'b>(__this: &'a mut crate::S, i: &'b mut ::core::ffi::c_int)
-> &'a mut ::core::ffi::c_int;
}
);
Ok(())
}
#[gtest]
fn test_annotated_lifetimes() -> Result<()> {
let ir = ir_from_cc(&with_lifetime_macros(
r#"
int& $a f(int& $a i1, int& $a i2);
"#,
))?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
pub fn f<'a>(i1: &'a mut ::core::ffi::c_int, i2: &'a mut ::core::ffi::c_int) -> &'a mut ::core::ffi::c_int { ... }
}
);
assert_rs_matches!(
rs_api,
quote! {
pub(crate) unsafe fn __rust_thunk___Z1fRiS_<'a>(i1: &'a mut ::core::ffi::c_int, i2: &'a mut ::core::ffi::c_int)
-> &'a mut ::core::ffi::c_int;
}
);
Ok(())
}
#[gtest]
fn test_format_generic_params() -> Result<()> {
assert!(
format_generic_params(/* lifetimes= */ &[], std::iter::empty::<syn::Ident>()).is_empty(),
);
let idents = ["T1", "T2"].iter().map(|s| make_rs_ident(s));
assert_rs_matches!(format_generic_params(/* lifetimes= */ &[], idents), quote! { < T1, T2 > });
let lifetimes = ["a", "b", "_"].iter().map(|s| Lifetime::new(s)).collect::<Vec<_>>();
assert_rs_matches!(
format_generic_params(&lifetimes, std::iter::empty::<syn::Ident>()),
quote! { < 'a, 'b > }
);
Ok(())
}
#[gtest]
fn test_overloaded_functions() -> Result<()> {
// TODO(b/213280424): We don't support creating bindings for overloaded
// functions yet, except in the case of overloaded constructors with a
// single parameter.
let ir = ir_from_cc(
r#" #pragma clang lifetime_elision
void f() {}
void f(int i) {}
struct S1 final {
void f() {}
void f(int i) {}
};
struct S2 final {
void f();
};
struct S3 final {
S3(int i);
S3(double d);
};
namespace foo { void not_overloaded(); }
namespace bar { void not_overloaded(); }
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
// Cannot overload free functions.
assert_cc_matches!(rs_api, {
let txt = "Generated from: ir_from_cc_virtual_header.h;l=4\n\
Error while generating bindings for function 'f':\n\
Cannot generate bindings for overloaded function";
quote! { __COMMENT__ #txt }
});
assert_rs_not_matches!(rs_api, quote! {pub fn f()});
assert_rs_not_matches!(rs_api, quote! {pub fn f(i: ::core::ffi::c_int)});
assert_cc_matches!(rs_api, {
let txt = "Generated from: ir_from_cc_virtual_header.h;l=7\n\
Error while generating bindings for function 'S1::f':\n\
Cannot generate bindings for overloaded function";
quote! { __COMMENT__ #txt }
});
assert_rs_not_matches!(rs_api, quote! {pub fn f(... S1 ...)});
// And thunks aren't generated for either.
assert_cc_not_matches!(rs_api_impl, quote! {f ( ... )});
// But we can import member functions that have the same name as a free
// function.
assert_rs_matches!(rs_api, quote! {pub fn f<'a>(&'a mut self)});
// We can also import overloaded single-parameter constructors.
assert_rs_matches!(rs_api, quote! {impl From<::core::ffi::c_int> for S3});
assert_rs_matches!(rs_api, quote! {impl From<f64> for S3});
// And we can import functions that have the same name + signature, but that are
// in 2 different namespaces.
assert_rs_matches!(rs_api, quote! { pub fn not_overloaded() });
Ok(())
}
/// !Unpin references should not be pinned.
#[gtest]
fn test_nonunpin_ref_param() -> Result<()> {
let rs_api = generate_bindings_tokens_for_test(ir_from_cc(
r#"
#pragma clang lifetime_elision
struct S {~S();};
void Function(const S& s);
"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
fn Function<'a>(s: &'a crate::S) { ... }
}
);
Ok(())
}
/// !Unpin mut references must be pinned.
#[gtest]
fn test_nonunpin_mut_param() -> Result<()> {
let rs_api = generate_bindings_tokens_for_test(ir_from_cc(
r#"
#pragma clang lifetime_elision
struct S {~S();};
void Function(S& s);
"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
fn Function<'a>(s: ::core::pin::Pin<&'a mut crate::S>) { ... }
}
);
Ok(())
}
/// !Unpin &self should not be pinned.
#[gtest]
fn test_nonunpin_ref_self() -> Result<()> {
let rs_api = generate_bindings_tokens_for_test(ir_from_cc(
r#"
#pragma clang lifetime_elision
struct S {
~S();
void Function() const;
};
"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
fn Function<'a>(&'a self) { ... }
}
);
Ok(())
}
/// !Unpin &mut self must be pinned.
#[gtest]
fn test_nonunpin_mut_self() -> Result<()> {
let rs_api = generate_bindings_tokens_for_test(ir_from_cc(
r#"
#pragma clang lifetime_elision
struct S {
~S();
void Function();
};
"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
fn Function<'a>(self: ::core::pin::Pin<&'a mut Self>) { ... }
}
);
Ok(())
}
/// Drop::drop must not use self : Pin<...>.
#[gtest]
fn test_nonunpin_drop() -> Result<()> {
let rs_api = generate_bindings_tokens_for_test(ir_from_cc(
r#"
struct S {~S();};
"#,
)?)?
.rs_api;
assert_rs_matches!(
rs_api,
quote! {
unsafe fn pinned_drop<'a>(self: ::core::pin::Pin<&'a mut Self>) { ... }
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_0_arg_constructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct HasConstructor {
explicit HasConstructor() {}
~HasConstructor();
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(rs_api, quote! {#[::ctor::recursively_pinned(PinnedDrop)]});
assert_rs_matches!(
rs_api,
quote! {
impl ::ctor::CtorNew<()> for HasConstructor {
type CtorType = impl ::ctor::Ctor<Output = Self, Error = ::ctor::Infallible>;
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___ZN14HasConstructorC1Ev(dest as *mut ::core::ffi::c_void);
})
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_1_arg_constructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct HasConstructor {
explicit HasConstructor(unsigned char input) {}
~HasConstructor();
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(rs_api, quote! {#[::ctor::recursively_pinned(PinnedDrop)]});
assert_rs_matches!(
rs_api,
quote! {
impl ::ctor::CtorNew<::core::ffi::c_uchar> for HasConstructor {
type CtorType = impl ::ctor::Ctor<Output = Self, Error = ::ctor::Infallible>;
type Error = ::ctor::Infallible;
#[inline (always)]
fn ctor_new(args: ::core::ffi::c_uchar) -> Self::CtorType {
let mut input = args;
unsafe {
::ctor::FnCtor::new(move |dest: *mut Self| {
crate::detail::__rust_thunk___ZN14HasConstructorC1Eh(dest as *mut ::core::ffi::c_void, input);
})
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_2_arg_constructor() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct HasConstructor {
explicit HasConstructor(unsigned char input1, signed char input2) {}
~HasConstructor();
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(rs_api, quote! {#[::ctor::recursively_pinned(PinnedDrop)]});
assert_rs_matches!(
rs_api,
quote! {
impl ::ctor::CtorNew<(::core::ffi::c_uchar, ::core::ffi::c_schar)> for HasConstructor {
type CtorType = impl ::ctor::Ctor<Output = Self, Error = ::ctor::Infallible>;
type Error = ::ctor::Infallible;
#[inline (always)]
fn ctor_new(args: (::core::ffi::c_uchar, ::core::ffi::c_schar)) -> Self::CtorType {
let (mut input1, mut input2) = args;
unsafe {
::ctor::FnCtor::new(move |dest: *mut Self| {
crate::detail::__rust_thunk___ZN14HasConstructorC1Eha(dest as *mut ::core::ffi::c_void, input1, input2);
})
}
}
}
}
);
Ok(())
}
/// Traits which monomorphize the `Ctor` parameter into the caller must
/// synthesize an RvalueReference parameter, with an appropriate
/// lifetime parameter.
#[gtest]
fn test_nonunpin_by_value_params() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct HasConstructor {
// int& x is here to create a 'b lifetime, which collides with a synthesized
// lifetime name. But that's OK! We handle collisions!
// (`a` would also work, but that's just because the left hand doesn't know what
// the right is doing: the `a` lifetime is present in some places, but eventually
// removed from the public interface.)
explicit HasConstructor(const int& x, HasConstructor y, HasConstructor b) {}
~HasConstructor();
};"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(rs_api, quote! {#[::ctor::recursively_pinned(PinnedDrop)]});
assert_rs_matches!(
rs_api,
quote! {
impl <'b, 'y, 'b_2> ::ctor::CtorNew<(
&'b ::core::ffi::c_int,
::ctor::RvalueReference<'y, Self>,
::ctor::RvalueReference<'b_2, Self>)
> for HasConstructor {
// The captures are why we need explicit lifetimes for the two rvalue reference
// parameters.
type CtorType = impl ::ctor::Ctor<Output = Self, Error = ::ctor::Infallible> + use<'b, 'y, 'b_2>;
type Error = ::ctor::Infallible;
#[inline (always)]
fn ctor_new(args: (
&'b ::core::ffi::c_int,
::ctor::RvalueReference<'y, Self>,
::ctor::RvalueReference<'b_2, Self>)
) -> Self::CtorType {
let (mut x, mut y, mut b) = args;
unsafe {
::ctor::FnCtor::new(move |dest: *mut Self| {
crate::detail::__rust_thunk___ZN14HasConstructorC1ERKiS_S_(dest as *mut ::core::ffi::c_void, x, y, b);
})
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_return() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct Nontrivial {~Nontrivial();};
Nontrivial ReturnsByValue(const int& x, const int& y);
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
pub fn ReturnsByValue<'a, 'b>(x: &'a ::core::ffi::c_int, y: &'b ::core::ffi::c_int)
-> impl ::ctor::Ctor<Output=crate::Nontrivial, Error=::ctor::Infallible> + use<'a, 'b> {
unsafe {
::ctor::FnCtor::new(move |dest: *mut crate::Nontrivial| {
crate::detail::__rust_thunk___Z14ReturnsByValueRKiS0_(dest as *mut ::core::ffi::c_void, x, y);
})
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z14ReturnsByValueRKiS0_(
struct Nontrivial* __return, int const* x, int const* y) {
new(__return) auto(ReturnsByValue(*x, *y));
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_const_return() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct Nontrivial {~Nontrivial();};
const Nontrivial ReturnsByValue(const int& x, const int& y);
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
pub fn ReturnsByValue<'a, 'b>(x: &'a ::core::ffi::c_int, y: &'b ::core::ffi::c_int)
-> impl ::ctor::Ctor<Output=crate::Nontrivial, Error=::ctor::Infallible> + use<'a, 'b> {
unsafe {
::ctor::FnCtor::new(move |dest: *mut crate::Nontrivial| {
crate::detail::__rust_thunk___Z14ReturnsByValueRKiS0_(dest as *mut ::core::ffi::c_void, x, y);
})
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z14ReturnsByValueRKiS0_(
struct Nontrivial* __return, int const* x, int const* y) {
new(__return) auto(ReturnsByValue(*x, *y));
}
}
);
Ok(())
}
#[gtest]
fn test_unpin_by_value_param() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Trivial final {
int trivial_field;
};
void foo(Trivial param);
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn foo(mut param: crate::Trivial) {
unsafe { crate::detail::__rust_thunk___Z3foo7Trivial(&mut param) }
}
}
);
assert_rs_matches!(
rs_api,
quote! {
pub(crate) unsafe fn __rust_thunk___Z3foo7Trivial(param: &mut crate::Trivial);
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z3foo7Trivial(struct Trivial* param) {
foo(std::move(*param));
}
}
);
Ok(())
}
#[gtest]
fn test_unpin_by_value_return() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct Trivial final {
int trivial_field;
};
Trivial foo();
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn foo() -> crate::Trivial {
unsafe {
let mut __return = ::core::mem::MaybeUninit::<crate::Trivial>::uninit();
crate::detail::__rust_thunk___Z3foov(&raw mut __return as *mut ::core::ffi::c_void);
__return.assume_init()
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
pub(crate) unsafe fn __rust_thunk___Z3foov(
__return: *mut ::core::ffi::c_void
);
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z3foov(struct Trivial* __return) {
new (__return) auto(foo());
}
}
);
Ok(())
}
#[gtest]
fn test_unpin_rvalue_ref_qualified_method() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct TrivialWithRvalueRefQualifiedMethod final {
void rvalue_ref_qualified_method() &&;
};
"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn rvalue_ref_qualified_method<'a>(self: ::ctor::RvalueReference<'a, Self>) {
unsafe {
crate::detail::__rust_thunk___ZNO35TrivialWithRvalueRefQualifiedMethod27rvalue_ref_qualified_methodEv(self)
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
#[link_name = "_ZNO35TrivialWithRvalueRefQualifiedMethod27rvalue_ref_qualified_methodEv"]
pub(crate) unsafe fn __rust_thunk___ZNO35TrivialWithRvalueRefQualifiedMethod27rvalue_ref_qualified_methodEv < 'a > (__this :
:: ctor :: RvalueReference < 'a , crate :: TrivialWithRvalueRefQualifiedMethod >) ;
}
);
Ok(())
}
#[gtest]
fn test_unpin_rvalue_ref_const_qualified_method() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct TrivialWithRvalueRefConstQualifiedMethod final {
void rvalue_ref_const_qualified_method() const &&;
};
"#,
)?;
let BindingsTokens { rs_api, .. } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
#[inline(always)]
pub fn rvalue_ref_const_qualified_method<'a>(self: ::ctor::ConstRvalueReference<'a, Self>) {
unsafe {
crate::detail::__rust_thunk___ZNKO40TrivialWithRvalueRefConstQualifiedMethod33rvalue_ref_const_qualified_methodEv(self)
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
#[link_name = "_ZNKO40TrivialWithRvalueRefConstQualifiedMethod33rvalue_ref_const_qualified_methodEv"]
pub(crate) unsafe fn __rust_thunk___ZNKO40TrivialWithRvalueRefConstQualifiedMethod33rvalue_ref_const_qualified_methodEv < 'a > (__this :
:: ctor :: ConstRvalueReference < 'a , crate :: TrivialWithRvalueRefConstQualifiedMethod >) ;
}
);
Ok(())
}
/// Assignment is special in that it discards the return type.
/// So if the return type is !Unpin, it needs to emplace!() it.
#[gtest]
fn test_nonunpin_return_assign() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct Nontrivial {
~Nontrivial();
Nontrivial operator=(const Nontrivial& other);
};
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl<'b> ::ctor::Assign<&'b Self> for Nontrivial {
#[inline(always)]
fn assign<'a>(self: ::core::pin::Pin<&'a mut Self>, other: &'b Self) {
unsafe {
let _ = ::ctor::emplace!(::ctor::FnCtor::new(
move |dest: *mut Self| {
crate::detail::__rust_thunk___ZN10NontrivialaSERKS_(
dest as *mut ::core::ffi::c_void,
self,
other
);
}
));
}
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___ZN10NontrivialaSERKS_(
struct Nontrivial* __return, struct Nontrivial* __this,
struct Nontrivial const * other
) {
new(__return) auto(__this->operator=(*other));
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_param() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct Nontrivial {
Nontrivial(Nontrivial&&);
~Nontrivial();
};
void TakesByValue(Nontrivial x);
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
pub fn TakesByValue(x: impl ::ctor::Ctor<Output=crate::Nontrivial, Error=::ctor::Infallible>) {
unsafe {
crate::detail::__rust_thunk___Z12TakesByValue10Nontrivial(::core::pin::Pin::into_inner_unchecked(::ctor::emplace!(x)))
}
}
}
);
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z12TakesByValue10Nontrivial(struct Nontrivial*x) {
TakesByValue(std::move(*x));
}
}
);
Ok(())
}
#[gtest]
fn test_nonunpin_trait_param() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin`.
struct Nontrivial {
Nontrivial(Nontrivial&&);
Nontrivial& operator=(Nontrivial) {}
~Nontrivial();
};
struct Trivial final {
/*implicit*/ Trivial(Nontrivial) {}
};
"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl<'__param_0> From<::ctor::RvalueReference<'__param_0, crate::Nontrivial>> for Trivial {
#[inline(always)]
fn from(__param_0: ::ctor::RvalueReference<'__param_0, crate::Nontrivial>) -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN7TrivialC1E10Nontrivial(
&raw mut tmp as *mut _,
__param_0
);
tmp.assume_init()
}
}
}
}
);
Ok(())
}
#[gtest]
fn test_nonmovable_param() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
// This type must be `!Unpin` and non-move constructible.
struct Nonmovable {
Nonmovable(Nonmovable&&) = delete;
};
void TakesByValue(Nonmovable) {}
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
// Bindings for TakesByValue cannot be generated.
assert_rs_matches!(rs_api, quote! {TakesByValue<'error>});
assert_cc_not_matches!(rs_api_impl, quote! {TakesByValue});
Ok(())
}
#[gtest]
fn test_invalid_unsafe_annotation_causes_fatal_error() -> googletest::Result<()> {
let ir = ir_from_cc(
r#"
struct Trivial final {
[[clang::annotate("crubit_override_unsafe", true)]]
~Trivial();
};
"#,
)
.or_fail()?;
let error_message = generate_bindings_tokens_for_test(ir).err().or_fail()?.to_string();
assert_that!(
error_message,
contains_substring("Unsafe annotations on destructors are not supported")
);
Ok(())
}
#[gtest]
fn test_function_returning_rvalue_reference() -> Result<()> {
let ir = ir_from_cc(
r#"#pragma clang lifetime_elision
struct SomeStruct final {
// Inline to force generation (and test coverage) of C++ thunks.
inline SomeStruct&& GetRValueReference() {
return static_cast<SomeStruct&&>(*this);
}
int field;
};
"#,
)?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens_for_test(ir)?;
assert_rs_matches!(
rs_api,
quote! {
impl SomeStruct {
...
#[inline(always)]
pub fn GetRValueReference<'a>(&'a mut self)
-> ::ctor::RvalueReference<'a, crate::SomeStruct> {
unsafe {
crate::detail::__rust_thunk___ZN10SomeStruct18GetRValueReferenceEv(self)
}
}
}
}
);
assert_rs_matches!(
rs_api,
quote! {
extern "C" {
...
pub(crate) unsafe fn __rust_thunk___ZN10SomeStruct18GetRValueReferenceEv<'a>(
__this: &'a mut crate::SomeStruct
) -> ::ctor::RvalueReference<'a, crate::SomeStruct>;
...
}
}
);
// Note that you can't just convert directly from xvalue to lvalue:
//
// return &static_cast<SomeStruct&>(__this->GetRValueReference());
//
// For the above, Clang will emit an error that "non-const lvalue reference to
// type 'struct SomeStruct' cannot bind to a temporary of type
// 'SomeStruct'" (This is somewhat misleading, because there are no
// temporaries here). We must first bind the return value to a name
// (`lvalue` below), so that it becomes an lvalue. Only then can it be
// converted to a pointer.
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" struct SomeStruct*
__rust_thunk___ZN10SomeStruct18GetRValueReferenceEv(struct SomeStruct* __this) {
struct SomeStruct&& lvalue = __this->GetRValueReference();
return &lvalue;
}
}
);
Ok(())
}
#[gtest]
fn test_c_abi_compatible_type_by_value_with_move() -> Result<()> {
let ir = ir_from_cc(
r#"
typedef int MyTypedefDecl;
inline void f(MyTypedefDecl a, void* b, int c) {}
"#,
)?;
let BindingsTokens { rs_api_impl, .. } = generate_bindings_tokens_for_test(ir)?;
assert_cc_matches!(
rs_api_impl,
quote! {
extern "C" void __rust_thunk___Z1fiPvi(MyTypedefDecl a, void* b, int c) {
f(a, b, c);
}
}
);
Ok(())
}