You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can't exclude undefined from indexed Partial with simple guard #31908
functiongetValue<T,KextendskeyofT>(partial: Partial<T>,key: K): T[K]|null{constvalue=partial[key]if(value!==undefined){returnvalue// error: TypeScript thinks this can be undefined...}returnnull}
Expected behavior:
Inside the if statement, value should have type T[K]. Especially since partial[key] is assignable to T[K] | undefined, and in fact TypeScript reports the type in the error message as T[K] | undefined, inside the if statement! It's like it just doesn't simplify Partial<T>[K] until it tries returning it.
Here is the workaround, which doesn't involve any casting, just hints:
EDIT: Nope, sorry. Misread the example. I'm guessing that maybe the narrowing code doesn't do the same level of probing to the expression type compared to when types are related. Is this a perf thing or just a missing feature?
That's right, it's not that TypeScript doesn't narrow the type of the expression partial[key], it doesn't narrow the type of value (which = partial[key]) either.
TypeScript doesn't know that partial[key] is equivalent to T[K] | undefined, only that it's assignable to that type. Knowing that it's equivalent basically requires higher-order understanding of what Partial<T>does by working backwards through the mapped type and reasoning about what kind of values come out the other side. If we had #22348 (which is a scary bag of worms), this would probably end up working out OK.
Anyway with all that said, having the only inference position for T be a Partial<T> is going to be a serious damper on your inference. It'd be better to write the signature as
typeMyEvents='start'|'stop'|'suspend';typeEventCounts={[keyinMyEvents]?: number};consteventCounts: EventCounts={start: 0};functionfoo<TextendsMyEvents>(event: T){// not working - undefined is not assignable to numberconstcount: number=eventCounts[event]||0;// workingconstfoo: number|undefined=eventCounts[event];constcount2: number=foo||0;// other workaroundsconstcount3: number=(eventCounts[event]||0)!;constcount4: number=eventCounts[event]asnumber|undefined||0;}
I didn't really understand the reasoning about
requires higher-order understanding of what Partial does
In my case this is more direct (optional index signature). Does this hit the same design limitation?
(I know that in this example the generic is only constructed, i could just use "event: MyEvents" non generic, that's also working, but not the point :)).
In the following example, it seems like the compiler does know that Dict<K>[K] is equivalent to number | undefined, not just assignable, because the compiler itself generates the type number | undefined for value, just not in time to narrow it:
typeDict<Kextendsstring>={[keyinK]?: number}functiongetValue<Kextendsstring>(dict: Dict<K>,key: K): number{constvalue=dict[key]// value: Dict<K>[K]if(value){// or: if (value !== undefined)// Error: Type 'number | undefined' is not assignable to type 'number'.returnvalue}thrownewError()}
Ryan Cavanaugh (@RyanCavanaugh) Thanks for the input. PR #22348 does seem related, and in particular issue #22137 that it aims to fix. The function in my example is artificial, like the function in this example; it's a reduced case, not necessarily a good helper function I would write.
So my understanding is there is one solution, which is scary, which is to make type guards basically introduce an Exclude/Extract (though I could have sworn this happens in some cases).
What about making it so that indexing a mapped type checks if you are indexing with the key type, or something assignable to the key type, something like that? Something like this must be happening for non-generics, because indexing a Dict<string> by string works. In other words, would it be incorrect to eagerly simplify Dict<K>[K] to number | undefined?
functiongetValue<T,KextendskeyofT>(partial: Partial<T>,key: K): T[K]|null{constvalue=partial[key]if(value!==undefined){returnvalue// error: TypeScript thinks this can be undefined...}returnnull}
Basically the type of value is Partial<T>[K]. Note, it is not Partial<T>[K] | undefined. So narrowing down, removing undefined, does not change anything on the type. And since structure of Partial<T> is not known yet, the type can at this point only be described in the generic way Partial<T>[K].
What if focus is not on general type narrowing, but only related to indexed types and ?-indices, which should be equivalent for any indexed type (correct me if I am wrong). So you can narrow down undefined !== t[p] based on indexed type and index is ?. Does it change the situation/simplify it?
I have an issue which probably has the same cause:
interface Todo {
title: string,
}
function foo(x: {[name: string]: string}) {
}
function bar(todo: Partial<Todo>) {
foo(todo);
}
When compiled with --strictNullChecks it fails to compile with error:
t.ts:9:9 - error TS2345: Argument of type 'Partial<Todo>' is not assignable to parameter of type '{ [name: string]: string; }'.
Property 'title' is incompatible with index signature.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
9 foo(todo);
~~~~
Found 1 error.
Stepan Koltsov (@stepancheg) Your type in foo says it has arbitrary properties that are all of the type string. But your Partial<Todo> type has a property that is string | undefined. You can't assign undefined to string.
Activity
jack-williams commentedon Jun 16, 2019
Duplicate of #11483.EDIT: Nope, sorry. Misread the example. I'm guessing that maybe the narrowing code doesn't do the same level of probing to the expression type compared to when types are related. Is this a perf thing or just a missing feature?
dgreensp commentedon Jun 19, 2019
That's right, it's not that TypeScript doesn't narrow the type of the expression
partial[key], it doesn't narrow the type ofvalue(which= partial[key]) either.RyanCavanaugh commentedon Jun 26, 2019
TypeScript doesn't know that
partial[key]is equivalent toT[K] | undefined, only that it's assignable to that type. Knowing that it's equivalent basically requires higher-order understanding of whatPartial<T>does by working backwards through the mapped type and reasoning about what kind of values come out the other side. If we had #22348 (which is a scary bag of worms), this would probably end up working out OK.Anyway with all that said, having the only inference position for
Tbe aPartial<T>is going to be a serious damper on your inference. It'd be better to write the signature asThomasKruegl commentedon Jun 27, 2019
I might have a similar issue:
I didn't really understand the reasoning about
In my case this is more direct (optional index signature). Does this hit the same design limitation?
(I know that in this example the generic is only constructed, i could just use "event: MyEvents" non generic, that's also working, but not the point :)).
Example on Playground:
https://www.typescriptlang.org/play/?target=6#code/C4TwDgpgBAsiCiA3CA7YBnKBeKBydwAhgE7C5QA+eBA9mOVfgK7qQoAmuA3AFA+iQoSVMADCNJmkw4A3lADaAawggoASxSwEyKQF0A-AC4oKJgFsARhGIBfXjwDGNFASgQdYiVOPC04yRjYUHIEJMDGAAxQdnwAZpIOwGrOULE0NAA8ACpuAB7AqOyYcL4YAHwAFO4ixlkAlME8UM1QTS0A9O0mNMBQAO40xIoaAObNALRQkuwQsRoQ7OqYKD1QhOjoaiMohBYANtDANCbmVsRtzU4uvU4BxqaW1kHVfl4Y8i-Aus0UVBH2LSgnX6g2GKBGFygV1caRo91OTyo01m80WOE+-ikHw8ul4gOhNzeACZ4Y9iEFYZQ-vZIcCegALJ4DIYkLxFSEEqFvADMpLOQSqHkx70+31+UAidQAhHiWpzbmgACx8p7ooVvdDYkTfdYnMmUKYcFEoBZUiX2GxAA
dgreensp commentedon Jun 27, 2019
In the following example, it seems like the compiler does know that
Dict<K>[K]is equivalent tonumber | undefined, not just assignable, because the compiler itself generates the typenumber | undefinedforvalue, just not in time to narrow it:Ryan Cavanaugh (@RyanCavanaugh) Thanks for the input. PR #22348 does seem related, and in particular issue #22137 that it aims to fix. The function in my example is artificial, like the function in this example; it's a reduced case, not necessarily a good helper function I would write.
So my understanding is there is one solution, which is scary, which is to make type guards basically introduce an
Exclude/Extract(though I could have sworn this happens in some cases).What about making it so that indexing a mapped type checks if you are indexing with the key type, or something assignable to the key type, something like that? Something like this must be happening for non-generics, because indexing a
Dict<string>by string works. In other words, would it be incorrect to eagerly simplifyDict<K>[K]tonumber | undefined?akomm commentedon Jul 11, 2019
David Greenspan (@dgreensp)
I have encountered the same issue. Given the example you provided:
Basically the type of
valueisPartial<T>[K]. Note, it is notPartial<T>[K] | undefined. So narrowing down, removingundefined, does not change anything on the type. And since structure ofPartial<T>is not known yet, the type can at this point only be described in the generic wayPartial<T>[K].Ryan Cavanaugh (@RyanCavanaugh)
Given this:
What if focus is not on general type narrowing, but only related to
indexed typesand?-indices, which should be equivalent forany indexed type(correct me if I am wrong). So you can narrow downundefined !== t[p]based onindexed typeand index is?. Does it change the situation/simplify it?stepancheg commentedon Jun 3, 2020
I have an issue which probably has the same cause:
When compiled with
--strictNullChecksit fails to compile with error:MartinJohns commentedon Jun 3, 2020
Stepan Koltsov (@stepancheg) Your type in
foosays it has arbitrary properties that are all of the typestring. But yourPartial<Todo>type has a property that isstring | undefined. You can't assignundefinedtostring.stepancheg commentedon Jun 3, 2020
Martin Johns (@MartinJohns) then I don't understand how
Partialworks.So my problem can be written like this then:
Thank you! And sorry for distraction.