Skip to content

Make interface vtable slot numbering a single source of truth#253

Merged
ASDAlexander77 merged 1 commit into
mainfrom
interface-vtable-simplify-pr1
Jul 19, 2026
Merged

Make interface vtable slot numbering a single source of truth#253
ASDAlexander77 merged 1 commit into
mainfrom
interface-vtable-simplify-pr1

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

  • InterfaceInfo::getVirtualTable() used to assign each field/method's virtualIndex as a side effect of building the vtable for whichever object was being cast at the time - correct only for the most recently cast implementer. Two concrete bugs fell out of this:
    • An optional member's compile-time -1 marker (InterfaceFieldAccess) leaked into unrelated access sites compiled after a cast of a different object missing that member - silently returning undefined for a member that was actually present on the object actually being read.
    • For extends-derived interfaces, a base interface's slot position within the derived interface's combined vtable clobbered its own standalone slot position, breaking access through the base interface directly once it had also been used as a base.
  • Fix: InterfaceInfo::assignCanonicalVirtualIndexes() (introduced in Fix cross-module interface field/method vtable index mismatch #252) is now the sole writer of virtualIndex, computed once from the interface's own declaration. getVirtualTable() no longer mutates it. findField()/findMethod() gained an offset-accumulating overload that walks the extends chain so access sites can add the declaring interface's slot-block offset to its standalone index. InterfaceFieldAccess drops the compile-time optional-member bypass in favor of the runtime slot == -1 check the lowering (InterfaceSymbolRefOpLowering) already has.
  • Uncovered and fixed two pre-existing, previously-masked bugs along the way: mergeInterfaces() (intersection type synthesis, e.g. type t = A & B & { c: number }) had a mismatched aggregate initializer that left virtualIndex zero for every field merged from a { ... } member; and the intersection-type synthesis path never ran the canonical index pass at all, being a separate programmatic InterfaceInfo builder rather than the AST-walk path.
  • Full design writeup, including the parts that changed during implementation, in docs/interface-vtable-simplification-design.md (§4, §5, and the "Implementation notes" subsection).

Test plan

  • New regression test 00interface_optional_cast_order.ts (compile-time-optional-member-bypass bug), both AOT and JIT variants
  • Full suite: ctest -C Debug - 716/716 passed, no regressions (including 00interface_object4.ts and 00interface_conjunction.ts, which briefly regressed mid-implementation and are now exercising the extends-offset path for the first time)

🤖 Generated with Claude Code

InterfaceInfo::getVirtualTable() used to assign each field/method's
virtualIndex as a side effect of building the vtable for whichever
object was being cast at the time - correct only for the most
recently cast implementer. This broke two ways: an optional member's
compile-time -1 marker leaked into unrelated access sites compiled
afterward (silently returning undefined for a member that was
actually present), and for `extends`-derived interfaces a base
interface's slot position within the derived interface's combined
vtable clobbered its own standalone slot position.

Fix: InterfaceInfo::assignCanonicalVirtualIndexes() is now the sole
writer of virtualIndex, computed once from the interface's own
declaration. getVirtualTable() no longer mutates it.
findField()/findMethod() gained an offset-accumulating overload that
walks the extends chain so access sites can add the declaring
interface's slot-block offset to its standalone index.
InterfaceFieldAccess drops the compile-time optional-member bypass in
favor of the runtime slot==-1 check the lowering already has.

Also fixes two latent bugs this uncovered: mergeInterfaces() (used by
intersection type synthesis) had a mismatched aggregate initializer
that left virtualIndex zero for every field merged from a `{ ... }`
member, and the intersection-type synthesis path never ran the
canonical index pass at all.

See docs/interface-vtable-simplification-design.md for the full
design and PR1 implementation notes. New regression test:
00interface_optional_cast_order.ts. Full suite: 716/716.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 441ff7b into main Jul 19, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the interface-vtable-simplify-pr1 branch July 19, 2026 11:47
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