Skip to content

Fix gentrace assertions when tracing tuples containing type aliases#5196

Merged
SeanTAllen merged 1 commit intomainfrom
fix-type-alias-tuple-trace
Apr 9, 2026
Merged

Fix gentrace assertions when tracing tuples containing type aliases#5196
SeanTAllen merged 1 commit intomainfrom
fix-type-alias-tuple-trace

Conversation

@SeanTAllen
Copy link
Copy Markdown
Member

Two assertions in gentrace.c fire when codegen traces a dynamically-traced tuple whose element is a type alias. Both are PR #5145 regressions — aliases now persist as TK_TYPEALIASREF through codegen instead of being expanded during name resolution, and these two sites were missed when the dispatch logic was updated. This is what's breaking the ponylang/crdt nightly build: _Dot is (ID, U64) trips the first one.

The TRACE_TUPLE branch of trace_dynamic_tuple now unfolds the alias once before recursing, so the recursive call sees a concrete TK_TUPLETYPE instead of iterating (TK_ID, typeargs, cap, eph) as tuple children.

The TK_TYPEALIASREF case of trace_dynamic splices typealias_unfold's detached result into the alias's parent before recursing. That keeps the tree attached so downstream ast_swap calls in trace_dynamic_tuple always see a non-NULL parent. The rare detached-alias path is only reachable with tuple == NULL, which short-circuits the failing ast_swap, and the else branch asserts the invariant.

A new full-program-test covers both bugs with independent discrimination — one field exercises each fix.

Chained aliases (alias of alias of tuple) still trip the first fix because typealias_unfold only unfolds one level. I could not construct a test that reaches this site with a chained alias because other PR #5145 regressions (the static trace_tuple path in gentrace.c and gen_digestof_value in genreference.c) fire first. The broader typealias_unfold call-site audit is tracked in #5195.

This is against unreleased code, so no changelog or release notes entry.

PR #5145 stopped expanding type aliases during name resolution, so a
type alias now persists as TK_TYPEALIASREF through codegen. Every
codegen dispatch site was updated to unfold TK_TYPEALIASREF on demand,
but two sites in gentrace.c were missed. Both only surface when a tuple
is traced dynamically (e.g. as an element of a union), which is why
ponylang/crdt's nightly started failing its build after #5145 landed:
its `_Dot is (ID, U64)` alias wrapping a tuple exercises the first of
these paths.

trace_dynamic_tuple's TRACE_TUPLE branch duplicated the alias ref with
ast_dup and passed it to the recursive trace_dynamic_tuple, which then
iterated the alias ref's TK_ID / typeargs / cap / eph children as if
they were tuple elements and tripped trace_type's default assertion.
Unfold the alias once before the recursion so the callee sees the
concrete TK_TUPLETYPE.

trace_dynamic's TK_TYPEALIASREF case called typealias_unfold (which
always returns a detached subtree) and passed the detached tree into
the recursive trace_dynamic. When the detached subtree contained a
tuple that reached trace_dynamic_tuple with tuple != NULL, the
ast_swap(type, dontcare) at the top of trace_dynamic_tuple asserted on
the NULL parent. Splice the unfolded form into the alias's position in
its parent so downstream ast_swap always sees an attached tree; the
rare detached-alias case is only reachable when tuple == NULL, so the
in_tuple branch is never taken and the missing parent does not matter.

A single regression test under test/full-program-tests exercises both
scenarios: one field with an alias that expands directly to a tuple
(bug 1) and another field with an alias that expands to a union
containing a second alias-to-tuple (bug 2).

Chained type aliases (alias of alias of tuple) are a known gap in the
TRACE_TUPLE fix — typealias_unfold only unfolds one level. Constructing
a test that reaches this site with a chained alias is currently blocked
by other #5145 regressions elsewhere in codegen (the static trace_tuple
path in gentrace.c and gen_digestof_value in genreference.c), which
fire first for every configuration tried. That broader audit of every
typealias_unfold call site is tracked in #5195.

This is a regression against unreleased code, so no changelog or
release notes entry.
@ponylang-main ponylang-main added the discuss during sync Should be discussed during an upcoming sync label Apr 9, 2026
@SeanTAllen SeanTAllen merged commit 4b85f11 into main Apr 9, 2026
21 checks passed
@SeanTAllen SeanTAllen deleted the fix-type-alias-tuple-trace branch April 9, 2026 12:49
@ponylang-main ponylang-main removed the discuss during sync Should be discussed during an upcoming sync label Apr 9, 2026
SeanTAllen added a commit that referenced this pull request Apr 9, 2026
PR #5145 stopped expanding type aliases during name resolution, leaving
them as TK_TYPEALIASREF in the AST. Every site that needs the underlying
concrete type calls typealias_unfold on demand. The helper unfolded
exactly one level, so chained aliases (`type A is B; type B is (U64,
U64)`) returned another TK_TYPEALIASREF and any caller that inspected
the head of the result crashed or miscompiled. PRs #5193 and #5196
fixed two crash sites with per-site unfolds, but the same bug class
kept surfacing elsewhere.

Make typealias_unfold transitive: if reify and apply_cap produce another
TK_TYPEALIASREF, recurse until the head is concrete. The helper now
guarantees its result has a non-alias head, so callers can rely on that
postcondition. Termination is bounded by the alias chain length, which
is finite because pass/names.c rejects cyclic alias defs via
AST_FLAG_RECURSE_1 before any TK_TYPEALIASREF is emitted.

Also fixes a related #5145 regression in gentrace_needed's TRACE_TUPLE
branch, which iterated tuple element children without unfolding the
alias and crashed on both single-level and chained tuple aliases. And
removes the "known gap" comment in trace_dynamic_tuple's TRACE_TUPLE
branch (added by #5196) since chained aliases now resolve to a concrete
TK_TUPLETYPE through the transitive unfold.

11 new full-program-tests cover the affected codegen and expression
paths. Each asserts a specific exit code so it catches both assertion
crashes and silent miscompilation.

Closes #5195
SeanTAllen added a commit that referenced this pull request Apr 9, 2026
PR #5145 stopped expanding type aliases during name resolution, leaving
them as TK_TYPEALIASREF in the AST. Every site that needs the underlying
concrete type calls typealias_unfold on demand. The helper unfolded
exactly one level, so chained aliases (`type A is B; type B is (U64,
U64)`) returned another TK_TYPEALIASREF and any caller that inspected
the head of the result crashed or miscompiled. PRs #5193 and #5196
fixed two crash sites with per-site unfolds, but the same bug class
kept surfacing elsewhere.

Make typealias_unfold transitive: if reify and apply_cap produce another
TK_TYPEALIASREF, recurse until the head is concrete. The helper now
guarantees its result has a non-alias head, so callers can rely on that
postcondition. Termination is bounded by the alias chain length, which
is finite because pass/names.c rejects cyclic alias defs via
AST_FLAG_RECURSE_1 before any TK_TYPEALIASREF is emitted. A coupling
comment in names.c documents this dependency for future maintainers.

Also fixes a related #5145 regression in gentrace_needed's TRACE_TUPLE
branch, which iterated tuple element children without unfolding the
operand types and crashed on both single-level and chained tuple
aliases (found during review via an asymmetric argument/parameter
pattern). And removes the now-stale "known gap" comment in
trace_dynamic_tuple's TRACE_TUPLE branch (added by #5196) since chained
aliases now resolve to a concrete TK_TUPLETYPE through the transitive
unfold.

The new full-program-tests cover the affected codegen and expression
paths at specific exit codes so they catch both assertion crashes and
silent miscompilation.

Closes #5195
SeanTAllen added a commit that referenced this pull request Apr 9, 2026
PR #5145 stopped expanding type aliases during name resolution, leaving
them as TK_TYPEALIASREF in the AST. Every site that needs the underlying
concrete type calls typealias_unfold on demand. The helper unfolded
exactly one level, so chained aliases (`type A is B; type B is (U64,
U64)`) returned another TK_TYPEALIASREF and any caller that inspected
the head of the result crashed or miscompiled. PRs #5193 and #5196
fixed two crash sites with per-site unfolds, but the same bug class
kept surfacing elsewhere.

Make typealias_unfold transitive: if reify and apply_cap produce another
TK_TYPEALIASREF, recurse until the head is concrete. The helper now
guarantees its result has a non-alias head, so callers can rely on that
postcondition. Termination is bounded by the alias chain length, which
is finite because pass/names.c rejects cyclic alias defs via
AST_FLAG_RECURSE_1 before any TK_TYPEALIASREF is emitted. A coupling
comment in names.c documents this dependency for future maintainers.

Also fixes a related #5145 regression in gentrace_needed's TRACE_TUPLE
branch, which iterated tuple element children without unfolding the
operand types and crashed on both single-level and chained tuple
aliases (found during review via an asymmetric argument/parameter
pattern). And removes the now-stale "known gap" comment in
trace_dynamic_tuple's TRACE_TUPLE branch (added by #5196) since chained
aliases now resolve to a concrete TK_TUPLETYPE through the transitive
unfold.

The new full-program-tests cover the affected codegen and expression
paths at specific exit codes so they catch both assertion crashes and
silent miscompilation.

Closes #5195
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.

2 participants