Skip to content

Fix #685: array destructuring uses the iterator protocol (generators, Set, Map)#750

Merged
nickna merged 1 commit into
mainfrom
wrk/hopeful-bohr-a2528d
Jun 16, 2026
Merged

Fix #685: array destructuring uses the iterator protocol (generators, Set, Map)#750
nickna merged 1 commit into
mainfrom
wrk/hopeful-bohr-a2528d

Conversation

@nickna

@nickna nickna commented Jun 16, 2026

Copy link
Copy Markdown
Owner

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:

function* g() { yield 10; yield 20; }
const [a, b] = g();               // was: Type Error: Index type '0' is not valid for indexing 'Generator<…>'

const s = new Set<number>([1, 2, 3]);
const [x, y] = s;                 // was: Type Error indexing 'Set<number>'

const m = new Map<string, number>([["k", 1]]);
const [first] = m;                // was: Type Error indexing 'Map<…>'

All three now bind correctly in both interpreter and compiled mode (first is the ["k", 1] entry), matching tsc/JS. Closes #685.

How

Mirrors the existing __objectRest intrinsic. DesugarArrayPattern wraps the source in a new __arrayDestructure(...) helper, so every lowering site is covered centrally (declarations, for…of, function/arrow parameters, and nested patterns):

Phase Behavior
Type checker (NormalizeArrayDestructureSourceType) Arrays/tuples/strings/any pass 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.
Interpreter (NormalizeArrayDestructureSource) Index-addressable sources pass through (fast path); everything else is materialized into a SharpTSArray via GetIterableElements — the same routine spread uses.
Compiled (ArrayDestructureSource + ArrayDestructureHandler) Same split; materializes via IterateToList and wraps the result in $Array so an out-of-range index (pattern longer than the iterable) yields undefined (binding default applies) instead of throwing IndexOutOfRange. 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 in TypeChecker.Statements.Classes.cs that 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)

  • Numeric Set/Map materialization 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.
  • String rest element binds a substring instead of an array (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/slice fast path (the issue scoped them as "correct").
  • Array/object assignment destructuring without declaration ([a, b] = expr) still fails to parse — a separate feature gap.

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).
@nickna nickna merged commit 24f835b into main Jun 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Array destructuring desugars to index access, so it fails over non-indexable iterables (generators, Set, Map)

1 participant