Skip to content

Bind an NSDMI's implicit this to the right aggregate [GH #324] - #2

Closed
Momen-G-Ar wants to merge 1 commit into
mainfrom
gh-324-nsdmi-this-nested-aggregate
Closed

Momen-G-Ar wants to merge 1 commit into
mainfrom
gh-324-nsdmi-this-nested-aggregate

Conversation

@Momen-G-Ar

@Momen-G-Ar Momen-G-Ar commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes GH #324 (discussion): when the default member initializer of an aggregate class aggregate-initializes another object of the same class, the implicit this in the elements of that nested initializer referred to the nested object instead of to the object being initialized.

struct Y { unsigned k; unsigned n = Y{2, k}.n; };  // n = this->k
int main() {
  Y *p = new Y{1};
  if (p->n != 1) abort();  // EDG: p->n == 2
}

k here is this->k, and this is the aggregate whose element is initialized from the default member initializer ([dcl.init.aggr]/5.1). g++ and Clang give n == 1.

Initialization Before After
Y y{1};, Y a = Y{1}; (folded to a constant by the front end) correct unchanged
new Y{1}, return Y{1};, inner object passed by value or by const Y & (lowered) this bound to the nested Y{2, k} correct

Same result in strict, --g++ and --clang modes.

Root cause

lower_param_ref (lower_il.c) replaces the enk_param_ref for this in a non-constant aggregate initialization with the innermost entry on aggregate_this_stack whose type matches. A comment there assumed that "there can only be one". In the example, the inner Y{2, k} pushes a second Y entry (in lower_dynamic_init_aggregate_constant, lower_init.c) on top of the outer one before its explicit element k is lowered, so this bound to the inner temporary.

Change

Only an element that comes from a default member initializer, which the front end marks with implicit_aggr_element, can refer to the aggregate it initializes. An element written in the braces refers to the this of the enclosing context. The constexpr interpreter already uses this rule (interpret.c, set_up_param_ref_for_this_ptr is called only for implicit_aggr_element elements).

  • lower_il.h: new an_init_pos_descr::not_this_candidate flag, cleared in clear_init_pos_descr.
  • lower_init.c: lower_dynamic_init_aggregate_constant sets the flag on its stack entry while an explicit element is lowered. Anonymous unions are not affected, since their members' default member initializers refer to the enclosing class.
  • lower_il.c: the stack search moves to replace_param_ref_with_aggregate_this. It skips flagged entries first; if nothing matches, it repeats the previous search, so any case not covered gives the same result as before.

Changes entry: src/pending_changes/gh-324-nsdmi-this-nested-aggregate.txt (passes edg-collect-changes --check). Based on main at 35b7c47.

Tests

Test Type Checks
regressions/gh-324.sft.cpp rp new, return, by-value and by-reference cases; folded Y y{1}; the inner object's own default member initializer still binds to the inner object (W); anonymous union, aggregate base class, lambda capturing this, array new. --c++20 strict, --g++ 15, --clang

The W check is skipped in --clang mode: there, a member skipped by a designated initializer does not get its default member initializer (W{.k = 2, .n = 0}.j is 0). That happens without this change too and is the issue in discussion #259.

  • The new test fails on main and passes with this change (edg-docker-test, edg_x86_64 and edg_x86_64_cp, all three variants).
  • The recordings were created with edg-docker-test -W, and the default.* files were reviewed.
  • 320 related tests (NSDMI, aggregate, designated initializer, lambda this) match their expectations in edg_x86_64 and edg_x86_64_cp.
  • Full suite on GitHub Actions (Build & Test CI, all four configurations): run 40556389927.

Discussion: edgcpp#324

@Momen-G-Ar
Momen-G-Ar force-pushed the gh-324-nsdmi-this-nested-aggregate branch from 5eb58e8 to f1664e1 Compare October 27, 2026 17:56
When lowering a non-constant aggregate initialization, lower_param_ref
replaced the enk_param_ref for "this" with the innermost aggregate on
aggregate_this_stack whose type matched.  A default member initializer
that aggregate-initializes another object of the same class then bound
the implicit "this" of its explicit elements to that nested object:

  struct Y { unsigned k; unsigned n = Y{2, k}.n; };
  Y *p = new Y{1};  // p->n was 2, not 1.

Only an element that comes from a default member initializer (marked
implicit_aggr_element) can refer to the aggregate it initializes, which
is also the rule the constexpr interpreter uses.  An aggregate is now
skipped in the search while one of its explicit elements is lowered;
if nothing else matches, the previous search is used.
@Momen-G-Ar
Momen-G-Ar force-pushed the gh-324-nsdmi-this-nested-aggregate branch from f1664e1 to e7c7820 Compare October 27, 2026 18:03
@Momen-G-Ar

Copy link
Copy Markdown
Owner Author

Merged upstream as edgcpp#408 (e6d9621).

@Momen-G-Ar Momen-G-Ar closed this Oct 30, 2026
@Momen-G-Ar
Momen-G-Ar deleted the gh-324-nsdmi-this-nested-aggregate branch October 30, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant