Skip to content
Draft
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
40 changes: 25 additions & 15 deletions src/lower_il.c
Original file line number Diff line number Diff line change
Expand Up @@ -16074,32 +16074,42 @@ Several nested aggregates can have the type of "this", as in:

where "k" refers to the Y being initialized, not to the temporary
initialized by "Y{2, k}". The aggregates for which an explicitly specified
element is being lowered are therefore skipped.
element is being lowered are therefore skipped. When no other aggregate
matches, the "this" of a constructor init comes next, as in:

struct D : Y { D() {} };

where the default member initializer of Y::n is lowered by the constructor
of Y, and "k" refers to the object that constructor initializes. Only
then are the skipped aggregates searched.
*/
{
a_boolean found = FALSE;

check_assertion(expr->kind == (an_expr_node_kind)enk_param_ref &&
expr->variant.param_ref.param_num == 0);
if (aggregate_this_stack != NULL) {
/* We must be lowering an aggregate constant. If no candidate aggregate
matches, fall back on all the aggregates on the stack. */
/* We must be lowering an aggregate constant. Look for a candidate
aggregate. */
found = replace_param_ref_with_aggregate_this(expr,
/*candidates_only=*/TRUE);
} /* if */
if (!found && ctor_init_this != NULL &&
identical_types_ignoring_qualifiers(ctor_init_this->type,
expr->type)) {
/* We're lowering a constructor init; use the "this" parameter for
the function. */
set_expr_node_kind(expr, (an_expr_node_kind)enk_variable);
node_variable(expr) = ctor_init_this;
found = TRUE;
} /* if */
if (!found && aggregate_this_stack != NULL) {
/* Fall back on all the aggregates on the stack. */
found = replace_param_ref_with_aggregate_this(expr,
/*candidates_only=*/TRUE) ||
replace_param_ref_with_aggregate_this(expr,
/*candidates_only=*/FALSE);
} /* if */
if (!found) {
if (ctor_init_this != NULL) {
/* We're lowering a constructor init; use the "this" parameter for
the function. */
check_assertion(identical_types_ignoring_qualifiers(ctor_init_this->type,
expr->type));
set_expr_node_kind(expr, (an_expr_node_kind)enk_variable);
node_variable(expr) = ctor_init_this;
} else {
unexpected_condition();
} /* if */
unexpected_condition();
} /* if */
} /* lower_param_ref */

Expand Down
3 changes: 2 additions & 1 deletion src/lower_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -6773,7 +6773,8 @@ dealt with).
initializer (i.e., it is implicit); an explicitly specified element
refers to the "this" of the enclosing context, as in:
struct Y { unsigned k; unsigned n = Y{2, k}.n; };
where "k" in "Y{2, k}" designates the outer Y, not the temporary.
where "k" in "Y{2, k}" designates the outer Y, not the temporary;
the outer Y may also be the object of a constructor init.
The default member initializers of the members of an anonymous
union refer to the enclosing class, so an anonymous union does not
affect the search. */
Expand Down
16 changes: 16 additions & 0 deletions src/pending_changes/gh-324-nsdmi-this-ctor-init.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[GH #324]
Implicit "this" in a default member initializer used by a constructor

The previous fix for a default member initializer that contains an
aggregate initialization of another object of the same class did not cover
the case where the default member initializer is used by a constructor (an
implicitly-defined default constructor, possibly called for a base class or
a member). An implicit use of "this" in the elements of the nested
initialization still referred to the nested object instead of to the object
being constructed. For example:

struct Y { unsigned k = 7; unsigned n = Y{2, k}.n; };
struct D : Y { D() {} };
unsigned n = D().n; // n is now 7 (previously 2).

This is now fixed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=1 --c++14 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=2 --c++17 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=3 --c++20 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=4 --c++20 --g++ --gnu_version=150000 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=5 --c++20 --clang Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eccp -DTEST_NUMBER=6 --c++23 Test_name.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./a.out
130 changes: 130 additions & 0 deletions tests/tests/regressions/gh-324-ctor-init.sft.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//type:rp
//options:--c++14:--c++17:--c++20:--c++20 --g++ --gnu_version=150000:--c++20 --clang:--c++23
// An implicit "this" in a default member initializer that is used by a
// constructor refers to the object being constructed, even when the
// initializer aggregate-initializes another object of the same class.
int fails;
void check(bool b) { if (!b) ++fails; }

struct Y { unsigned k = 7; unsigned n = Y{2, k}.n; }; // n = this->k

// Initializers whose value is known only at run time, so the initialization
// cannot be folded into a constant.
volatile unsigned seven = 7;
unsigned get_seven() { return seven; }
struct YV { unsigned k = seven; unsigned n = YV{2, k}.n; };
struct YF { unsigned k = get_seven(); unsigned n = YF{2, k}.n; };

// The constructor of Y is called by another constructor.
struct D : Y { D() {} };
struct D2 : Y {};
struct D3 : Y { D3() : Y() {} };
struct D4 : Y { D4() : Y{} {} };
struct H { Y y; H() {} };
struct H2 { Y y = Y(); };
struct HA { Y a[2]; HA() {} };

// Other forms of the default member initializer.
struct T1 { unsigned k = 7; unsigned n = T1{2, this->k}.n; };
struct T2 { unsigned k = 7; unsigned n = T2{2, T2{3, k}.n}.n; };
struct T3 {
static T3 bar(T3 y) { return y; }
unsigned k = 7;
unsigned n = bar(T3{2, k}).n;
};
struct T4 {
static unsigned get(const T4 &y) { return y.n; }
unsigned k = 7;
unsigned n = get(T4{2, k});
};
struct T5 { unsigned k = 7; unsigned n = [this] { return T5{2, k}.n; }(); };
#if __cplusplus > 201703L
struct T6 { unsigned k = 7; unsigned n = T6{.k = 2, .n = k}.n; };
#endif

#if __cplusplus >= 201703L
// An aggregate with a base class.
struct B { unsigned k = 7; };
struct Z : B { unsigned n = Z{{2}, k}.n; };
struct DZ : Z { DZ() {} };
#endif

template <class T> struct TY { T k = 7; T n = TY{2, k}.n; };
struct DT : TY<unsigned> { DT() {} };

// A user-provided constructor makes sure the default member initializers
// are used by a constructor (and not folded into a constant).
template <class T> struct Der : T { Der() {} };

// Nested aggregates of different classes (PR c++/100252).
struct PA { unsigned x; unsigned y = x; };
struct PB { unsigned x = 42; PA a = PA{x}; };
struct PC { unsigned x = 42; PB b = PB{x}; PC() {} };

Y gy;
YV gyv;

int main() {
Y y;
check(y.n == 7);
check(gy.n == 7);
static Y sy;
check(sy.n == 7);
check((new Y)->n == 7);
check((new Y())->n == 7);
check(Y().n == 7);
check(Y{}.n == 7);
YV yv;
check(yv.n == 7); // Previously 2.
check(gyv.n == 7); // Previously 2.
check((new YV)->n == 7); // Previously 2.
check(YV().n == 7); // Previously 2.
check(YV{}.n == 7);
YF yf;
check(yf.n == 7); // Previously 2.
check(D().n == 7); // Previously 2.
D2 d2;
check(d2.n == 7);
check(D3().n == 7); // Previously 2.
check(D4().n == 7);
check(H().y.n == 7); // Previously 2.
H2 h2;
check(h2.y.n == 7);
check(HA().a[1].n == 7); // Previously 2.
Y arr[3];
check(arr[2].n == 7);
check((new Y[2])[1].n == 7); // Previously 2.
T1 t1;
check(t1.n == 7);
T2 t2;
check(t2.n == 7);
T3 t3;
check(t3.n == 7); // Previously 2.
T4 t4;
check(t4.n == 7); // Previously 2.
T5 t5;
check(t5.n == 7);
check(Der<T1>().n == 7); // Previously 2.
check(Der<T2>().n == 7); // Previously 2.
check(Der<T5>().n == 7); // Previously 2.
#if __cplusplus > 201703L
T6 t6;
check(t6.n == 7);
check(Der<T6>().n == 7); // Previously 2.
#endif
#if __cplusplus >= 201703L
check(DZ().n == 7); // Previously 2.
#endif
TY<unsigned> ty;
check(ty.n == 7);
check(TY<unsigned>().n == 7);
check(DT().n == 7); // Previously 2.
check(Der<TY<int>>().n == 7); // Previously 2.
Y a{1};
check(a.n == 1);
Y c = D();
check(c.n == 7); // Previously 2.
PC pc;
check(pc.b.a.y == 42);
return fails;
}
Loading