Repro
class K { static m(): number { return 1; } }
let s: any = K;
for (let i = 0; i < 8; i++) s += i; // also reproduces without the loop: K + 1
console.log(typeof s, String(s).slice(0, 20));
node 26.5.1: string class K { static m() — + on a class does ToPrimitive → the function source string → concatenation.
perry (main b2f163c5a0, also with PERRY_PACKED_LOOP_NUMERIC_ACCUMULATOR=0, so unrelated to #9060): number 29 — the class reference participates as a NUMBER.
Analysis
A ClassRef is NaN-boxed with INT32_TAG carrying the registered class id (see the #321 note on value_bits_to_number in array/header.rs, which added is_class_id_registered exactly to keep class refs off numeric paths — for array slots). The dynamic + path (or an upstream numeric-operand classifier) apparently lacks that exclusion, so class + number takes the numeric arm instead of ToPrimitive/concat.
Found while adversarially testing #9060's accumulator admission (which is not implicated — its tag test rejects the whole NaN-box band including INT32); reported by the ECS/ops perf campaign, session https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ. Repro file preserved at /private/tmp/acc-int32.ts shape above.
The same missing exclusion may affect other INT32-band consumers (-/* coercions, comparisons); worth auditing is_class_id_registered call sites vs the numeric-classifier entry points.
Repro
node 26.5.1:
string class K { static m()—+on a class does ToPrimitive → the function source string → concatenation.perry (main
b2f163c5a0, also withPERRY_PACKED_LOOP_NUMERIC_ACCUMULATOR=0, so unrelated to #9060):number 29— the class reference participates as a NUMBER.Analysis
A
ClassRefis NaN-boxed withINT32_TAGcarrying the registered class id (see the #321 note onvalue_bits_to_numberinarray/header.rs, which addedis_class_id_registeredexactly to keep class refs off numeric paths — for array slots). The dynamic+path (or an upstream numeric-operand classifier) apparently lacks that exclusion, soclass + numbertakes the numeric arm instead of ToPrimitive/concat.Found while adversarially testing #9060's accumulator admission (which is not implicated — its tag test rejects the whole NaN-box band including INT32); reported by the ECS/ops perf campaign, session https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ. Repro file preserved at
/private/tmp/acc-int32.tsshape above.The same missing exclusion may affect other INT32-band consumers (
-/*coercions, comparisons); worth auditingis_class_id_registeredcall sites vs the numeric-classifier entry points.