Regarding TypeScript_Playground, I'm struggling with fixing type inference. I used a generic type to enforce type safety—specifically, if templateType is CIRCLE, I expect a type error when adding incompatible binding keys like RECTANGLE. However, that's not happening right now.
I suspect the issue might be with ShapeTemplate[]. When I change it to this way, I can no longer add multiple template types. Does anyone have expertise in type inference to help with this?
The issue is with your definition of the ShapeTemplate
type. The current definition works well when the type generic is an "exact" type with no unions.
So if you had a function like the following:
const makeShape = <T extends ShapeTypes>(
templateType: T,
bindings: ShapeBindings[T],
) => {...};
Then a call like the following:
makeShape(ShapeTypes.CIRCLE, {
[RectangleShapeBindingKey.RECTANGLE_PROPERTY_1]: true,
});
will return throw an error:
Object literal may only specify known properties, and '[RectangleShapeBindingKey.RECTANGLE_PROPERTY_1]' does not exist in type 'Partial<{ CIRCLE_PROPERTY_1: string | undefined; }
The problem you encounter arises with the following defintion of ShapeBase
export interface ShapeBase {
templates: ShapeTemplate<ShapeTypes>[];
options?: any[];
}
If you expand the types, they look something like
type T1 = {
templateType: ShapeTypes;
bindings?: Partial<{
CIRCLE_PROPERTY_1: string | undefined;
}> | Partial<{
RECTANGLE_PROPERTY_1: boolean | undefined;
}> | Partial<...> | undefined;
info?: string | undefined;
}
You can see that there is no relationship between the types expected on templateType
and bindings
fields. This happens because once you pass the generic variable ShapeTypes
to ShapeTemplate
it simply gets passed into any place requiring it, without any particular logic binding fields to each other.
Essentially, you are creating a type that says: I want templateType
to satisfy ShapeTypes
and bindings
to satisfy ShapeBindings[T]
; but in this case T
is simply ShapeTypes
so the bindings
field will accept any value with a type that satisfies ShapeBindings[ShapeTypes]
. And that is just going to be the union of all the values defined in the type.
So basically what you want to do is change from something that abstractly looks like:
type T<U> = {a:U1|U2|U3, b:F(U1)|F(U2)|F(U3)}
to a type like
type T<U> = {a:U1, b:F(U1)} | {a:U2, b:F(U2)} | {a:U3, b:F(U3)}
So to achieve this, you want to create a mapped type that represents the union of all possible combinations and index it on its own keys, so something like this:
type SomeShapeBase = {
[K in keyof typeof ShapeTypes]: {
templateType: (typeof ShapeTypes)[K];
bindings: ShapeBindings[(typeof ShapeTypes)[K]];
};
}[keyof typeof ShapeTypes];