Typescript: Compile Error when using generic type with `[K in keyof T]?: XXX`

Created on 23 Apr 2019  路  7Comments  路  Source: microsoft/TypeScript


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

Bug Mapped Types

Most helpful comment

@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.

All 7 comments

T could also be a child of Entity with extra fields. These fields are now not included.

T could also be a child of Entity with 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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MartynasZilinskas picture MartynasZilinskas  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

wmaurer picture wmaurer  路  3Comments

seanzer picture seanzer  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments