| // 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 googletest::prelude::*; |
| |
| // pathological shadowed names: shadow important modules that the macros use. |
| mod std {} |
| mod forward_declare {} |
| |
| mod test_is_same_0 { |
| type _Expected = ::forward_declare::internal::Symbol<()>; |
| fn _is_same(x: _Expected) -> ::forward_declare::symbol!("") { |
| x |
| } |
| } |
| |
| mod test_is_same_1 { |
| type _Expected = ::forward_declare::internal::Symbol<(::forward_declare::internal::C<'x'>,)>; |
| fn _is_same(x: _Expected) -> ::forward_declare::symbol!("x") { |
| x |
| } |
| } |
| |
| mod test_is_same_3 { |
| type _Expected = ::forward_declare::internal::Symbol<( |
| ::forward_declare::internal::C<'f'>, |
| ::forward_declare::internal::C<'o'>, |
| ::forward_declare::internal::C<'o'>, |
| )>; |
| fn _is_same(x: _Expected) -> ::forward_declare::symbol!("foo") { |
| x |
| } |
| } |
| |
| #[gtest] |
| fn test_conversions() { |
| use ::forward_declare::CcCast as _; // test becomes too verbose otherwise. |
| struct MyType; |
| type MyTypeSymbol = ::forward_declare::symbol!("X"); |
| ::forward_declare::unsafe_define!(MyTypeSymbol, MyType); |
| |
| let mut complete = MyType; |
| ::forward_declare::forward_declare!(MyTypeIncomplete = MyTypeSymbol); |
| |
| fn ptr_location(x: impl ::std::ops::Deref) -> usize { |
| &*x as *const _ as *const u8 as usize |
| } |
| |
| let loc = ptr_location(&complete); |
| |
| // & -> & |
| { |
| let incomplete_ref: &MyTypeIncomplete = (&complete).cc_cast(); |
| let complete_ref: &MyType = incomplete_ref.cc_cast(); |
| assert_eq!(ptr_location(incomplete_ref), ptr_location(complete_ref)); |
| } |
| |
| // Pin<&> <-> Pin<&> |
| { |
| let incomplete_pin_ref: ::std::pin::Pin<&MyTypeIncomplete> = |
| ::std::pin::Pin::new(&complete).cc_cast(); |
| let complete_pin_ref: ::std::pin::Pin<&MyType> = incomplete_pin_ref.cc_cast(); |
| assert_eq!(ptr_location(incomplete_pin_ref), loc); |
| assert_eq!(ptr_location(complete_pin_ref), loc); |
| let complete_unpinned_ref: &MyType = incomplete_pin_ref.cc_cast(); |
| assert_eq!(ptr_location(complete_unpinned_ref), loc); |
| } |
| |
| // Pin<&mut> <-> Pin<&mut> |
| { |
| let incomplete_pin_mut: ::std::pin::Pin<&mut MyTypeIncomplete> = |
| ::std::pin::Pin::new(&mut complete).cc_cast(); |
| assert_eq!(ptr_location(&*incomplete_pin_mut), loc); |
| let complete_pin_mut: ::std::pin::Pin<&mut MyType> = incomplete_pin_mut.cc_cast(); |
| assert_eq!(ptr_location(complete_pin_mut), loc); |
| } |
| |
| { |
| // &mut -> Pin<&mut> |
| let mut incomplete_pin_mut: ::std::pin::Pin<&mut MyTypeIncomplete> = |
| (&mut complete).cc_cast(); |
| assert_eq!(ptr_location(&*incomplete_pin_mut), loc); |
| // Pin<&mut> -> &mut |
| { |
| let complete_unpinned_mut: &mut MyType = incomplete_pin_mut.as_mut().cc_cast(); |
| assert_eq!(ptr_location(complete_unpinned_mut), loc); |
| } |
| // Pin<&mut> -> & |
| { |
| let complete_unpinned_ref: &MyType = incomplete_pin_mut.as_ref().cc_cast(); |
| assert_eq!(ptr_location(complete_unpinned_ref), loc); |
| } |
| } |
| |
| /// Typeless location&length info for a slice. |
| fn slice_location<T>(slice: &[T]) -> (usize, usize) { |
| (slice.as_ptr() as usize, slice.len()) |
| } |
| |
| // Vec<&> <-> Vec<&> |
| { |
| let complete_vec: Vec<&MyType> = vec![&complete]; |
| let loc = slice_location(&complete_vec); |
| |
| let incomplete_vec: Vec<&MyTypeIncomplete> = complete_vec.cc_cast(); |
| assert_eq!(slice_location(&incomplete_vec), loc); |
| let complete_vec: Vec<&MyType> = incomplete_vec.cc_cast(); |
| assert_eq!(slice_location(&complete_vec), loc); |
| } |
| |
| // &[&] <-> &[&] |
| { |
| let complete_vec: Vec<&MyType> = vec![&complete]; |
| let complete_slice: &[&MyType] = complete_vec.as_slice(); |
| let loc = slice_location(complete_slice); |
| |
| let incomplete_slice: &[&MyTypeIncomplete] = complete_slice.cc_cast(); |
| assert_eq!(slice_location(incomplete_slice), loc); |
| let complete_slice: &[&MyType] = incomplete_slice.cc_cast(); |
| assert_eq!(slice_location(complete_slice), loc); |
| } |
| |
| // [&; N] <-> [&; N] |
| { |
| let complete_array: [&MyType; 2] = [&complete, &complete]; |
| let incomplete_array: [&MyTypeIncomplete; 2] = complete_array.cc_cast(); |
| // TODO(jeanpierreda, lukasza): Avoid copying the array to a different memory location |
| // (maybe by tweaking `cc_cast()` to use `std::mem::transmute` |
| // instead of `std::mem::transmute_copy` when the input and output types |
| // are both `Sized`). Once that is done, we should be able to add |
| // asserts that say: |
| // |
| // let loc = slice_location(&complete_array); |
| // ... |
| // assert_eq!(slice_location(&incomplete_array), loc) |
| // ... |
| // assert_eq!(slice_location(&complete_array), loc) |
| let _complete_array: [&MyType; 2] = incomplete_array.cc_cast(); |
| } |
| } |
| |
| /// You should be able to call unsafe_define!() twice (on different types) in |
| /// the same scope. |
| #[gtest] |
| fn test_hygiene() { |
| struct MyType1; |
| type MyTypeSymbol1 = ::forward_declare::symbol!("X1"); |
| ::forward_declare::unsafe_define!(MyTypeSymbol1, MyType1); |
| |
| struct MyType2; |
| type MyTypeSymbol2 = ::forward_declare::symbol!("X2"); |
| ::forward_declare::unsafe_define!(MyTypeSymbol2, MyType2); |
| } |
| |
| /// Suppose a library used to define its API using an incomplete type, but |
| /// changed to using a complete type? |
| /// This test verifies that callers continue to work as normal. |
| /// |
| /// (The reverse direction, fundamentally, is a lot less likely to work in |
| /// idiomatic code.) |
| #[gtest] |
| fn test_formerly_incomplete() { |
| use ::forward_declare::CcCast as _; // test becomes too verbose otherwise. |
| struct MyType; |
| ::forward_declare::unsafe_define!(::forward_declare::symbol!("X"), MyType); |
| |
| mod callee { |
| ::forward_declare::forward_declare!(pub MyType = ::forward_declare::symbol!("X")); |
| } |
| mod caller { |
| ::forward_declare::forward_declare!(pub MyType = ::forward_declare::symbol!("X")); |
| } |
| fn takes_incomplete(_: &callee::MyType) {} |
| fn takes_complete(_: &MyType) {} |
| |
| // calls which previously were converting a complete type to incomplete type are |
| // now converting a complete type to itself -- not great, but still compiles |
| // and works. |
| let x = MyType; |
| let x = &x; |
| takes_incomplete(x.cc_cast()); // before |
| takes_complete(x.cc_cast()); // after |
| |
| // Calls which previously were converting an incomplete type to an incomplete |
| // will also continue to work. In fact, this is required, since different crates |
| // will define different incomplete types. |
| let x: &caller::MyType = x.cc_cast(); |
| takes_incomplete(x.cc_cast()); // before |
| takes_complete(x.cc_cast()); // after |
| |
| // However, if you passed an incomplete type in without calling |
| // .cc_cast(), that will no longer work. |
| // takes_incomplete(x); // COMPILATION ERROR |
| // takes_complete(x); // COMPILATION ERROR |
| |
| // Symmetrically, you can also convert complete types to incomplete if all |
| // callers call .cc_cast(), but this is much less reasonable a |
| // requirement. |
| } |
| |
| /// In C++, you can define a type as so: |
| /// template<typename T> struct Vector {T* x; size_t length;}; |
| /// and it can be passed by value, even for forward-declared T. |
| /// How would this look in Rust? |
| /// |
| /// The aim of this test is to establish that we can define such a Vector that |
| /// supports conversion for complete/incomplete T, though being Rust, it cannot |
| /// be passed by *value*, only by *reference*. |
| /// |
| /// We can then add an additional trait bound on all methods, `where T : |
| /// Complete`. This turns out to be easier, for silly typing reasons, than |
| /// defining a whole new vector type for incomplete T. |
| #[gtest] |
| fn test_vector_alike() { |
| use ::forward_declare::{ |
| forward_declare, internal::CcType, symbol, unsafe_define, CcCast, Complete, |
| }; |
| struct MyComplete; |
| unsafe_define!(symbol!("T"), MyComplete); |
| forward_declare!(MyIncomplete = symbol!("T")); |
| |
| /// An equivalent to Vector from the function comment, which is a compound |
| /// type that supports conversion. |
| struct Vector<T: ?Sized>(*mut T, usize); |
| unsafe impl<T: ?Sized> CcType for Vector<T> |
| where |
| T: CcType, |
| { |
| type Name = (Vector<()>, T::Name); |
| } |
| |
| /// Methods on `Vector` that don't require complete `T` |
| impl<T: ?Sized> Vector<T> { |
| fn len(&self) -> usize { |
| self.1 |
| } |
| } |
| /// Methods on Vector that require complete `T`. |
| impl<T: Complete> Vector<T> { |
| fn back(&self) -> Option<&T> { |
| if self.len() == 0 { |
| return None; |
| } |
| unimplemented!("An *actual* implementation is not important for this test"); |
| } |
| } |
| |
| fn expects_incomplete(_: &Vector<MyIncomplete>) {} |
| fn expects_complete(_: &Vector<MyComplete>) {} |
| |
| let complete = &Vector(0 as *mut MyComplete, 0); |
| let incomplete: &Vector<MyIncomplete> = complete.cc_cast(); |
| |
| expects_incomplete(complete.cc_cast()); |
| expects_incomplete(incomplete.cc_cast()); |
| expects_complete(incomplete.cc_cast()); |
| expects_complete(complete.cc_cast()); |
| |
| assert!(complete.back().is_none()); // works fine |
| // incomplete.back() // compilation error due to unsatisfied trait bounds |
| // (`!Complete`) |
| } |