diff --git a/src/lower_il.c b/src/lower_il.c index 832fee1d98..9b27d73a3a 100644 --- a/src/lower_il.c +++ b/src/lower_il.c @@ -16074,7 +16074,14 @@ 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; @@ -16082,24 +16089,27 @@ element is being lowered are therefore skipped. 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 */ diff --git a/src/lower_init.c b/src/lower_init.c index 156d63c8cc..89a1d4f1e0 100644 --- a/src/lower_init.c +++ b/src/lower_init.c @@ -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. */ diff --git a/src/pending_changes/gh-324-nsdmi-this-ctor-init.txt b/src/pending_changes/gh-324-nsdmi-this-ctor-init.txt new file mode 100644 index 0000000000..87e9db29f8 --- /dev/null +++ b/src/pending_changes/gh-324-nsdmi-this-ctor-init.txt @@ -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. diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.1.txt new file mode 100644 index 0000000000..30e4550776 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c++14 Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.1.txt new file mode 100644 index 0000000000..322b88700b --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=2 --c++17 Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.2.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.1.txt new file mode 100644 index 0000000000..2ee989041b --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=3 --c++20 Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.3.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.1.txt new file mode 100644 index 0000000000..582096beac --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=4 --c++20 --g++ --gnu_version=150000 Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.4.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.1.txt new file mode 100644 index 0000000000..ed5910b924 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=5 --c++20 --clang Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.5.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.1.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.1.txt new file mode 100644 index 0000000000..c827090d96 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=6 --c++23 Test_name.c diff --git a/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.2.txt b/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-324-ctor-init.rto/default.6.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-324-ctor-init.sft.cpp b/tests/tests/regressions/gh-324-ctor-init.sft.cpp new file mode 100644 index 0000000000..805a44e41c --- /dev/null +++ b/tests/tests/regressions/gh-324-ctor-init.sft.cpp @@ -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 struct TY { T k = 7; T n = TY{2, k}.n; }; +struct DT : TY { DT() {} }; + +// A user-provided constructor makes sure the default member initializers +// are used by a constructor (and not folded into a constant). +template 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().n == 7); // Previously 2. + check(Der().n == 7); // Previously 2. + check(Der().n == 7); // Previously 2. +#if __cplusplus > 201703L + T6 t6; + check(t6.n == 7); + check(Der().n == 7); // Previously 2. +#endif +#if __cplusplus >= 201703L + check(DZ().n == 7); // Previously 2. +#endif + TY ty; + check(ty.n == 7); + check(TY().n == 7); + check(DT().n == 7); // Previously 2. + check(Der>().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; +}