diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4bbbfb..31aa8c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,17 +17,16 @@ jobs: steps: - uses: actions/checkout@v3 - uses: mymindstorm/setup-emsdk@v14 - - name: Install Rust nightly + - name: Install Rust stable run: | - rustup toolchain install nightly - rustup default nightly + rustup toolchain install stable rustup target add wasm32-wasip1 rustup component add rustfmt - name: Install Clang 19 run: brew install llvm@19 - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 - - run: CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci + - run: CC=$(brew --prefix llvm@19)/bin/clang CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci build: runs-on: ubuntu-latest @@ -42,14 +41,16 @@ jobs: steps: - uses: actions/checkout@v3 - uses: mymindstorm/setup-emsdk@v14 - - name: Install Rust nightly + - name: Install Rust stable run: | - rustup toolchain install nightly - rustup default nightly + rustup toolchain install stable rustup target add wasm32-wasip1 rustup component add rustfmt - name: Install osmium run: sudo apt install libosmium2-dev + - name: Install LLD for clang++ + if: matrix.cpp_compiler == 'clang++' + run: sudo apt-get update && sudo apt-get install -y lld - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 - name: Cache WASI SDK @@ -57,4 +58,9 @@ jobs: with: path: examples/tutorial-wasm32/wasi-sdk-25.0-x86_64-linux key: wasi-sdk-25.0-x86_64-linux-v1 - - run: cargo xtask ci + - name: Run CI (clang++) + if: matrix.cpp_compiler == 'clang++' + run: CC=clang cargo xtask ci + - name: Run CI (g++) + if: matrix.cpp_compiler == 'g++' + run: cargo xtask ci diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 9570893..04a326f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,6 +7,7 @@ - [Name mapping](./call_rust_from_cpp/name_mapping.md) - [Wellknown traits](./call_rust_from_cpp/wellknown_traits.md) - [Layout policy](./call_rust_from_cpp/layout_policy.md) + - [Fields](./call_rust_from_cpp/fields.md) - [Types with special support](./call_rust_from_cpp/special_types.md) - [Panic and exceptions](./call_rust_from_cpp/panic_and_exceptions.md) - [Calling C++ from Rust](./call_cpp_from_rust/index.md) diff --git a/book/src/call_rust_from_cpp/fields.md b/book/src/call_rust_from_cpp/fields.md new file mode 100644 index 0000000..7d03799 --- /dev/null +++ b/book/src/call_rust_from_cpp/fields.md @@ -0,0 +1,38 @@ +# Fields as underlying types + +When you declare fields in a tuple or struct using `field name (offset = X, type = T);`, the generated C++ exposes helper wrapper types: + +- `rust::FieldOwned` for fields on owning types +- `rust::FieldRef` for fields on `Ref` +- `rust::FieldRefMut` for fields on `RefMut` + +These wrappers now act as their underlying type `T` in many contexts: + +- `Ref` construction from any `Field*` +- Implicit read via `operator T()` for value-like access +- Method calls are forwarded when applicable + +Example: + +```C++ +rust::Tuple t{42, "hi"_rs.to_owned()}; + +// Read value +int32_t v = t.f0; // operator T() on FieldOwned + +// Get a Ref from a field +rust::Ref r = t.f0; + +// Access methods through Ref from Field wrappers +rust::Ref sref = t.f1; +auto len = sref.len(); + +// From references to container, fields become FieldRef/FieldRefMut +rust::Ref rt = t; +auto l1 = rt.f1.len(); + +rust::RefMut mt = t; +mt.f1.push_str("!"_rs); +``` + +See `examples/regression_test1` for a runnable demonstration. diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 47eff4f..7c14583 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -49,3 +49,13 @@ Test fields and constructor work -- started [main.cpp:59] v4.f1.field2.len() = 12 Test fields and constructor work -- finished +Test Field* underlying conversions -- started +[main.cpp:70] v0 = 42 +[main.cpp:74] v1 = "hi" +[main.cpp:78] sref.len() = 2 +[main.cpp:81] int32_t(pref.f0) = 42 +[main.cpp:82] pref.f1.len() = 2 +[main.cpp:85] int32_t(pmut.f0) = 42 +[main.cpp:87] pmut.f1.len() = 3 +Test Field* underlying conversions -- finished + diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index 7465cf0..fa5b22f 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -59,7 +59,36 @@ void test_fields_and_constructor() { zngur_dbg(v4.f1.field2.len()); } +void test_field_underlying_conversions() { + auto scope = rust::crate::Scoped::new_("Test Field* underlying conversions"_rs); + + rust::Tuple pair{42, "hi"_rs.to_owned()}; + + // FieldOwned conversion to Ref and value + rust::Ref r0 = pair.f0; + int32_t v0 = pair.f0; + zngur_dbg(v0); + // Types which are not `Copy` cannot support implicit conversion to T. + // We must use `.clone()` or similar methods to get a copy. + rust::std::string::String v1 = pair.f1.clone(); + zngur_dbg(v1); + + // FieldOwned to Ref and call a method + rust::Ref sref = pair.f1; + zngur_dbg(sref.len()); + + rust::Ref> pref = pair; + zngur_dbg(int32_t(pref.f0)); + zngur_dbg(pref.f1.len()); + + rust::RefMut> pmut = pair; + zngur_dbg(int32_t(pmut.f0)); + pmut.f1.push_str("!"_rs); + zngur_dbg(pmut.f1.len()); +} + int main() { test_dbg_works_for_ref_and_refmut(); test_fields_and_constructor(); + test_field_underlying_conversions(); } diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index 66872ca..35819b7 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -12,6 +12,7 @@ type ::std::string::String { #layout(size = 24, align = 8); wellknown_traits(Debug); + fn clone(&self) -> ::std::string::String; fn push_str(&mut self, &str); fn len(&self) -> usize; } @@ -32,8 +33,16 @@ type (::std::string::String, crate::Foo) { field 1 (offset = 24, type = crate::Foo); } +type (i32, ::std::string::String) { + #layout(size = 32, align = 8); + wellknown_traits(Debug); + + field 0 (offset = 0, type = i32); + field 1 (offset = 8, type = ::std::string::String); +} + type crate::Scoped { #layout(size = 16, align = 8); fn new(&str) -> crate::Scoped; -} \ No newline at end of file +} diff --git a/zngur-generator/src/cpp.rs b/zngur-generator/src/cpp.rs index 3d47a05..c7d93a7 100644 --- a/zngur-generator/src/cpp.rs +++ b/zngur-generator/src/cpp.rs @@ -1658,13 +1658,19 @@ namespace rust { struct RefMut; template - struct FieldOwned; + struct FieldOwned { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; template - struct FieldRef; + struct FieldRef { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; template - struct FieldRefMut; + struct FieldRefMut { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; template struct Tuple; @@ -1759,6 +1765,21 @@ namespace rust { data = reinterpret_cast(__zngur_internal_data_ptr(t)); }} + template + Ref(const FieldOwned< {ty}, OFFSET >& f) {{ + data = reinterpret_cast(&f) + OFFSET; + }} + + template + Ref(const FieldRef< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + + template + Ref(const FieldRefMut< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + {ty}& operator*() {{ return *reinterpret_cast< {ty}*>(data); }} @@ -1777,6 +1798,16 @@ namespace rust { data = reinterpret_cast(__zngur_internal_data_ptr(t)); }} + template + RefMut(const FieldOwned< {ty}, OFFSET >& f) {{ + data = reinterpret_cast(&f) + OFFSET; + }} + + template + RefMut(const FieldRefMut< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + {ty}& operator*() {{ return *reinterpret_cast< {ty}*>(data); }}