fix: compile void ternaries in discarded-value positions - #204
Open
not-the-ccp wants to merge 2 commits into
Open
fix: compile void ternaries in discarded-value positions#204not-the-ccp wants to merge 2 commits into
not-the-ccp wants to merge 2 commits into
Conversation
) A conditional expression over two `void` calls (`flag ? a() : b()`) tripped SC9001 "ternary must not be void" everywhere — including the positions the SC1090 'void' hint advertises as supported: statement position (`flag ? a() : b();`) and void-returning concise arrow bodies (`const pick = () => (flag ? a() : b())`, the shape in vercel-labs#33). JS discards the value at those sites, so they now lower exactly: the already-lowered pieces are reshaped into if/else statements (voidTernaryIfStmt), recursing through nested arms. The reshape applies at every discarded-value landing — lowerExprStatement's fallback, both concise-arrow-body paths, and lowerLambda's. A CONSUMED void ternary still has no IR representation; it now gets a pointed SC1090 ("write it as an if/else statement, or as a void-returning arrow body") instead of the validator ICE, gated on a stateless parent walk that recognizes exactly the discard sites. Corpus 2692 pins the compiled behavior against Node byte-for-byte; diagnostics/void-ternary-value-position snapshots both fence shapes. Fixes vercel-labs#33
Contributor
|
@not-the-ccp is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
`return flag ? a() : b();` in a void-returning function drops the value exactly like the statement and arrow-body sites — it now lowers to the same if/else reshape, with an explicit bare return in each branch so trailing statements stay unreachable. The discard-site walk recognizes the position via ctx.returnType; non-void returns keep their SC0001 (void is not assignable), which fires before lowering either way.
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.
Fixes #33
Problem
A conditional expression over two
voidcalls trippedSC9001: internal compiler error: in %fn0: ternary must not be void — please report thisin every position — including the ones theSC1090'void'-in-value-position hint explicitly advertises as supported:The validator correctly fences void ternaries as IR expressions (there is no void value to select between), but the frontend kept producing them in positions where JavaScript discards the value anyway.
Fix
At a discarded-value site, the exact semantics are "evaluate the condition, run only the taken arm, drop both values" — which is
if/elseover the same lowered pieces. The discarded-value landings now reshape the already-lowered IR:lowerExprStatement's fallback (statement position, including thevoid eand comma spellings that hand operands to the same lowering),lowerLambda),return e;inside a void-returning function — each branch of the reshape carries its own explicit barereturn, so trailing statements stay unreachable.via a new
voidTernaryIfStmt/voidTernaryIfStmtOrExprStmthelper inlower-stmts.ts. The rewrite recurses, so nested void ternaries (deep ? (flag ? a() : b()) : b()) reshape into nestedifs.Because the reshape happens after lowering, everything
lowerTernaryalready does is preserved: constant-condition folding,typeof/optional narrowing bridges, laziness of the untaken arm.A consumed void ternary still has no IR representation, but it now gets a pointed fence instead of the ICE:
The fence is gated on a stateless parent walk (
discardedVoidTernarySite, same style asinLogicalLeftPosition) that recognizes exactly the discard sites: expression statements (through parens,void e, and commas), concise bodies of void-returning arrows,returnstatements whose function's return type maps tovoid, and arms of enclosing ternaries that themselves join tovoid(those are the ones the reshape rewrites). Non-void returns keep their existingSC0001assignability diagnostic, which fires before lowering either way.Testing
tests/corpus/2692-void-ternary-statement.ts— differential program pinning the compiled behavior against Node byte-for-byte: statement position, arm laziness, nesting, concise/generic arrow bodies, narrowing into arms, comma spelling, and void-return statements with unreachable trailing code.tests/diagnostics/void-ternary-value-position.ts— snapshots both remaining fence shapes (direct value position; void arm under a non-void sibling).Locally: full plain differential lane (1087 programs) and LLVM differential lane pass except four pre-existing environment failures (
1423-text-codec,1640-fd-read-decode,2124-imports-field-wildcard,2599-stream-arg-ladders— verified identical on a clean checkout), diagnostics corpus 115/115, smoke/deadstrip/library suites green (two pre-existing ASan environment failures excluded, also verified on clean checkout). Lint clean.