Issue
enum X {
A = 'x',
B = 'y'
}
type A<T> = {
prop1: T
prop2: ?
}
let r:A<X> = {
prop1: X.A,
prop2: X
}
Which type must A.prop2 have to make X and only X assignable to it?
(and by only X, I mean without using any and other "tricks")
Solution
This is not possible as asked. When you declare an enum like
enum X {
A = 'x',
B = 'y'
}
you are declaring a value named X
(the enum object, with keys A
and B
) and a type named X
(the union of types of the enum values, similar to "x" | "y"
). Types and values reside in different namespaces, so there's no "connection" between them in the language.
Generic types like
type A<T> = {
prop1: T
prop2: ⋯
}
take type arguments, not value arguments. So when you write
type B = A<X>;
you are passing the type named X
, similar to "x" | "y"
into A
. It does not contain the information needed to require that prop2
should be the type of the value named X
. After all, the type "x" | "y"
doesn't know anything about the keys "A"
and "B"
.
If you want to keep it so that you pass in X
, then the closest you can get is to say that prop2
should be some object type with properties of type X
, but with any keys. That gives you this:
type A<T> = {
prop1: T
prop2: Record<string, T>
}
let r: A<X> = {
prop1: X.A,
prop2: X // okay
}
r.prop2 = { whoopsieDaisy: X.A } // also okay
Obviously that doesn't require the correct keys.
If you refactor so that you pass in typeof X
instead of X
, then you can get closer:
type A<T extends object> = {
prop1: T[keyof T]
prop2: T
}
Now prop1
is of type T[keyof T]
, meaning it's the union of property types of T
. If T
is typeof X
then T[keyof T]
will be X
(NOTE WELL: this is specifically for enums like X
and not for types and values with the same name in general. Again, types and values live in different namespaces and there's no relationship between them you can rely on. You could write const Y = 0;
and type Y = 1;
, and then if T
is typeof Y
, then T[keyof T]
will absolutely not be Y
). This forces you to now supply the right keys:
let r: A<typeof X> = {
prop1: X.A,
prop2: X
}
r.prop2 = { whoopsieDaisy: X.A } // error
but it's still not exactly what you want; there's no way to require a particular value be passed. You can only require that the value passed is of the expected type or a subtype of that type. The following value, for example:
const myX = { A: X.A, B: X.B, C: 123 } as const;
has a type which is assignable to typeof X
. So nothing prevents this:
r.prop2 = myX; // okay
If it's important for you to prevent that, you'd need to start getting even more intense about your efforts, and it would still be something a determined user could work around. I'm not going to digress further here; the point is that trying to require a particular value is antithetical to the TypeScript type system and so it is almost certainly more trouble than it's worth.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.