-
Notifications
You must be signed in to change notification settings - Fork 267
Fix deferred init narrowing #1446 #1449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1488,11 +1488,19 @@ class deferred_init { | |
| auto destroy() -> void { if (init) { t().~T(); } init = false; } | ||
|
|
||
| public: | ||
| using value_type = T; | ||
|
|
||
| constexpr deferred_init() noexcept { } | ||
| constexpr ~deferred_init() noexcept { destroy(); } | ||
| constexpr auto value() noexcept -> T& { cpp2_default.enforce(init); return t(); } | ||
|
|
||
| constexpr auto construct(auto&& ...args) -> void { cpp2_default.enforce(!init); new (&data) T{CPP2_FORWARD(args)...}; init = true; } | ||
| constexpr auto construct(auto&& ...args) -> void { | ||
| construct_from([&]() -> T { return T{CPP2_FORWARD(args)...}; }); | ||
| } | ||
|
|
||
| constexpr auto construct_from(auto&& factory) -> void | ||
| requires std::same_as<decltype(CPP2_FORWARD(factory)()), T> | ||
| { cpp2_default.enforce(!init); new (&data) T(CPP2_FORWARD(factory)()); init = true; } | ||
| }; | ||
|
|
||
|
|
||
|
|
@@ -1512,6 +1520,8 @@ class out { | |
| bool called_construct_ = false; | ||
|
|
||
| public: | ||
| using value_type = T; | ||
|
|
||
| constexpr out(T* t_) noexcept : t{ t_}, has_t{true} { cpp2_default.enforce( t); } | ||
| constexpr out(deferred_init<T>* dt_) noexcept : dt{dt_}, has_t{false} { cpp2_default.enforce(dt); } | ||
| constexpr out(out<T>* ot_) noexcept : ot{ot_}, has_t{ot_->has_t} { cpp2_default.enforce(ot); | ||
|
|
@@ -1535,10 +1545,16 @@ class out { | |
| } | ||
|
|
||
| constexpr auto construct(auto&& ...args) -> void { | ||
| construct_from([&]() -> T { return T{CPP2_FORWARD(args)...}; }); | ||
| } | ||
|
|
||
| constexpr auto construct_from(auto&& factory) -> void | ||
| requires std::same_as<decltype(CPP2_FORWARD(factory)()), T> | ||
| { | ||
| if (has_t || called_construct()) { | ||
| if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) { | ||
| if constexpr (requires { *t = CPP2_FORWARD(factory)(); }) { | ||
| cpp2_default.enforce( t ); | ||
| *t = T(CPP2_FORWARD(args)...); | ||
| *t = CPP2_FORWARD(factory)(); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, instead of parens-init we switched to braces-init to align with deferred init, which is also used in |
||
| } | ||
| else { | ||
| cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available"); | ||
|
|
@@ -1547,15 +1563,15 @@ class out { | |
| else { | ||
| cpp2_default.enforce( dt ); | ||
| if (dt->init) { | ||
| if constexpr (requires { *t = T(CPP2_FORWARD(args)...); }) { | ||
| dt->value() = T(CPP2_FORWARD(args)...); | ||
| if constexpr (requires { dt->value() = CPP2_FORWARD(factory)(); }) { | ||
| dt->value() = CPP2_FORWARD(factory)(); | ||
| } | ||
| else { | ||
| cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available"); | ||
| } | ||
| } | ||
| else { | ||
| dt->construct(CPP2_FORWARD(args)...); | ||
| dt->construct_from(CPP2_FORWARD(factory)); | ||
| called_construct() = true; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #include <cstdint> | ||
|
|
||
| struct S { | ||
| std::int8_t x; | ||
| std::int8_t y; | ||
| }; | ||
|
|
||
| struct Nested { | ||
| S left; | ||
| S right; | ||
| }; | ||
|
|
||
| inline constexpr std::int16_t constexpr_i16 = 100; | ||
| inline constexpr std::int8_t constexpr_i8 = 8; | ||
|
|
||
| main: () -> int = { | ||
| runtime_i8: i8 = 8; | ||
|
|
||
| s_good0: S = (constexpr_i16, runtime_i8); | ||
| s_good: S; | ||
| s_good = (constexpr_i16, runtime_i8); | ||
|
|
||
| s_reverse0: S = (runtime_i8, constexpr_i16); | ||
| s_reverse: S; | ||
| s_reverse = (runtime_i8, constexpr_i16); | ||
|
|
||
| s_constexpr0: S = (constexpr_i16, constexpr_i16); | ||
| s_constexpr: S; | ||
| s_constexpr = (constexpr_i16, constexpr_i16); | ||
|
|
||
| s_runtime0: S = (runtime_i8, runtime_i8); | ||
| s_runtime: S; | ||
| s_runtime = (runtime_i8, runtime_i8); | ||
|
|
||
| nested0: Nested = ( | ||
| :S = (constexpr_i16, runtime_i8), | ||
| :S = (constexpr_i8, runtime_i8) | ||
| ); | ||
| nested: Nested; | ||
| nested = ( | ||
| :S = (constexpr_i16, runtime_i8), | ||
| :S = (constexpr_i8, runtime_i8) | ||
| ); | ||
|
|
||
| assert(s_good0.x == 100 && s_good.y == 8); | ||
| assert(s_reverse0.x == 8 && s_reverse.y == 100); | ||
| assert(s_constexpr0.x == 100 && s_constexpr.y == 100); | ||
| assert(s_runtime0.x == 8 && s_runtime.y == 8); | ||
| assert(nested0.left.x == 100 && nested.right.y == 8); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| #include <cstdint> | ||
| #include <array> | ||
|
|
||
| struct S { | ||
| std::int8_t x; | ||
| std::int8_t y; | ||
| }; | ||
|
|
||
| struct Nested { | ||
| S left; | ||
| S right; | ||
| }; | ||
|
|
||
| inline constexpr std::int16_t constexpr_i16 = 100; | ||
|
|
||
| fill_i8: (out x: i8) = { | ||
| x = 42; | ||
| } | ||
|
|
||
| fill_array: (out a: std::array<i8, 3>) = { | ||
| a = (1, 2, 3); | ||
| } | ||
|
|
||
| fill_s: (out s: S, x: i8) = { | ||
| s = (constexpr_i16, x); | ||
| } | ||
|
|
||
| fill_nested: (out n: Nested, x: i8) = { | ||
| n = ( | ||
| :S = (constexpr_i16, x), | ||
| :S = (x, constexpr_i16) | ||
| ); | ||
| } | ||
|
|
||
| // out -> out forwarding still keeps brace-init in the final writer | ||
| fill_via_fwd: (out x: i8) = { | ||
| fill_i8(out x); | ||
| } | ||
|
|
||
| // first assignment constructs; second goes through the assign path | ||
| refill_s: (out s: S, x: i8) = { | ||
| s = (x, constexpr_i16); | ||
| s = (constexpr_i16, x); | ||
| } | ||
|
|
||
| overwrite_i8: (out x: i8) = { | ||
| x = 100; | ||
| } | ||
|
|
||
| main: () -> int = { | ||
| a: i8; | ||
| fill_i8(out a); | ||
|
|
||
| arr: std::array<i8, 3>; | ||
| fill_array(out arr); | ||
|
|
||
| runtime_i8: i8 = 8; | ||
|
|
||
| s: S; | ||
| fill_s(out s, runtime_i8); | ||
|
|
||
| n: Nested; | ||
| fill_nested(out n, runtime_i8); | ||
|
|
||
| b: i8; | ||
| fill_via_fwd(out b); | ||
|
|
||
| s2: S; | ||
| refill_s(out s2, runtime_i8); | ||
|
|
||
| // already-initialized target: out assigns rather than constructs | ||
| c: i8 = 1; | ||
| overwrite_i8(out c); | ||
|
|
||
| assert(a == 42); | ||
| assert(arr[0] == 1 && arr[1] == 2 && arr[2] == 3); | ||
| assert(s.x == 100 && s.y == 8); | ||
| assert(n.left.x == 100 && n.left.y == 8); | ||
| assert(n.right.x == 8 && n.right.y == 100); | ||
| assert(b == 42); | ||
| assert(s2.x == 100 && s2.y == 8); | ||
| assert(c == 100); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| value: i32 == 20; | ||
|
|
||
| foo: () -> i32 == 20; | ||
|
|
||
| add: (x: i32, y: i32) -> i32 == x + y; | ||
|
|
||
| main: () -> int = { | ||
| a0: i8 = value + 100; | ||
| a: i8; | ||
| a = value + 100; | ||
|
|
||
| b0: i8 = foo() + 100; | ||
| b: i8; | ||
| b = foo() + 100; | ||
|
|
||
| c0: i16 = add(value, foo()) + 100; | ||
| c: i16; | ||
| c = add(value, foo()) + 100; | ||
|
|
||
| local: i32 == 10; | ||
|
|
||
| d0: i16 = add(local, foo()) * 2 + value; | ||
| d: i16; | ||
| d = add(local, foo()) * 2 + value; | ||
|
|
||
| e0: std::array<i8, 3> = (value + 100, foo() + 100, add(local, foo())); | ||
| e: std::array<i8, 3>; | ||
| e = (value + 100, foo() + 100, add(local, foo())); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| main: () -> int = { | ||
| a0: u8 = 'a'; | ||
| a: u8; | ||
| a = 'a'; | ||
|
|
||
| b0: i8 = 1; | ||
| b: i8; | ||
| b = 1; | ||
|
|
||
| c0: u8 = 2; | ||
| c: u8; | ||
| c = 2; | ||
|
|
||
| d0: i16 = 3; | ||
| d: i16; | ||
| d = 3; | ||
|
|
||
| e0: u16 = 4; | ||
| e: u16; | ||
| e = 4; | ||
|
|
||
| f0: std::array<i8, 3> = (5, 6, 7); | ||
| f: std::array<i8, 3>; | ||
| f = (5, 6, 7); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Target type is {i8, i8}. We try every pair from: | ||
| // ce_i8, ce_i16, rt_i8, rt_i16 | ||
| // = 4 x 4 = 16 combinations. | ||
| // | ||
| // Constexpr sources must appear by name in the initializer, otherwise they | ||
| // stop being constant expressions and every i16 case would fail. | ||
|
|
||
| ce_i8: i8 == 8; | ||
| ce_i16: i16 == 8; | ||
|
|
||
| try_all_combinations: (rt_i8: i8, rt_i16: i16) = { | ||
| a00: std::array<i8, 2>; | ||
| a00 = (ce_i8, ce_i8); | ||
| a01: std::array<i8, 2>; | ||
| a01 = (ce_i8, ce_i16); | ||
| a02: std::array<i8, 2>; | ||
| a02 = (ce_i8, rt_i8); | ||
| a03: std::array<i8, 2>; | ||
| a03 = (ce_i8, rt_i16); // fail: runtime i16 -> i8 | ||
|
|
||
| a10: std::array<i8, 2>; | ||
| a10 = (ce_i16, ce_i8); | ||
| a11: std::array<i8, 2>; | ||
| a11 = (ce_i16, ce_i16); | ||
| a12: std::array<i8, 2>; | ||
| a12 = (ce_i16, rt_i8); | ||
| a13: std::array<i8, 2>; | ||
| a13 = (ce_i16, rt_i16); // fail: runtime i16 -> i8 | ||
|
|
||
| a20: std::array<i8, 2>; | ||
| a20 = (rt_i8, ce_i8); | ||
| a21: std::array<i8, 2>; | ||
| a21 = (rt_i8, ce_i16); | ||
| a22: std::array<i8, 2>; | ||
| a22 = (rt_i8, rt_i8); | ||
| a23: std::array<i8, 2>; | ||
| a23 = (rt_i8, rt_i16); // fail: runtime i16 -> i8 | ||
|
|
||
| a30: std::array<i8, 2>; | ||
| a30 = (rt_i16, ce_i8); // fail: runtime i16 -> i8 | ||
| a31: std::array<i8, 2>; | ||
| a31 = (rt_i16, ce_i16); // fail: runtime i16 -> i8 | ||
| a32: std::array<i8, 2>; | ||
| a32 = (rt_i16, rt_i8); // fail: runtime i16 -> i8 | ||
| a33: std::array<i8, 2>; | ||
| a33 = (rt_i16, rt_i16); // fail: runtime i16 -> i8 | ||
| } | ||
|
|
||
| main: () -> int = { | ||
| rt_i8: i8 = 8; | ||
| rt_i16: i16 = 8; | ||
| try_all_combinations(rt_i8, rt_i16); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| main: () -> int = { | ||
| a0: i8 = -1; | ||
| a: i8; | ||
| a = -1; | ||
|
|
||
| b0: i16 = -2; | ||
| b: i16; | ||
| b = -2; | ||
|
|
||
| c0: std::array<i8, 3> = (-3, -4, -5); | ||
| c: std::array<i8, 3>; | ||
| c = (-3, -4, -5); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:19:125: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tests errors, i.e. failing cases, when we do want the compilation to fail. |
||
| 19 | a03.construct_from([&]() -> typename CPP2_TYPEOF(a03)::value_type { return typename CPP2_TYPEOF(a03)::value_type{ce_i8, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:19:125: note: insert an explicit cast to silence this issue | ||
| 19 | a03.construct_from([&]() -> typename CPP2_TYPEOF(a03)::value_type { return typename CPP2_TYPEOF(a03)::value_type{ce_i8, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:28:126: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 28 | a13.construct_from([&]() -> typename CPP2_TYPEOF(a13)::value_type { return typename CPP2_TYPEOF(a13)::value_type{ce_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:28:126: note: insert an explicit cast to silence this issue | ||
| 28 | a13.construct_from([&]() -> typename CPP2_TYPEOF(a13)::value_type { return typename CPP2_TYPEOF(a13)::value_type{ce_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:37:125: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 37 | a23.construct_from([&]() -> typename CPP2_TYPEOF(a23)::value_type { return typename CPP2_TYPEOF(a23)::value_type{rt_i8, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:37:125: note: insert an explicit cast to silence this issue | ||
| 37 | a23.construct_from([&]() -> typename CPP2_TYPEOF(a23)::value_type { return typename CPP2_TYPEOF(a23)::value_type{rt_i8, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:40:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 40 | a30.construct_from([&]() -> typename CPP2_TYPEOF(a30)::value_type { return typename CPP2_TYPEOF(a30)::value_type{rt_i16, ce_i8}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:40:118: note: insert an explicit cast to silence this issue | ||
| 40 | a30.construct_from([&]() -> typename CPP2_TYPEOF(a30)::value_type { return typename CPP2_TYPEOF(a30)::value_type{rt_i16, ce_i8}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:42:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 42 | a31.construct_from([&]() -> typename CPP2_TYPEOF(a31)::value_type { return typename CPP2_TYPEOF(a31)::value_type{rt_i16, ce_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:42:118: note: insert an explicit cast to silence this issue | ||
| 42 | a31.construct_from([&]() -> typename CPP2_TYPEOF(a31)::value_type { return typename CPP2_TYPEOF(a31)::value_type{rt_i16, ce_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:44:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 44 | a32.construct_from([&]() -> typename CPP2_TYPEOF(a32)::value_type { return typename CPP2_TYPEOF(a32)::value_type{rt_i16, rt_i8}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:44:118: note: insert an explicit cast to silence this issue | ||
| 44 | a32.construct_from([&]() -> typename CPP2_TYPEOF(a32)::value_type { return typename CPP2_TYPEOF(a32)::value_type{rt_i16, rt_i8}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:118: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:118: note: insert an explicit cast to silence this issue | ||
| 46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:126: error: non-constant-expression cannot be narrowed from type 'cpp2::impl::in<cpp2::i16>' (aka 'const short') to 'signed char' in initializer list [-Wc++11-narrowing] | ||
| 46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| pure2-bugfix-for-deferred-init-narrowing-matrix-error.cpp2:46:126: note: insert an explicit cast to silence this issue | ||
| 46 | a33.construct_from([&]() -> typename CPP2_TYPEOF(a33)::value_type { return typename CPP2_TYPEOF(a33)::value_type{rt_i16, rt_i16}; });// fail: runtime i16 -> i8 | ||
| | ^~~~~~ | ||
| | static_cast<signed char>( ) | ||
| 8 errors generated. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the factory, all the values are still on the caller context, which means their constexpr-ness isn't lost during crossing the call boundaries.