| // 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(()) | |
| } |