-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fix(eslint-plugin): [no-unsafe-type-assertion] fix for unsafe assertion to a constrained type parameter #10461
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
fix(eslint-plugin): [no-unsafe-type-assertion] fix for unsafe assertion to a constrained type parameter #10461
Conversation
Thanks for the PR, @controversial! typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community. The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately. Thanks again! 🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint. |
✅ Deploy Preview for typescript-eslint ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #10461 +/- ##
=======================================
Coverage 86.78% 86.79%
=======================================
Files 445 445
Lines 15366 15376 +10
Branches 4475 4478 +3
=======================================
+ Hits 13336 13346 +10
Misses 1675 1675
Partials 355 355
Flags with carried forward coverage won't be shown. Click here to find out more.
|
With respect to a custom error message, @kirkwaiblinger suggested:
In the original PR of this rule, #10051, a discussion about the appropriate verbosity of the error message resolved to omit any types other than the explicitly written type from the error message in order to prevent excessively verbose errors. With that in mind, I’m starting out with the following error message:
I’m happy to amend this message to be more verbose if we decide to go that way, though! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a great first PR to typescript-eslint! This is very much on the right track, and in a tricky area of the codebase, too.
Left a few comments and questions.
checker.getBaseConstraintOfType(assertedType); | ||
const isAssignableToConstraint = | ||
!!assertedTypeConstraint && | ||
checker.isTypeAssignableTo( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(no specific changes requested in the following)
While I was thinking deeply, it occurred to make that I don't think that asserting from any concrete type to any type parameter is ever safe, so I was wondering whether we could gain some performance by checking for this early and avoiding any checker.isTypeAssignableTo()
checks..
This would sacrifice the ability to have distinct error messages for whether it's not assignable because of ordinary unassignability (string | number as T where T extends string) or due to the generic subtype instantiation (string as T where T extends string | number), it could potentially be mitigated by a separate error message just saying A concrete type can never be safely assigned to a type parameter (or similar).
However, I think we still need the current infrastructure in order to deal with the case of assignment from one type parameter to another type parameter (since T -> V where V extends T is ok), so I'm leaning against going that route, i.e. leaning towards your current strategy. If you want to give this a thought, though, I'm curious for your opinion as well!
…ssage + light refactor to prefer early returns over nested conditionals
…r extends unconstrained parameter”
Thanks so much for the thoughtful review @kirkwaiblinger !! I added the test cases from your playground and addressed the missing cases. For the missing case of unconstrained type parameters, I added another message to match the one Typescript uses:
Here’s an updated playground link!
The only test case that tripped me up a bit was the one you had called
|
Merged the latest When you get a chance, let me know if there’s anything else you need from me @kirkwaiblinger :) |
I feel strongly that TS is wrong here, since
🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really outstanding work! Great attention to detail on noticing the additional error message, nicely done on pushing back where it made sense to do so!
Trivial cleanup requested but this is otherwise an enthusiastic approval from me! 🙂 We'll probably leave this open for a little bit as well for another team member to take a look if they like, since it's nuanced stuff.
Thanks!
packages/eslint-plugin/tests/rules/no-unsafe-type-assertion.test.ts
Outdated
Show resolved
Hide resolved
packages/eslint-plugin/tests/rules/no-unsafe-type-assertion.test.ts
Outdated
Show resolved
Hide resolved
unit tests should only test one thing
cleanup completed! thanks again for your thoughtful reviews, excited to have this merged at some point :) |
4c91ed5
into
typescript-eslint:main
| datasource | package | from | to | | ---------- | -------------------------------- | ------ | ------ | | npm | @typescript-eslint/eslint-plugin | 8.18.1 | 8.18.2 | | npm | @typescript-eslint/parser | 8.18.1 | 8.18.2 | ## [v8.18.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8182-2024-12-23) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-condition] handle noUncheckedIndexedAccess true ([#10514](typescript-eslint/typescript-eslint#10514)) - **eslint-plugin:** \[consistent-type-assertions] allow default assertionStyle option ([#10512](typescript-eslint/typescript-eslint#10512)) - **eslint-plugin:** \[no-unnecessary-type-arguments] handle type/value context ([#10503](typescript-eslint/typescript-eslint#10503)) - **eslint-plugin:** \[no-unsafe-type-assertion] fix for unsafe assertion to a constrained type parameter ([#10461](typescript-eslint/typescript-eslint#10461)) - **eslint-plugin:** \[consistent-indexed-object-style] use a suggestion over an auto-fix if can't reliably determine that produced index signature is valid ([#10490](typescript-eslint/typescript-eslint#10490)) - **eslint-plugin:** \[no-unnecessary-condition] don't flag values of an unconstrained or valid type parameter ([#10473](typescript-eslint/typescript-eslint#10473)) - **eslint-plugin:** \[prefer-reduce-type-parameter] don't report cases in which the fix results in a type error ([#10494](typescript-eslint/typescript-eslint#10494)) - **eslint-plugin:** \[no-deprecated] not reporting usages of deprecated declared constants as object value ([#10498](typescript-eslint/typescript-eslint#10498)) ##### ❤️ Thank You - Luke Deen Taylor [@controversial](https://github.com/controversial) - Ronen Amiel - Scott O'Hara - YeonJuan [@yeonjuan](https://github.com/yeonjuan) - Yukihiro Hasegawa [@y-hsgw](https://github.com/y-hsgw) You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
##### [v8.18.2](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8182-2024-12-23) ##### 🩹 Fixes - **eslint-plugin:** \[no-unnecessary-condition] handle noUncheckedIndexedAccess true ([#10514](typescript-eslint/typescript-eslint#10514)) - **eslint-plugin:** \[consistent-type-assertions] allow default assertionStyle option ([#10512](typescript-eslint/typescript-eslint#10512)) - **eslint-plugin:** \[no-unnecessary-type-arguments] handle type/value context ([#10503](typescript-eslint/typescript-eslint#10503)) - **eslint-plugin:** \[no-unsafe-type-assertion] fix for unsafe assertion to a constrained type parameter ([#10461](typescript-eslint/typescript-eslint#10461)) - **eslint-plugin:** \[consistent-indexed-object-style] use a suggestion over an auto-fix if can't reliably determine that produced index signature is valid ([#10490](typescript-eslint/typescript-eslint#10490)) - **eslint-plugin:** \[no-unnecessary-condition] don't flag values of an unconstrained or valid type parameter ([#10473](typescript-eslint/typescript-eslint#10473)) - **eslint-plugin:** \[prefer-reduce-type-parameter] don't report cases in which the fix results in a type error ([#10494](typescript-eslint/typescript-eslint#10494)) - **eslint-plugin:** \[no-deprecated] not reporting usages of deprecated declared constants as object value ([#10498](typescript-eslint/typescript-eslint#10498)) ##### ❤️ Thank You - Luke Deen Taylor [@controversial](https://github.com/controversial) - Ronen Amiel - Scott O'Hara - YeonJuan [@yeonjuan](https://github.com/yeonjuan) - Yukihiro Hasegawa [@y-hsgw](https://github.com/y-hsgw) You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website.
PR Checklist
Overview
Attempts to fix the issue described in #10453:
After experimenting with the logic in
no-unsafe-type-assertion
, I found that obtaining the “types to check” usingservices.getTypeAtLocation
in place ofgetConstrainedTypeAtLocation
(i.e. omitting a call togetBaseConstraintOfType
) allows the subsequentisTypeAssignableTo
check to correctly catch this case.Additionally, I added a new error message for this case: if the “expression type” is not assignable to the “asserted type”, but the asserted type has a constraint to which the expression type is assignable, the rule will report the following:
This is my first contribution to this project and I’m broadly unfamiliar with both ESLint plugin development and TypeScript internals, so it’s very possible that I’m missing a hidden implication of this change—i.e. I’m not sure if there was an important reason to use
getConstrainedTypeAtLocation
in the original implementation. However, the existing tests for this rule are still passing.