From 82001579e7bd0690bf91ff84bb31353d4b72efa2 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Tue, 25 Aug 2026 13:16:49 +0200 Subject: [PATCH 1/2] Add operator* and operator-> to Ref/RefMut of user-defined types The primitive specializations of Ref/RefMut always provided operator*, but the per-type specializations generated for user-defined types did not, leaving no way to access the pointee of a Ref other than calling zngur-declared methods on it. We emit operator*/operator-> for those specializations too, returning a reference aliasing the underlying Rust memory without taking ownership. Copying the value out works when the type is Copy, since its copy constructor performs the deep copy; non-Copy types keep their ownership discipline with respect to the deleted copy constructors on the C++ side. Signed-off-by: Raito Bezarius --- examples/regression_test1/expected_output.txt | 102 ++++++++++-------- examples/regression_test1/main.cpp | 29 +++++ examples/regression_test1/main.zng | 12 ++- examples/regression_test1/src/lib.rs | 7 ++ zngur-generator/templates/cpp_header.sptl | 32 ++++++ 5 files changed, 138 insertions(+), 44 deletions(-) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index fe907a8..06ed6cd 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -59,19 +59,35 @@ Test Field* underlying conversions -- started [main.cpp:88] pmut.f1.len() = 3 Test Field* underlying conversions -- finished +Test Ref and RefMut deref of user types -- started +[main.cpp:101] view.flag = true +[main.cpp:102] int32_t(view.data.buzz_3) = 9 +[main.cpp:107] snapshot = BoolHolder { + flag: true, + data: FieldTypeC { + buzz_1: 7, + buzz_2: 8, + buzz_3: 9, + }, +} +[main.cpp:110] int32_t(r->data.buzz_3) = 9 +[main.cpp:115] holder.flag = false +[main.cpp:117] holder.flag = true +Test Ref and RefMut deref of user types -- finished + Test floats -- started -[main.cpp:98] *r1 = 12.3 -[main.cpp:100] v1 = 12.3 -[main.cpp:105] fvec = [ +[main.cpp:127] *r1 = 12.3 +[main.cpp:129] v1 = 12.3 +[main.cpp:134] fvec = [ 42.24, 147.0, ] -[main.cpp:106] fvec.get(0) = Some( +[main.cpp:135] fvec.get(0) = Some( 42.24, ) -[main.cpp:107] fvec.get(2) = None -[main.cpp:108] *fvec.get(1).unwrap() = 147 -[main.cpp:110] fvec = [ +[main.cpp:136] fvec.get(2) = None +[main.cpp:137] *fvec.get(1).unwrap() = 147 +[main.cpp:139] fvec = [ 42.24, 5.43, ] @@ -86,19 +102,19 @@ End of call_dyn_fn_multi_args Test dyn Fn() with multiple arguments -- finished Test Ref> -- started -[main.cpp:131] strvec = [ +[main.cpp:160] strvec = [ "a str", "foobar", "a third str", ] -[main.cpp:132] strvec.get(0) = Some( +[main.cpp:161] strvec.get(0) = Some( "a str", ) -[main.cpp:133] strvec.get(2) = Some( +[main.cpp:162] strvec.get(2) = Some( "a third str", ) -[main.cpp:138] *strvec.get(1).unwrap() = "foobar" -[main.cpp:140] strvec = [ +[main.cpp:167] *strvec.get(1).unwrap() = "foobar" +[main.cpp:169] strvec = [ "a str", "flip flop", "a third str", @@ -107,11 +123,11 @@ Test Ref> -- finished Test zero-sized type -- started Method call on ZST -[main.cpp:147] zst = ZeroSizedType +[main.cpp:176] zst = ZeroSizedType Test zero-sized type -- finished Test nested Ref where T is #heap_allocated and auto field offsets -- started -[main.cpp:162] a = TypeA { +[main.cpp:191] a = TypeA { foo: 10, bar: FieldTypeA { fizz: FieldTypeC { @@ -128,12 +144,12 @@ Test nested Ref where T is #heap_allocated and auto field offsets -- started }, }, } -[main.cpp:163] ::rust::Ref(a.foo) = 10 -[main.cpp:164] ::rust::Ref(a.bar.fizz.buzz_2) = 30 -[main.cpp:165] ::rust::RefMut(a.baz.fizz.buzz_3) = 70 -[main.cpp:168] ::rust::Ref(a_fa_fizz.buzz_1) = 20 -[main.cpp:170] ::rust::Ref(a_fb_fizz.buzz_2) = 60 -[main.cpp:181] b = TypeB { +[main.cpp:192] ::rust::Ref(a.foo) = 10 +[main.cpp:193] ::rust::Ref(a.bar.fizz.buzz_2) = 30 +[main.cpp:194] ::rust::RefMut(a.baz.fizz.buzz_3) = 70 +[main.cpp:197] ::rust::Ref(a_fa_fizz.buzz_1) = 20 +[main.cpp:199] ::rust::Ref(a_fb_fizz.buzz_2) = 60 +[main.cpp:210] b = TypeB { foo: 100, bar: FieldTypeA { fizz: FieldTypeC { @@ -150,40 +166,40 @@ Test nested Ref where T is #heap_allocated and auto field offsets -- started }, }, } -[main.cpp:182] ::rust::Ref(b.foo) = 100 -[main.cpp:183] ::rust::Ref(b.bar.fizz.buzz_2) = 300 -[main.cpp:184] ::rust::RefMut(b.baz.fizz.buzz_3) = 700 -[main.cpp:187] ::rust::Ref(b_fa_fizz.buzz_1) = 200 -[main.cpp:189] ::rust::Ref(b_fb_fizz.buzz_2) = 600 -[main.cpp:194] fa = FieldTypeA { +[main.cpp:211] ::rust::Ref(b.foo) = 100 +[main.cpp:212] ::rust::Ref(b.bar.fizz.buzz_2) = 300 +[main.cpp:213] ::rust::RefMut(b.baz.fizz.buzz_3) = 700 +[main.cpp:216] ::rust::Ref(b_fa_fizz.buzz_1) = 200 +[main.cpp:218] ::rust::Ref(b_fb_fizz.buzz_2) = 600 +[main.cpp:223] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } -[main.cpp:195] ::rust::Ref(fa.fizz.buzz_1) = 21 -[main.cpp:196] ::rust::Ref(fa.fizz.buzz_3) = 41 -[main.cpp:201] fa = FieldTypeA { +[main.cpp:224] ::rust::Ref(fa.fizz.buzz_1) = 21 +[main.cpp:225] ::rust::Ref(fa.fizz.buzz_3) = 41 +[main.cpp:230] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } -[main.cpp:202] ::rust::Ref(fb.fizz.buzz_2) = 61 -[main.cpp:203] ::rust::Ref(fb.fizz.buzz_3) = 71 +[main.cpp:231] ::rust::Ref(fb.fizz.buzz_2) = 61 +[main.cpp:232] ::rust::Ref(fb.fizz.buzz_3) = 71 Test nested Ref where T is #heap_allocated and auto field offsets -- finished Test #layout_conservative -- started -[main.cpp:212] c_layout = ConservativeLayoutType { +[main.cpp:241] c_layout = ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", } Rust( size = 32 , align = 8 ) c++( size = 48 , align = 8 ) -[main.cpp:224] layouts = [ +[main.cpp:253] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, @@ -195,26 +211,26 @@ c++( size = 48 , align = 8 ) field3: "Another test string", }, ] -[main.cpp:225] layouts.get(0) = Some( +[main.cpp:254] layouts.get(0) = Some( ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", }, ) -[main.cpp:226] layouts.get(1) = Some( +[main.cpp:255] layouts.get(1) = Some( ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", }, ) -[main.cpp:227] layouts.get(1).unwrap() = ConservativeLayoutType { +[main.cpp:256] layouts.get(1).unwrap() = ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", } -[main.cpp:229] layouts = [ +[main.cpp:258] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, @@ -229,28 +245,28 @@ c++( size = 48 , align = 8 ) Test #layout_conservative -- finished Test dbg of Ref> -- started -[main.cpp:241] r = RefHolder( +[main.cpp:270] r = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:242] rr = RefHolder( +[main.cpp:271] rr = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:243] rrr = RefHolder( +[main.cpp:272] rrr = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:244] rust::Ref(r.f0) = SimpleFoo( +[main.cpp:273] rust::Ref(r.f0) = SimpleFoo( 42, ) -[main.cpp:245] rust::Ref((*rr).f0) = SimpleFoo( +[main.cpp:274] rust::Ref((*rr).f0) = SimpleFoo( 42, ) -[main.cpp:246] rust::Ref((**rrr).f0) = SimpleFoo( +[main.cpp:275] rust::Ref((**rrr).f0) = SimpleFoo( 42, ) Test dbg of Ref> -- finished diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index e29631c..0b14268 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -88,6 +88,34 @@ void test_field_underlying_conversions() { zngur_dbg(pmut.f1.len()); } +void test_ref_deref_of_user_types() { + auto scope = + rust::crate::Scoped::new_("Test Ref and RefMut deref of user types"_rs); + + rust::crate::BoolHolder holder{true, rust::crate::FieldTypeC{7, 8, 9}}; + + // Ref of a user-defined type dereferences to the aliased object. + rust::Ref r = holder; + rust::crate::BoolHolder& view = *r; + zngur_dbg(view.flag); + zngur_dbg(int32_t(view.data.buzz_3)); + + // Copying out of a Ref works when the type is Copy, no ownership of the + // aliased object changes hands either way. + rust::crate::BoolHolder snapshot = *r; + zngur_dbg(snapshot); + + // Member access through -> + zngur_dbg(int32_t(r->data.buzz_3)); + + // Mutation through a RefMut deref is visible in the parent + rust::RefMut m = holder; + *rust::RefMut(m->flag) = false; + zngur_dbg(holder.flag); + *rust::RefMut(m->flag) = true; + zngur_dbg(holder.flag); +} + void test_floats() { auto scope = rust::crate::Scoped::new_("Test floats"_rs); @@ -250,6 +278,7 @@ int main() { test_dbg_works_for_ref_and_refmut(); test_fields_and_constructor(); test_field_underlying_conversions(); + test_ref_deref_of_user_types(); test_floats(); test_dyn_fn_with_multiple_arguments(); test_refref(); diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index 48f434e..e29c2cf 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -2,7 +2,7 @@ type bool { #layout(size = 1, align = 1); - wellknown_traits(Copy); + wellknown_traits(Debug, Copy); } type str { @@ -216,6 +216,16 @@ type crate::TypeB { field baz (offset = 16, type = crate::FieldTypeB ); } +type crate::BoolHolder { + #layout(size = 16, align = 4); + wellknown_traits(Debug, Copy); + constructor { flag: bool, data: crate::FieldTypeC }; + + // rustc reordering + field data (offset = 0, type = crate::FieldTypeC ); + field flag (offset = 12, type = bool ); +} + type crate::ConservativeLayoutType { // bigger than the real size of 32 #layout_conservative(size = 48, align = 8 ); diff --git a/examples/regression_test1/src/lib.rs b/examples/regression_test1/src/lib.rs index 9656277..5ebc00b 100644 --- a/examples/regression_test1/src/lib.rs +++ b/examples/regression_test1/src/lib.rs @@ -57,6 +57,13 @@ struct ConservativeLayoutType { } #[allow(unused)] +#[derive(Debug, Copy, Clone)] +/// bool field + Copy struct field, to test Ref/RefMut deref of user types +struct BoolHolder { + pub flag: bool, + pub data: FieldTypeC, +} + impl ConservativeLayoutType { pub fn mem_size(&self) -> usize { std::mem::size_of::() diff --git a/zngur-generator/templates/cpp_header.sptl b/zngur-generator/templates/cpp_header.sptl index f4ab748..1f69650 100644 --- a/zngur-generator/templates/cpp_header.sptl +++ b/zngur-generator/templates/cpp_header.sptl @@ -702,6 +702,22 @@ namespace {{ self.namespace }} { } {% endif %} + {% if !is_unsized && !td.layout.is_only_by_ref() %} + // deref to the aliased object without taking ownership + {{ td.ty }}& operator*() noexcept { + return *reinterpret_cast< {{ td.ty }}* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }} const& operator*() const noexcept { + return *reinterpret_cast< {{ td.ty }} const* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }}* operator->() noexcept { + return reinterpret_cast< {{ td.ty }}* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }} const* operator->() const noexcept { + return reinterpret_cast< {{ td.ty }} const* >(reinterpret_cast(__zngur_data)); + } + {% endif %} + {{ td.render_make_box_ref(td.ty.path.name(), self.namespace, self.crate_name) }} {% if let Some(cpp_value) = td.cpp_value %} @@ -925,6 +941,22 @@ namespace {{ self.namespace }} { } {% endif %} + {% if !is_unsized && !td.layout.is_only_by_ref() %} + // deref to the aliased object without taking ownership + {{ td.ty }}& operator*() noexcept { + return *reinterpret_cast< {{ td.ty }}* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }} const& operator*() const noexcept { + return *reinterpret_cast< {{ td.ty }} const* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }}* operator->() noexcept { + return reinterpret_cast< {{ td.ty }}* >(reinterpret_cast(__zngur_data)); + } + {{ td.ty }} const* operator->() const noexcept { + return reinterpret_cast< {{ td.ty }} const* >(reinterpret_cast(__zngur_data)); + } + {% endif %} + {{ td.render_make_box_ref_only(td.ty.path.name(), self.namespace, self.crate_name) }} From 8d192e248331c3752e6cbe85925109fe432f44ba Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Tue, 25 Aug 2026 13:19:28 +0200 Subject: [PATCH 2/2] Restore operator T() on FieldOwned/FieldRef/FieldRefMut specializations The FieldOwned/FieldRef/FieldRefMut primary templates define a conversion to T, but the per-type explicit specializations generated for each user-defined type silently dropped it. Types with a specialization (e.g. Bool) therefore lost the conversion that primitive field types had. Only Copy types can support the conversion: it copies the value out of the parent object, which is impossible for non-Copy types whose copy constructor is deleted. The definition is emitted out-of-line near the end of the header because the specializations are generated before the type and Ref definitions it needs. Regression tests cover Bool, Copy structs and Copy generic instantiations (Option<&T>, Option). Fixes #133. Signed-off-by: Raito Bezarius --- examples/regression_test1/expected_output.txt | 157 +++++++++++++----- examples/regression_test1/main.cpp | 71 ++++++++ examples/regression_test1/main.zng | 27 +++ examples/regression_test1/src/lib.rs | 32 +++- zngur-generator/src/template.rs | 19 +++ zngur-generator/templates/cpp_header.sptl | 8 + 6 files changed, 270 insertions(+), 44 deletions(-) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 06ed6cd..4d1d7a1 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -75,19 +75,90 @@ Test Ref and RefMut deref of user types -- started [main.cpp:117] holder.flag = true Test Ref and RefMut deref of user types -- finished +Test Bool and Copy struct field conversions -- started +[main.cpp:129] holder.flag = true +[main.cpp:131] b1 = true +[main.cpp:135] c1 = FieldTypeC { + buzz_1: 7, + buzz_2: 8, + buzz_3: 9, +} +[main.cpp:139] v1 = 8 +[main.cpp:143] r.flag = true +[main.cpp:145] c2 = FieldTypeC { + buzz_1: 7, + buzz_2: 8, + buzz_3: 9, +} +[main.cpp:148] m.flag = true +[main.cpp:150] int32_t(c3.buzz_1) = 7 +Test Bool and Copy struct field conversions -- finished + +Test Option field conversions -- started +[main.cpp:157] some = OptionHolder { + opt_ref: Some( + 1234, + ), + opt_copy: Some( + FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, + }, + ), +} +[main.cpp:161] o1 = Some( + 1234, +) +[main.cpp:165] o2 = Some( + FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, + }, +) +[main.cpp:166] o2.unwrap() = FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, +} +[main.cpp:171] o3 = Some( + 1234, +) +[main.cpp:173] o4 = Some( + FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, + }, +) +[main.cpp:177] o5 = Some( + 1234, +) +[main.cpp:179] o6 = Some( + FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, + }, +) +[main.cpp:184] o7 = None +[main.cpp:186] o8 = None +Test Option field conversions -- finished + Test floats -- started -[main.cpp:127] *r1 = 12.3 -[main.cpp:129] v1 = 12.3 -[main.cpp:134] fvec = [ +[main.cpp:196] *r1 = 12.3 +[main.cpp:198] v1 = 12.3 +[main.cpp:203] fvec = [ 42.24, 147.0, ] -[main.cpp:135] fvec.get(0) = Some( +[main.cpp:204] fvec.get(0) = Some( 42.24, ) -[main.cpp:136] fvec.get(2) = None -[main.cpp:137] *fvec.get(1).unwrap() = 147 -[main.cpp:139] fvec = [ +[main.cpp:205] fvec.get(2) = None +[main.cpp:206] *fvec.get(1).unwrap() = 147 +[main.cpp:208] fvec = [ 42.24, 5.43, ] @@ -102,19 +173,19 @@ End of call_dyn_fn_multi_args Test dyn Fn() with multiple arguments -- finished Test Ref> -- started -[main.cpp:160] strvec = [ +[main.cpp:229] strvec = [ "a str", "foobar", "a third str", ] -[main.cpp:161] strvec.get(0) = Some( +[main.cpp:230] strvec.get(0) = Some( "a str", ) -[main.cpp:162] strvec.get(2) = Some( +[main.cpp:231] strvec.get(2) = Some( "a third str", ) -[main.cpp:167] *strvec.get(1).unwrap() = "foobar" -[main.cpp:169] strvec = [ +[main.cpp:236] *strvec.get(1).unwrap() = "foobar" +[main.cpp:238] strvec = [ "a str", "flip flop", "a third str", @@ -123,11 +194,11 @@ Test Ref> -- finished Test zero-sized type -- started Method call on ZST -[main.cpp:176] zst = ZeroSizedType +[main.cpp:245] zst = ZeroSizedType Test zero-sized type -- finished Test nested Ref where T is #heap_allocated and auto field offsets -- started -[main.cpp:191] a = TypeA { +[main.cpp:260] a = TypeA { foo: 10, bar: FieldTypeA { fizz: FieldTypeC { @@ -144,12 +215,12 @@ Test nested Ref where T is #heap_allocated and auto field offsets -- started }, }, } -[main.cpp:192] ::rust::Ref(a.foo) = 10 -[main.cpp:193] ::rust::Ref(a.bar.fizz.buzz_2) = 30 -[main.cpp:194] ::rust::RefMut(a.baz.fizz.buzz_3) = 70 -[main.cpp:197] ::rust::Ref(a_fa_fizz.buzz_1) = 20 -[main.cpp:199] ::rust::Ref(a_fb_fizz.buzz_2) = 60 -[main.cpp:210] b = TypeB { +[main.cpp:261] ::rust::Ref(a.foo) = 10 +[main.cpp:262] ::rust::Ref(a.bar.fizz.buzz_2) = 30 +[main.cpp:263] ::rust::RefMut(a.baz.fizz.buzz_3) = 70 +[main.cpp:266] ::rust::Ref(a_fa_fizz.buzz_1) = 20 +[main.cpp:268] ::rust::Ref(a_fb_fizz.buzz_2) = 60 +[main.cpp:279] b = TypeB { foo: 100, bar: FieldTypeA { fizz: FieldTypeC { @@ -166,40 +237,40 @@ Test nested Ref where T is #heap_allocated and auto field offsets -- started }, }, } -[main.cpp:211] ::rust::Ref(b.foo) = 100 -[main.cpp:212] ::rust::Ref(b.bar.fizz.buzz_2) = 300 -[main.cpp:213] ::rust::RefMut(b.baz.fizz.buzz_3) = 700 -[main.cpp:216] ::rust::Ref(b_fa_fizz.buzz_1) = 200 -[main.cpp:218] ::rust::Ref(b_fb_fizz.buzz_2) = 600 -[main.cpp:223] fa = FieldTypeA { +[main.cpp:280] ::rust::Ref(b.foo) = 100 +[main.cpp:281] ::rust::Ref(b.bar.fizz.buzz_2) = 300 +[main.cpp:282] ::rust::RefMut(b.baz.fizz.buzz_3) = 700 +[main.cpp:285] ::rust::Ref(b_fa_fizz.buzz_1) = 200 +[main.cpp:287] ::rust::Ref(b_fb_fizz.buzz_2) = 600 +[main.cpp:292] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } -[main.cpp:224] ::rust::Ref(fa.fizz.buzz_1) = 21 -[main.cpp:225] ::rust::Ref(fa.fizz.buzz_3) = 41 -[main.cpp:230] fa = FieldTypeA { +[main.cpp:293] ::rust::Ref(fa.fizz.buzz_1) = 21 +[main.cpp:294] ::rust::Ref(fa.fizz.buzz_3) = 41 +[main.cpp:299] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } -[main.cpp:231] ::rust::Ref(fb.fizz.buzz_2) = 61 -[main.cpp:232] ::rust::Ref(fb.fizz.buzz_3) = 71 +[main.cpp:300] ::rust::Ref(fb.fizz.buzz_2) = 61 +[main.cpp:301] ::rust::Ref(fb.fizz.buzz_3) = 71 Test nested Ref where T is #heap_allocated and auto field offsets -- finished Test #layout_conservative -- started -[main.cpp:241] c_layout = ConservativeLayoutType { +[main.cpp:310] c_layout = ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", } Rust( size = 32 , align = 8 ) c++( size = 48 , align = 8 ) -[main.cpp:253] layouts = [ +[main.cpp:322] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, @@ -211,26 +282,26 @@ c++( size = 48 , align = 8 ) field3: "Another test string", }, ] -[main.cpp:254] layouts.get(0) = Some( +[main.cpp:323] layouts.get(0) = Some( ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", }, ) -[main.cpp:255] layouts.get(1) = Some( +[main.cpp:324] layouts.get(1) = Some( ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", }, ) -[main.cpp:256] layouts.get(1).unwrap() = ConservativeLayoutType { +[main.cpp:325] layouts.get(1).unwrap() = ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", } -[main.cpp:258] layouts = [ +[main.cpp:327] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, @@ -245,28 +316,28 @@ c++( size = 48 , align = 8 ) Test #layout_conservative -- finished Test dbg of Ref> -- started -[main.cpp:270] r = RefHolder( +[main.cpp:339] r = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:271] rr = RefHolder( +[main.cpp:340] rr = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:272] rrr = RefHolder( +[main.cpp:341] rrr = RefHolder( SimpleFoo( 42, ), ) -[main.cpp:273] rust::Ref(r.f0) = SimpleFoo( +[main.cpp:342] rust::Ref(r.f0) = SimpleFoo( 42, ) -[main.cpp:274] rust::Ref((*rr).f0) = SimpleFoo( +[main.cpp:343] rust::Ref((*rr).f0) = SimpleFoo( 42, ) -[main.cpp:275] rust::Ref((**rrr).f0) = SimpleFoo( +[main.cpp:344] rust::Ref((**rrr).f0) = SimpleFoo( 42, ) Test dbg of Ref> -- finished diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index 0b14268..ad675a4 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -116,6 +116,75 @@ void test_ref_deref_of_user_types() { zngur_dbg(holder.flag); } +void test_bool_and_copy_field_conversions() { + auto scope = rust::crate::Scoped::new_( + "Test Bool and Copy struct field conversions"_rs); + + rust::crate::BoolHolder holder{true, rust::crate::FieldTypeC{7, 8, 9}}; + + // FieldOwned specializations used to drop operator T(), so types with a + // specialization (like Bool) lost the conversion that primitives like + // int32_t had. + zngur_dbg(holder.flag); + rust::Bool b1 = holder.flag; + zngur_dbg(b1); + + // Copy struct field converts by value + rust::crate::FieldTypeC c1 = holder.data; + zngur_dbg(c1); + + // Nested field conversion still works + int32_t v1 = holder.data.buzz_2; + zngur_dbg(v1); + + // Same conversions on Ref and RefMut fields + rust::Ref r = holder; + zngur_dbg(r.flag); + rust::crate::FieldTypeC c2 = r.data; + zngur_dbg(c2); + + rust::RefMut m = holder; + zngur_dbg(m.flag); + rust::crate::FieldTypeC c3 = m.data; + zngur_dbg(int32_t(c3.buzz_1)); +} + +void test_option_field_conversions() { + auto scope = rust::crate::Scoped::new_("Test Option field conversions"_rs); + + auto some = rust::crate::make_option_holder(); + zngur_dbg(some); + + // `Option<&T>` is Copy, so it converts out of a FieldOwned by value + rust::std::option::Option> o1 = some.opt_ref; + zngur_dbg(o1); + + // Same for `Option` + rust::std::option::Option o2 = some.opt_copy; + zngur_dbg(o2); + zngur_dbg(o2.unwrap()); + + // Conversions also work on Ref and RefMut fields + rust::Ref r = some; + rust::std::option::Option> o3 = r.opt_ref; + zngur_dbg(o3); + rust::std::option::Option o4 = r.opt_copy; + zngur_dbg(o4); + + rust::RefMut m = some; + rust::std::option::Option> o5 = m.opt_ref; + zngur_dbg(o5); + rust::std::option::Option o6 = m.opt_copy; + zngur_dbg(o6); + + // And the None case works too + auto none = rust::crate::make_empty_option_holder(); + rust::std::option::Option> o7 = none.opt_ref; + zngur_dbg(o7); + rust::std::option::Option o8 = none.opt_copy; + zngur_dbg(o8); +} + void test_floats() { auto scope = rust::crate::Scoped::new_("Test floats"_rs); @@ -279,6 +348,8 @@ int main() { test_fields_and_constructor(); test_field_underlying_conversions(); test_ref_deref_of_user_types(); + test_bool_and_copy_field_conversions(); + test_option_field_conversions(); test_floats(); test_dyn_fn_with_multiple_arguments(); test_refref(); diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index e29c2cf..40ea416 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -79,6 +79,22 @@ mod ::std::option { fn unwrap(self) -> &&str; } + type Option<&i32> { + #layout(size = 8, align = 8); + wellknown_traits(Debug, Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &i32; + } + + type Option { + #layout(size = 16, align = 4); + wellknown_traits(Debug, Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> crate::FieldTypeC; + } + type Option<&mut &str> { #layout(size = 8, align = 8); wellknown_traits(Debug); @@ -167,6 +183,9 @@ type crate::ZeroSizedType { } mod crate { + fn make_option_holder() -> crate::OptionHolder; + fn make_empty_option_holder() -> crate::OptionHolder; + fn call_dyn_fn_multi_args(Box); } @@ -226,6 +245,14 @@ type crate::BoolHolder { field flag (offset = 12, type = bool ); } +type crate::OptionHolder { + #layout(size = 24, align = 8); + wellknown_traits(Debug, Copy); + + field opt_ref (offset = auto, type = ::std::option::Option<&i32> ); + field opt_copy (offset = auto, type = ::std::option::Option ); +} + type crate::ConservativeLayoutType { // bigger than the real size of 32 #layout_conservative(size = 48, align = 8 ); diff --git a/examples/regression_test1/src/lib.rs b/examples/regression_test1/src/lib.rs index 5ebc00b..e3073df 100644 --- a/examples/regression_test1/src/lib.rs +++ b/examples/regression_test1/src/lib.rs @@ -58,12 +58,42 @@ struct ConservativeLayoutType { #[allow(unused)] #[derive(Debug, Copy, Clone)] -/// bool field + Copy struct field, to test Ref/RefMut deref of user types +/// bool field + Copy struct field, to test Ref/RefMut/FieldOwned conversion on +/// specialized (non-primitive) field types struct BoolHolder { pub flag: bool, pub data: FieldTypeC, } +#[allow(unused)] +#[derive(Debug, Copy, Clone)] +/// Option fields, to test Field* conversion for Copy generic instantiations +/// (`Option<&T>` and `Option` are both `Copy`) +struct OptionHolder<'a> { + pub opt_ref: Option<&'a i32>, + pub opt_copy: Option, +} + +fn make_option_holder() -> OptionHolder<'static> { + let leaked: &'static i32 = Box::leak(Box::new(1234)); + OptionHolder { + opt_ref: Some(leaked), + opt_copy: Some(FieldTypeC { + buzz_1: 11, + buzz_2: 22, + buzz_3: 33, + }), + } +} + +fn make_empty_option_holder() -> OptionHolder<'static> { + OptionHolder { + opt_ref: None, + opt_copy: None, + } +} + +#[allow(unused)] impl ConservativeLayoutType { pub fn mem_size(&self) -> usize { std::mem::size_of::() diff --git a/zngur-generator/src/template.rs b/zngur-generator/src/template.rs index da9ac23..fd17eff 100644 --- a/zngur-generator/src/template.rs +++ b/zngur-generator/src/template.rs @@ -257,6 +257,25 @@ impl<'a> CppHeaderTemplate<'a> { )); } } + + // Out-of-line definitions for the `operator T` conversion declared in the + // per-type `Field*` specializations. The specializations are emitted before + // the type and `Ref` definitions. + if td.has_copy() && !is_unsized && !td.layout.is_only_by_ref() { + for field_kind in ["FieldOwned", "FieldRef", "FieldRefMut"] { + s.push_str(&format!( + r#" + template + inline {namespace}::{field_kind}< {ty}, Offset, Offsets... >::operator {ty}() const {{ + return *::{namespace}::Ref< {ty} >(*this); + }} +"#, + namespace = self.namespace, + field_kind = field_kind, + ty = td.ty, + )); + } + } s } diff --git a/zngur-generator/templates/cpp_header.sptl b/zngur-generator/templates/cpp_header.sptl index 1f69650..d50141a 100644 --- a/zngur-generator/templates/cpp_header.sptl +++ b/zngur-generator/templates/cpp_header.sptl @@ -189,6 +189,14 @@ namespace {{ self.namespace }} { }; {% endif %} + {# The primary template of `{{ field_kind }}` provides a conversion to `T`, #} + {# but this explicit specialization would silently drop it. Only `Copy` #} + {# types can support it, since it copies the value out of the parent. #} + {# Defined out-of-line, where `Ref` and `T` are complete. #} + {% if td.has_copy() && !td.has_unsized() %} + inline operator {{ td.ty }}() const; + {% endif %} + {% for method in td.methods %} {% if method.is_valid_field_method(field_kind) %} {{ method.sig.output }} {{ method.name }}(