Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Author

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.

requires std::same_as<decltype(CPP2_FORWARD(factory)()), T>
{ cpp2_default.enforce(!init); new (&data) T(CPP2_FORWARD(factory)()); init = true; }
};


Expand All @@ -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);
Expand All @@ -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)();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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 out down below.

}
else {
cpp2_default.report_violation("attempted to copy assign, but copy assignment is not available");
Expand All @@ -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;
}
}
Expand Down
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;
}
85 changes: 85 additions & 0 deletions regression-tests/mixed-bugfix-for-out-narrowing.cpp2
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]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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.
Loading