blob: a54156d3b8e49ce80b5470f44e68e58037a1d8d6 [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(negative_impls)]
#[cfg(test)]
mod tests {
use ctor::{ctor, emplace, mov, ConstRvalueReference, Ctor, Emplace, RvalueReference};
use ctor::{Assign as _, CtorNew as _, ReconstructUnchecked as _};
use googletest::prelude::*;
use nonunpin::{Nonmovable, Nonunpin, NonunpinStruct, ReturnsNonmovable};
use std::pin::Pin;
/// When a value is constructed in-place, it is initialized, has the correct
/// address.
#[gtest]
fn test_onearg_ctor() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(42);
}
assert_eq!(x.value(), 42);
assert_eq!(x.addr(), &*x as *const _ as usize);
}
#[gtest]
fn test_default_ctor() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(());
}
assert_eq!(x.value(), 0);
assert_eq!(x.addr(), &*x as *const _ as usize);
}
#[gtest]
fn test_move_construct() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(42);
let mut y = ctor::mov!(x.as_mut());
}
assert_eq!(x.value(), 0); // moved-from
assert_eq!(y.value(), 42); // moved-to
assert_eq!(x.addr(), &*x as *const _ as usize);
assert_eq!(y.addr(), &*y as *const _ as usize);
}
#[gtest]
fn test_move_assign() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(42);
let mut y = Nonunpin::ctor_new(8);
}
y.as_mut().assign(ctor::mov!(x.as_mut()));
assert_eq!(x.value(), 0); // moved-from
assert_eq!(y.value(), 42); // moved-to
assert_eq!(x.addr(), &*x as *const _ as usize);
assert_eq!(y.addr(), &*y as *const _ as usize);
}
#[gtest]
fn test_copy_construct() {
ctor::emplace! {
let x = Nonunpin::ctor_new(42);
let y = ctor::copy(&*x);
}
assert_eq!(x.value(), 42);
assert_eq!(y.value(), 42);
assert_eq!(x.addr(), &*x as *const _ as usize);
assert_eq!(y.addr(), &*y as *const _ as usize);
}
#[gtest]
fn test_copy_assign() {
ctor::emplace! {
let x = Nonunpin::ctor_new(42);
let mut y = Nonunpin::ctor_new(8);
}
y.as_mut().assign(&*x);
assert_eq!(x.value(), 42);
assert_eq!(y.value(), 42);
assert_eq!(x.addr(), &*x as *const _ as usize);
assert_eq!(y.addr(), &*y as *const _ as usize);
}
#[gtest]
fn test_methods() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(42);
}
x.as_mut().set_value(24);
assert_eq!(x.value(), 24);
}
/// Test that the struct can be returned and passed as all the reference
/// types, and passed by value.
#[gtest]
fn test_ref() {
ctor::emplace! {
let mut x = Nonunpin::ctor_new(42);
}
{
let x_ref: Pin<&mut Nonunpin> = x.as_mut().AsMutRef();
assert_eq!(nonunpin::GetValueFromMutRef(x_ref), 42);
assert_eq!(nonunpin::GetValueFromMutRef(x.as_mut()), 42);
}
{
let x_ref: &Nonunpin = x.AsConstRef();
assert_eq!(nonunpin::GetValueFromConstRef(x_ref), 42);
assert_eq!(nonunpin::GetValueFromConstRef(&x), 42);
}
{
let x_ref: RvalueReference<Nonunpin> = x.as_mut().AsRvalueRef();
assert_eq!(nonunpin::GetValueFromRvalueRef(x_ref), 42);
assert_eq!(nonunpin::GetValueFromRvalueRef(ctor::mov!(x.as_mut())), 42);
}
{
let x_ref: ConstRvalueReference<Nonunpin> = x.AsConstRvalueRef();
assert_eq!(nonunpin::GetValueFromConstRvalueRef(x_ref), 42);
assert_eq!(nonunpin::GetValueFromConstRvalueRef(ctor::const_mov!(&*x)), 42);
assert_eq!(nonunpin::GetValueFromConstRvalueRef(ctor::const_mov!(x.as_mut())), 42);
}
{
assert_eq!(nonunpin::GetValueFromValue(ctor::copy(&*x)), 42);
assert_eq!(nonunpin::GetValueFromValue(ctor::mov!(x)), 42);
}
}
#[gtest]
fn test_aggregate() {
ctor::emplace! {
let mut x = ctor!(NonunpinStruct {value: 42});
}
assert_eq!(x.value, 42);
{
// Read/write via a pin-projection.
let mut x = x.as_mut().project_pin();
assert_eq!(*x.value, 42);
*x.value = 0;
assert_eq!(*x.value, 0);
}
assert_eq!(x.value, 0);
}
#[gtest]
fn test_return_by_value() {
ctor::emplace! {
let x = Nonunpin::ctor_new(42);
let y = x.AsValue();
}
assert_eq!(x.value(), 42);
assert_eq!(y.value(), 42);
assert_eq!(x.addr(), &*x as *const _ as usize);
assert_eq!(y.addr(), &*y as *const _ as usize);
}
#[gtest]
fn test_nonmovable_ctor() {
ctor::emplace! {
let x = Nonmovable::ctor_new(());
}
assert_eq!(x.addr, &*x as *const _ as usize);
}
/// Thanks to C++17 prvalue semantics, we can in fact return a non-movable
/// type by value.
#[gtest]
fn test_nonmovable_return_value() {
ctor::emplace! {
let x = ReturnsNonmovable();
}
assert_eq!(x.addr, &*x as *const _ as usize);
}
/// An example showing a C++ non-trivially-relocatable class as a field in a
/// Rust struct. There are two ways to do this:
///
/// 1. storing C++ class indirectly (e.g., in a Box), or,
/// 2. storing by-value.
///
/// This test specicially demonstrates the second: storing a C++ class by
/// value, even in the worst case of it not being trivially-relocatable.
/// In that case, the struct containing it must *also* become
/// non-trivially-relocatable, and it becomes ~exactly as difficult to deal
/// with as the C++ class it contains.
#[gtest]
fn test_struct_field() {
#[ctor::recursively_pinned]
struct MyStruct {
field_1: u32,
field_2: Nonunpin,
}
impl MyStruct {
fn new() -> impl Ctor<Output = Self> {
ctor!(MyStruct { field_1: 4, field_2: Nonunpin::ctor_new(2) })
}
}
emplace! { let mut my_struct = MyStruct::new(); }
assert_eq!(my_struct.field_1, 4);
assert_eq!(my_struct.field_2.value(), 2);
// use projection (from recursively_pinned/pin_project) to mutate the struct:
let mut my_struct = my_struct.project_pin();
*my_struct.field_1 = 5;
my_struct.field_2.as_mut().assign(mov!(emplace!(Nonunpin::ctor_new(3))));
assert_eq!(*my_struct.field_1, 5);
assert_eq!(my_struct.field_2.value(), 3);
}
/// An example showing a C++ non-trivially-relocatable class as a field in a
/// Rust union. This mirrors the struct case, storing by value.
///
/// It is also quite ugly, but, fortunately, these unions are not common.
#[gtest]
fn test_union_field() {
union MyUnion {
int: u32,
cxx_class: ::std::mem::ManuallyDrop<Nonunpin>,
}
unsafe impl ctor::RecursivelyPinned for MyUnion {
type CtorInitializedFields = Self;
}
// No safe helpers here. :)
unsafe {
emplace! {
let mut my_union = ctor!(MyUnion {
cxx_class: ctor::ManuallyDropCtor::new(Nonunpin::ctor_new(4))
});
}
assert_eq!(my_union.cxx_class.value(), 4);
std::mem::ManuallyDrop::drop(
&mut Pin::into_inner_unchecked(my_union.as_mut()).cxx_class,
);
my_union.as_mut().reconstruct_unchecked(ctor!(MyUnion { int: 2 }));
assert_eq!(my_union.int, 2);
}
}
/// The example from the ctor.rs docs; copy-pasted.
#[gtest]
fn test_swap() {
fn swap(mut x: Pin<&mut Nonunpin>, mut y: Pin<&mut Nonunpin>) {
emplace! { let mut tmp = mov!(x.as_mut()); }
x.assign(mov!(y.as_mut()));
y.assign(mov!(tmp));
}
let mut c1 = Box::emplace(Nonunpin::ctor_new(1));
let mut c2 = Box::emplace(Nonunpin::ctor_new(2));
swap(c1.as_mut(), c2.as_mut());
assert_eq!(c1.value(), 2);
assert_eq!(c2.value(), 1);
}
}