blob: 8c41601440d02d81f61daf2375c8a6d1ef366a79 [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
#![feature(rustc_private)]
use code_gen_utils::{CcInclude, NamespaceQualifier};
use generate_bindings::format_namespace_bound_cc_tokens;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::quote;
use run_compiler_test_support::run_compiler_for_testing;
use rustc_span::symbol::Symbol;
use test_helpers::{bindings_db_for_tests, test_format_item, test_generated_bindings};
use token_stream_matchers::{
assert_cc_matches, assert_cc_not_matches, assert_rs_matches, assert_rs_not_matches,
};
use token_stream_printer::cc_tokens_to_formatted_string_for_tests;
/// This test covers only a single example of a function that should get a
/// C++ binding. The test focuses on verification that the output from
/// `generate_function` gets propagated all the way to
/// `GenerateBindings::new`. Additional coverage of how functions are
/// formatted is provided by `test_format_item_..._fn_...` tests (which
/// work at the `generate_function` level).
#[test]
fn test_generated_bindings_fn_no_mangle_extern_c() {
let test_src = r#"
#[unsafe(no_mangle)]
pub extern "C" fn public_function() {
println!("foo");
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
extern "C" void public_function();
}
);
// No Rust thunks should be generated in this test scenario.
assert_rs_not_matches!(bindings.cc_api_impl, quote! { public_function });
});
}
/// Tests that `toposort` is used to reorder item bindings.
#[test]
fn test_generated_bindings_prereq_defs_field_deps_require_reordering() {
let test_src = r#"
#![allow(dead_code)]
// In the generated bindings `Outer` needs to come *after* `Inner`.
pub struct Outer(Inner);
pub struct Inner(bool);
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(1) [[clang::trivial_abi]] Inner final {
... union { ... bool __field0; }; ...
};
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(1) [[clang::trivial_abi]] Outer final {
... union { ... ::rust_out::Inner __field0; }; ...
};
...
} // namespace rust_out
}
);
});
}
/// Tests that item bindings have a stable order.
// Because the source location of both calls to the proc macro are the same,
// "span" will be the same, which means that without a stable sort for items
// where the span is equivalent, the output will be non-deterministic.
#[test]
fn test_generated_bindings_have_stable_order() {
fn idents_in_stream(stream: TokenStream, idents: &mut Vec<Ident>) {
for tree in stream {
match tree {
TokenTree::Group(group) => idents_in_stream(group.stream(), idents),
TokenTree::Ident(ident) => idents.push(ident),
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
}
}
}
/// Returns the index in `idents` of `{name} final`.
#[track_caller]
fn get_final_decl_position(idents: &[Ident], name: &str, debug_string: &str) -> usize {
let Some((i, _ident)) = idents.iter().enumerate().find(|(i, ident)| {
*ident == name && idents.get(i + 1).map(|next| next == "final").unwrap_or(false)
}) else {
panic!("`{name} final` declaration not found in input:\n{debug_string}")
};
i
}
let test_src = r#"
#![allow(dead_code)]
macro_rules! make_struct {
($ty: ty, $name:ident) => {
pub struct $name {
value: $ty
}
}
}
make_struct!(i32, HelloF32);
make_struct!(f32, HelloI32);
"#;
const NUM_ITERATIONS: u8 = 5;
// Check that the ordering of `HelloI32` and `HelloF32` is consistent across
// all iterations.
let mut i32_was_first: Option<bool> = None;
for _ in 0..NUM_ITERATIONS {
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
let cc_api: TokenStream = bindings.cc_api;
let cc_api_debug_string =
cc_tokens_to_formatted_string_for_tests(cc_api.clone()).unwrap();
let mut idents = Vec::new();
idents_in_stream(cc_api, &mut idents);
let i32_decl_position =
get_final_decl_position(&idents, "HelloI32", &cc_api_debug_string);
let f32_decl_position =
get_final_decl_position(&idents, "HelloF32", &cc_api_debug_string);
let i32_is_first = i32_decl_position < f32_decl_position;
if let Some(i32_was_first) = i32_was_first {
if i32_is_first != i32_was_first {
panic!("`HelloI32` declaration ordering is inconsistent.");
}
} else {
i32_was_first = Some(i32_is_first);
}
});
}
}
/// Tests that a forward declaration is present when it is required to
/// preserve the original source order. In this test the
/// `CcPrerequisites::fwd_decls` dependency comes from a pointer parameter.
#[test]
fn test_generated_bindings_prereq_fwd_decls_for_ptr_param() {
let test_src = r#"
#![allow(dead_code)]
// To preserve original API order we need to forward declare S.
pub fn f(_: *const S) {}
pub struct S(bool);
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
...
// Verifying the presence of this forward declaration
// it the essence of this test. The order of the items
// below also matters.
struct S;
...
void f(::rust_out::S const* __param_0);
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(...) [[clang::trivial_abi]] S final { ... }
...
inline void f(::rust_out::S const* __param_0) { ... }
...
} // namespace rust_out
}
);
});
}
/// Tests that a forward declaration is present when it is required to
/// preserve the original source order. In this test the
/// `CcPrerequisites::fwd_decls` dependency comes from a
/// function declaration that has a parameter that takes a struct by value.
#[test]
fn test_generated_bindings_prereq_fwd_decls_for_cpp_fn_decl() {
let test_src = r#"
#[unsafe(no_mangle)]
pub extern "C" fn f(s: S) -> bool { s.0 }
#[repr(C)]
pub struct S(bool);
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
...
// Verifying the presence of this forward declaration
// is the essence of this test. The order also matters:
// 1. The fwd decl of `S` should come first,
// 2. Declaration of `f` and definition of `S` should come next
// (in their original order - `f` first and then `S`).
struct S;
...
// `CcPrerequisites` of `f` declaration below (the main api of `f`) should
// include `S` as a `fwd_decls` edge, rather than as a `defs` edge.
bool f(::rust_out::S s);
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(...) [[clang::trivial_abi]] S final { ... }
...
} // namespace rust_out
}
);
});
}
/// This test verifies that a forward declaration for a given ADT is only
/// emitted once (and not once for every API item that requires the
/// forward declaration as a prerequisite).
#[test]
fn test_generated_bindings_prereq_fwd_decls_no_duplication() {
let test_src = r#"
#![allow(dead_code)]
// All three functions below require a forward declaration of S.
pub fn f1(_: *const S) {}
pub fn f2(_: *const S) {}
pub fn f3(_: *const S) {}
pub struct S(bool);
// This function also includes S in its CcPrerequisites::fwd_decls
// (although here it is not required, because the definition of S
// is already available above).
pub fn f4(_: *const S) {}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap().cc_api.to_string();
// Only a single forward declaration is expected.
assert_eq!(1, bindings.matches("struct S ;").count(), "bindings = {bindings}");
});
}
/// This test verifies that forward declarations are emitted in a
/// deterministic order. The particular order doesn't matter _that_
/// much, but it definitely shouldn't change every time
/// `cc_bindings_from_rs` is invoked again. The current order preserves
/// the original source order of the Rust API items.
#[test]
fn test_generated_bindings_prereq_fwd_decls_deterministic_order() {
let test_src = r#"
#![allow(dead_code)]
// To try to mix things up, the bindings for the functions below
// will *ask* for forward declarations in a different order:
// * Different from the order in which the forward declarations
// are expected to be *emitted* (the original source order).
// * Different from alphabetical order.
pub fn f1(_: *const b::S3) {}
pub fn f2(_: *const a::S2) {}
pub fn f3(_: *const a::S1) {}
pub mod a {
pub struct S1(bool);
pub struct S2(bool);
}
pub mod b {
pub struct S3(bool);
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
...
// Verifying that we get the same order in each test
// run is the essence of this test.
namespace a {
struct S1;
struct S2;
}
namespace b {
struct S3;
}
...
void f1 ...
void f2 ...
void f3 ...
namespace a { ...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(...) [[clang::trivial_abi]] S1 final { ... } ...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(...) [[clang::trivial_abi]] S2 final { ... } ...
} ...
namespace b { ...
struct CRUBIT_INTERNAL_RUST_TYPE(...) alignas(...) [[clang::trivial_abi]] S3 final { ... } ...
} ...
} // namespace rust_out
}
);
});
}
/// This test verifies that forward declarations are not emitted if they are
/// not needed (e.g. if bindings the given `struct` or other ADT have
/// already been defined earlier). In particular, we don't want to emit
/// forward declarations for *all* `structs` (regardless if they are
/// needed or not).
#[test]
fn test_generated_bindings_prereq_fwd_decls_not_needed_because_of_initial_order() {
let test_src = r#"
#[allow(dead_code)]
pub struct S(bool);
// S is already defined above - no need for forward declaration in C++.
pub fn f(_s: *const S) {}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_not_matches!(bindings.cc_api, quote! { struct S; });
assert_cc_matches!(bindings.cc_api, quote! { void f(::rust_out::S const* _s); });
});
}
/// This test verifies that a method declaration doesn't ask for a forward
/// declaration to the struct.
#[test]
fn test_generated_bindings_prereq_fwd_decls_not_needed_inside_struct_definition() {
let test_src = r#"
#![allow(dead_code)]
pub struct S {
// This shouldn't require a fwd decl of S.
field: *const S,
}
impl S {
// This shouldn't require a fwd decl of S.
pub fn create() -> S { Self{ field: std::ptr::null() } }
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_not_matches!(bindings.cc_api, quote! { struct S; });
assert_cc_matches!(
bindings.cc_api,
quote! {
static ::rust_out::S create(); ...
union { ... ::rust_out::S const* field; }; ...
}
);
});
}
#[test]
fn test_generated_bindings_module_basics() {
let test_src = r#"
pub mod some_module {
pub fn some_func() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
namespace some_module {
...
inline void some_func() { ... }
...
} // namespace some_module
} // namespace rust_out
}
);
assert_rs_matches!(
bindings.cc_api_impl,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C"
fn ...() -> () {
unsafe { ::rust_out::some_module::some_func() }
}
}
);
});
}
#[test]
fn test_generated_bindings_module_name_is_cpp_reserved_keyword() {
let test_src = r#"
pub mod reinterpret_cast {
pub fn working_module_f1() {}
pub fn working_module_f2() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
namespace rust_out {
namespace reinterpret_cast_ {
...
void working_module_f1();
...
void working_module_f2();
...
} // namespace reinterpret_cast_
} // namespace rust_out
}
);
});
}
/// `test_generated_bindings_non_pub_items` verifies that non-public items
/// are not present/propagated into the generated bindings.
#[test]
fn test_generated_bindings_non_pub_items() {
let test_src = r#"
#![allow(dead_code)]
extern "C" fn private_function() {
println!("foo");
}
struct PrivateStruct {
x: i32,
y: i32,
}
pub struct PublicStruct(i32);
impl PublicStruct {
fn private_method() {}
}
pub mod public_module {
fn priv_func_in_pub_module() {}
}
mod private_module {
pub fn pub_func_in_priv_module() { priv_func_in_priv_module() }
fn priv_func_in_priv_module() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_not_matches!(bindings.cc_api, quote! { private_function });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { private_function });
assert_cc_not_matches!(bindings.cc_api, quote! { PrivateStruct });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { PrivateStruct });
assert_cc_not_matches!(bindings.cc_api, quote! { private_method });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { private_method });
assert_cc_not_matches!(bindings.cc_api, quote! { priv_func_in_priv_module });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { priv_func_in_priv_module });
assert_cc_not_matches!(bindings.cc_api, quote! { priv_func_in_pub_module });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { priv_func_in_pub_module });
assert_cc_not_matches!(bindings.cc_api, quote! { private_module });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { private_module });
assert_cc_not_matches!(bindings.cc_api, quote! { pub_func_in_priv_module });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { pub_func_in_priv_module });
});
}
#[test]
fn test_generated_bindings_top_level_items() {
let test_src = "pub fn public_function() {}";
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
let expected_comment_txt =
"Automatically @generated C++ bindings for the following Rust crate:\n\
rust_out\n\
Features: experimental, supported";
assert_cc_matches!(
bindings.cc_api,
quote! {
__COMMENT__ #expected_comment_txt
...
__HASH_TOKEN__ pragma once
...
namespace rust_out {
...
}
}
);
assert_cc_matches!(
bindings.cc_api_impl,
quote! {
__COMMENT__ #expected_comment_txt
}
);
})
}
#[test]
fn test_format_item_reexport_private_type() {
let test_src = r#"
#![allow(dead_code)]
mod test_mod {
pub struct ReExportedStruct{
pub field: i32
}
pub struct NotReExportedStruct{
pub field: i32
}
}
pub use crate::test_mod::ReExportedStruct as Z;
pub use crate::test_mod::ReExportedStruct as X;
pub use crate::test_mod::ReExportedStruct as Y;
#[allow(unused_imports)]
use crate::test_mod::ReExportedStruct as PrivateUse;
"#;
test_format_item(test_src, "NotReExportedStruct", |result| {
let result = result.unwrap();
assert!(result.is_none());
});
test_format_item(test_src, "PrivateUse", |result| {
let result = result.unwrap();
assert!(result.is_none());
});
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
...
namespace __crubit_internal {
...
struct CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: X") alignas(4)
[[clang::trivial_abi]] ReExportedStruct final
...
}
}
);
assert_rs_matches!(
bindings.cc_api_impl,
quote! {
const _: () = assert!(::std::mem::size_of::<::rust_out::X>() == 4);
}
);
assert_rs_not_matches!(bindings.cc_api_impl, quote! { ::rust_out::Y });
assert_rs_not_matches!(bindings.cc_api_impl, quote! { ::rust_out::Z });
});
}
#[test]
fn test_generated_bindings_module_deprecated_no_args() {
let test_src = r#"
#[deprecated]
pub mod some_module {
pub fn some_function() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
...
namespace [[deprecated]] some_module {
...
} // namespace some_module
...
}
);
});
}
#[test]
fn test_generated_bindings_module_deprecated_with_message() {
let test_src = r#"
#[deprecated = "Use other_module instead"]
pub mod some_module {
pub fn some_function() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
...
namespace
[[deprecated("Use other_module instead")]]
some_module {
...
} // namespace some_module
...
}
);
});
}
#[test]
fn test_generated_bindings_module_deprecated_named_args() {
let test_src = r#"
#[deprecated(since = "3.14", note = "Use other_module instead")]
pub mod some_module {
pub fn some_function() {}
}
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
...
namespace
[[deprecated("Use other_module instead")]]
some_module {
...
} // namespace some_module
...
}
);
});
}
#[test]
fn test_format_bridged_type_pointer_like_errors() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=const CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
pub struct MissingReprTransparent {
pub cpp_type: *const core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn with_missing_repr_transparent(_: MissingReprTransparent) {}
#[doc="CRUBIT_ANNOTATE: cpp_type=const CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[repr(transparent)]
pub struct NotPointerLike {
pub value: i32,
}
#[unsafe(no_mangle)]
pub fn not_pointer_like(_: NotPointerLike) {}
"#;
test_format_item(test_src, "with_missing_repr_transparent", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error handling parameter #0 of type `MissingReprTransparent`: Can't convert \
MissingReprTransparent to a C++ pointer as it's not `repr(transparent)`"
);
});
test_format_item(test_src, "not_pointer_like", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error handling parameter #0 of type `NotPointerLike`: Can't convert \
NotPointerLike to a C++ pointer as its layout is not pointer-like. To be \
considered pointer-like it may only have one non-ZST field that needs to be a C \
ABI compatible pointer."
);
});
}
#[test]
fn test_format_bridged_func_arg_pointer_like() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=const CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[repr(transparent)]
pub struct RustTypeView {
pub cpp_type: *const core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn foo(_: RustTypeView) {}
"#;
test_format_item(test_src, "RustTypeView", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustTypeView suppressed \
due to being mapped to an existing C++ type (const CppType*)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
assert_cc_matches!(
main_api.tokens,
quote! {
void foo(const CppType* __param_0);
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(const CppType*);
}
inline void foo(const CppType* __param_0) {
return __crubit_internal::__crubit_thunk_foo(__param_0);
}
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(__param_0: *const core::ffi::c_void) -> () {
unsafe {
let __param_0 = {
let mut __crubit_temp = ::core::mem::MaybeUninit::<::rust_out::RustTypeView>::uninit();
__crubit_temp.write(::core::mem::transmute(__param_0));
__crubit_temp.assume_init()
};
::rust_out::foo(__param_0)
}
}
}
);
});
}
#[test]
fn test_format_bridged_func_arg_by_pointer() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=CppType const*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=cpp_pointer_to_rust_struct"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=rust_struct_to_cpp_pointer"]
#[repr(transparent)]
pub struct RustTypeView {
pub cpp_type: *const core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn foo(_: RustTypeView) {}
"#;
test_format_item(test_src, "RustTypeView", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustTypeView suppressed \
due to being mapped to an existing C++ type (CppType const*)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
assert_cc_matches!(
main_api.tokens,
quote! {
void foo(CppType const* __param_0);
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(CppType const*);
}
inline void foo(CppType const* __param_0) {
return __crubit_internal::__crubit_thunk_foo(__param_0);
}
}
);
let extern_c_decl = result.rs_details.extern_c_decls.first().unwrap();
assert_eq!(extern_c_decl.symbol, Symbol::intern("cpp_pointer_to_rust_struct"));
assert_rs_matches!(
extern_c_decl.decl,
quote! {
fn cpp_pointer_to_rust_struct(cpp_in: *const core::ffi::c_void,
rs_out: *mut core::ffi::c_void);
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(__param_0: *const core::ffi::c_void) -> () {
unsafe {
let __param_0 = {
let mut __crubit_temp = ::core::mem::MaybeUninit::<::rust_out::RustTypeView>::uninit();
cpp_pointer_to_rust_struct(
__param_0,
__crubit_temp.as_mut_ptr() as *mut core::ffi::c_void
);
__crubit_temp.assume_init()
};
::rust_out::foo(__param_0)
}
}
}
);
});
}
#[test]
fn test_format_bridged_func_arg_by_value() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=cpp_ns::CppType"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=convert_cpp_to_rust_type"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=convert_rust_to_cpp_type"]
pub struct RustType {
pub x: i32,
}
#[unsafe(no_mangle)]
pub fn foo(_a: RustType) {}
"#;
test_format_item(test_src, "RustType", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustType suppressed \
due to being mapped to an existing C++ type (cpp_ns::CppType)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
assert_eq!(
*main_api.prereqs.includes.first().unwrap(),
CcInclude::user_header("cpp_ns/cpp_type.h".into())
);
assert_eq!(result.rs_details.extern_c_decls.len(), 1);
let extern_c_decl = result.rs_details.extern_c_decls.first().unwrap();
assert_eq!(extern_c_decl.symbol, Symbol::intern("convert_cpp_to_rust_type"));
assert_rs_matches!(
extern_c_decl.decl,
quote! {
fn convert_cpp_to_rust_type(cpp_in: *const core::ffi::c_void,
rs_out: *mut core::ffi::c_void);
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(_a: *const core::ffi::c_void) -> () {
unsafe {
let _a = {
let mut __crubit_temp = ::core::mem::MaybeUninit::<::rust_out::RustType>::uninit();
convert_cpp_to_rust_type(_a, __crubit_temp.as_mut_ptr() as *mut core::ffi::c_void);
__crubit_temp.assume_init()
};
::rust_out::foo(_a)
}
}
}
);
assert_cc_matches!(
main_api.tokens,
quote! {
void foo(cpp_ns::CppType _a);
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(cpp_ns::CppType*);
}
inline void foo(cpp_ns::CppType _a) {
return __crubit_internal::__crubit_thunk_foo(&_a);
}
}
);
});
}
#[test]
fn test_format_bridged_return_type_pointer_like() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[repr(transparent)]
pub struct RustTypeOwned {
pub cpp_type: *mut core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn foo() -> RustTypeOwned { todo!() }
"#;
test_format_item(test_src, "RustTypeOwned", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustTypeOwned suppressed \
due to being mapped to an existing C++ type (CppType*)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(__ret_ptr: *mut core::ffi::c_void) -> () {
unsafe {
let __rs_return_value = ::rust_out::foo();
(__ret_ptr as *mut ::rust_out::RustTypeOwned).write(__rs_return_value);
}
}
}
);
assert_cc_matches!(
main_api.tokens,
quote! {
CppType* foo();
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(CppType** __ret_ptr);
}
inline CppType* foo() {
union __return_value_crubit_return_union {
constexpr __return_value_crubit_return_union() {}
~__return_value_crubit_return_union() { std::destroy_at(&this->val); }
CppType* val;
} __return_value_ret_val_holder;
auto* __return_value_storage = &__return_value_ret_val_holder.val;
__crubit_internal::__crubit_thunk_foo(__return_value_storage);
return std::move(__return_value_ret_val_holder.val);
}
}
);
})
}
#[test]
fn test_format_brided_type_deduplicate_extern_c_decls() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=rust_struct_to_cpp_pointer"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=cpp_pointer_to_rust_struct"]
pub struct RustType1 {
pub cpp_type: *const core::ffi::c_void,
}
#[doc="CRUBIT_ANNOTATE: cpp_type=CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=rust_struct_to_cpp_pointer"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=cpp_pointer_to_rust_struct"]
pub struct RustType2 {
pub cpp_type: *const core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn foo(_: RustType1, _: RustType2) { todo!() }
"#;
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
assert_eq!(result.rs_details.extern_c_decls.len(), 1);
let extern_c_decl = result.rs_details.extern_c_decls.first().unwrap();
assert_eq!(extern_c_decl.symbol, Symbol::intern("cpp_pointer_to_rust_struct"));
assert_rs_matches!(
extern_c_decl.decl,
quote! {
fn cpp_pointer_to_rust_struct(cpp_in: *const core::ffi::c_void,
rs_out: *mut core::ffi::c_void);
}
);
});
}
#[test]
fn test_format_bridged_return_type_by_pointer() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=CppType*"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=rust_struct_to_cpp_pointer"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=cpp_pointer_to_rust_struct"]
pub struct RustTypeOwned {
pub cpp_type: *const core::ffi::c_void,
}
#[unsafe(no_mangle)]
pub fn foo() -> RustTypeOwned { todo!() }
"#;
test_format_item(test_src, "RustTypeOwned", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustTypeOwned suppressed \
due to being mapped to an existing C++ type (CppType*)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
let extern_c_decl = result.rs_details.extern_c_decls.first().unwrap();
assert_eq!(extern_c_decl.symbol, Symbol::intern("rust_struct_to_cpp_pointer"));
assert_rs_matches!(
extern_c_decl.decl,
quote! {
fn rust_struct_to_cpp_pointer(
rs_in: *const core::ffi::c_void,
cpp_out: *mut core::ffi::c_void
);
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(__ret_ptr: *mut core::ffi::c_void) -> () {
unsafe {
let __rs_return_value = ::rust_out::foo();
rust_struct_to_cpp_pointer(
std::ptr::from_ref(&__rs_return_value) as *const core::ffi::c_void,
__ret_ptr
);
}
}
}
);
assert_cc_matches!(
main_api.tokens,
quote! {
CppType* foo();
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(CppType** __ret_ptr);
}
inline CppType* foo() {
union __return_value_crubit_return_union {
constexpr __return_value_crubit_return_union() {}
~__return_value_crubit_return_union() { std::destroy_at(&this->val); }
CppType* val;
} __return_value_ret_val_holder;
auto* __return_value_storage = &__return_value_ret_val_holder.val;
__crubit_internal::__crubit_thunk_foo(__return_value_storage);
return std::move(__return_value_ret_val_holder.val);
}
}
);
})
}
#[test]
fn test_format_bridged_return_type_by_value() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=cpp_ns::CppType"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=rust_to_cpp_converter"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=cpp_to_rust_converter"]
pub struct RustType {
pub x: i32,
}
#[unsafe(no_mangle)]
pub fn foo() -> RustType {
RustType { x: 10 }
}
"#;
test_format_item(test_src, "RustType", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Type bindings for RustType suppressed \
due to being mapped to an existing C++ type (cpp_ns::CppType)"
);
});
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_eq!(main_api.prereqs.includes.len(), 1);
assert_eq!(
*main_api.prereqs.includes.first().unwrap(),
CcInclude::user_header("cpp_ns/cpp_type.h".into())
);
let extern_c_decl = result.rs_details.extern_c_decls.first().unwrap();
assert_eq!(extern_c_decl.symbol, Symbol::intern("rust_to_cpp_converter"));
assert_rs_matches!(
extern_c_decl.decl,
quote! {
fn rust_to_cpp_converter(rs_in: *const core::ffi::c_void,
cpp_out: *mut core::ffi::c_void);
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn __crubit_thunk_foo(__ret_ptr: *mut core::ffi::c_void) -> () {
unsafe {
let __rs_return_value = ::rust_out::foo();
rust_to_cpp_converter(
std::ptr::from_ref(&__rs_return_value) as *const core::ffi::c_void,
__ret_ptr
);
}
}
}
);
assert_cc_matches!(
main_api.tokens,
quote! {
cpp_ns::CppType foo();
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void __crubit_thunk_foo(cpp_ns::CppType* __ret_ptr);
}
inline cpp_ns::CppType foo() {
union __return_value_crubit_return_union {
constexpr __return_value_crubit_return_union() {}
~__return_value_crubit_return_union() { std::destroy_at(&this->val); }
cpp_ns::CppType val;
} __return_value_ret_val_holder;
auto* __return_value_storage = &__return_value_ret_val_holder.val;
__crubit_internal::__crubit_thunk_foo(__return_value_storage);
return std::move(__return_value_ret_val_holder.val);
}
}
);
})
}
#[test]
fn test_bridged_type_unsupported() {
let test_src = r#"
#[doc="CRUBIT_ANNOTATE: cpp_type=cpp_ns::CppType"]
#[doc="CRUBIT_ANNOTATE: include_path=cpp_ns/cpp_type.h"]
#[doc="CRUBIT_ANNOTATE: rust_to_cpp_converter=convert_rust_to_cpp_type"]
#[doc="CRUBIT_ANNOTATE: cpp_to_rust_converter=convert_cpp_to_rust_type"]
pub struct RustType {
pub x: i32,
}
#[unsafe(no_mangle)]
pub fn unsupported_thunk_arg(_: fn() -> RustType) {}
#[unsafe(no_mangle)]
pub fn unsupported_return_ref() -> &'static RustType { todo!(); }
#[unsafe(no_mangle)]
pub fn unsupported_return_ptr() -> *const RustType { todo!(); }
#[unsafe(no_mangle)]
pub fn unsupported_accept_ref<'a>(_: &'a RustType) {}
#[unsafe(no_mangle)]
pub fn unsupported_accept_ptr(_: *const RustType) {}
"#;
test_format_item(test_src, "unsupported_thunk_arg", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error handling parameter #0 of type `fn() -> RustType`: Function pointers can't \
have a thunk: Any calling convention other than `extern \"C\"` requires a thunk"
);
});
test_format_item(test_src, "unsupported_return_ref", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error formatting function return type `&'static RustType`: Failed to format the referent of the reference type `&'static RustType`: Bridged types must appear in a bridgeable type location"
);
});
test_format_item(test_src, "unsupported_return_ptr", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error formatting function return type `*const RustType`: Failed to format the pointee of the pointer type `*const RustType`: Bridged types must appear in a bridgeable type location"
);
});
test_format_item(test_src, "unsupported_accept_ref", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error handling parameter #0 of type `&'a RustType`: Failed to format the referent of the reference type `&'a RustType`: Bridged types must appear in a bridgeable type location"
);
});
test_format_item(test_src, "unsupported_accept_ptr", |result| {
let err = result.unwrap_err();
assert_eq!(
err,
"Error handling parameter #0 of type `*const RustType`: Failed to format the pointee of the pointer type `*const RustType`: Bridged types must appear in a bridgeable type location"
);
});
}
#[test]
fn test_format_item_slice() {
let test_src = r#"
pub fn foo(_a: *const [u32], _b: *const [u8], _c: *mut [i16], _d: *mut [bool]) { todo!() }
"#;
test_format_item(test_src, "foo", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
void
foo(
rs_std::SliceRef<const std::uint32_t> _a,
rs_std::SliceRef<const std::uint8_t> _b,
rs_std::SliceRef<std::int16_t> _c,
rs_std::SliceRef<bool> _d
);
}
);
});
}
#[test]
fn test_format_item_static_method() {
let test_src = r#"
#![allow(dead_code)]
/// No-op `f32` placeholder is used, because ZSTs are not supported
/// (b/258259459).
pub struct Math(f32);
impl Math {
pub fn add_i32(x: f32, y: f32) -> f32 {
x + y
}
}
"#;
test_format_item(test_src, "Math", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... Math final {
...
public:
...
static float add_i32(float x, float y);
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" float ... (float, float);
}
inline float Math::add_i32(float x, float y) {
return __crubit_internal::...(x, y);
}
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(x: f32, y: f32) -> f32 {
unsafe { ::rust_out::Math::add_i32(x, y) }
}
}
);
});
}
#[test]
fn test_format_item_static_method_with_generic_type_parameters() {
let test_src = r#"
#![allow(dead_code)]
/// No-op `f32` placeholder is used, because ZSTs are not supported
/// (b/258259459).
pub struct SomeStruct(f32);
impl SomeStruct {
// To make this testcase distinct / non-overlapping wrt
// test_format_item_static_method_with_generic_lifetime_parameters
// `t` is taken by value below.
pub fn generic_method<T: Clone>(t: T) -> T {
t.clone()
}
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let unsupported_msg = "Error generating bindings for `SomeStruct::generic_method` \
defined at <crubit_unittests.rs>;l=12: \
Generic functions are not supported yet (b/259749023)";
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
__COMMENT__ #unsupported_msg
...
};
...
}
);
assert_cc_not_matches!(result.cc_details.tokens, quote! { SomeStruct::generic_method },);
assert_rs_not_matches!(result.rs_details.tokens, quote! { generic_method },);
});
}
#[test]
fn test_format_item_static_method_with_generic_lifetime_parameters_at_fn_level() {
let test_src = r#"
#![allow(dead_code)]
/// No-op `f32` placeholder is used, because ZSTs are not supported
/// (b/258259459).
pub struct SomeStruct(f32);
impl SomeStruct {
pub fn fn_taking_reference<'a>(x: &'a i32) -> i32 { *x }
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
static std::int32_t fn_taking_reference(
std::int32_t const* [[clang::annotate_type("lifetime", "a")]] x);
...
};
...
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" std::int32_t ...(
std::int32_t const* [[clang::annotate_type("lifetime", "a")]]);
}
inline std::int32_t SomeStruct::fn_taking_reference(
std::int32_t const* [[clang::annotate_type("lifetime", "a")]] x) {
return __crubit_internal::...(x);
}
},
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(x: &'static i32) -> i32 {
unsafe { ::rust_out::SomeStruct::fn_taking_reference(x) }
}
},
);
});
}
#[test]
fn test_format_item_static_method_with_generic_lifetime_parameters_at_impl_level() {
let test_src = r#"
#![allow(dead_code)]
/// No-op `f32` placeholder is used, because ZSTs are not supported
/// (b/258259459).
pub struct SomeStruct(f32);
impl<'a> SomeStruct {
pub fn fn_taking_reference(x: &'a i32) -> i32 { *x }
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
static std::int32_t fn_taking_reference(
std::int32_t const* [[clang::annotate_type("lifetime", "a")]] x);
...
};
...
}
);
assert_cc_matches!(result.cc_details.tokens, quote! { SomeStruct::fn_taking_reference },);
assert_rs_matches!(result.rs_details.tokens, quote! { fn_taking_reference },);
});
}
fn test_format_item_method_taking_self_by_value(test_src: &str) {
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
float into_f32() &&;
...
};
...
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" float ...(::rust_out::SomeStruct*);
}
inline float SomeStruct::into_f32() && {
auto&& self = *this;
return __crubit_internal::...(&self);
}
},
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(__self: &'static mut ::core::mem::MaybeUninit<::rust_out::SomeStruct>) -> f32 {
unsafe {
let __self = __self.assume_init_read();
::rust_out::SomeStruct::into_f32(__self)
}
}
...
},
);
});
}
#[test]
fn test_format_item_method_taking_self_by_value_implicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn into_f32(self) -> f32 {
self.0
}
}
"#;
test_format_item_method_taking_self_by_value(test_src);
}
/// One difference from
/// `test_format_item_method_taking_self_by_value_implicit_type` is that
/// `fn_sig.decl.implicit_self` is `ImplicitSelfKind::None` here (vs
/// `ImplicitSelfKind::Imm` in the other test).
#[test]
fn test_format_item_method_taking_self_by_value_explicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn into_f32(self: SomeStruct) -> f32 {
self.0
}
}
"#;
test_format_item_method_taking_self_by_value(test_src);
}
fn test_format_item_method_taking_self_by_const_ref(test_src: &str) {
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
float get_f32() const;
...
};
...
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" float ...(::rust_out::SomeStruct const&);
}
inline float SomeStruct::get_f32() const {
auto&& self = *this;
return __crubit_internal::...(self);
}
},
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(__self: &'static ::rust_out::SomeStruct) -> f32 {
unsafe { ::rust_out::SomeStruct::get_f32(__self) }
}
...
},
);
});
}
#[test]
fn test_format_item_method_taking_self_by_const_ref_implicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn get_f32(&self) -> f32 {
self.0
}
}
"#;
test_format_item_method_taking_self_by_const_ref(test_src);
}
#[test]
fn test_format_item_method_taking_self_by_const_ref_explicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn get_f32(self: &SomeStruct) -> f32 {
self.0
}
}
"#;
test_format_item_method_taking_self_by_const_ref(test_src);
}
fn test_format_item_method_taking_self_by_mutable_ref(test_src: &str) {
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
void set_f32(float new_value);
...
};
...
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void ...(::rust_out::SomeStruct&, float);
}
inline void SomeStruct::set_f32(float new_value) {
auto&& self = *this;
return __crubit_internal::...(self, new_value);
}
},
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static mut ::rust_out::SomeStruct,
new_value: f32
) -> () {
unsafe { ::rust_out::SomeStruct::set_f32(__self, new_value) }
}
...
},
);
});
}
#[test]
fn test_format_item_method_taking_self_by_mutable_ref_implicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn set_f32(&mut self, new_value: f32) {
self.0 = new_value;
}
}
"#;
test_format_item_method_taking_self_by_mutable_ref(test_src);
}
#[test]
fn test_format_item_method_taking_self_by_mutable_ref_explicit_type() {
let test_src = r#"
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn set_f32(self: &mut SomeStruct, new_value: f32) {
self.0 = new_value;
}
}
"#;
test_format_item_method_taking_self_by_mutable_ref(test_src);
}
#[test]
fn test_format_item_method_taking_self_by_arc() {
let test_src = r#"
use std::sync::Arc;
pub struct SomeStruct(pub f32);
impl SomeStruct {
pub fn get_f32(self: Arc<Self>) -> f32 {
self.0
}
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let unsupported_msg = "Error generating bindings for `SomeStruct::get_f32` \
defined at <crubit_unittests.rs>;l=7: \
Unsupported `self` type `std::sync::Arc<SomeStruct>`";
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
__COMMENT__ #unsupported_msg
...
};
...
}
);
assert_cc_not_matches!(result.cc_details.tokens, quote! { SomeStruct::get_f32 },);
assert_rs_not_matches!(result.rs_details.tokens, quote! { get_f32 },);
});
}
#[test]
fn test_format_item_method_taking_self_by_pinned_mut_ref() {
let test_src = r#"
use core::pin::Pin;
pub struct SomeStruct(f32);
impl SomeStruct {
pub fn set_f32(mut self: Pin<&mut Self>, f: f32) {
self.0 = f;
}
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let unsupported_msg = "Error generating bindings for `SomeStruct::set_f32` \
defined at <crubit_unittests.rs>;l=7: \
Unsupported `self` type `std::pin::Pin<&'__anon1 mut SomeStruct>`";
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
__COMMENT__ #unsupported_msg
...
};
...
}
);
assert_cc_not_matches!(result.cc_details.tokens, quote! { SomeStruct::set_f32 },);
assert_rs_not_matches!(result.rs_details.tokens, quote! { set_f32 },);
});
}
#[test]
fn test_format_item_struct_with_default_constructor() {
let test_src = r#"
#![allow(dead_code)]
#[derive(Default)]
pub struct Point(i32, i32);
"#;
test_format_item(test_src, "Point", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... Point final {
...
public:
__COMMENT__ "Default::default"
Point();
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void ...(::rust_out::Point* __ret_ptr);
}
inline Point::Point() {
...(this);
}
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__ret_ptr: *mut core::ffi::c_void
) -> () {
unsafe {
let __rs_return_value =
<::rust_out::Point as ::core::default::Default>::default();
(__ret_ptr as *mut ::rust_out::Point).write(__rs_return_value);
}
}
}
);
});
}
#[test]
fn test_format_item_struct_with_copy_trait() {
let test_src = r#"
#![allow(dead_code)]
#[derive(Clone, Copy)]
pub struct Point(i32, i32);
"#;
let msg = "Rust types that are `Copy` get trivial, `default` C++ copy constructor \
and assignment operator.";
test_format_item(test_src, "Point", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... Point final {
...
public:
...
__COMMENT__ #msg
Point(const Point&) = default;
Point& operator=(const Point&) = default;
...
};
}
);
// Trivial copy doesn't require any C++ details except `static_assert`s.
assert_cc_not_matches!(result.cc_details.tokens, quote! { Point::Point(const Point&) },);
assert_cc_not_matches!(result.cc_details.tokens, quote! { Point::operator=(const Point&) },);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
static_assert(std::is_trivially_copy_constructible_v<Point>);
static_assert(std::is_trivially_copy_assignable_v<Point>);
},
);
// Trivial copy doesn't require any Rust details.
assert_rs_not_matches!(result.rs_details.tokens, quote! { Copy });
assert_rs_not_matches!(result.rs_details.tokens, quote! { copy });
});
}
/// Test of `generate_copy_ctor_and_assignment_operator` when the ADT
/// implements a `Clone` trait.
///
/// Notes:
/// * `Copy` trait is covered in `test_format_item_struct_with_copy_trait`.
/// * The test below implements `clone` and uses the default `clone_from`.
#[test]
fn test_format_item_struct_with_clone_trait() {
let test_src = r#"
#![allow(dead_code)]
pub struct Point(i32, i32);
impl Clone for Point {
fn clone(&self) -> Self {
unimplemented!()
}
}
"#;
test_format_item(test_src, "Point", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... Point final {
...
public:
...
__COMMENT__ "Clone::clone"
Point(const Point&);
__COMMENT__ "Clone::clone_from"
Point& operator=(const Point&);
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
extern "C" void ...(::rust_out::Point const&, ::rust_out::Point* __ret_ptr);
}
namespace __crubit_internal {
extern "C" void ...(::rust_out::Point&, ::rust_out::Point const&);
}
inline Point::Point(const Point& other) {
__crubit_internal::...(other, this);
}
inline Point& Point::operator=(const Point& other) {
if (this != &other) {
__crubit_internal::...(*this, other);
}
return *this;
}
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static ::rust_out::Point,
__ret_ptr: *mut core::ffi::c_void
) -> () {
unsafe {
let __rs_return_value =
<::rust_out::Point as ::core::clone::Clone>::clone(__self);
(__ret_ptr as *mut ::rust_out::Point).write(__rs_return_value);
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static mut ::rust_out::Point,
source: &'static ::rust_out::Point
) -> () {
unsafe { <::rust_out::Point as ::core::clone::Clone>::clone_from(__self, source) }
}
}
);
});
}
fn test_format_item_struct_with_custom_drop_and_no_default_nor_clone_impl(
test_src: &str,
pass_by_value_line_number: i32,
) {
test_format_item(test_src, "TypeUnderTest", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let move_deleted_msg = "C++ moves are deleted \
because there's no non-destructive implementation available.";
let pass_by_value_msg = format!(
"Error generating bindings for `TypeUnderTest::pass_by_value` \
defined at <crubit_unittests.rs>;l={pass_by_value_line_number}: \
Can't pass a type by value without a move constructor"
);
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... TypeUnderTest final {
...
public:
...
__COMMENT__ "Drop::drop"
~TypeUnderTest();
__COMMENT__ #move_deleted_msg
TypeUnderTest(TypeUnderTest&&) = delete;
TypeUnderTest& operator=(TypeUnderTest&&) = delete;
...
__COMMENT__ #pass_by_value_msg
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
// `drop` thunk decl
extern "C" void ...(::rust_out::TypeUnderTest&);
}
inline TypeUnderTest::~TypeUnderTest() {
__crubit_internal::...(*this);
}
}
);
assert_cc_not_matches!(result.cc_details.tokens, quote! { pass_by_value });
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
#[unsafe(no_mangle)]
extern "C" fn ...(
__self: &'static mut ::core::mem::MaybeUninit<::rust_out::TypeUnderTest>
) {
unsafe { __self.assume_init_drop() };
}
...
}
);
assert_rs_not_matches!(result.rs_details.tokens, quote! { pass_by_value });
});
}
#[test]
fn test_format_item_struct_with_custom_drop_impl_and_no_default_nor_clone_impl() {
let test_src = r#"
pub struct TypeUnderTest {
pub x: i32,
pub y: i32,
}
impl Drop for TypeUnderTest {
fn drop(&mut self) {}
}
impl TypeUnderTest {
pub fn return_by_value() -> Self { unimplemented!() }
pub fn pass_by_value(_: Self) { unimplemented!() }
}
"#;
let pass_by_value_line_number = 13;
test_format_item_struct_with_custom_drop_and_no_default_nor_clone_impl(
test_src,
pass_by_value_line_number,
);
}
#[test]
fn test_format_item_struct_with_custom_drop_glue_and_no_default_nor_clone_impl() {
let test_src = r#"
#![allow(dead_code)]
// `i32` is present to avoid hitting the ZST checks related to (b/258259459)
struct StructWithCustomDropImpl(i32);
impl Drop for StructWithCustomDropImpl {
fn drop(&mut self) {
println!("dropping!");
}
}
pub struct TypeUnderTest {
field: StructWithCustomDropImpl,
}
impl TypeUnderTest {
pub fn return_by_value() -> Self { unimplemented!() }
pub fn pass_by_value(_: Self) { unimplemented!() }
}
"#;
let pass_by_value_line_number = 19;
test_format_item_struct_with_custom_drop_and_no_default_nor_clone_impl(
test_src,
pass_by_value_line_number,
);
}
fn test_format_item_struct_with_custom_drop_and_with_default_impl(test_src: &str) {
test_format_item(test_src, "TypeUnderTest", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... TypeUnderTest final {
...
public:
...
__COMMENT__ "Drop::drop"
~TypeUnderTest();
TypeUnderTest(TypeUnderTest&&);
TypeUnderTest& operator=(
TypeUnderTest&&);
...
static ::rust_out::TypeUnderTest pass_by_value();
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
// `drop` thunk decl
extern "C" void ...(::rust_out::TypeUnderTest&);
}
inline TypeUnderTest::~TypeUnderTest() {
__crubit_internal::...(*this);
}
inline TypeUnderTest::TypeUnderTest(
TypeUnderTest&& other)
: TypeUnderTest() {
*this = std::move(other);
}
inline TypeUnderTest& TypeUnderTest::operator=(
TypeUnderTest&& other) {
crubit::MemSwap(*this, other);
return *this;
}
namespace __crubit_internal { // `pass_by_value` thunk decl
extern "C" void ...(::rust_out::TypeUnderTest* __ret_ptr);
}
inline ::rust_out::TypeUnderTest TypeUnderTest::pass_by_value() {
crubit::Slot<::rust_out::TypeUnderTest> __return_value_ret_val_holder;
auto* __return_value_storage = __return_value_ret_val_holder.Get();
__crubit_internal::...(__return_value_storage);
return std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static mut ::core::mem::MaybeUninit<::rust_out::TypeUnderTest>
) {
unsafe { __self.assume_init_drop() };
}
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__ret_ptr: *mut core::ffi::c_void
) -> () {
unsafe {
let __rs_return_value = ::rust_out::TypeUnderTest::pass_by_value();
(__ret_ptr as *mut ::rust_out::TypeUnderTest).write(__rs_return_value);
}
}
...
}
);
});
}
#[test]
fn test_format_item_struct_with_custom_drop_impl_and_with_default_impl() {
let test_src = r#"
#[derive(Default)]
pub struct TypeUnderTest {
pub x: i32,
pub y: i32,
}
impl Drop for TypeUnderTest {
fn drop(&mut self) {}
}
impl TypeUnderTest {
pub fn pass_by_value() -> Self { unimplemented!() }
}
"#;
test_format_item_struct_with_custom_drop_and_with_default_impl(test_src);
}
#[test]
fn test_format_item_struct_with_custom_drop_glue_and_with_default_impl() {
let test_src = r#"
#![allow(dead_code)]
// `i32` is present to avoid hitting the ZST checks related to (b/258259459)
#[derive(Default)]
struct StructWithCustomDropImpl(i32);
impl Drop for StructWithCustomDropImpl {
fn drop(&mut self) {
println!("dropping!");
}
}
#[derive(Default)]
pub struct TypeUnderTest {
field: StructWithCustomDropImpl,
}
impl TypeUnderTest {
pub fn pass_by_value() -> Self { unimplemented!() }
}
"#;
test_format_item_struct_with_custom_drop_and_with_default_impl(test_src);
}
fn test_format_item_struct_with_custom_drop_and_no_default_and_clone(test_src: &str) {
test_format_item(test_src, "TypeUnderTest", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... TypeUnderTest final {
...
public:
...
__COMMENT__ "Drop::drop"
~TypeUnderTest();
...
static ::rust_out::TypeUnderTest pass_by_value();
...
};
}
);
// Implicit, but not `=default`-ed move constructor and move assignment
// operator.
assert_cc_not_matches!(main_api.tokens, quote! { TypeUnderTest(TypeUnderTest&&) });
assert_cc_not_matches!(main_api.tokens, quote! { operator=(TypeUnderTest&&) });
// No definition of a custom move constructor nor move assignment operator.
assert_cc_not_matches!(result.cc_details.tokens, quote! { TypeUnderTest(TypeUnderTest&&) },);
assert_cc_not_matches!(result.cc_details.tokens, quote! { operator=(TypeUnderTest&&) },);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
namespace __crubit_internal {
// `drop` thunk decl
extern "C" void ...(::rust_out::TypeUnderTest&);
}
...
namespace __crubit_internal { // `pass_by_value` thunk decl
extern "C" void ...(::rust_out::TypeUnderTest* __ret_ptr);
}
inline ::rust_out::TypeUnderTest TypeUnderTest::pass_by_value() {
crubit::Slot<::rust_out::TypeUnderTest> __return_value_ret_val_holder;
auto* __return_value_storage = __return_value_ret_val_holder.Get();
__crubit_internal::__crubit_thunk_pass_uby_uvalue(__return_value_storage);
return std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
...
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
#[unsafe(no_mangle)]
extern "C" fn ...(
__self: &'static mut ::core::mem::MaybeUninit<::rust_out::TypeUnderTest>
) {
unsafe { __self.assume_init_drop() };
}
...
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(__ret_ptr: *mut core::ffi::c_void) -> () {
unsafe {
let __rs_return_value = ::rust_out::TypeUnderTest::pass_by_value();
(__ret_ptr as *mut ::rust_out::TypeUnderTest).write(__rs_return_value);
}
}
...
}
);
});
}
#[test]
fn test_format_item_struct_with_custom_drop_impl_and_no_default_and_clone() {
let test_src = r#"
#[derive(Clone)]
pub struct TypeUnderTest {
pub x: i32,
pub y: i32,
}
impl Drop for TypeUnderTest {
fn drop(&mut self) {}
}
impl TypeUnderTest {
pub fn pass_by_value() -> Self { unimplemented!() }
}
"#;
test_format_item_struct_with_custom_drop_and_no_default_and_clone(test_src);
}
#[test]
fn test_format_item_struct_with_custom_drop_glue_and_no_default_and_clone() {
let test_src = r#"
#![allow(dead_code)]
// `i32` is present to avoid hitting the ZST checks related to (b/258259459)
#[derive(Clone)]
struct StructWithCustomDropImpl(i32);
impl Drop for StructWithCustomDropImpl {
fn drop(&mut self) {
println!("dropping!");
}
}
#[derive(Clone)]
pub struct TypeUnderTest {
field: StructWithCustomDropImpl,
}
impl TypeUnderTest {
pub fn pass_by_value() -> Self { unimplemented!() }
}
"#;
test_format_item_struct_with_custom_drop_and_no_default_and_clone(test_src);
}
#[test]
fn test_format_item_unsupported_struct_with_custom_drop_and_default_and_nonunpin() {
let test_src = r#"
#![feature(negative_impls)]
#[derive(Default)]
pub struct SomeStruct {
pub x: i32,
pub y: i32,
}
impl !Unpin for SomeStruct {}
impl Drop for SomeStruct {
fn drop(&mut self) {}
}
impl SomeStruct {
pub fn return_by_value() -> Self { unimplemented!() }
pub fn pass_by_value(_: Self) { unimplemented!() }
}
"#;
test_format_item(test_src, "SomeStruct", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let move_deleted_msg = "C++ moves are deleted \
because there's no non-destructive implementation available.";
let pass_by_value_msg = "Error generating bindings for `SomeStruct::pass_by_value` \
defined at <crubit_unittests.rs>;l=18: \
Can't pass a type by value without a move constructor";
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct ... SomeStruct final {
...
public:
...
__COMMENT__ "Default::default"
SomeStruct();
__COMMENT__ "Drop::drop"
~SomeStruct();
__COMMENT__ #move_deleted_msg
SomeStruct(SomeStruct&&) = delete;
SomeStruct& operator=(SomeStruct&&) = delete;
...
static ::rust_out::SomeStruct return_by_value();
__COMMENT__ #pass_by_value_msg
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
...
namespace __crubit_internal {
// `default` thunk decl
extern "C" void ...(::rust_out::SomeStruct* __ret_ptr);
}
inline SomeStruct::SomeStruct() {
__crubit_internal::...(this);
}
namespace __crubit_internal {
// `drop` thunk decl
extern "C" void ...(::rust_out::SomeStruct&);
}
inline SomeStruct::~SomeStruct() {
__crubit_internal::...(*this);
}
...
}
);
assert_cc_not_matches!(result.cc_details.tokens, quote! { pass_by_value });
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__ret_ptr: *mut core::ffi::c_void
) -> () {
unsafe {
let __rs_return_value = <::rust_out::SomeStruct as ::core::default::Default>::default();
(__ret_ptr as *mut ::rust_out::SomeStruct).write(__rs_return_value);
}
}
#[unsafe(no_mangle)]
extern "C" fn ...(
__self: &'static mut ::core::mem::MaybeUninit<::rust_out::SomeStruct>
) {
unsafe { __self.assume_init_drop() };
}
...
}
);
assert_rs_not_matches!(result.rs_details.tokens, quote! { pass_by_value });
});
}
#[test]
fn test_format_item_source_loc_macro_rules() {
let test_src = r#"
#![allow(dead_code)]
macro_rules! some_tuple_struct_macro_for_testing_source_loc {
() => {
/// Some doc on SomeTupleStructMacroForTesingSourceLoc.
pub struct SomeTupleStructMacroForTesingSourceLoc(i32);
};
}
some_tuple_struct_macro_for_testing_source_loc!();
"#;
test_format_item(test_src, "SomeTupleStructMacroForTesingSourceLoc", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let source_loc_comment = " Some doc on SomeTupleStructMacroForTesingSourceLoc.\n\n\
Generated from: <crubit_unittests.rs>;l=7";
assert_cc_matches!(
main_api.tokens,
quote! {
__COMMENT__ #source_loc_comment
struct ... SomeTupleStructMacroForTesingSourceLoc final {
...
}
...
},
);
});
}
#[test]
fn test_format_item_source_loc_with_no_doc_comment() {
let test_src = r#"
#![allow(dead_code)]
pub struct SomeTupleStructWithNoDocComment(i32);
"#;
test_format_item(test_src, "SomeTupleStructWithNoDocComment", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
let comment = "Generated from: <crubit_unittests.rs>;l=4";
assert_cc_matches!(
main_api.tokens,
quote! {
__COMMENT__ #comment
struct ... SomeTupleStructWithNoDocComment final {
...
}
...
},
);
});
}
#[test]
fn test_format_item_unsupported_static_value() {
let test_src = r#"
#[unsafe(no_mangle)]
pub static STATIC_VALUE: i32 = 42;
"#;
test_format_item(test_src, "STATIC_VALUE", |result| {
let err = result.unwrap_err();
assert_eq!(err, "Unsupported rustc_hir::hir::DefKind: Static { safety: Safe, mutability: Not, nested: false }");
});
}
#[test]
fn test_format_item_use_normal_type() {
let test_src = r#"
pub mod test_mod {
pub struct S{
pub field: i32
}
}
pub use test_mod::S as G;
"#;
test_format_item(test_src, "G", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
using G CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: test_mod :: S") = ::rust_out::test_mod::S;
}
);
});
}
#[test]
fn test_generate_bindings_use_list_items() {
let test_src = r#"
pub mod test_mod {
pub struct X{
pub field: i32
}
pub struct Y{
pub field: i32
}
}
pub use test_mod::{X, Y};
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
using X CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: test_mod :: X") = ::rust_out::test_mod::X;
using Y CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: test_mod :: Y") = ::rust_out::test_mod::Y;
}
);
});
}
#[test]
fn test_generate_bindings_use_glob() {
let test_src = r#"
pub mod test_mod {
pub struct X{
pub field: i32
}
pub struct Y{
pub field: i32
}
}
pub use test_mod::*;
"#;
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
assert_cc_matches!(
bindings.cc_api,
quote! {
using X CRUBIT_INTERNAL_RUST_TYPE (":: rust_out :: test_mod :: X") = ::rust_out::test_mod::X;
using Y CRUBIT_INTERNAL_RUST_TYPE (":: rust_out :: test_mod :: Y") = ::rust_out::test_mod::Y;
}
);
});
}
#[test]
fn test_format_item_type_alias() {
let test_src = r#"
pub type TypeAlias = i32;
"#;
test_format_item(test_src, "TypeAlias", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
using TypeAlias CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: TypeAlias") = std::int32_t;
}
);
});
}
#[test]
fn test_format_item_type_alias_should_give_underlying_type() {
let test_src = r#"
pub type TypeAlias1 = i32;
pub type TypeAlias2 = TypeAlias1;
"#;
test_format_item(test_src, "TypeAlias2", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
using TypeAlias2 CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: TypeAlias2") = std::int32_t;
}
);
});
}
#[test]
fn test_format_item_private_type_alias_wont_generate_bindings() {
let test_src = r#"
#[allow(dead_code)]
type TypeAlias = i32;
"#;
test_format_item(test_src, "TypeAlias", |result| {
let result = result.unwrap();
assert!(result.is_none());
});
}
#[test]
fn test_format_item_pub_type_alias_on_private_type_wont_generate_bindings() {
let test_src = r#"
#![allow(private_interfaces)]
struct SomeStruct;
pub type TypeAlias = SomeStruct;
"#;
test_format_item(test_src, "TypeAlias", |result| {
let err = result.unwrap_err();
assert_eq!(err, "Not a public or a supported reexported type (b/262052635).");
});
}
#[test]
fn test_format_item_unsupported_generic_type_alias() {
let test_src = r#"
pub type TypeAlias<T> = T;
"#;
test_format_item(test_src, "TypeAlias", |result| {
let err = result.unwrap_err();
assert_eq!(err, "The following Rust type is not supported yet: T");
});
}
#[test]
fn test_format_item_unsupported_type_without_direct_existence() {
let test_src = r#"
pub trait Evil {
type Type;
}
const _ : () = {
pub struct NamelessType;
impl Evil for i64 {
type Type = NamelessType;
}
};
pub type EvilAlias = <i64 as Evil>::Type;
"#;
test_format_item(test_src, "EvilAlias", |result| {
let err = result.unwrap_err();
assert_eq!(err, "The following Rust type is not supported yet: <i64 as Evil>::Type");
});
}
#[test]
fn test_format_item_type_alias_deprecated() {
let test_src = r#"
#[deprecated = "Use `OtherTypeAlias` instead"]
pub type TypeAlias = i32;
"#;
test_format_item(test_src, "TypeAlias", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
using TypeAlias
CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: TypeAlias")
[[deprecated("Use `OtherTypeAlias` instead")]]
= std::int32_t;
}
);
});
}
#[test]
fn test_format_item_generate_bindings_for_top_level_type_alias() {
let test_src = r#"
#![feature(inherent_associated_types)]
#![allow(incomplete_features)]
#![allow(dead_code)]
pub struct Evil {
dumb: i32,
}
impl Evil {
pub type Type = i64;
}
pub type EvilAlias = Evil::Type;
"#;
test_format_item(test_src, "Evil", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_not_matches!(
main_api.tokens,
quote! {
std::int64_t
}
);
});
}
#[test]
fn test_format_namespace_bound_cc_tokens() {
run_compiler_for_testing("", |tcx| {
let db = bindings_db_for_tests(tcx);
let top_level = NamespaceQualifier::new::<&str>([]);
let m1 = NamespaceQualifier::new(["m1"]);
let m2 = NamespaceQualifier::new(["m2"]);
let input = [
(None, top_level.clone(), quote! { void f0a(); }),
(None, m1.clone(), quote! { void f1a(); }),
(None, m1.clone(), quote! { void f1b(); }),
(None, top_level.clone(), quote! { void f0b(); }),
(None, top_level.clone(), quote! { void f0c(); }),
(None, m2.clone(), quote! { void f2a(); }),
(None, m1.clone(), quote! { void f1c(); }),
(None, m1.clone(), quote! { void f1d(); }),
];
assert_cc_matches!(
format_namespace_bound_cc_tokens(&db, input, tcx),
quote! {
void f0a();
namespace m1 {
void f1a();
void f1b();
} // namespace m1
void f0b();
void f0c();
namespace m2 {
void f2a();
}
namespace m1 {
void f1c();
void f1d();
} // namespace m1
},
);
});
}
#[test]
fn test_multiple_attributes() {
let test_src = r#"
#[must_use = "Must use"]
#[deprecated = "Deprecated"]
pub fn add(x: i32, y: i32) -> i32 {
x + y
}"#;
test_format_item(test_src, "add", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
[[nodiscard("Must use")]] [[deprecated("Deprecated")]] std::int32_t add(std::int32_t x, std::int32_t y);
...
}
)
})
}
#[test]
fn test_repr_c_union_fields_impl_clone() {
let test_src = r#"
#[repr(C)]
pub union SomeUnion {
pub x: u32,
}
impl Clone for SomeUnion {
fn clone(&self) -> SomeUnion {
return SomeUnion {x: 1}
}
}
const _: () = assert!(std::mem::size_of::<SomeUnion>() == 4);
const _: () = assert!(std::mem::align_of::<SomeUnion>() == 4);
"#;
test_format_item(test_src, "SomeUnion", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
...
union CRUBIT_INTERNAL_RUST_TYPE(...) alignas(4) [[clang::trivial_abi]] SomeUnion final {
public:
...
__COMMENT__ "Clone::clone"
SomeUnion(const SomeUnion&);
__COMMENT__ "Clone::clone_from"
SomeUnion& operator=(const SomeUnion&);
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
...
static_assert(std::is_trivially_destructible_v<SomeUnion>);
static_assert(std::is_trivially_move_constructible_v<SomeUnion>);
static_assert(std::is_trivially_move_assignable_v<SomeUnion>);
...
inline SomeUnion::SomeUnion(const SomeUnion& other) {...}
inline SomeUnion& SomeUnion::operator=(const SomeUnion& other) {...}
...
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static ::rust_out::SomeUnion,
__ret_ptr: *mut core::ffi::c_void
) -> () {
unsafe {
let __rs_return_value = <::rust_out::SomeUnion as ::core::clone::Clone>::clone(__self);
(__ret_ptr as *mut ::rust_out::SomeUnion).write(__rs_return_value);
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn ...(
__self: &'static mut ::rust_out::SomeUnion,
source: &'static ::rust_out::SomeUnion
) -> () {
unsafe { <::rust_out::SomeUnion as ::core::clone::Clone>::clone_from(__self, source) }
}
}
);
})
}
#[test]
fn test_repr_c_union_fields_impl_drop() {
let test_src = r#"
#[repr(C)]
pub union SomeUnion {
pub x: u32,
}
impl Drop for SomeUnion {
fn drop(&mut self) {
println!(":)")
}
}
const _: () = assert!(std::mem::size_of::<SomeUnion>() == 4);
const _: () = assert!(std::mem::align_of::<SomeUnion>() == 4);
"#;
test_format_item(test_src, "SomeUnion", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
...
union CRUBIT_INTERNAL_RUST_TYPE(...) alignas(4) [[clang::trivial_abi]] SomeUnion final {
public:
...
__COMMENT__ "Drop::drop"
~SomeUnion();
...
SomeUnion(SomeUnion&&) = delete;
SomeUnion& operator=(SomeUnion&&) = delete;
...
...
};
}
);
assert_cc_matches!(
result.cc_details.tokens,
quote! {
...
inline SomeUnion::~SomeUnion() {...}
...
}
);
assert_rs_matches!(
result.rs_details.tokens,
quote! {
...
extern "C" fn ... (__self: &'static mut ::core::mem::MaybeUninit<::rust_out::SomeUnion>...) { unsafe { __self.assume_init_drop() }; }
...
}
);
})
}
#[test]
fn test_repr_c_enum_drop() {
let test_src = r#"
#[repr(C, i32)]
pub enum SomeEnum {
A(i32),
B{x: u32},
C,
D{foo: i32, bar: i32} = 3,
}
impl Drop for SomeEnum {
fn drop(&mut self) {
println!(":)")
}
}
const _: () = assert!(std::mem::size_of::<SomeEnum>() == 12);
const _: () = assert!(std::mem::align_of::<SomeEnum>() == 4);
"#;
test_format_item(test_src, "SomeEnum", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) ... [[clang::trivial_abi]] SomeEnum final {
public:
...
__COMMENT__ "Drop::drop"
~SomeEnum();
...
SomeEnum(SomeEnum&&) = delete;
SomeEnum& operator=(SomeEnum&&) = delete;
...
...
};
}
);
})
}
#[test]
fn test_repr_c_enum_clone() {
let test_src = r#"
#[repr(C, i32)]
pub enum SomeEnum {
A(i32),
B{x: u32},
C,
D{foo: i32, bar: i32} = 3,
}
impl Clone for SomeEnum {
fn clone(&self) -> SomeEnum {
return SomeEnum::A(1)
}
}
const _: () = assert!(std::mem::size_of::<SomeEnum>() == 12);
const _: () = assert!(std::mem::align_of::<SomeEnum>() == 4);
"#;
test_format_item(test_src, "SomeEnum", |result| {
let result = result.unwrap().unwrap();
let main_api = &result.main_api;
assert!(!main_api.prereqs.is_empty());
assert_cc_matches!(
main_api.tokens,
quote! {
...
struct CRUBIT_INTERNAL_RUST_TYPE(...) ... [[clang::trivial_abi]] SomeEnum final {
public:
...
__COMMENT__ "Clone::clone"
SomeEnum(const SomeEnum&);
__COMMENT__ "Clone::clone_from"
SomeEnum& operator=(const SomeEnum&);
...
};
}
);
})
}