[compiler] Treat non-null assertions as dependency boundaries#36735
Open
poteto wants to merge 2 commits into
Open
[compiler] Treat non-null assertions as dependency boundaries#36735poteto wants to merge 2 commits into
poteto wants to merge 2 commits into
Conversation
2e6efaf to
78a1c60
Compare
`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]>
78a1c60 to
5b7d95e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
data!.idinside a callback contributed an unconditional hoisted dependency: the memo compare chain readdata.idwithout a null guard. Whendatais 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
NonNullExpressionHIR instruction with the same shape as TypeCastExpression: dependency collection stops at the asserted value (the callback dep becomes the null-safedata), the assertion is preserved in output, and render-scope assertions keep property granularity through the materialized temporary, matching howas-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-assertionmaterializesprops.name!as a temporary (granularity preserved, assertion kept in output);ts-non-null-expression-default-valuekeeps 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