From 37fcffe2a67396b65e6bba3ebc8617331446b896 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 23 Sep 2026 10:18:46 +0200 Subject: [PATCH 1/3] Fake generics, use c_size_t, improve overload coverage --- cpp-overload-test/src/lib.rs | 2 +- cpp-overload-test/src/stdcpp/forward_list.rs | 95 ++++++++++++++------ 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/cpp-overload-test/src/lib.rs b/cpp-overload-test/src/lib.rs index 5c30d60..b72552c 100644 --- a/cpp-overload-test/src/lib.rs +++ b/cpp-overload-test/src/lib.rs @@ -1,7 +1,7 @@ //! Test C++ overloads that can be successfully represented in Rust. //! Each type in the C++ standard library has a module under `stdcpp`. -#![feature(splat, tuple_trait)] +#![feature(splat, tuple_trait, c_size_t)] #![allow(incomplete_features)] /// The C++ standard library. diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index a998ef1..f6cd6e6 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -1,15 +1,18 @@ //! Test that C++ overloads for `std::forward_list` can be successfully represented in Rust. + #![allow(unused_braces)] use cpp::cpp; use splat_overload::overload; -use std::ffi::{c_int, c_void}; +use std::ffi::{c_size_t, c_void}; +use std::marker::PhantomData; // C++ header includes cpp! {{ #include #include #include + #include }} /// A wrapper struct to hold the returned C++ pointer. @@ -17,8 +20,11 @@ cpp! {{ /// ### Limitations /// /// This struct and the impl should be fully generic over the list type. -/// FIXME: try this and see if the `overload!` macro and `splat` feature can handle it. -struct StdForwardList(*mut c_void); +/// FIXME: make the `overload!` macro support generics. +struct StdForwardList(*mut c_void, PhantomData); + +/// Workaround for missing generic support in the `overload!` macro. +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. @@ -29,25 +35,25 @@ impl StdForwardList { return new std::forward_list(); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } - fn repeat_default(count: usize) -> StdForwardList { + fn repeat_default(count: c_size_t) -> StdForwardList { let list = unsafe { cpp!([count as "size_t"] -> *mut c_void as "std::forward_list*" { return new std::forward_list(count); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } - fn repeat_with(value: c_int, count: usize) -> StdForwardList { + fn repeat_with(value: T, count: c_size_t) -> StdForwardList { let list = unsafe { cpp!([value as "int", count as "size_t"] -> *mut c_void as "std::forward_list*" { return new std::forward_list(value, count); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } /// ### Limitations @@ -55,16 +61,16 @@ impl StdForwardList { /// The slice satisfies C++ `ContiguousIterator`, even though it's not a requirement for this /// C++ iterator overload. /// FIXME: allow non-contiguous iterators, if that makes sense in Rust. - fn from_slice(items: &[c_int]) -> StdForwardList { + fn from_slice(items: &[T]) -> StdForwardList { let len = items.len(); - let items: *const c_int = items.as_ptr(); + let items: *const T = items.as_ptr(); let list = unsafe { cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list*" { // In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`. return new std::forward_list(items, items + len); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } /// ### Limitations @@ -75,10 +81,10 @@ impl StdForwardList { /// /// This implementation builds a Rust `Vec` and C++ `std::vector` from the iterator for simplicity. /// A production implementation could use a Rust-to-C++ iterator-to-range adapter. - fn from_iter(iter: &mut dyn Iterator) -> StdForwardList { - let items: Vec = iter.collect(); + fn from_iter(iter: &mut dyn Iterator) -> StdForwardList { + let items: Vec = iter.collect(); let len = items.len(); - let items: *const c_int = items.as_ptr(); + let items: *const T = items.as_ptr(); let list = unsafe { cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list*" { // Use the legacy iterator constructor for `vector`. @@ -92,7 +98,7 @@ impl StdForwardList { #endif }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } fn copy_from(other: &StdForwardList) -> StdForwardList { @@ -103,7 +109,7 @@ impl StdForwardList { return new std::forward_list(other_); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } fn move_from(other: &mut StdForwardList) -> StdForwardList { @@ -114,19 +120,37 @@ impl StdForwardList { return new std::forward_list(other_); }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } /// ### Limitations /// - /// This is just an example, a production implementation would support any number of arguments. - fn from_initializer_list(a: c_int, b: c_int, c: c_int) -> StdForwardList { + /// These are just examples, a production implementation would support any number of arguments. + fn from_initializer_list_1(a: T) -> StdForwardList { + let list = unsafe { + cpp!([a as "int"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list{ a }; + }) + }; + StdForwardList(list, PhantomData) + } + + fn from_initializer_list_2(a: T, b: T) -> StdForwardList { + let list = unsafe { + cpp!([a as "int", b as "int"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list{ a, b }; + }) + }; + StdForwardList(list, PhantomData) + } + + fn from_initializer_list_3(a: T, b: T, c: T) -> StdForwardList { let list = unsafe { cpp!([a as "int", b as "int", c as "int"] -> *mut c_void as "std::forward_list*" { return new std::forward_list{ a, b, c }; }) }; - StdForwardList(list) + StdForwardList(list, PhantomData) } } @@ -140,17 +164,17 @@ overload! { } /// Construct a list with `count` default-constructed items. - fn new(count: usize) -> StdForwardList { + 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: c_int, count: usize) -> StdForwardList { + 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: &[c_int]) -> StdForwardList { + fn new(items: &[T]) -> StdForwardList { StdForwardList::from_slice(items) } @@ -159,12 +183,13 @@ overload! { /// ### Limitations /// /// `&mut dyn Iterator` (and `&mut Iterator`) can never be the same type as `&[T]`, - /// but passing an owned `impl Iterator` here could overlap with the `&[T]` overload. + /// but passing an `impl Iterator` (or `&impl ...`) here could overlap with the `&[T]` + /// 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) -> StdForwardList { + fn new(iter: &mut dyn Iterator) -> StdForwardList { StdForwardList::from_iter(iter) } @@ -187,13 +212,23 @@ overload! { /// have variadic tuples, so we can't overload on the tuple trait itself. /// /// The following initializer argument counts clash with other overloads: - /// - 0: the default no-argument constructor + /// - 0: the default no-argument constructor, but this is acceptable, because the returned + /// value is the empty list in both cases. /// - 1: the `count` default-value repetition constructor, if `T` is `size_t` /// - most constructors take 1 argument, so there could be other clashes in unusual /// circumstances. /// - 2: the `value` repetition constructor, if `T` is `size_t` - fn new(a: c_int, b: c_int, c: c_int) -> StdForwardList { - StdForwardList::from_initializer_list(a, b, c) + /// fn new(_a: T, _b: T, _c: T) -> StdForwardList { + fn new(a: T) -> StdForwardList { + StdForwardList::from_initializer_list_1(a) + } + + fn new(a: T, b: T) -> StdForwardList { + StdForwardList::from_initializer_list_2(a, b) + } + + fn new(a: T, b: T, c: T) -> StdForwardList { + StdForwardList::from_initializer_list_3(a, b, c) } } } @@ -214,10 +249,12 @@ pub fn test_forward_list() { // 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 = [1, 2, 3].iter().copied(); + let mut iter: std::array::IntoIter = [1 as T, 2, 3].into_iter(); let _from_iter = StdForwardList::new(&mut iter as &mut dyn Iterator); 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); } From 8c1705399bc3df038a96c5b77b3b9644a515fcce Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 23 Sep 2026 11:31:45 +0200 Subject: [PATCH 2/3] Add CI bless comments --- .github/workflows/check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 6a5150a..579448d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -130,6 +130,8 @@ jobs: - name: Check diagnostic output # To bless (update the expected diagnostics), change '/tmp' to 'splat-overload-diagnostics/src/bin', # and disable color output. + # Or just do `cargo build --package splat-overload-diagnostics --bin NAME > splat-overload-diagnostics/src/bin/NAME.stderr 2>&1` + # and delete the cargo Updating/Download*/Compiling logs. # FIXME: turn this into a script so we can run it locally. run: | cargo fmt --package splat-overload-diagnostics -- --check @@ -200,6 +202,8 @@ jobs: - name: Check C++ overload fail cases # To bless (update the expected output), change '/tmp' to 'cpp-overload-test/examples', # and disable color output. + # Or just do `cargo build --package cpp-overload-test --example NAME > cpp-overload-test/examples/NAME.stderr 2>&1` + # and delete the cargo Updating/Download*/Compiling logs. # FIXME: turn this into a script so contributors can run it locally. run: | echo "Checking must-fail example builds:" From 3db3a3bf428c624efcbceb51904959850e31f299 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 23 Sep 2026 11:31:58 +0200 Subject: [PATCH 3/3] Add failing overloads --- .../examples/stdcpp-forward-list.rs | 139 +++++++++++++++++- .../examples/stdcpp-forward-list.stderr | 40 ++++- 2 files changed, 169 insertions(+), 10 deletions(-) diff --git a/cpp-overload-test/examples/stdcpp-forward-list.rs b/cpp-overload-test/examples/stdcpp-forward-list.rs index 37620c9..03cd12c 100644 --- a/cpp-overload-test/examples/stdcpp-forward-list.rs +++ b/cpp-overload-test/examples/stdcpp-forward-list.rs @@ -2,8 +2,141 @@ //! //! The `cpp!` macro doesn't work here, because of technical limitations it can only run one build //! per crate. (Examples and binaries are built separately from `lib.rs`.) -//! To add C++ code here, use /* ... */ code comment blocks. +//! To add C++ code here, use /* ... */ code comment blocks, or just skip it. +//! +//! ### Potential Resolutions +//! +//! There isn't a clear equivalent of list initializers in Rust, so if we decided the initializer +//! list overloads were inexpressible in Rust, all overloads would be compatible. (And initializer +//! lists could be supported using the slice or iterator overloads.) + +#![feature(splat, tuple_trait, c_size_t)] +#![allow(incomplete_features)] +#![allow(unused_braces)] + +use splat_overload::overload; +use std::ffi::c_size_t; + +/// A wrapper struct to hold the returned C++ pointer, with the `T` 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 StdForwardListT(Vec); + +/// Workaround for missing generic support in the `overload!` macro. +type T = std::ffi::c_size_t; -fn main() { - const _: () = panic!("There are no failing overloads for C++ `std::forward_list` yet"); +// This overload set has overloads which are incompatible when `T` is `c_size_t`. +overload! { + impl StdForwardListT { + /// Construct a list with `count` default-constructed items. + fn new(_count: c_size_t) -> StdForwardListT { + todo!() + } + + /// Construct a list filled with `count` instances of the given value. + fn new(_value: T, _count: c_size_t) -> StdForwardListT { + todo!() + } + + /// Construct a list from the supplied item. + /// + /// ### Incompatibilities + /// + /// This overload has the same types as `new(count)` when `T` is `c_size_t`, but different + /// semantics: + /// - `new(count)` fills the list with the default value of `T` repeated`count` times. + /// - `new(value)` fills the list with one instance of `value`. + fn new(_value: T) -> StdForwardListT { + todo!() + } + + /// Construct a list from the supplied 2 items. + /// + /// ### Incompatibilities + /// + /// This overload has the same types as `new(value, count)` when `T` is `c_size_t`, but different + /// semantics: + /// - `new(value, count)` fills the list with `count` instances of `value`. + /// - `new(a, b)` fills the list with two values: `a` and `b`. + fn new(_a: T, _b: T) -> StdForwardListT { + todo!() + } + } } + +/// A wrapper struct to hold the returned C++ pointer, with the `U` 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 StdForwardListU(Vec); + +/// Workaround for missing generic support in the `overload!` macro. +type U = StdForwardListU; + +// This overload set has overloads which are compatible, but have poor ergonomics, +// when `U` is `StdForwardListU` (which is possible with some boxed types). +overload! { + impl StdForwardListU { + /// Clone list items into a new list from a shared list reference. + fn newer(_other: &StdForwardListU) -> StdForwardListU where U: Clone { + todo!() + } + + /// Move list items into a new list from a mutable list reference. + fn newer(_other: &mut StdForwardListU) -> StdForwardListU { + todo!() + } + + /// Construct a list from the supplied item. + /// + /// ### Potential Overlap + /// + /// This overload has similar types to `new(&/&mut other)` when `U` is `StdForwardListU`, + /// but different semantics: + /// - `new(&/&mut other)` clones or moves the list items into a new list. + /// - `new(value)` fills the list with one instance of `value`. + /// + /// Due to the references, this is technically compatible. But the ergonomics are likely + /// to be poor, particularly when auto-ref and auto-deref are used. + fn newer(_a: U) -> StdForwardListU { + todo!() + } + } +} + +/// A wrapper struct to hold the returned C++ pointer, with the `V` 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 StdForwardListV(Vec); + +/// Workaround for missing generic support in the `overload!` macro. +/// Currently this type alias fails compilation very early due to cycle checking, but with +/// generics the *possibility* of an identical impl existing is enough to make compilation fail, +/// even if it can't be implemented in practice. +//type V<'v> = &'v mut dyn Iterator>; +type V = Box>; +type VCycleBreaker = (); + +// This overload set has overloads which are incompatible when `V` is +// `&mut dyn Iterator`. This is a contrived example, but trait solving would fail anyway. +overload! { + impl StdForwardListV { + /// Construct a list from an iterator. + fn newest(_iter: &mut dyn Iterator) -> StdForwardListV { + todo!() + } + + /// Construct a list from the supplied items. + /// + /// ### Incompatibilities + /// + /// This overload has the same type as `new(iter)` when `V` is + /// `&mut dyn Iterator`, but different semantics: + /// - `new(iter)` constructs a list from the items in the iterator. + /// - `new(a)` constructs a list from a single item (where the item is an iterator). + fn newest(_a: V) -> StdForwardListV { + todo!() + } + } +} + +pub fn main() {} diff --git a/cpp-overload-test/examples/stdcpp-forward-list.stderr b/cpp-overload-test/examples/stdcpp-forward-list.stderr index b165653..7d6e9ea 100644 --- a/cpp-overload-test/examples/stdcpp-forward-list.stderr +++ b/cpp-overload-test/examples/stdcpp-forward-list.stderr @@ -1,8 +1,34 @@ -error[E0080]: evaluation panicked: There are no failing overloads for C++ `std::forward_list` yet - --> cpp-overload-test/examples/stdcpp-forward-list.rs:8:19 - | -8 | const _: () = panic!("There are no failing overloads for C++ `std::forward_list` yet"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::_` failed here +error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize,)` + --> cpp-overload-test/examples/stdcpp-forward-list.rs:29:1 + | +29 | / overload! { +30 | | impl StdForwardListT { +31 | | /// Construct a list with `count` default-constructed items. +32 | | fn new(_count: c_size_t) -> StdForwardListT { +... | +65 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(usize,)` + | + = 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 E0080`. -error: could not compile `cpp-overload-test` (example "stdcpp-forward-list") due to 1 previous error +error[E0119]: conflicting implementations of trait `NewArgs` for type `(usize, usize)` + --> cpp-overload-test/examples/stdcpp-forward-list.rs:29:1 + | +29 | / overload! { +30 | | impl StdForwardListT { +31 | | /// Construct a list with `count` default-constructed items. +32 | | fn new(_count: c_size_t) -> StdForwardListT { +... | +65 | | } + | | ^ + | | | + | |_first implementation here + | conflicting implementation for `(usize, usize)` + | + = 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