TypeScript Version: 3.4.4
Search Terms:
keyof index-properties generic-type
Code
type Numeric<T> = {
[K in keyof T]?: number
}
class Entity {
aNumber: number;
}
const fn = <T extends Entity>() => {
const n: Numeric<T> = {
// . ^
// . Type '{ aNumber: number; }' is not assignable to type 'Numeric<T>'
aNumber: 1
};
};
Expected behavior:
Should not report compile error since in the type constraint of function fn was <T extends Entity> which means T should have aNumber: number and { aNumber: 1 } should be satisfied.
Actual behavior:
Compile error said Type '{ aNumber: number; }' is not assignable to type 'Numeric<T>'.
Playground Link:
https://www.typescriptlang.org/play/index.html#src=type%20Numeric%3CT%3E%20%3D%20%7B%0D%0A%20%20%20%20%5BK%20in%20keyof%20T%5D%3F%3A%20number%0D%0A%7D%0D%0A%0D%0Aclass%20Entity%20%7B%0D%0A%20%20%20%20aNumber%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Aconst%20fn%20%3D%20%3CT%20extends%20Entity%3E()%20%3D%3E%20%7B%0D%0A%20%20%20%20const%20n%3A%20Numeric%3CT%3E%20%3D%20%7B%0D%0A%2F%2F%20.%20%20%20%20%20%20%5E%0D%0A%2F%2F%20.%20%20%20%20%20%20Type%20'%7B%20aNumber%3A%20number%3B%20%7D'%20is%20not%20assignable%20to%20type%20'Numeric%3CT%3E'%0D%0A%20%20%20%20%20%20%20%20aNumber%3A%201%0D%0A%20%20%20%20%7D%3B%0D%0A%7D%3B
Related Issues:
https://stackoverflow.com/questions/55804034/keyof-reported-compile-error-when-using-generic-type
T could also be a child of Entity with extra fields. These fields are now not included.
Tcould also be a child ofEntitywith extra fields. These fields are now not included.
But the properties defined in Numeric<T> are all optional. So I think even though T could be any child of Entity with more properties, they are optional in Numeric<T> which is OK.
I can see why this possibly should work: there is special handling for assigning empty object types to optional generic mapped types. This works:
const fn = <T extends Entity>() => {
const n: Numeric<T> = {};
n.aNumber = 1;
};
Though IMO there is always a red flag when assigning something concrete to something generic, in particular regarding future compatibility with homomorphic mapped types and new modifiers.
EDIT: Never mind! 馃槄
Looks like a duplicate of #13442 with the error catching such (rare and possibly not important) issues as this:
class SevenEntity extends Entity {
aNumber: 7; // narrowed property type, acceptable
}
fn<SevenEntity>();
Inside fn, the n constant is annotated as type { aNumber?: 7 } but is not one of those. 馃挜
@jcalz make sense if the case is:
const fn = <T extends Entity>() => {
// let's pretend T is SevenEntity
// then error report is justified, understood
const n: T = { aNumber: 1 };
};
However, we're talking about const n: Numeric<T>, and in this case, Numeric only cares about the key, not value. Yes, I can understand TS guards user super carefully value-wise, but key-wise this behavior looks buggy to me.
Yeah, you are right. It's not a duplicate of #13442 because only the keys and not the values are involved in the mapped type. I retract my statement!
@jack-williams Thanks for your workaround. But I may not be able to force use to specify n to an empty object and assign properties.
Just an update. If I defined type Numeric<T> = (keyof T)[];. Then in the following function I can use const n: Numeric<T> = [ "aNumber" ]; without any error. I my understanding (keyof T)[] should have the same restriction as { [K in keyof T]?: number; } but one is OK the other is not.
Most helpful comment
@jcalz make sense if the case is:
However, we're talking about
const n: Numeric<T>, and in this case,Numericonly cares about the key, not value. Yes, I can understand TS guards user super carefully value-wise, but key-wise this behavior looks buggy to me.