mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
A class field typed AbortController or AbortSignal (plain, optional, nullable, or in an array) maps to checked-dynamic storage, and the class-field rule refused it (SC1090) although the same handles already live in locals, Map values, and closures. They now take the checked-dynamic slot, like the fetch Headers, Request, and Response handles. controller?.abort() on a nullable controller was refused as a method call (also for locals): the static abort lowering ran only without an optional chain. It now runs again once the chain has narrowed the receiver. Found by pi's TUI Editor (autocompleteAbort: AbortController | undefined). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
// Class fields typed AbortController and AbortSignal (plain, optional,
|
|
// `| undefined`, `| null`, in arrays) hold the live handles: aborting
|
|
// through the field is seen through every other reference, and optional
|
|
// calls on a nullable field skip an empty slot.
|
|
class Editor {
|
|
private abort: AbortController | undefined;
|
|
private signal: AbortSignal | null = null;
|
|
private readonly lifetime = new AbortController();
|
|
history: AbortController[] = [];
|
|
last?: AbortSignal;
|
|
start(): AbortSignal {
|
|
this.abort?.abort();
|
|
this.abort = new AbortController();
|
|
this.history.push(this.abort);
|
|
this.signal = this.abort.signal;
|
|
this.last = this.signal;
|
|
return this.signal;
|
|
}
|
|
cancel(reason?: string): boolean {
|
|
this.abort?.abort(reason);
|
|
return this.signal?.aborted ?? false;
|
|
}
|
|
reason(): unknown {
|
|
return this.signal?.reason;
|
|
}
|
|
dispose(): boolean {
|
|
this.lifetime.abort();
|
|
return this.lifetime.signal.aborted;
|
|
}
|
|
}
|
|
|
|
const idle = new Editor();
|
|
console.log(idle.cancel(), idle.reason());
|
|
|
|
const e = new Editor();
|
|
const first = e.start();
|
|
let events = 0;
|
|
first.addEventListener("abort", () => {
|
|
events++;
|
|
});
|
|
const second = e.start();
|
|
console.log(first.aborted, second.aborted, events, e.history.length);
|
|
console.log(e.cancel("stop"), e.reason(), second.aborted, second.reason);
|
|
console.log(e.history[0]!.signal === first, e.last === second, e.dispose());
|
|
|
|
// A nullable local takes the same optional call.
|
|
let maybe: AbortController | undefined = e.history.length > 5 ? new AbortController() : undefined;
|
|
maybe?.abort();
|
|
console.log(maybe?.signal.aborted);
|
|
maybe = new AbortController();
|
|
maybe?.abort("late");
|
|
console.log(maybe?.signal.aborted, maybe?.signal.reason);
|