Reputation: 615
I can't figure out how to infer the type of a generic property based on the generic type of the object it's on. In the following example, how can I say that Something.aProp
needs to match the type of Something's U.obj.prop
?
interface Prop {
a: number;
}
interface FancyProp extends Prop {
b: number;
}
interface Obj<T extends Prop> {
prop: T;
}
interface FancyObj extends Obj<FancyProp> {}
interface Parent<T extends Obj<any>> { // <-- the <any> here seems wrong too
obj: T;
}
interface FancyParent extends Parent<FancyObj> {
fancy: number;
}
class Something<U extends Parent<any>> {
aProp: typeof U.obj.prop;
}
I.e. Something<Parent>.aProp
should be of type Prop
, and Something<FancyParent>.aProp
is of type FancyProp
?
Upvotes: 1
Views: 2124
Reputation: 327624
For your main question, the way to look up the type of a property value given an object type T
and a key type K
is to use lookup types, a.k.a., indexed access types, via the bracket syntax T[K]
. So if you want to look up the type of the "prop"
-keyed property of the "obj"
-keyed property of an object of type U
, you would write that type as U["obj"]["prop"]
.
Note that dot syntax doesn't work for types, even if the key types are string literals. It would be nice if U.obj.prop
were a synonym for U["obj"]["prop"]
in the type system, but unfortunately that syntax would collide with namespaces, since there could be a namespace named U
, with a subnamespace named obj
, with an exported type named prop
, and then U.obj.prop
would refer to that type.
For your comments about any
, it's not really wrong to use X extends Y<any>
when Y<T>
's type parameter T
has a generic constraint, but it might be a bit less type safe than you can get. If the type Y<T>
is related to T
in a covariant way, then you can use the generic constraint instead of any
.
That would mean, for example, Parent<T extends Obj<any>>
could be replaced with Parent<T extends Obj<Prop>>
, and U extends Parent<any>
could be replaced with U extends Parent<Obj<Prop>>
.
Those changes give you code like this:
interface Parent<T extends Obj<Prop>> {
obj: T;
}
class Something<U extends Parent<Obj<Prop>>> {
aProp: U['obj']['prop'];
constructor(u: U) {
this.aProp = u.obj.prop;
}
}
I also added a constructor to Something
because class properties should be initialized and I wanted to show that aProp
could be assigned with a value from u.obj.pop
when u
is a U
.
And this should work as you expect:
interface PlainObj extends Obj<Prop> { }
interface PlainParent extends Parent<PlainObj> { }
new Something<PlainParent>({ obj: { prop: { a: 1 } } }).aProp.a; // number
interface FancyObj extends Obj<FancyProp> { }
interface FancyParent extends Parent<FancyObj> {
fancy: number;
}
new Something<FancyParent>({ obj: { prop: { a: 1, b: 2 } }, fancy: 3 }).aProp.b; // number
Okay, hope that helps; good luck!
Upvotes: 1