Skip to content

Fix accessor vtable dispatch through base-typed references#289

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix/accessor-vtable-dispatch
Jul 23, 2026
Merged

Fix accessor vtable dispatch through base-typed references#289
ASDAlexander77 merged 1 commit into
mainfrom
fix/accessor-vtable-dispatch

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

  • Closes the "much bigger gap" flagged alongside super-accessor-crash-fix (Fix cross-module super-accessor crash and add class coverage #278): overriding a get/set accessor pair in a subclass and accessing it through a BASE-TYPED reference (an ordinary upcast, not super) always resolved statically to the base's own accessor instead of dispatching virtually to the override - a real correctness gap (standard OOP virtual dispatch), unlike ordinary methods which already dispatch through the vtable correctly.
  • Root cause: an accessor's backing get/set functions are ALREADY registered as ordinary MethodInfo entries in ClassInfo::methods (same list ordinary methods use) with a real vtable slot assigned via registerClassMethodMember/getVirtualTable - accessors are virtual by default exactly like methods. But ClassAccessorAccess never checked isVirtual at all: it unconditionally built ThisAccessorOp with a hardcoded FlatSymbolRefAttr naming THIS class's own get/set symbol, completely bypassing the vtable that was already being correctly built.
  • Fix: mirror ClassMethodAccess's own isVirtual/isStorageType check (isStorageType is true for super.<accessor>'s raw-struct thisValue, so super access is naturally excluded from virtual dispatch, same as methods) and route through the vtable via VirtualSymbolRefOp (the same op static virtual method access already uses) feeding into the existing "indirect" (function-value) ThisIndirectAccessorOp, instead of the hardcoded-symbol ThisAccessorOp. Checked before the isDynamicImport branch, matching ClassMethodAccess's ordering.
  • One regression found and fixed during verification: getThisRefOfClass only narrows thisValue for the isSuperClass case, so an ordinary access whose static type is still a wider union (e.g. T | null, narrowed only at the type-info level, never cast at the value level - hit by 00iterator_bug.ts's spread-operator-generated .length access) reached the new vtable lookup with an un-narrowed union type, which mlirGenPropertyAccessExpression's PropertyRefOp rejects (unlike the old AnyType-typed ThisAccessorOp it replaces). Fixed by explicitly casting to classInfo->classType before the vtable lookup.

Test plan

  • New same-file regression test 00class_accessor_virtual.ts (compile + jit tiers)
  • Closed the historical NOTE comment in import_class_accessor.ts documenting this exact gap as deliberately untested - now asserts virtual dispatch through a base-typed reference works, cross-module, across all 3 tiers (plain, -shared compile, -jit -shared)
  • Full suite: ctest -C Debug -j8 — 810/810 green (808 previously green + 2 new; caught and fixed a 00iterator_bug.ts regression mid-development, see commit message)

🤖 Generated with Claude Code

Closes the "much bigger gap" flagged alongside super-accessor-crash-fix
(PR #278): overriding a get/set accessor pair in a subclass and
accessing it through a BASE-TYPED reference (an ordinary upcast, not
`super`) always resolved statically to the base's own accessor instead
of dispatching virtually to the override - a real correctness gap
(this is standard OOP virtual dispatch, not an edge case), unlike
ordinary methods which already dispatch through the vtable correctly.

Root cause: an accessor's backing get/set functions are ALREADY
registered as ordinary MethodInfo entries in ClassInfo::methods (same
list ordinary methods use) with a real vtable slot assigned via
registerClassMethodMember/getVirtualTable - accessors are virtual by
default exactly like methods. But ClassAccessorAccess never checked
isVirtual at all: it unconditionally built ThisAccessorOp with a
hardcoded FlatSymbolRefAttr naming THIS class's own get/set symbol,
completely bypassing the vtable that was already being correctly
built.

Fix: mirror ClassMethodAccess's own isVirtual/isStorageType check
(isStorageType is true for `super.<accessor>`'s raw-struct thisValue,
so super access is naturally excluded from virtual dispatch, same as
methods) and route through the vtable via VirtualSymbolRefOp (the same
op static virtual method access already uses) feeding into the
existing "indirect" (function-value) ThisIndirectAccessorOp, instead
of the hardcoded-symbol ThisAccessorOp. Checked before the
isDynamicImport branch, matching ClassMethodAccess's ordering - the
vtable's own construction already resolves dynamic-import-owned slots,
so virtual dispatch through it is correct regardless of module
boundaries.

One regression found and fixed during verification: getThisRefOfClass
only narrows `thisValue` for the isSuperClass case, so an ordinary
access whose static type is still a wider union (e.g. `T | null`
narrowed only at the type-info level, never cast at the value level -
hit by 00iterator_bug.ts's spread-operator-generated `.length` access)
reached the new vtable lookup with an un-narrowed union type, which
mlirGenPropertyAccessExpression's PropertyRefOp rejects (unlike the
old AnyType-typed ThisAccessorOp it replaces). Fixed by explicitly
casting to classInfo->classType before the vtable lookup.

New same-file regression test 00class_accessor_virtual.ts (compile +
jit tiers). Also closed the historical NOTE in
import_class_accessor.ts documenting this exact gap as deliberately
untested - now asserts virtual dispatch through a base-typed reference
works, cross-module, across all 3 tiers (plain, -shared compile,
-jit -shared). Full suite: 810/810 green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 7183690 into main Jul 23, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix/accessor-vtable-dispatch branch July 23, 2026 12:21
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