Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow "catch (e as Error)" instead of "catch (e: unknown)" #42596

Open
5 tasks done
octogonz opened this issue Feb 2, 2021 · 10 comments
Open
5 tasks done

Allow "catch (e as Error)" instead of "catch (e: unknown)" #42596

octogonz opened this issue Feb 2, 2021 · 10 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@octogonz
Copy link

octogonz commented Feb 2, 2021

Suggestion

TypeScript 3.x forbids type annotations in catch blocks:

try {
  // ...
} catch (e: Error) { // <-- TS1996 "Catch clause variable cannot have a type annotation"
  // ...
}

But TypeScript 4.x relaxed this rule to accept any or unknown only:

try {
  // ...
} catch (e: Error) { // <-- TS1996 "Catch clause variable type annotation must be 'any' or 'unknown' if specified"
  // ...
}

Suggestion: Could we relax the rule to allow Error as well?

Such a declaration would not accurately describe pathological JavaScript code.

But here's why it makes sense anyway:

  1. In a professional code base, thrown objects always implement the Error interface. We have lint rules that enforce this. And external packages generally follow this rule as well, at least the kind we'd use for professional work.
  2. It's wasteful for every single catch block to perform paranoid runtime tests for instanceof Error.
  3. The TypeScript compiler doesn't even support instanceof Error for transpiled code.
  4. Relaxing this rule won't cause any trouble; if some people really prefer unknown they can enable it via a lint rule like no-implicit-any-catch without any involvement from the compiler.

Alternate syntax

In the thread below, @MickeyPhoenix suggested to use as instead of : to clarify that technically this is a type cast, while still keeping the syntax concise and intuitive:

try {
  // ...
} catch (e as Error) {
  // ...
}

🔍 Search Terms

catch instanceof TS1996 1996

✅ Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Feb 5, 2021
@lightrow
Copy link

lightrow commented Sep 15, 2021

YES, PLEASE. And ideally also a rule to bring back the TypeScript 3.x behaviour with Error being default type for catch block errors, so we don't have to specify Error type in every try-catch block

right now I'm forced to annotate all caught errors as "any" which ruins intellisense.

@sanjom
Copy link

sanjom commented Sep 30, 2021

Yes! I think that would be a good trade-off for situations where we can be sure to receive an "Error" object!

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Oct 1, 2021

Duplicate of #20024

@octogonz
Copy link
Author

octogonz commented Oct 13, 2021

@ExE-Boss There is a slight difference between the two issues: #20024 is asking for arbitrary type annotations for catch blocks, whereas #42596 is /only/ asking for catch (e: Error) only.

The rationale given above was:

  1. In a professional code base, thrown objects always implement the Error interface. We have lint rules that enforce this. And external packages generally follow this rule as well, at least the kind we'd use for professional work.
  2. It's wasteful for every single catch block to perform paranoid runtime tests for instanceof Error.
  3. The TypeScript compiler doesn't even support instanceof Error for transpiled code.
  4. Relaxing this rule won't cause any trouble; if some people really prefer unknown they can enable it via a lint rule like no-implicit-any-catch without any involvement from the compiler.

These points apply to Error because that interface describes the bare minimum properties that any sane error object should have. Whereas these points maybe would NOT be convincing for custom error interfaces.

That said it does look like @DanielRosenwasser suggested the same idea in #20024 (comment):

I do like the idea of catch (err as Error) because, damn it, if you were going to cast it anyway, maybe we should make your life a little easier while keeping it explicit.

@MickeyPhoenix
Copy link

I think that catch (e as Error) is preferable to catch (e: Error). The latter looks like it's something the type-checker would verify for us (which it's not), while the former looks exactly like the unsafe cast that it is.

The practical result of not having a syntax like this is that people just use the default any behavior. And catch (e) is not only less visibly a type risk than catch (e as error), it is also dangerously over-flexible as a typing:

  • It won't catch typos like messsage, and
  • It won't catch usage errors like e.stack.substr(10).

Adding catch (e as Error) will make code safer, easier to read, and more self-documenting.

@octogonz
Copy link
Author

It's worth pointing out that the TypeScript code base builds with useUnknownInCatchVariables=false.

Maybe the priority for this problem would be more apparent if the compiler developers had to use their own feature. 😋

@octogonz
Copy link
Author

octogonz commented Jan 21, 2022

I think that catch (e as Error) is preferable to catch (e: Error). The latter looks like it's something the type-checker would verify for us (which it's not), while the former looks exactly like the unsafe cast that it is.

@MickeyPhoenix This is a really great suggestion. I'm going to update the issue description to include your idea.

@MickeyPhoenix
Copy link

MickeyPhoenix commented Jan 21, 2022

Thanks! I wish I could take credit for it, but actually it's from @jaredru's comment (here) as highlighted by @DanielRosenwasser (here). I just spelled out why I think it's such a great idea. :-)

As much as I'd like to, I can't quite support the catch (e: Error) variant, because it is an invisible type-cast. But I sure would love the explicit-cast option!

@octogonz
Copy link
Author

It's worth pointing out that the TypeScript code base builds with useUnknownInCatchVariables=false.

Setting useUnknownInCatchVariables=false is a pretty good workaround for this problem. 👍

Turns out it requires TypeScript 4.4 however (which was surprising since the problematic validation was introduced in 4.0). As soon as I added the setting to our rig, we started seeing breaks in projects that build using an older compiler. Just something to be aware of.

It would be super awesome if useUnknownInCatchVariables could be backported to 4.0.x-4.3.x.

@eduhsoto
Copy link

eduhsoto commented Feb 4, 2023

I tried this, without setting nothig. I don't know if it's a optimal solution. I use firebase auth.
code

@octogonz octogonz changed the title Allow "catch (e: Error)" instead of "catch (e: unknown)" Allow "catch (e as Error)" instead of "catch (e: unknown)" Feb 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants