diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4482b7a..6a5150a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -32,6 +32,8 @@ jobs: test-rust: name: 2. Test Rust crates runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -44,15 +46,17 @@ jobs: cargo test --doc --all-features run: - name: 3. Run Rust example binaries + name: 3. Run Rust overload-test binaries runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: toolchain: nightly components: cargo,clippy,rustfmt - - name: Run Rust example binaries + - name: Run Rust overload-test binaries # Find all test crate binaries, extract the basename (no directories or extensions), # then run them one at a time with cargo. # There is no default-run binary set in Cargo.toml. @@ -100,6 +104,8 @@ jobs: publish-dry-run: name: 7. Dry run publishing runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -112,6 +118,8 @@ jobs: diagnostics-rust: name: 8. Diagnostics tests on Rust crates runs-on: ubuntu-latest + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -150,7 +158,10 @@ jobs: cpp-overload-test-ok: name: 9. Check C++ overloads - runs-on: ubuntu-latest + # Ubuntu 26.04 is needed for C++23 standard library features. + runs-on: ubuntu-26.04 + needs: + - build-rust steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -161,6 +172,12 @@ jobs: # FIXME: turn this into a script so contributors can run it locally. - name: Check C++ overload ok cases run: | + echo "Checking the crate library builds on a newer platform:" + cargo build --all-targets --all-features + cargo clippy --all-targets --all-features -- --deny warnings + cargo test --all-targets --all-features + cargo test --doc --all-features + echo "Checking the C++ overload test crate builds:" cargo build --all-features --package cpp-overload-test --lib cargo clippy --all-features --package cpp-overload-test -- --deny warnings cargo test --all-features --package cpp-overload-test --lib @@ -169,7 +186,10 @@ jobs: cpp-overload-test-fail: name: 10. Must-fail C++ overloads - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 + # Don't run unless the test crate has built successfully, crates arealways built for examples. + needs: + - cpp-overload-test-ok steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 @@ -182,6 +202,7 @@ jobs: # and disable color output. # FIXME: turn this into a script so contributors can run it locally. run: | + echo "Checking must-fail example builds:" export CARGO_TERM_COLOR=never set -o pipefail TEST_NAMES=$(find cpp-overload-test/examples -name "*.rs" | \ diff --git a/cpp-overload-test/build.rs b/cpp-overload-test/build.rs index 507c620..5cf150f 100644 --- a/cpp-overload-test/build.rs +++ b/cpp-overload-test/build.rs @@ -1,5 +1,10 @@ fn main() { // `cpp_build` doesn't work with multiple example/binary builds in the same crate, so we use // lib.rs submodules for successful overloads. - cpp_build::build("src/lib.rs"); + cpp_build::Config::new() + // Required for range overloads. + .flag("-std=c++23") + // Ignore `warning: 'std::rel_ops' is deprecated: use '<=>' instead [-Wdeprecated-declarations]` + .flag("-Wno-deprecated-declarations") + .build("src/lib.rs"); } diff --git a/cpp-overload-test/src/stdcpp/forward_list.rs b/cpp-overload-test/src/stdcpp/forward_list.rs index 075b27e..a998ef1 100644 --- a/cpp-overload-test/src/stdcpp/forward_list.rs +++ b/cpp-overload-test/src/stdcpp/forward_list.rs @@ -8,9 +8,16 @@ use std::ffi::{c_int, c_void}; // C++ header includes cpp! {{ #include + #include + #include }} /// A wrapper struct to hold the returned C++ pointer. +/// +/// ### 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); // The cpp! macro doesn't work inside the overload! macro, so we extract C++ calls into separate methods. @@ -25,19 +32,64 @@ impl StdForwardList { StdForwardList(list) } - fn with_capacity(capacity: usize) -> StdForwardList { + fn repeat_default(count: usize) -> StdForwardList { let list = unsafe { - cpp!([capacity as "size_t"] -> *mut c_void as "std::forward_list*" { - return new std::forward_list(capacity); + cpp!([count as "size_t"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list(count); }) }; StdForwardList(list) } - fn repeat_with(value: c_int, capacity: usize) -> StdForwardList { + fn repeat_with(value: c_int, count: usize) -> StdForwardList { let list = unsafe { - cpp!([value as "int", capacity as "size_t"] -> *mut c_void as "std::forward_list*" { - return new std::forward_list(value, capacity); + cpp!([value as "int", count as "size_t"] -> *mut c_void as "std::forward_list*" { + return new std::forward_list(value, count); + }) + }; + StdForwardList(list) + } + + /// ### Limitations + /// + /// 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 { + let len = items.len(); + let items: *const c_int = 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) + } + + /// ### Limitations + /// + /// `std::from_range` is used as a tag to disambiguate this constructor, it is part of the C++ + /// standard library API. The Rust overload requires a cast to `dyn Iterator` instead, but this + /// is likely just a macro limitation. + /// + /// 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(); + let len = items.len(); + let items: *const c_int = 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`. + // In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`. + std::vector vec = std::vector(items, items + len); + // `vector` is a range, so we can use it to construct a `forward_list`. + #ifdef __cpp_lib_containers_ranges + return new std::forward_list(std::from_range, vec); + #else + #error "std::from_range is not available, try using `-std=c++23` or a newer compiler" + #endif }) }; StdForwardList(list) @@ -64,6 +116,18 @@ impl StdForwardList { }; StdForwardList(list) } + + /// ### 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 { + 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) + } } // We ignore constructors that only differ by an allocator argument, because they're not @@ -75,37 +139,85 @@ overload! { StdForwardList::default() } - /// Construct a list with a given capacity. - fn new(capacity: usize) -> StdForwardList { - StdForwardList::with_capacity(capacity) + /// Construct a list with `count` default-constructed items. + fn new(count: usize) -> StdForwardList { + StdForwardList::repeat_default(count) + } + + /// Construct a list filled with `count` instances of the given value. + fn new(value: c_int, count: usize) -> StdForwardList { + StdForwardList::repeat_with(value, count) } - /// Construct a list filled with the given value. - fn new(value: c_int, capacity: usize) -> StdForwardList { - StdForwardList::repeat_with(value, capacity) + /// Construct a list by copying items from a slice. + fn new(items: &[c_int]) -> StdForwardList { + StdForwardList::from_slice(items) } - /// Clone a list from a shared reference. + /// Construct a list from an iterator. + /// + /// ### 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. + /// + /// 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 { + StdForwardList::from_iter(iter) + } + + /// Clone list items into a new list from a shared list reference. fn new(other: &StdForwardList) -> StdForwardList { StdForwardList::copy_from(other) } - /// Move list items from a mutable reference into a new list. + /// Move list items into a new list from a mutable list reference. fn new(other: &mut StdForwardList) -> StdForwardList { StdForwardList::move_from(other) } - // TODO: - // iterator - // range - // initializer_list + /// Construct a list from the supplied items. + /// + /// ### Limitations + /// + /// This is just an example, a production implementation would support any number of arguments. + /// Rust doesn't have a language equivalent to C++'s `initializer_list`, and it doesn't + /// 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 + /// - 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) + } } } pub fn test_forward_list() { let mut default_list = StdForwardList::new(); - let _with_capacity = StdForwardList::new(10); + let _repeat_default = StdForwardList::new(10); let _repeat_with = StdForwardList::new(42, 100); + + // ### 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()); + + // ### 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 = [1, 2, 3].iter().copied(); + 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, 2, 3); }