I stumbled accross the following situation for the standard utility type Required
:
type A = {x?: number;}
type B1 = Required<A>['x']; // number, OK
type B2<T extends A> = Required<T>['x']; // number, OK
class C1 {
public f(x: Required<A>['x']) { // x is number, OK
const y = x 1; // OK
}
}
class C2<T extends A> {
public f(x: Required<T>['x']) { // x is number | undefined, NOT OK
const y = x 1; // ERROR
}
}
I cannot imagine that this is expected behavior. Why is the C2
-case different from the C1
-case? I thought at first it may have something to do with generics in general, but only class generics seem to be affected since the B2
-case works.
Can I do anything - other than explicitly saying something like x: Exclude<Required<T>['x'], undefined>
- to make x
(and potentially other properties of A
) really required (i.e. not undefined)?
CodePudding user response:
So, the answer is that T
is not equal with A
. It means that T
can be another type, that can do something and implements keys from A
if you want them to be equal you need to do this.
class C2<T extends Required<A>> {
public f(x: Required<T>["x"]) {
// x is number, OK
const y = x 1; // OK
}
}
Here, we’ll create an interface that has a single .length property and then we’ll use this interface and the extends keyword to denote our constraint:
UPD:
Required
makes from {x?: number} -> {x: number}
.
But if call
Required<{x: number | undefined}>
The x
still undefined.
When you pass T
to generic then field?: someType
converts to field: someType | undefined
.
In your case you should get rid of undefined twice, the ?
and undefined
type as well.
type FullRequired<T extends object> = Required<{
[K in keyof T]: Exclude<T[K], undefined>;
}>;
class C2<T extends A> {
public f(x: FullRequired<T>["x"]) {
// x is number OK
const y = x 1; // OK
}
}