TypeScript: Allow `unknown` type annotation on catch clause variable
Search Terms
catch clause unknown exception
Suggestion
Now any kind of type annotation on catch clauses is not allowed.
In my understanding, it’s because that it’s unsafe to annotate like catch (err: SpecialError)
since any value will be captured actually.
However, due to the type of err
is any
, it’s also not type safe.
So I suggest to allow annotating unknown
, as most safe typing.
(And annotation with any other type won’t be allowed as is)
Examples
try {
throw 42;
} catch (err) {
console.error(err.specialFunction());
}
can be written safer as
try {
throw 42;
} catch (err: unknown) {
if (err instanceof SpecialError) {
console.error(err.specialFunction());
} else {
...
}
}
Related Issue
Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 68
- Comments: 38 (17 by maintainers)
Links to this issue
Commits related to this issue
- Implements #36775 — committed to RyanCavanaugh/TypeScript by RyanCavanaugh 4 years ago
- Allow `e: unknown` in `catch` arguments In addition, allow an explicit `any`; anything else throws an error. Also adjust and reorganize existing tests. Fixes #36775. — committed to elibarzilay/TypeScript by elibarzilay 4 years ago
- Allow `e: unknown` in `catch` arguments In addition, allow an explicit `any`; anything else throws an error. Also adjust and reorganize existing tests. Fixes #36775. — committed to elibarzilay/TypeScript by elibarzilay 4 years ago
- Allow `e: unknown` in `catch` arguments In addition, allow an explicit `any`; anything else throws an error. Also adjust and reorganize existing tests. Fixes #36775. — committed to microsoft/TypeScript by elibarzilay 4 years ago
- feat(typescript-estree): allow catch clause to have a type See https://github.com/microsoft/TypeScript/issues/36775 — committed to phiresky/typescript-eslint by phiresky 4 years ago
- feat(typescript-estree): allow catch clause to have a type See https://github.com/microsoft/TypeScript/issues/36775 — committed to phiresky/typescript-eslint by phiresky 4 years ago
- Remove catch clause variable type annotation, this is a feature of TypeScript 4, but version 3.6.4 is currently being used. https://github.com/microsoft/TypeScript/issues/36775 — committed to mslosarek/lambda-backend-challenge by mslosarek 3 years ago
- Fix type issues with Typescript 4 The catch blocks have some "new" syntax we need to adhere to: https://github.com/microsoft/TypeScript/issues/36775 As of Feathers 4, the request and response body... — committed to FossPrime/feathers-saml by FossPrime 3 years ago
Assuming this is the appropriate place to discuss this, I would like to advocate that the change in 4.0 be either (in order of preference):
catch (error)
results inerror
having a type ofunknown
error
be of typeunknown
catch (error: unknown)
I understand that
error
isany
for legacy reasons (it was beforeunknown
existed) and therefore can’t be trivially changed (at least without a major version bump). However, I would like to see a path that allows us to get away from that eventually, either with a breaking change (error
isunknown
) or with a new compiler setting that is flipped on with thestrict
flag.I would really love this as a compiler option. Being able to set a flag that makes all caught exceptions
unknown
instead of manually annotating them would be much better, but I guess it would be harder to migrate to.It should probably also be possible to do:
In case
unknown
ever becomes the default inferred type (e.g.: https://github.com/microsoft/TypeScript/issues/27265).For a 4.0 change (major version update) this certainly feels like it should be included in
noImplicitAny
. At the moment, I believe this is the last place where implicit any is allowed whennoImplicitAny
is on, and getting rid of that last place sure would be nice.Awesome! I’ve made a PR to force adding a
unknown
or explicitany
type to all catch clauses to typescript-eslint: https://github.com/typescript-eslint/typescript-eslint/pull/2202This thing is only about adding
unknown
, the discussion about properly adding types to exceptions is should move to #13219.@hazae41 Sure, but
throw error
is standard other things are not.Note that the above adds the ability to add an
any
/unknown
type, but no new flag for its default any not changing existing flags.I doubt adding checked exception is ever going to work in Typescript, because anytime you call an API you don’t fully control you are adding
any
to your error type, which makes the whole type any.I’ve previously tried doing this:
Never use throw. Instead create this simple API:
(optionally add some monadic functions like and_then etc akin to Rust Result)
. Then you always do this:
This sounds great in theory and gives you fully checked exceptions always… Except when you use literally any external or other API, and then you’re back to where you started, except it’s worse: you now have lots of additional effort, two layers of kinds of exceptions and still can get untyped / unchecked exceptions all the time.
The best that can be done is making a union type of all your own possible checked errors (or inherited error classes if you’re okay with not having exhaustive checking / open domain errors):
and then on top of each catch block, add a assertion / condition to always instantly narrow your type and rethrow others:
This is then basically the same as catch blocks you expect from other languages:
I’m already doing this in my own code bases, but to make it ergonomic these things are missing:
I am excited typescript 4.0 will address this issue.
We had to add a specific
--ignore-catch
flag to thetype-coverage
command ( https://github.com/plantain-00/type-coverage#ignore-catch ) to handle the fact thatcatch {}
is the only place an implicitany
is mandatory and unavoidable.Any way to force it to unknown? We have “noImplictAny” checked, which says “Warn on expressions and declarations with an implied ‘any’ type.”
But we get no warning that err is an ‘any’ type.
@hazae41 the go style doesn’t work well in typescript cause the type system doesn’t understand if error is none, the result must be valid. But the rust style is different, typescript do recognize this tagged union pattern therefore you can’t miss the error handing
We’re using ts-results (https://github.com/vultix/ts-results) to use Rust style checked exception in our program. But it is not good enough cause TypeScript doesn’t and won’t have the Rust
?
try operator to automatically spread the type to the containing function.And another concern is that we cant use the checked exception with confidence, because the rest of the world, including the JS builtin library, the npm libraries… doesn’t obey the checked exception way, they throw.
@akxe: It won’t work because the calling function might come from the npm library with a
dts
declaration. The source code even might not be the JavaScript / TypeScript (e.g. other languages that compile to JavaScript) so that it is impossible to analyze the source code to infer thethrows
type.@phiresky: Hi, please try ts-results, it’s awesome! (I have made a PR to implement
into_ok
andand_then
and not merged yet)Why not to try infer the possible types of error? They are very well known to the compiler. Isn’t it right that the
error
is union of all thrown & Error?You may ask, why to handle all errors at the same place. For me, the reason was that Express. For every response you can only send headers once (, logically but annoying to handle an error with it when trying to parallel all async tasks).
This would be great (together with a lint that forces doing this always).
Right now, catch (e) {} is basically a situation that should be forbidden by
noImplicitAny
, except it can’t be because you can’t annotate it with any type (except for unknown) since that would be misleading.