I would guess it is because just about every function can throw, and TS can't detect it with certainty, even for functions which are 100% TS. It also doesn't improve transcription or performance.
Why can’t provenance of throws be traced with certainty? It is possible to trace the return type of functions.
Even if it were an opt-in/progressive adoption keyword like “throws” that enforced some compiler guardrails, I think that could be a huge win for reducing unexpected runtime exceptions.
function arrmin<T extends {field: number}>(a: T[]): number {
let m = a[0].field;
for (let i = 1; i < a.length; i++) {
if (a[i].field < m) {
m = a[i].field;
}
}
return m;
}
Now call arrmin([]). There's no way to avoid a throw. You would have to annotate every function that uses field selection on an array element (and a bunch of other conditions) as "throws", or enforce error checking code (if (0 <= i < a.length) {...}).
Rust didn't solve that either, does it? It just panics.
From memory: even if the arguments in favor were thoroughly convincing, it’s basically an insurmountable task to produce the types to cover even common runtimes, let alone the vast ecosystem of libraries (often themselves with community-maintained types). The failure mode for poor coverage of error types is worse than the failure mode of untyped errors, for instance because it would tend to influence where developers focus their error handling efforts in misleading ways.
Maybe I am misreading you but for example, in Swift, marking a function as “throws” makes it a compiler error to not handle the error, and makes it an error to not throw an error or return in the function. In Typescript, the error in a catch handler is of type unknown. I don’t see how it could be any worse than basically untyped errors AND unpredictable runtime errors without reading the docs or implementation. At least you would get some compiler help.
Here’s an example of why it would be worse to have poor error type coverage:
function libFoo(foo: string): throws FooError;
function libBar(bar: number): boolean;
Both of these functions throw. With untyped errors, you have to assume that this is at least a possibility. Now you have every reason to assume it’s safe to call one of them without handling errors.
The argument as I understand it, which is convincing, is that types like this are inevitable if typed errors were to be introduced. The scope of even well typed code with no documentation of even the errors thrown directly is enormous. The scope of code which might propagate errors from calls deeper in its stack is unimaginable. And there is basically no way to do static analysis to meaningfully reduce that space.
It could be fair to say this was a missed opportunity earlier in TypeScript’s development. But introducing typed errors retroactively would lead, trivially, to millions of cases like the above. Many in very hard to find ways.
Could those types be produced and refined to be as high quality as existing types (first party or community provided)? Of course. But it would take years. And unlike introducing types where none exist, the gradual story just doesn’t exist. You’d have to treat literally every function and property access as `throws any` to get safety equivalent to the gradual types story for non-errors.
And the incentive to type one’s own code with errors is hard to imagine: how can I, as a library author, say with any confidence what anything I write throws, if I have no idea what the underlying code throws (or if it really even does)? Why would I take on the maintenance burden of getting it wrong? Or incomplete? Or incomplete and wrong?