Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cpp-overload-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,16 @@ This code is automatically run in CI:

- Submodules of `lib.rs` are expected to succeed. They are named after each C++ class.
- Examples are expected to fail, with output in the corresponding `.stderr` file, also named after C++ classes.

Sometimes the C++ code extracted by `cpp_build` will become outdated, and you will get errors like:

```text
error: This cpp! macro is not found in the library's rust-cpp metadata.
NOTE: Only cpp! macros found directly in the program source will be parsed -
NOTE: They cannot be generated by macro expansion.
...
error: cannot find macro `__cpp_closure_impl` in this scope
```

To fix these errors, run `cargo clean` then rebuild.
If the errors still happen after a clean, check your Rust and C++ code is compiling correctly.
25 changes: 25 additions & 0 deletions cpp-overload-test/examples/stdcpp-forward-list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,29 @@ overload! {
}
}

/// A wrapper struct to hold the returned C++ pointer, with the `W` fake generic type.
/// We also fake the C++ list pointer with a Rust vector to help with type checking.
#[expect(dead_code, reason = "Incompatible overloads can't be used")]
struct StdForwardListW(Vec<W>);

/// Workaround for missing generic support in the `overload!` macro.
type W = std::ffi::c_int;

// Accessors: `front(...)`
overload! {
impl StdForwardList {
/// ### Incompatibilities
///
/// The `overload!` macro can't overload on `&self` vs `&mut self`, because it has to
/// dispatch the overload through a single function, which can only have one receiver type.
fn front(&self) -> Option<&T> where T: Sized {
StdForwardList::front_const(self)
}

fn front(&mut self) -> Option<&mut T> where T: Sized {
StdForwardList::front_mut(self)
}
}
}

pub fn main() {}
15 changes: 14 additions & 1 deletion cpp-overload-test/examples/stdcpp-forward-list.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
error: proc macro panicked
--> cpp-overload-test/examples/stdcpp-forward-list.rs:151:1
|
151 | / overload! {
152 | | impl StdForwardList {
153 | | /// ### Incompatibilities
154 | | ///
... |
165 | | }
| |_^
|
= help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got front: &self and front: &mut self

error[E0119]: conflicting implementations of trait `NewArgs<StdForwardListT>` for type `(usize,)`
--> cpp-overload-test/examples/stdcpp-forward-list.rs:29:1
|
Expand Down Expand Up @@ -31,4 +44,4 @@ error[E0119]: conflicting implementations of trait `NewArgs<StdForwardListT>` fo
= note: this error originates in the macro `overload` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0119`.
error: could not compile `cpp-overload-test` (example "stdcpp-forward-list") due to 2 previous errors
error: could not compile `cpp-overload-test` (example "stdcpp-forward-list") due to 3 previous errors
192 changes: 168 additions & 24 deletions cpp-overload-test/src/stdcpp/forward_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

use cpp::cpp;
use splat_overload::overload;
use std::ffi::{c_size_t, c_void};
// ### Limitations
//
// We want `c_bool` here, but Rust only has `c_char`.
use std::ffi::{c_char, c_size_t, c_void};
use std::marker::PhantomData;

// C++ header includes
Expand All @@ -21,14 +24,17 @@ cpp! {{
///
/// This struct and the impl should be fully generic over the list type.
/// FIXME: make the `overload!` macro support generics.
struct StdForwardList(*mut c_void, PhantomData<T>);
#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))]
pub struct StdForwardList(*mut c_void, PhantomData<T>);

/// Workaround for missing generic support in the `overload!` macro.
type T = std::ffi::c_int;
pub type T = std::ffi::c_int;

// The cpp! macro doesn't work inside the overload! macro, so we extract C++ calls into separate methods.
// A mature overload feature (or a production C++ project) wouldn't need this impl block.
#[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))]
impl StdForwardList {
// Constructors: internal C++ implementations for `new(...)` overloads
fn default() -> StdForwardList {
let list = unsafe {
cpp!([] -> *mut c_void as "std::forward_list<int>*" {
Expand All @@ -41,6 +47,11 @@ impl StdForwardList {
fn repeat_default(count: c_size_t) -> StdForwardList {
let list = unsafe {
cpp!([count as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
// ### Ergonomics
//
// This code is potentially confusing, because it is similar to an initializer
// list constructor:
// `new std::forward_list<int>{ item }`
return new std::forward_list<int>(count);
})
};
Expand Down Expand Up @@ -152,29 +163,80 @@ impl StdForwardList {
};
StdForwardList(list, PhantomData)
}

// Accessors: internal C++ implementations for `front(...)` overloads
fn front_const(&self) -> Option<&T>
where
T: Sized,
{
let slf = self.0 as *const c_void;
unsafe {
cpp!([slf as "const std::forward_list<int>*"] -> Option<&T> as "const int*" {
// This check is required to avoid C++ undefined behaviour on empty lists.
if (slf->empty()) {
return nullptr;
} else {
return &slf->front();
}
})
}
}

fn front_mut(&mut self) -> Option<&mut T>
where
T: Sized,
{
let slf: *mut c_void = self.0;
unsafe {
cpp!([slf as "std::forward_list<int>*"] -> Option<&mut T> as "int*" {
// This check is required to avoid C++ undefined behaviour on empty lists.
if (slf->empty()) {
return nullptr;
} else {
return &slf->front();
}
})
}
}

// List checks
fn is_empty(&self) -> bool {
let slf = self.0 as *const c_void;
let empty = unsafe {
cpp!([slf as "const std::forward_list<int>*"] -> c_char as "char" {
// `char` is FFI-safe, but Rust `bool` is not.
// C++ automatically promotes `bool` to `char`.
return slf->empty();
})
};
// And then we convert the `c_char` back to a valid Rust `bool`.
empty != 0
}
}

// Constructors: `new(...)`
//
// We ignore constructors that only differ by an allocator argument, because they're not
// interesting overloads to test.
overload! {
impl StdForwardList {
/// Construct an empty list.
fn new() -> StdForwardList {
pub fn new() -> StdForwardList {
StdForwardList::default()
}

/// Construct a list with `count` default-constructed items.
fn new(count: c_size_t) -> StdForwardList {
pub fn new(count: c_size_t) -> StdForwardList {
StdForwardList::repeat_default(count)
}

/// Construct a list filled with `count` instances of the given value.
fn new(value: T, count: c_size_t) -> StdForwardList {
pub fn new(value: T, count: c_size_t) -> StdForwardList {
StdForwardList::repeat_with(value, count)
}

/// Construct a list by copying items from a slice.
fn new(items: &[T]) -> StdForwardList {
pub fn new(items: &[T]) -> StdForwardList {
StdForwardList::from_slice(items)
}

Expand All @@ -189,17 +251,17 @@ overload! {
/// The `overload!` macro doesn't support generics in this position yet, so we use
/// `dyn Trait` instead.
/// FIXME: make the macro support generic iterators, or handle `impl Iterator` correctly.
fn new(iter: &mut dyn Iterator<Item = T>) -> StdForwardList {
pub fn new(iter: &mut dyn Iterator<Item = T>) -> StdForwardList {
StdForwardList::from_iter(iter)
}

/// Clone list items into a new list from a shared list reference.
fn new(other: &StdForwardList) -> StdForwardList {
pub fn new(other: &StdForwardList) -> StdForwardList {
StdForwardList::copy_from(other)
}

/// Move list items into a new list from a mutable list reference.
fn new(other: &mut StdForwardList) -> StdForwardList {
pub fn new(other: &mut StdForwardList) -> StdForwardList {
StdForwardList::move_from(other)
}

Expand All @@ -219,42 +281,124 @@ overload! {
/// circumstances.
/// - 2: the `value` repetition constructor, if `T` is `size_t`
/// fn new(_a: T, _b: T, _c: T) -> StdForwardList {
fn new(a: T) -> StdForwardList {
pub fn new(a: T) -> StdForwardList {
StdForwardList::from_initializer_list_1(a)
}

fn new(a: T, b: T) -> StdForwardList {
pub fn new(a: T, b: T) -> StdForwardList {
StdForwardList::from_initializer_list_2(a, b)
}

fn new(a: T, b: T, c: T) -> StdForwardList {
pub fn new(a: T, b: T, c: T) -> StdForwardList {
StdForwardList::from_initializer_list_3(a, b, c)
}
}
}

// Accessors: `front(...)`
overload! {
impl StdForwardList {
/// ### Limitations
///
/// The `overload!` macro can't overload on `&self` vs `&mut self`, because it has to
/// dispatch the overload through a single function, which can only have one receiver type.
///
/// The macro also doesn't support generic lifetimes, which are required here, because
/// lifetime elision doesn't work with a non-self type (in the code generated by the
/// macro).
///
/// ### Workaround
///
/// We use associated functions instead. Since `&StdForwardList` and `&mut StdForwardList`
/// are different types, the function can be overloaded on those types.
///
/// To avoid generic lifetimes, we require `&'static`. This makes usage very un-ergonomic.
/// We could also take a `StdForwardList` and clone the element, but that doesn't work for
/// `&mut`.
///
/// ### Ergonomics
///
/// Changing the return type from `&T` to `&mut T` based on the receiver is unusual in
/// Rust, and is likely to cause confusion.
///
/// ### Potential Resolutions
///
/// Fall back to associated functions instead. Since different receivers can happen by
/// accident, the macro error message should suggest either:
/// - using the same receiver type, or
/// - using associated functions with different receiver types.
///
/// Or try modifying the macro to always add the `self` receiver to the list of overloaded
/// types.
pub fn front(this: &'static StdForwardList) -> Option<&'static T> where T: Sized {
StdForwardList::front_const(this)
}

pub fn front(this: &'static mut StdForwardList) -> Option<&'static mut T> where T: Sized {
StdForwardList::front_mut(this)
}
}
}

#[test]
pub fn test_forward_list() {
// Constructors: `new(...)`
let mut default_list = StdForwardList::new();
let _repeat_default = StdForwardList::new(10);
let _repeat_with = StdForwardList::new(42, 100);
assert!(default_list.is_empty());

// ### Ergonomics
//
// This overload is easily confused with the single-argument "add item" overload.
//
// These `StdForwardList` type annotations are currently required, but inference should work.
// FIXME: fix the macro or `splat` compiler implementation so inference works here.
let repeat_default: StdForwardList = StdForwardList::new(10_usize);
assert_eq!(repeat_default.front_const(), Some(&0));

let repeat_with: StdForwardList = StdForwardList::new(42, 100);
assert_eq!(repeat_with.front_const(), Some(&42));

// ### Limitations
//
// The `as_slice` call is required to match the overload, an array doesn't automatically coerce.
// FIXME: maybe add a const generic overload for arrays.
let _from_slice = StdForwardList::new([1, 2, 3].as_slice());
let from_slice = StdForwardList::new([1, 2, 3].as_slice());
assert_eq!(from_slice.front_const(), Some(&1));

// ### Limitations
//
// The cast is required to match the overload, an iterator doesn't automatically coerce.
// It would be more ergonomic for users to collect the iterator themselves, then use the slice
// overload, or create a Rust/C++ iterator-to-range adapter.
let mut iter: std::array::IntoIter<T, 3> = [1 as T, 2, 3].into_iter();
let _from_iter = StdForwardList::new(&mut iter as &mut dyn Iterator<Item = _>);

let _ref_clone = StdForwardList::new(&default_list);
let _mut_clone = StdForwardList::new(&mut default_list);
let _from_initializer_list = StdForwardList::new(1);
let _from_initializer_list = StdForwardList::new(1, 2);
let _from_initializer_list = StdForwardList::new(1, 2, 3);
let mut iter: std::array::IntoIter<T, 3> = [1, 2, 3].into_iter();
let from_iter = StdForwardList::new(&mut iter as &mut dyn Iterator<Item = _>);
assert_eq!(from_iter.front_const(), Some(&1));

let ref_clone = StdForwardList::new(&default_list);
assert!(ref_clone.is_empty());
let mut_clone = StdForwardList::new(&mut default_list);
assert!(mut_clone.is_empty());

let from_initializer_list: StdForwardList = StdForwardList::new(1);
assert_eq!(from_initializer_list.front_const(), Some(&1));
let from_initializer_list: StdForwardList = StdForwardList::new(2, 1);
assert_eq!(from_initializer_list.front_const(), Some(&2));
let from_initializer_list = StdForwardList::new(3, 2, 1);
assert_eq!(from_initializer_list.front_const(), Some(&3));

// ### Workaround
//
// Leak these lists to get static references to them. This would never work in production.
let static_ref =
|list: StdForwardList| -> &'static StdForwardList { &*Box::leak(Box::new(list)) };
let static_mut =
|list: StdForwardList| -> &'static mut StdForwardList { Box::leak(Box::new(list)) };

assert_eq!(StdForwardList::front(static_ref(ref_clone)), None);
assert_eq!(StdForwardList::front(static_mut(mut_clone)), None);
assert_eq!(StdForwardList::front(static_ref(repeat_default)), Some(&0));
assert_eq!(
StdForwardList::front(static_mut(repeat_with)),
Some(&mut 42)
);
}
Loading