Skip to content

A cast between unrelated classes needs their fields to fit - #395

Merged
ASDAlexander77 merged 1 commit into
mainfrom
unrelated-class-cast-check
Sep 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
unrelated-class-cast-check

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner
class A { constructor(public v: number) {} }
class B { constructor(public v: string) {} }
const b: B = new A(1);   // compiled without complaint
print(b.v);              // read a number as a string: 0xC0000005

A cast between two classes where neither extends the other became a plain ts.Cast. #389 covers two specializations of one generic; this covers unrelated classes.

Fix (verifyUnrelatedClassCast, MLIRGenCast.cpp): two unrelated classes are compatible as in TypeScript, by their members. One side's data fields, inherited ones included, must all be found in the other side with types that extend them. Either direction is enough, as for a type assertion, because cast() serves both assignments and <T>x. Otherwise it's an error:

error: type A is not assignable to type B: 'v' is number, not string

The check covers fields only, skipping internal ones such as .vtbl and the storage a derived class embeds for its base. A field read at the wrong type is what crashed.

Not affected:

  • Classes related by inheritance, in either direction.
  • Two specializations of one generic (A cast between two specializations of one generic is checked #389).
  • A type guard narrowing to an unrelated class (isB(a) on an A): the value is checked at run time, and TypeScript narrows it to A & B. addSafeCastStatement keeps its plain cast for that case; 00type_guard_function.ts needs it.

Tests:

  • class-cast/unrelated.ts: an error test matched on the message.
  • 00class_cast_structural.ts: a same-shape unrelated class and a type guard between classes without fields. Registered for compile, jit and the corpus.

Results:

  • The suite passes locally, 3018/3018.
  • I rebuilt DefaultLib (release, gc) with this compiler: it builds without errors, and its tests pass 156/156 in both jit and compile modes. The local DefaultLib build was restored afterwards.

Limitation: a cast TypeScript allows between classes whose layouts differ, such as a class to one with a subset of its fields in another order, still compiles to a plain cast of the storage.

🤖 Generated with Claude Code

@ASDAlexander77
ASDAlexander77 force-pushed the unrelated-class-cast-check branch 2 times, most recently from 3b56f08 to 1aff748 Compare September 28, 2026 16:37
`const b: B = new A(1)` with `A { v: number }` and `B { v: string }`
compiled to a plain ts.Cast, and reading `b.v` read a number as a string
and crashed. Two classes where neither extends the other are now
compatible as in TypeScript, by their fields: one side's fields, inherited
ones included, must all be found in the other with types that extend them
(either direction, as for an assertion). A type guard's narrowing to an
unrelated class (`isB(a)`) is checked at run time and keeps its cast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 force-pushed the unrelated-class-cast-check branch from 1aff748 to b029e68 Compare September 28, 2026 17:01
@ASDAlexander77
ASDAlexander77 merged commit 5c67e06 into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the unrelated-class-cast-check branch September 28, 2026 17:20
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