Skip to content

Fix #661/#658 (inferred class method return propagation), #593 (class-instance iterability)#695

Merged
nickna merged 1 commit into
mainfrom
wrk/silly-bassi-f8bb22
Jun 16, 2026
Merged

Fix #661/#658 (inferred class method return propagation), #593 (class-instance iterability)#695
nickna merged 1 commit into
mainfrom
wrk/silly-bassi-f8bb22

Conversation

@nickna

@nickna nickna commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Completes four related generator/iterator type-checker issues.

#661 / #658 — inferred class method return types now reach call sites

CheckClassBody resolves a method's inferred (un-annotated) return type during the body pass and writes it to the mutable class, but the class had already been frozen with <inferred> placeholders before that pass — so new C().m() read <inferred> (~any). Now, when any inferred method return was resolved, the frozen Class/GenericClass (and the TypeMap the compiler reads) are rebuilt from the resolved mutable state and re-published after the body pass. The structural compatibility cache is cleared afterward so results computed against the placeholder (keyed by the stable DeclarationId) aren't reused.

#593 — class-instance iterability

A class is iterable only via an inherited [Symbol.iterator]. extends Array<number> resolved Array to a name-only placeholder that dropped the element type, so SharpTS bound any for a plain instance in for...of (under-rejecting) and threw a spurious TS2488 for an extends Array instance in yield* (over-rejecting).

  • extends Array/Set/Map/String/TypedArray records the element type as an @@iterator on the placeholder; for...of / yield* / spread now bind the real element type (number, not any).
  • A plain class instance (no inherited [Symbol.iterator]) is now TS2488 — matching tsc and the runtime, which already throws "for...of requires an iterable". The #592-dependent direction (user [Symbol.iterator](), which doesn't parse yet) is out of scope and forward-compatible.

#487 / #532 — already fixed by 533d0cf; regression coverage added/confirmed

Both were resolved by commit 533d0cf (#548/#532/#487/#483). Added explicit Generator/AsyncGenerator 3-arg lib-spelling regression tests (#487); #532's nested-generator tests already exist. Verified both repros pass in both modes.

Issues filed for pre-existing gaps surfaced here

Testing

  • Full suite: 12714 / 0 (incl. 24 new regression tests).
  • TSConformance: 31 / 31 (no baseline change).
  • Test262: unaffected — all changes are type-checker-only and the harness runs .js untyped by default. Corpus isn't initialized in this worktree.
  • Updated MethodCompletionValueTests to probe the undefined sentinel through as any, since an off-the-end method now correctly infers void and void + 10 is the TS2365 tsc reports.

Fixes #661
Fixes #658
Fixes #593
Closes #487
Closes #532

…ass-instance iterability)

#661/#658 — inferred class method return types now reach call sites.
CheckClassBody resolves a method's inferred (un-annotated) return type during the
body pass and writes it to the mutable class, but the class was already frozen with
<inferred> placeholders before that pass, so `new C().m()` read <inferred> (~any):
ordinary methods skipped assignability checks and a generator method's result was
rejected as non-iterable by spread/for...of/yield*. After the body pass, when any
inferred method return was resolved, the frozen Class/GenericClass (and the TypeMap
the compiler reads) are now rebuilt from the resolved mutable state and re-published;
the structural compatibility cache is cleared so results computed against the
placeholder (keyed by the stable DeclarationId) are not reused. New
MutableClass.ResetFrozenCache() drops the cached frozen forms for the rebuild.
  - `class C { *m() { yield 1; } }` → `[...new C().m()]` type-checks (was "got '<inferred>'").
  - `class C { name() { return "hi"; } }` → `const n: number = new C().name()` is now TS2322.
  Covers static/async/private methods and recursion.

#593 — class-instance iterability. A class is iterable only via an inherited
[Symbol.iterator]; `extends Array<number>` resolved Array to a name-only placeholder
that dropped the element type, so for...of bound `any` on a plain instance
(under-rejecting) and yield* threw a spurious TS2488 on an `extends Array` instance
(over-rejecting). Now `extends Array/Set/Map/String/TypedArray` records the element
type as an @@iterator on the placeholder, which the structural iterable probe reads by
walking the hierarchy directly (the placeholder superclass is a name-only MutableClass
the normal member walk skips), so for...of/yield*/spread bind the real element type. A
plain class instance with no inherited [Symbol.iterator] is now TS2488 — matching tsc
and the runtime, which already throws "for...of requires an iterable".

#487/#532 were already fixed by 533d0cf; added explicit Generator/AsyncGenerator
3-arg lib-spelling regression tests (#487) — #532's nested-generator tests already exist.

Updated MethodCompletionValueTests to probe the undefined sentinel through `as any`,
since an off-the-end method now correctly infers `void` and `void + 10` is the TS2365
tsc reports.

Filed #685 (array destructuring desugars to index access, fails over non-indexable
iterables) and #692 (compiled static generator method lowering) for pre-existing gaps
surfaced here.

Full suite 12714/0; TSConformance 31/31 (no baseline change). Changes are
type-checker-only, so Test262 (runs .js untyped by default) is unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment