Fix #685: array destructuring uses the iterator protocol (generators, Set, Map)#750
Merged
Conversation
Array binding patterns (`const [a, b] = src`) desugared at parse time to positional index access, which is correct for arrays/tuples/strings but mis-evaluated (and the type checker rejected) any non-indexable iterable — generators, Set, Map, objects with [Symbol.iterator]. JS array destructuring uses the iterator protocol, so these must work. Mirroring the existing __objectRest intrinsic, DesugarArrayPattern now wraps the source in a new __arrayDestructure(...) helper (so all lowering sites — declarations, for-of, function/arrow params — are covered centrally): - Type checker (NormalizeArrayDestructureSourceType): arrays/tuples/strings/any pass through with their precise type (tuples keep positional element types); any other iterable becomes Array<element>, so `_dest0[i]` reads the element type instead of erroring. Non-iterable, non-indexable sources are left unchanged so the existing index-access diagnostic still fires. - Interpreter (NormalizeArrayDestructureSource) and compiled (ArrayDestructureSource): index-addressable sources pass through (fast path); everything else is materialized via the same routine spread uses (GetIterableElements / IterateToList), so destructuring and spread agree on what is iterable. The compiled path wraps the materialized list in $Array so an out-of-range index (pattern longer than the iterable) yields undefined and a binding default applies, instead of throwing IndexOutOfRange. #687 (spreading a generator method's result rejected as '<inferred>') was already fixed by the #658/#661 inferred-method-return propagation (PR #695) and is covered by existing tests; this only refreshes a stale comment that claimed the result was still unobservable at the call site, and tags the existing test. 18 new shared-mode destructuring tests (generators, Set, Map entries, nested, rest, holes+defaults, custom [Symbol.iterator], element-type preservation, array fast-path regression). Full suite green (12998).
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.
What
Array binding patterns (
const [a, b] = src) were desugared at parse time to positional index access. That is correct for arrays/tuples/strings, but JS array destructuring uses the iterator protocol, so any non-indexable iterable was mis-evaluated at runtime and rejected by the type checker:All three now bind correctly in both interpreter and compiled mode (
firstis the["k", 1]entry), matchingtsc/JS. Closes #685.How
Mirrors the existing
__objectRestintrinsic.DesugarArrayPatternwraps the source in a new__arrayDestructure(...)helper, so every lowering site is covered centrally (declarations,for…of, function/arrow parameters, and nested patterns):NormalizeArrayDestructureSourceType)anypass through with their precise type (tuples keep positional element types); other iterables →Array<element>so_dest0[i]reads the element type. Non-iterable, non-indexable sources are unchanged so the existing index-access diagnostic still fires.NormalizeArrayDestructureSource)SharpTSArrayviaGetIterableElements— the same routine spread uses.ArrayDestructureSource+ArrayDestructureHandler)IterateToListand wraps the result in$Arrayso an out-of-range index (pattern longer than the iterable) yieldsundefined(binding default applies) instead of throwingIndexOutOfRange. The handler skips the call entirely for statically index-addressable sources, preserving the array fast path.Interpreter and compiled both delegate the "what is iterable" decision to the existing complete implementations, so destructuring and spread always agree.
#687 (verified, not re-fixed)
#687 (spreading a generator method's result rejected as
<inferred>) was already fixed by the #658/#661 inferred-method-return propagation (PR #695) and is covered by existing tests (GeneratorMethod_InferredYield_IsIterable_RunsInBothModes). This PR only refreshes a stale comment inTypeChecker.Statements.Classes.csthat claimed the result was still unobservable at the call site, and tags that test with #687.Tests
18 new shared-mode (
Interpreted+Compiled) destructuring tests: generators, Set, Map entries, nested over Map entries, rest, holes+defaults, custom[Symbol.iterator], element-type preservation (arithmetic), and an array fast-path regression guard. Full suite green: 12998 passed, 0 failed.Follow-ups filed (pre-existing, out of scope)
Set/Mapmaterialization emits IL that fails--verify(Found=Double, Expected=ref object) — reproduces with pure spread[...new Set<number>([1,2,3])], no destructuring; runs correctly (JIT-tolerated). Destructuring inherits it for numeric Set/Map.const [a, ...rest] = "hello"→rest === "ello"); both modes. Array destructuring desugars to index access, so it fails over non-indexable iterables (generators, Set, Map) #685 deliberately keeps strings on the index/slicefast path (the issue scoped them as "correct").[a, b] = expr) still fails to parse — a separate feature gap.