Skip to content

Fix casting to an interface that extends another interface with methods - #266

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix/interface-extends-method-vtable
Jul 20, 2026
Merged

Fix casting to an interface that extends another interface with methods#266
ASDAlexander77 merged 1 commit into
mainfrom
fix/interface-extends-method-vtable

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

Follow-up to #265's "known follow-up" note: an extends-based version of the multi-method interface cast scenario (interface Base {base;addBase()} interface Accumulator extends Base {total;add();addTwice();scaled()}) still failed — confirmed unrelated to cross-module/import, reproduces with a plain same-module cast.

Two separate bugs, both in the interface vtable machinery:

  1. Compile-time failureInterfaceInfo::getVirtualTable's recursion into extends didn't propagate the methodsAsFields flag to the recursive call, so an inherited method always took the METHOD resolution path instead of the FIELD path object-literal methods actually need. That path's resolver is a deliberate no-op stub (comment: "we do not return method, as it should be resolved in fields request"), so any non-conditional inherited method made the whole cast fail with a bare error: failed statement — no diagnostic text anywhere.
  2. Runtime crash, after fixing (1) — the cast now compiled, but calling an inherited method (even the first one, vtable slot 0) crashed with an access violation. The runtime vtable-patch loop (mlirGenCreateInterfaceVTableForObject) only walked an interface's own directly-declared methods (InterfaceInfo::methods), never inherited ones — so an inherited method's vtable slot was never patched with a real function pointer and kept its initial offset-placeholder value, which got read and called as if it were a function pointer.

Fix

  1. Pass methodsAsFields through on the extends recursive call in getVirtualTable (one line).
  2. Added InterfaceInfo::getAllMethods() to flatten own + inherited methods (extends-first, matching getVirtualTable's own traversal order); the vtable-patch loop now uses this instead of the interface's own methods list alone.

Test plan

  • Same-module and cross-module repros manually verified end-to-end (105/3/7/14, all four members — one inherited, three own — correct)
  • New regression tests: 00object_annotated_method_extends_interface.ts (same-module, compile+jit) and export/import_object_literal_structural_typed_extends_interface.ts (cross-module, compile+jit)
  • Full 744-test suite (740 existing + 4 new): 100% pass, no regressions

Known limitation (not hit by this fix, flagged for later)

Only verified for single-level extends. An inherited InterfaceMethodInfo's virtualIndex is assigned standalone by the base interface's own assignCanonicalVirtualIndexes() — for a single extends target this coincidentally lines up with the derived interface's combined vtable (base slots start at offset 0), but multi-level or diamond extends isn't verified and may need the same vtableOffset-accumulation treatment findField/findMethod already document for a related scenario.

🤖 Generated with Claude Code

Two separate bugs in the interface vtable machinery, both unrelated to the
earlier decl-text-printer/object-literal-layout fixes:

1. InterfaceInfo::getVirtualTable's recursion into `extends` didn't
   propagate the methodsAsFields flag to the recursive call, so an
   inherited method always took the METHOD resolution path instead of the
   FIELD path object-literal methods actually need - that path's resolver
   is a deliberate no-op stub, so any non-conditional inherited method
   made the whole cast fail to compile with a bare "failed statement",
   no diagnostic.

2. After fixing (1), the cast compiled but crashed on the first call to an
   inherited method: the runtime vtable-patch loop only walked an
   interface's own directly-declared methods, so an inherited method's
   vtable slot was never patched with a real function pointer - it kept
   its initial offset-placeholder value, read and called as if it were a
   function pointer.

Fix (1): pass methodsAsFields through on the extends recursive call.
Fix (2): added InterfaceInfo::getAllMethods() to flatten own + inherited
methods (extends-first, matching getVirtualTable's own order), used by the
vtable-patch loop instead of the interface's own methods list alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 9b4b85a into main Jul 20, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix/interface-extends-method-vtable branch July 20, 2026 17:36
ASDAlexander77 added a commit that referenced this pull request Jul 20, 2026
…rectly (#267)

PR #266 fixed casting to an interface that extends another with methods,
but only verified single-level extends (Accumulator extends Base), flagging
deeper hierarchies as an unverified known limitation.

Manually verified (same-module and cross-module): a 3-level chain
(C extends B extends A), an interface with two extends targets
(Combined extends Left, Right), and a genuine diamond (both Left and Right
independently extend Base) - all correct for every inherited member at
every level. No further bug found; both fixes from #266 are themselves
naturally recursive/multi-target, not hardcoded to one level.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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