Repository navigation
A union of null and several types can be tested for truth - #540
Merged
Merged
Conversation
`if (x)` with `x: boolean | string | null` failed to compile ("can't
instantiate function. '___cast' not all generic types can be
identified"), and so did `if (enabled && handle)` for a boolean and a
`Handle | null`, whose `&&` makes such a union. The generated union cast
answered `return t` for the null member, which strict null mode refuses
to cast to boolean. Cast to boolean, a null or undefined member is now
false, as castFromAny already does for an `any`.
A null `Handle` carries the class's tag in that union, and the class
member's `instanceof` test read its vtable through the null pointer; for a
truth test of a union with one class, the class member is now just tested
for null.
Reported in docs/bugs/native-ts-graphics-findings.md (issue 6).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 11, 2026
…547) * A null class, string or array widened to a union becomes its null member 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> * A union of pointer types stores null in its pointer, not as a member A class or a string holds null in its own pointer, which is why `Class | null` is stored as the class. A union of several such types (`A | B | null`, `string | A | null`) still got a null member of its own; now it doesn't. Its value is null when its pointer is null, whichever member's tag it carries. The declared type keeps `| null` under strict null checks, which is what allows or refuses assigning null. A union with a member that can't hold null (a number: 0 is not null) keeps its null member. - needsNullMember is false when every non-null member absorbs null, and the storage type (disableStrickNullCheck) no longer adds null in strict mode on the merged path either. - `=== null` on a tagged union is no longer cast to null through a generated cast first (syncUnionTypes): LogicOp compares the union, by its tag or, for such a union, by its value's pointer - judged from the actual type isUnionTypeNeedsTag returns. - The generated union cast asks `t === null` first for such a union, so no `instanceof` meets a null instance: false for a truth test, "null" as text, null where the target takes it, else the usual throw. #540's one-class special case is gone. - CastLogicHelper uses MLIRTypeHelper::isNullAbsorbingType. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CastLogicHelper: drop isPointerNullableType again, the merge brought it back MLIRTypeHelper::isNullAbsorbingType replaces it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
boolean && (Class | null)used as a condition failed to compile:Reported in
docs/bugs/native-ts-graphics-findings.md, issue 6 (docs/bugs/ntg_and_nullable_class.ts). The problem isn't&&itself: any truth test of a union of null and two or more other types failed the same way, e.g.if (x)withx: boolean | string | null.enabled && handlesimply has the typeboolean | Handle | null.Cause
Casting such a union to
booleangoes throughcastFromUnion, which generates a__cast<T, U>function with oneif (typeof t == '...') return t;per member. For thenullmember,return tcasts null to boolean, which strict-null mode refuses ("can't cast from 'null' to 'boolean'"), so the instantiation fails. (That first error is also reported at a wrong location; that's a separate PR.)Once that compiled, a second bug showed up: when a
Handle | nullholding null is converted intoboolean | Handle | null, the value keeps theclasstag. The class member'sif (t instanceof Handle)then read the vtable through the null pointer and crashed.Fix
In the generated cast, when the target is
boolean:nullorundefinedmember returnsfalse(the same thingcastFromAnyalready does for anany);t. A class tests as non-null, so a null instance is false and nothing is dereferenced.Not covered: a union with two or more classes plus null still dereferences a null instance in its
instanceofchain. That was already the case before this PR, and it needs a different shape of generated code.Testing
00union_null_truthiness.ts(compile + JIT + rc/none corpus): the issue's function (includinghandlenarrowed inside the block), the reversedhandle && enabled,boolean | string | null,!xonboolean | number | null, ande && s ? 1 : 0. Under the current release it doesn't compile.docs/bugs/ntg_and_nullable_class.tsnow prints 7, done.🤖 Generated with Claude Code