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

Generics: False negative on type constraint validation involving a conditional type #25413

Closed
UselessPickles opened this issue Jul 3, 2018 · 6 comments · Fixed by #30639
Closed
Assignees
Labels
Bug A bug in TypeScript Domain: Conditional Types The issue relates to conditional types Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@UselessPickles
Copy link

UselessPickles commented Jul 3, 2018

TypeScript Version: 2.9.2

Search Terms:
conditional type constraint

Code

declare class Model<M extends MR, MR extends {}> {
    // Compiler error: 
    // Type 'M[K]' does not satisfy the constraint 'K extends keyof MR ? MR[K] : M[K]'.
    public getField2<K extends keyof M>(): Field<M[K], K extends keyof MR ? MR[K] : M[K]>
}

declare class Field<T extends TR, TR> {
}

Expected behavior:
No compiler error.

Because M extends MR, and K extends keyof M:

  • If K extends keyof MR, then I expect M[K] to satisfy the constraint MR[K].
  • Else, I expect M[K] to satisfy the constraint M[K].
  • Therefore, it seems that M[K] should satisfy the constraint K extends keyof MR ? MR[K] : M[K]

Actual behavior:
Compiler error: Type 'M[K]' does not satisfy the constraint 'K extends keyof MR ? MR[K] : M[K]'.

Playground Link:
https://www.typescriptlang.org/play/#src=declare%20class%20Model%3CM%20extends%20MR%2C%20MR%20extends%20%7B%7D%3E%20%7B%0D%0A%20%20%20%20%2F%2F%20Compiler%20error%3A%20%0D%0A%20%20%20%20%2F%2F%20Type%20'M%5BK%5D'%20does%20not%20satisfy%20the%20constraint%20'K%20extends%20keyof%20MR%20%3F%20MR%5BK%5D%20%3A%20M%5BK%5D'.%0D%0A%20%20%20%20public%20getField2%3CK%20extends%20keyof%20M%3E()%3A%20Field%3CM%5BK%5D%2C%20K%20extends%20keyof%20MR%20%3F%20MR%5BK%5D%20%3A%20M%5BK%5D%3E%0D%0A%7D%0D%0A%0D%0Adeclare%20class%20Field%3CT%20extends%20TR%2C%20TR%3E%20%7B%0D%0A%7D

Related Issues:
Possibly?

@UselessPickles
Copy link
Author

Interesting followup. As of now, at least, with TypeScript 3.3.3, I can get the correct behavior with a small change: use MR extends Record<string, any> instead of MR extends {} as my type constraint. The original MR extends {} seems to cause problems because type {} has no keys:

Type 'M[K]' does not satisfy the constraint 'K extends keyof MR ? MR[K] : M[K]'.
Type 'MR[K]' is not assignable to type 'K extends keyof MR ? MR[K] : M[K]'.
Type '{}[K]' is not assignable to type 'K extends keyof MR ? MR[K] : M[K]'.

The change to MR extends Record<string, any> seems to clarify that MR is guaranteed to have string keys.

I'm still a bit unclear on when it is appropriate to use type {} as a constraint. Is there still a bug that needs to be investigated to get my original code to compile as I expect it to, or is the change to MR extends Record<string, any>actually the correct solution?

@jack-williams
Copy link
Collaborator

I think should be covered by #26933 provided you convert the conditional type to be non-distributive.

@UselessPickles
Copy link
Author

@jack-williams I'm not familiar with what a "distributive" vs "non-distributive" type is. Could you elaborate on what you mean by "convert the conditional type to be non-distributive."?

@jack-williams
Copy link
Collaborator

@UselessPickles Here is a relevant comment that should have the information (or at least point it).

@UselessPickles
Copy link
Author

UselessPickles commented Mar 14, 2019

Thanks. I like the trick for preventing distribution (in this comment, referenced by the comment you pointed me to) :)

@weswigham weswigham added Fix Available A PR has been opened for this issue and removed Fix Available A PR has been opened for this issue labels Aug 13, 2019
weswigham added a commit to weswigham/TypeScript that referenced this issue Aug 13, 2019
@weswigham weswigham added the Fix Available A PR has been opened for this issue label Aug 13, 2019
@weswigham
Copy link
Member

@jack-williams is correct - #26933 + using a non-distributive conditional does fix this. The reason a distributive conditional can't be assumed to work is because the K in either branch of K extends keyof MR ? MR[K] : M[K] is some specific subtype of K (thanks to distribution), while the K in M[K] is just the input K. So if, for example, your input K was "ok" | "not ok", the K in each branch could be one of "ok" or "not ok", and the union of both is not assignable to each individually (it contains the other option, ofc).

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Aug 31, 2020
sandersn added a commit that referenced this issue Mar 11, 2021
…ional when assignable to both branches (#30639)

* Finally add that missing relationship allowing a type to be assignable to both branches of a conditional

* Explicitly write out Ternary.Maybe

* Add slightly modified example from #25413

* fix sick sentence

* Loosen check to skip false branch constraint check to consider `infer` parameters as always satisfied in the extends clause

* Simplify things a bit, only instantiate once

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Domain: Conditional Types The issue relates to conditional types Fix Available A PR has been opened for this issue Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
6 participants