Skip to content

Narrow a union to its only member of the typeof kind - #329

Merged
ASDAlexander77 merged 2 commits into
mainfrom
typeof-narrow-union-members
Sep 14, 2026
Merged

ASDAlexander77 merged 2 commits into
mainfrom
typeof-narrow-union-members

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

typeof u === "function" (also "class", "interface", "object", "array") names a kind, not a type, so narrowing cast u to Opaque (Opaque[] for "array"). For a union that threw the member type away:

  • let f: (() => number) | string; if (typeof f === "function") f() failed with "not a function to call".
  • let c: Box | number; if (typeof c === "class") c.value failed with "Can't resolve property 'value' of type Opaque".

Fix

A union already lists its members of that kind: the members whose runtime tag, TypeOfOpHelper::typeOfAsString, is the tested name. When there is exactly one, checkSafeCastTypeOf now narrows the union to that member through the new getUnionMembersOfTypeOf. The else branch and !== get the remaining members, as for the other typeof names.

With several members of the kind the value stays Opaque, as before. The cast helper that castFromUnion generates (if (typeof t == 'function') return t;) depends on it: narrowed to a smaller union, return t is a union cast again and instantiates the helper without end. A first version that narrowed to a union of the members failed 01union_type and logicalAssignment5_3 (stack overflow) and typeGuardFunction (thrown at run time).

Non-union values are #328's case and are not touched here.

Tests

New 00typeof_union_narrowing.ts (compile, JIT and corpus variants): calling the function member, !== leaving the string member, reading a class member's field, and length and element of an array member.

Full Windows debug suite (ctest -R "^test-"): 2745/2745 passed. After merging main (with #328), the 194 typeof, union, type-guard and logical-assignment tests pass again.

Found while testing, not fixed here

let u: number[] | string = [1, 2] prints "error: source array and destination array have different types, src: 'si32' dst: '!ts.number'", yet the compile exits 0 and the stored array is garbage (its length reads as a pointer value). The test uses an explicitly typed number[] source to stay clear of it.

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits September 14, 2026 23:53
`typeof u === "function"` (also "class", "interface", "object", "array")
names a kind, not a type, so it narrowed `u` to Opaque (Opaque[] for
"array"). For a union that threw the member type away:

- `let f: (() => number) | string; if (typeof f === "function") f()` failed
  with "not a function to call";
- `let c: Box | number; if (typeof c === "class") c.value` failed with
  "Can't resolve property 'value' of type Opaque".

A union already lists its members of that kind: the members whose runtime
tag, typeOfAsString, is the tested name. When there is exactly one,
checkSafeCastTypeOf now narrows the union to that member.

With several members of the kind the value stays Opaque. The cast helper
castFromUnion generates (`if (typeof t == 'function') return t;`) relies on
it: narrowed to a smaller union, `return t` is a union cast again and
instantiates the helper without end (01union_type and logicalAssignment5_3
overflowed the stack, typeGuardFunction threw at run time).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit f09c58a into main Sep 14, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the typeof-narrow-union-members branch September 14, 2026 23:31
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.

1 participant