Files
scriptc/tests/corpus/4596-error-subclass-int-fields.ts
Malte Ubl 8d8d478f80 Keep int32 fields when an Error subclass forwards its constructor arguments
A class extending Error without its own constructor (or one passing
options to super) initializes the builtin prefix through the
error.ctorOptions helper, whose message and options arguments are dynamic.
The int32 slot analysis did not list that helper among the dynamic helpers
that never store into a live class view, so a single such class anywhere in
a program counted as a dynamic store naming every property: every f64
field of every class exposed to dynamic code lost its int32 storage, and
the integer facts of the functions reading those fields went with it.

error.ctorOptions runs scr_error_init_options, the same initializer
error.newOptions (already listed) runs: it stamps the receiver's
runtime-owned error prefix and only reads `options.cause`. In the native
tsc-ts self-check, two such classes cost 7% total time and 10% check time.

Unit test: int32-slots.test.ts. Corpus: 4596-error-subclass-int-fields
pins Error subclasses forwarding message and options beside integer
fields.
2026-10-10 09:01:03 -07:00

46 lines
1.4 KiB
TypeScript

// A request counter keeps integer statistics while request failures are
// reported through Error subclasses that forward their message and options
// to Error, as a server's error type does next to its hot bookkeeping.
class Stats {
requests: number;
flags: number;
bytes: number;
constructor() {
this.requests = 0;
this.flags = 0;
this.bytes = 0;
}
record(size: number, flag: number): void {
this.requests = this.requests + 1;
this.flags |= flag;
this.bytes = (this.bytes + size) | 0;
}
}
class ServiceError extends Error {}
class TimeoutError extends ServiceError {
constructor(message: string, options: { cause: unknown }) {
super(`timeout: ${message}`, options);
}
}
const stats = new Stats();
const errors: ServiceError[] = [];
for (let i = 0; i < 12; i++) {
stats.record(i * 100, 1 << (i % 5));
if (i % 4 === 3) errors.push(new TimeoutError(`request ${i}`, { cause: `attempt ${stats.requests}` }));
if (i % 6 === 5) errors.push(new ServiceError(`failed ${i}`));
}
console.log(stats);
for (const error of errors) {
console.log(error instanceof TimeoutError, error.name, error.message, String(error.cause));
}
stats.record(7, 64);
console.log(stats.requests, stats.flags, stats.bytes);
try {
throw new TimeoutError("final", { cause: stats.flags });
} catch (error) {
if (error instanceof ServiceError) console.log(error.message, String(error.cause), error instanceof Error);
}