Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[compiler] Treat non-null assertions as dependency boundaries#36735

Open
poteto wants to merge 2 commits into
react:mainfrom
poteto:lauren/fix-34752-nonnull
Open

[compiler] Treat non-null assertions as dependency boundaries#36735
poteto wants to merge 2 commits into
react:mainfrom
poteto:lauren/fix-34752-nonnull

Conversation

@poteto

@poteto poteto commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

data!.id inside a callback contributed an unconditional hoisted dependency: the memo compare chain read data.id without a null guard. When data is legitimately null on a render and the assertion only holds inside a guarded callback, the compiled component crashes (TypeError: Cannot read properties of null) where the uncompiled source renders fine. This is the planned direction from #34752/#34194 discussion: treat ! as a source of nullability when computing deps.

TSNonNullExpression previously lowered as a transparent unwrap, indistinguishable from a plain member access by dependency collection. It now lowers to a NonNullExpression HIR instruction with the same shape as TypeCastExpression: dependency collection stops at the asserted value (the callback dep becomes the null-safe data), the assertion is preserved in output, and render-scope assertions keep property granularity through the materialized temporary, matching how as-casts behave today.

Builds on #36709 by Scarab Systems, extended with the two match sites that PR missed (the HIR debug printer and the ref-state passthrough in ValidateNoRefAccessInRender, where dropping ref-ness through ref! would change ref validation) and the full Rust port mirror (every TypeCastExpression match arm, driven by exhaustiveness checking).

Fixtures: the crash repro (null-first sequential renders now pass sprout), a mixed chain (data!.nested?.id), and a render-scope assertion. Corpus delta is two fixtures, both reviewed: non-null-assertion materializes props.name! as a temporary (granularity preserved, assertion kept in output); ts-non-null-expression-default-value keeps the ! in output.

Verification: TS snap 1807/1807, Rust snap 1807/1807, cargo workspace green, TS-vs-Rust HIR debug parity on the new instruction via the e2e harness.

Closes #34752
Closes #34194

@meta-cla meta-cla Bot added the CLA Signed label Jun 10, 2026
@poteto poteto marked this pull request as ready for review June 10, 2026 06:13
@poteto poteto force-pushed the lauren/fix-34752-nonnull branch from 2e6efaf to 78a1c60 Compare June 10, 2026 06:17
poteto and others added 2 commits June 17, 2026 01:01
`data!.id` inside a callback contributes an unconditional hoisted
dependency: the memo compare chain reads `data.id` without a null
guard. When data is legitimately null on a render (the assertion only
holds inside the guarded callback), the component crashes where the
uncompiled source renders fine. Sprout evidence: uncompiled renders
"empty" for data=null, compiled throws TypeError reading 'id'. Fixture
documents today's wrong output; sprout-skipped until the fix lands.
TSNonNullExpression lowered as a transparent unwrap, so `data!.id`
inside a callback produced the same PropertyLoad as `data.id` and
dependency hoisting emitted an unguarded `data.id` in the memo compare
chain. When data is legitimately null on a render and the assertion
only holds inside a guarded callback, the compiled component crashes
where the uncompiled source renders fine (react#34752, react#34194).

Lower `!` to a NonNullExpression instruction instead, the same shape as
TypeCastExpression: dependency collection now stops at the asserted
value (dep on `data`, never past the `!`), the assertion is preserved
in output, and render-scope assertions keep property granularity
through the materialized temporary. Both compilers implement the same
instruction; the Rust port mirrors every TypeCastExpression match arm
including the ref-state passthrough in ValidateNoRefAccessInRender and
the HIR debug printer, which the TS reference also gains here (the
adopted PR missed both sites).

Corpus deltas, both reviewed: non-null-assertion now materializes
`props.name!` as a temporary (granularity preserved, assertion kept);
ts-non-null-expression-default-value keeps the `!` in output. Verified
TS snap 1807/1807, Rust snap 1807/1807, cargo workspace tests green,
sprout green on all three new fixtures including the null-first
sequential render that crashed before.

Closes react#34752
Closes react#34194

Co-authored-by: Scarab Systems <[email protected]>
@poteto poteto force-pushed the lauren/fix-34752-nonnull branch from 78a1c60 to 5b7d95e Compare June 17, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant