TypeScript Version: 3.1.0-dev.201xxxxx
Search Terms:
Code
In a js file:
// @ts-check
const x = {
y: 12
};
/** @type {keyof typeof x} */
const q = "z"; // Assignment is allowed, but is not what was desired!
Expected behavior:
keyof
returns the keys the object actually has or is declared with.
Actual behavior:
keyof
returns string | number
, thanks the the any-index signature shoved into every object literal expression's type in JS.
This came up on DefinitelyTyped
- strongly typing react's propTypes
is virtually impossible because all of the key filtering and mapping operations (eg, Exclude
) just _don't work_ on any JS object literals (unless all of your keys are unique symbols (nobody would do this, I don't think), since those aren't mangled by subtype reduction with the index signature).
cc @mhegazy This is a blocker in rolling out the better React inferences to our JS users.
To be clear, this isn't blocking defaultProps
, is it?
had an offline discussion about this, we should do the right thing and mark the types of JS object literals with a flag to allow for looser property access checking and assignablity checking, instead of manifesting an index signature.
To be clear, this isn't blocking defaultProps, is it?
It is blocking it in JS, yes.
This is not really specific to JavaScript object literals. That's just a small part of the bigger problem. It's a limitation of keyof
with index signatures. I had this problem a few days ago when I wanted to extract all known keys of ts.CompilerOptions
.
I came up with a little helper type that extracts all known keys:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends {[_ in keyof T]: infer U} ? U : never;
This way you don't need to change any type inference.
Using inference to rebuild the keys sans index signature. Nifty, though very difficult to look at. It can work, though I think I'd have trouble explaining it to anyone.
Still - potentially needing to perform such contortions on any type from a js object literal expression seems a bit excessive - it's certainly not what came to my mind as a solution.
@ajafff Very clever; good work.
@ajafff Could you please help me understand the type? How exactly is it different from keyof
? Is it safe to assume that it returns exactly the same thing as keyof
but without index signatures? So is KnownTypes<T> & keyof T
the same as KnownTypes<T>
?
@m93a
The mapped type is used to create an object type where each property has a value that is exactly the key name. Index signatures have the value never
. If/when TypeScript adds more index signatures (symbols for example) they need to be handled there as well.
Afterwards that mapped type is used in a conditional type which is used to infer the union of value types. Since index signatures have type never
and never
is assignable to anything, they are removed from the union. The result is the list of all known property names.
This should be exactly what keyof T
would return if there was no index signature on T
.
I just noticed a little bug in my KnownTypes
definition above. It yields {}
when T
has no keys (e.g. empty object or primitives).
Here's the fixed version:
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends {[_ in keyof T]: infer U} ? ({} extends U ? never : U) : never;
Most helpful comment
This is not really specific to JavaScript object literals. That's just a small part of the bigger problem. It's a limitation of
keyof
with index signatures. I had this problem a few days ago when I wanted to extract all known keys ofts.CompilerOptions
.I came up with a little helper type that extracts all known keys:
This way you don't need to change any type inference.