Skip to content

A null class, string or array widened to a union becomes its null member - #546

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-null-pointer-into-tagged-union
Oct 11, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-null-pointer-into-tagged-union

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

A class, string or array value holding null, widened into a union that has its own null member, kept its type's tag:

function widen(h: A | null): boolean | A | B | null { return h; }
const u = widen(null);
typeof u      // "class" (should be "null")
u === null    // crashed (0x80000003): the value is a class holding a null pointer

This is the gap #540 left open (null in a union with two or more classes), and it isn't limited to truth tests. In #540 it was worked around only for a union with one class.

Cause

CastLogicHelper builds a tagged union from a non-union value with TypeOfOp(in): the tag comes from the value's static type. The Handle | null source goes there too, as its base type Handle. A null pointer therefore became the class member with a null pointer, while === null, typeof and the generated union cast all look for the null tag.

Fix

When the source is a single pointer that can be null (class, string, array, object, opaque) and the target union's storage has a null member, the tag is chosen at run time: select(pointer == null, typeof null, typeof value).

This is the conversion half of the <nullable type> | null rule. A follow-up will stop giving a null member to unions whose members can all hold null themselves (A | B | null); the declared type keeps null so strict-null checks still decide whether null may be assigned.

Testing

  • New 00null_into_tagged_union.ts (compile + JIT + rc/none corpus): a null A into boolean | A | B | null (=== null, !== null, typeof, truth test, ??), a null string into string | number | null, and a null array into number[] | boolean | null, each alongside a non-null value. On current main it crashes with 0x80000003.
  • Full ctest: 3970/3970 pass.

🤖 Generated with Claude Code

Cast into a tagged union, a value got its static type as the tag. A
class, string, array or object value holding null was therefore stored
as that type holding a null pointer: a null `Handle | null` widened to
`boolean | Handle | null` was tagged "class", so `=== null` missed it,
`typeof` said "class", and a truth test read its vtable through the
null pointer. Where the union has a null member of its own, such a value
now takes the null tag when its pointer is null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 37bcfd7 into main Oct 11, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-null-pointer-into-tagged-union branch October 11, 2026 13:54
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