-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Bug with mapped types and indexing #17238
Comments
It looks like you are experiencing the effects of #12351. The strange behaviour is happening because some of the usecases are expanded, while others are handled on the logical level, without carrying out the heavy lifting. |
|
Because of #12351
type E<S extends "a" | "b"> = {[key in A[S]]: true}[S] is converted to just type E<S extends "a" | "b"> = true by applying the above rule, without doing any actual "mapping" or "selection". PS: You can see the rule is in action, because "extra" keys are silently ignored: type E<S extends "a" | "b" | "extra"> = {[key in A[S]]: true}[S] // no error |
As strange as it may seem type C<S extends "a" | "b"> = {[key in A[S]]: true} // C = {} is actually correct. The reason is that
TypeScript plays it safe here and chooses declare const x: C<"a"|"b">; // { a: true } Note that the same applies for |
I disagree that choosing Also, it should never collapse a parametrized type, as you can just wait until it's used and check what the correct value is. A type constraint is just that, a constraint. It should pick the widest type possible for constraints, so in this case, it should pick In any case, this behavior is clearly buggy and needs to be fixed. |
I disagree. The compiler should (and does) choose the most neutral type. In this case, because at use site the |
So how would you expect someone to write a parameter constrained by string keys? Would that have different behavior from |
I'm not sure I understand you. The parameter is still constrained to the said set of keys, it's just that intelli-sense chooses one of the valid options which is the empty set. In subsequent uses of the alias, both value and type level, it works correctly. I'm not convinced that choosing the fullest set in intelli-sense would be better. |
Ah, you're just talking about intellisense? I really don't agree. Intellisense should always display the widest possible type. Otherwise you run into cases where intellisense tells you a certain type is That doesn't change the fact that there's a clear bug going on here though, that needs to be fixed. |
Which one do you feel is a bug? From the comments I'm left with the impression that type C<S extends "a" | "b"> = {[key in A[S]]: true} // C = {} and type E<S extends "a" | "b"> = {[key in A[S]]: true}[S] // = true are the ones you feel are odd, but I think I've explained them in #17238 (comment) and #17238 (comment). Edit: I've updated wording and gave more context. |
I think the example above provides ample amount of bugs, but the main one is this: type B<S extends "a" | "b"> = {[key in "a"]: true} // B = { a: true }
type C<S extends "a" | "b"> = {[key in A[S]]: true}
type F<S extends "a" | "b"> = C<S>[S] // = true
let a1: F<"a"> // a: true
let b1: F<"b"> // b: true How can b1 ever be |
You use double indexing. type StringA = "a"
type A = {
a: StringA
b: StringA
} |
OK, I see. Yes, it is a bug. The problem is what happens here:
|
I'll add one more example to make it clear: type F<S extends "a" | "b"> = C<S>[S] // = true
let a1: F<"a"> // a: true
let b1: F<"b"> // b: true
type G<S extends "a" | "b"> = (
C<S> &
{[key: string]: false}
)[S]
let a2: G<"a"> // a2: true
let b2: H<"b"> // b2: false Adding a bogus indexer to F makes it give different results. I'll edit G above with this version to make it clearer. |
I had a small typo in the above example, fixed it, should say And you are correct that if E is correctly discarded as an error, the rest of the bugs resulting from that behavior wouldn't matter. I still wanted to document all the behavior as I suspect there is more than one bug at play here. |
Minimal repro: type AB = {
a: 'a'
b: 'a'
}
type T1<K extends keyof AB> = { [key in AB[K]]: true }
type T2<K extends 'a'|'b'> = T1<K>[K] // BUG 1: should be error for K = 'b'
type R = AB[keyof AB]; // "a"
type T3 = { [key in R]: true }
type T4<K extends 'a'|'b'> = T3[K] // error as expected
// BUG 2: 'extra' not checked in AB[S]
type T5<S extends 'a'|'b'|'extra'> = {[key in AB[S]]: true}[S] |
|
@SimonMeskens |
@SimonMeskens Could you include type AB = {
a: 'a'
b: 'a'
}
type T1<K extends keyof AB> = { [key in AB[K]]: true }
type T2<K extends keyof AB> = T1<K>[K] // BUG: should be an error for K = 'b' in the main post as this is the minimal repro of the problem? I think it will help for clarity. |
Will do I was going to add your previous one with the second bug too. Or should we open a new issue for that? Can you recommend a better title for the bug report too? |
The second bug seems to be an incarnation of the same core bug as |
Thanks for the help, I didn't know how to get it down to a minimal repro. |
Out of curiosity, does it indeed relate to #15756 as the OP assumes or is it s disparate issue? Seems related, because it seems like the cause is the simplistic substitution of the type variable (if I understand the thread correctly), but I'm not quite sure. |
@SimonMeskens I've put a PR #17336 fixing the issues found in this thread. It turned out that |
Oh, that's great man! Thanks! |
Minimal repro:
I came across this in production code and made a contrived example to show the issue. Might be related to #15756.
Note that there might be two bugs here in play, one with mapped types and one with lookup types on mapped types. Once I start pushing the types a little, I come across a wild range of unexpected behavior.
Pay attention to the last type 'G', where the compiler is suddenly correct again, which is probably the oddest one of all.
This has to be the weirdest bug I've run across
The text was updated successfully, but these errors were encountered: