Skip to content

Bug: [no-unsafe-type-assertion] Fails to report unsafe assertions involving constrained type parameters #10453

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

Closed
4 tasks done
controversial opened this issue Dec 4, 2024 · 8 comments · Fixed by #10461
Closed
4 tasks done
Labels
accepting prs Go ahead, send a pull request that resolves this issue locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look

Comments

@controversial
Copy link
Contributor

controversial commented Dec 4, 2024

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.
  • I have searched for related issues and found none that matched my issue.
  • I have read the FAQ and my problem is not listed.

Playground Link

Playground link

Repro Code

type Object = {
  foo: string;
}

export function myfunc<CustomObjectT extends Object>(input: CustomObjectT): CustomObjectT {
  const newObject: Object = { foo: 'hey' };
  // This is UNSAFE because CustomObjectT can/will be instantiated with a narrower subtype of Object
  // no-unsafe-type-assertion should (but doesn’t) catch this
  const newCustomObject = newObject as CustomObjectT;
  return newCustomObject;
}

ESLint Config

{
  "rules": {
    "@typescript-eslint/no-unsafe-type-assertion": "error"
  }
}

tsconfig

{
  "compilerOptions": {
    "strict": true
  }
}

Expected Result

The line with the type assertion involving the generic parameter (newObject as CustomObjectT) should report @typescript-eslint/no-unsafe-type-assertion by the same logic wherein Typescript considers such a conversion unsafe:

2322: Type 'Object' is not assignable to type 'CustomObjectT'.
  'Object' is assignable to the constraint of type 'CustomObjectT', but 'CustomObjectT' could be instantiated with a different subtype of constraint 'Object'.

The first example in the playground shows that this case is not an allowable assignment according to TS, which, to my understanding, should be the heuristic used by no-unsafe-type-assertion

Actual Result

No error is reported from @typescript-eslint/no-unsafe-type-assertion

Additional Info

cc @ronami

thanks for your hard work on this rule!

@controversial controversial added bug Something isn't working package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Dec 4, 2024
@controversial controversial changed the title Bug: [no-unsafe-type-assertion] Missing case with extends Bug: [no-unsafe-type-assertion] Fails to report unsafe assertions involving constrained type parameters Dec 4, 2024
@ronami
Copy link
Member

ronami commented Dec 4, 2024

Interesting!

I think it is a valid case the rule should warn on, as this example literally narrows down a type from its type constraint to a potentially more specific one.

I initially thought this has to do with type-widening, but it looks like it's related to how type constraints are handled. The rule compares the types (or the type constraints if the compared types have any). Since one type has a type constraint, which is the same as the other type, the validation stops early on. I think a potential solution would be to check the specific types, rather than the type constraints if both type constraints are the same.

@controversial
Copy link
Contributor Author

Ok, interesting!

So to clarify my understanding of what you said: this behavior is because the rule obtains the expressionType and assertedType using getConstrainedTypeAtLocation, which “will resolve to the type's generic constraint, if it has one” ?

Which means the rule will need to have logic for the special case of generic constraints—because it’s not generally true that A is assignable to B if A’s generic constraint is assignable to B’s (since B “could be instantiated with a different subtype of constraint”)

@kirkwaiblinger
Copy link
Member

Huh! We're so used to checking generics' constraints, but, yeah, that might actually be a mistake for this rule.

@kirkwaiblinger kirkwaiblinger added accepting prs Go ahead, send a pull request that resolves this issue and removed bug Something isn't working labels Dec 5, 2024
@kirkwaiblinger
Copy link
Member

Might be nice if we can special-case the error message here since the TS error is one that always surprises and confuses me at first when I run into it.

@controversial
Copy link
Contributor Author

What do you think a better error message would be?

@kirkwaiblinger
Copy link
Member

What do you think a better error message would be?

As a first pass, I think we could just model it after the TS error message?

Unsafe type assertion: 'Object' is assignable to the constraint of type 'CustomObjectT', but 'CustomObjectT' could be instantiated with a different subtype of constraint 'Object'.

Doesn't necessarily have to be that, verbatim 🤷... if we get working code that detects this case as the reason for erroring, we could probably bikeshed about wording on the PR.

The only import technical thing here is ensuring that we don't give this error message on a case like

export function myfunc<CustomObjectT extends string>(input: number): CustomObjectT {
  const newCustomObject = input as CustomObjectT;
  return newCustomObject;
}

That snippet is unsafe, but because number is not assignable to string at all, not because of the generic subtypes thing. (Note that it's also a TS error in this case, so it's not super important, but I'm guessing we could come up with a corollary that's not a TS error)

@controversial
Copy link
Contributor Author

controversial commented Dec 5, 2024

I took a stab at this in #10461—but I really don’t know what I’m doing, so I’d welcome feedback!

@controversial
Copy link
Contributor Author

The only import technical thing here is ensuring that we don't give this error message on a case like

export function myfunc<CustomObjectT extends string>(input: number): CustomObjectT {
  const newCustomObject = input as CustomObjectT;
  return newCustomObject;
}

I added this example as a test case to ensure we don’t give the “assignable to the constraint” message here!

@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Dec 29, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 29, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepting prs Go ahead, send a pull request that resolves this issue locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look
Projects
None yet
3 participants