Fix #751, #753, #754: typed-array index IL-verify, string rest element, assignment destructuring#785
Merged
Merged
Conversation
…nment destructuring #751 — Indexing a typed-array (number[]/boolean[]) variable emitted an unverifiable merge: the typed List<T> fast path left a native double/bool on the stack where the sibling $Array/List<object>/fallback paths (and every consumer, which reads the clobbered StackType=Unknown) expected an object ref. It ran only because the typed branch is dead for an $Array-backed value. The fast path now boxes its result at all four sites (GetIndex/SetIndex × hoisted/non-hoisted) so every branch converges on object. Behavior- and perf-neutral (the unboxed value was never consumed unboxed). The numeric Set/Map spread/destructure repro inherits the fix. #753 — A rest element over a STRING source now materializes a fresh character array (via the #685 __arrayDestructure iterator path) instead of binding the trailing substring, matching ECMA-262. String dropped from the index-addressable pass-through in the type checker, interpreter, and compiled ArrayDestructureSource/ handler. Non-rest character bindings are unchanged. #754 — Assignment destructuring to existing l-values ([a, b] = rhs / ({a, b} = rhs)) now parses and runs in both modes. New Expr.DestructuringAssign carries the parser-lowered assignment statements (reusing __arrayDestructure / __objectRest) plus the original-rhs result value; every backend runs the statements then yields the result. Supports variables, member/index targets, defaults, holes, rest (array), nested patterns, object rename/rest/computed/ string keys, iterator sources, expression position, chaining, and await in the rhs. rhs is evaluated once and the expression yields the original rhs per spec. Follow-ups filed: #779 (nested pattern with default), #780 ({a=5} parse), #781/#782 (typed-array rest / Uint8Array compiled), #783 (mixed nested literal tuple inference), #784 (default on null vs undefined-only).
This was referenced Jun 16, 2026
nickna
added a commit
that referenced
this pull request
Jun 16, 2026
…gaps #784 - destructuring defaults applied on `null` instead of `undefined`-only. Replace the `?? default` (`Expr.NullishCoalescing`, triggers on null) at all six desugaring sites with a shared DefaultIfUndefined helper (`v = access; v === undefined ? default : v`), spilling the access so it is read exactly once (getter-safe) and the default stays lazy. Also fix a latent #754 compiled bug it exposed: an assignment-destructuring value-type default over an ABSENT element produced NaN, because the lowered spill temps live inside an expression and so escaped the whole-body numeric-slot taint pass, getting an unboxed double slot that coerced the undefined sentinel. Flag them in VisitDestructuringAssign. #781 - an array-destructuring rest over a typed array / Buffer bound a typed array, not a fresh Array. Materialize typed arrays/buffers element-by-element into a SharpTSArray in Interpreter.NormalizeArrayDestructureSource (they are not GetIterableElements-iterable, so they cannot route through it). Interpreter-only; compiled typed-array construction is blocked by #782. #780 - the object-literal parser rejected the cover-grammar `{ a = 5 }`. Accept it (stored as `{ a: (a = 5) }`) so it round-trips to the #754 assignment- destructuring lowering; an IsShorthandDefault flag on Expr.Property keeps a pure-expression `{ a = 5 }` a type error (TS1312), matching tsc. #779 - a nested pattern WITH a default in an assignment destructuring (`[[a] = []]`, `{p: {x} = {}}`) was rejected. DestructuringAssign now retains the un-lowered (RawTarget, RawDefault); the outer pattern walk re-lowers the inner pattern against the defaulted access. #753 (string rest -> array) was already fixed in main by PR #785; verified and covered by tests here. Adds 14 destructuring test cases (both modes where applicable). Representative compiled program passes --verify. Filed #796 (object destructuring with a default over a source lacking the property reports a spurious "Property does not exist" - pre-existing, shared root cause with #783). #783 left open with findings; its tuple-inference fix did not reach the headline nested case and was reverted.
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.
Completes three issues from the #685 array-destructuring cluster.
#754 — Array/object assignment destructuring (without declaration)
[a, b] = rhsand({ a, b } = rhs)(assigning to existing l-values) now parse and run in both interpreter and compiled mode.Expr.DestructuringAssigncarries the parser-lowered assignment statements (reusing the Array destructuring desugars to index access, so it fails over non-indexable iterables (generators, Set, Map) #685__arrayDestructureiterator path and__objectRest) plus the original rhs as the result value. Every backend lowers it identically — run the statements, yield the result — so async/generator contexts work for free (each statement goes through the normalEmitStatement/ExecuteStatementAsyncpath).Array), nested patterns (no default), object shorthand/rename/rest/computed-key/string-key, iterator sources (Set/Map/generators), expression position, chaining ([a,b]=[c,d]=x), andawaitin the rhs.([a,b]="hi") === "hi").#753 — String rest element binds a substring instead of an array
const [a, ...rest] = "hello"now bindsrest = ["e","l","l","o"](a freshArray), matching ECMA-262, instead of the substring"ello". String is dropped from the index-addressable pass-through in the type checker, interpreter (NormalizeArrayDestructureSource), and compiledArrayDestructureSource/handler, so it materializes through the iterator path. Non-rest character bindings are byte-for-byte unchanged.#751 — Numeric Set/Map spread/destructure fails
--verifyRoot cause was broader than the title: every typed-array (
number[]/boolean[]) index read/write emitted IL that failsILVerify. The typedList<T>fast path left a nativedouble/boolon the stack where the sibling$Array/List<object>/fallback paths — and every consumer, which reads the clobberedStackType=Unknown— expected anobjectref (StackUnexpected {Found=Double, Expected=ref 'object'}). It only ran because the typed branch is dead code for an$Array-backed value. The fast path now boxes its result at all four sites (GetIndex/SetIndex× hoisted/non-hoisted), converging every branch onobject. The fix follows this codebase's established "box a typed value before a generic object slot" pattern (#279/#344/#367). Behavior- and performance-neutral: the unboxed value was never actually consumed unboxed (the consumer always required a boxed object). The numericSet/Mapspread/destructure repro inherits the fix.Tests
DestructuringTests: string-rest (Array destructuring: string rest element binds a substring instead of an array #753) and assignment-destructuring (Parser: array/object assignment destructuring (without declaration) is rejected #754) cases — basics, swap, defaults/holes/rest, member targets, object rename/rest/default, iterator source, expression position, chaining — all over both execution modes.ILVerificationTests: typed-array index read+write (number & boolean, typed- and object-backed) and the numeric-Set spread/destructure repro (Compiled: spreading/destructuring a numeric Set/Map emits IL that fails ILVerify #751), each asserting clean IL verification + interpreter parity.Conformance
The TSConformance subset (
assignmentCompatibility+conditional) is unrelated to destructuring/parsing. Test262 runs.jswith type-checking off; the runtime/parser changes here were validated via the spec-semantics checks above and the both-mode unit tests rather than the heavy, uninitialized-submodule + stale-baseline Test262 isolation.Follow-ups filed
#779 (nested pattern with a default in assignment destructuring), #780 (
{a = 5}object-shorthand-default parse), #781 (typed-array rest →Array, interpreter), #782 (new Uint8Array([...])crashes compiled — pre-existing), #783 (mixed nested array-literal tuple inference — pre-existing, affects declaration too), #784 (destructuring default applies onnullvia??instead ofundefined-only — pre-existing).