From c22302cfae00dc5455c6089364d450c5f9164ffb Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 21 Oct 2026 23:22:03 -0500 Subject: [PATCH] Test runtime classes' dispatch branches before their subclasses' --- .../lowering/class-dynamic-dispatch.ts | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index a41751f01..c90e1135b 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -407,9 +407,27 @@ function withTypedReceiver(value: IrExpr, receiver: IrExpr): IrExpr { }); } -/** Re-adds the entries of `set` after the first `from` sorted. */ -function sortAdded(set: Set, from: number): void { - const added = [...set].slice(from).sort(); +/** Re-adds the classes of `set` after the first `from` in a fixed order: + * the program's classes by name, then the classes the runtime implements by + * name. The dispatch helpers add each class's branch in front of the + * earlier ones, so a runtime class's branch, which answers its methods for + * instances of the program's subclasses too, is tested before the + * subclasses' own branches. */ +function sortAdded(lowerer: Lowerer, set: Set, from: number): void { + const runtime = (name: string): boolean => { + const info = lowerer.classes.get(name); + return !!( + info && + (info.def.runtime || info.builtinEmitter || info.builtinStream || info.builtinError) + ); + }; + const added = [...set] + .slice(from) + .sort( + (left, right) => + Number(runtime(left)) - Number(runtime(right)) || + (left < right ? -1 : left > right ? 1 : 0), + ); for (const name of added) set.delete(name); for (const name of added) set.add(name); } @@ -932,10 +950,10 @@ export class ClassDynamicDispatch { return true; }, }); - // The classes this pass found join the tables by name, not in the order - // the scan met them. - sortAdded(this.boxed, boxedBefore); - sortAdded(this.boxedConstructors, boxedConstructorsBefore); + // The classes this pass found join the tables in a fixed order, not in + // the order the scan met them. + sortAdded(lowerer, this.boxed, boxedBefore); + sortAdded(lowerer, this.boxedConstructors, boxedConstructorsBefore); changed = this.caughtDispatch.dynamicPayloads(lowerer, caughtSites, this.boxed) || changed; if (this.boxed.size === 0 && rewrite.size === 0) return changed; const converting = this.convertingHints(lowerer);