Closed
Description
I was trying to add type safety to some model i have and it seems to sometimes work in a weird way.
Code
export enum FilterType {
String = 'string',
Number = 'number',
Boolean = 'boolean'
}
export interface FilterTypeToLiteralTypeMap {
[FilterType.String]: string;
[FilterType.Number]: number;
[FilterType.Boolean]: boolean;
}
export type AllowedFilterTypeLiteralTypes = FilterTypeToLiteralTypeMap[FilterType];
export type Values<T> = T[keyof T];
export type FilterTypeFromLiteralType<
T extends AllowedFilterTypeLiteralTypes
> = Exclude<
Values<
{
[key in FilterType]: FilterTypeToLiteralTypeMap[key] extends T
? key
: never;
}
>,
never
>;
export interface Filterable<T extends AllowedFilterTypeLiteralTypes> {
isFilterable: true;
filterType: FilterTypeFromLiteralType<T>;
}
export interface NotFilterable {
isFilterable: false;
}
export type FilterableTrait<T extends AllowedFilterTypeLiteralTypes> =
| Filterable<T>
| NotFilterable;
What i did here is created an enum and mapped each entry to a literal type. That way when using the FilterableTrait
interface it can be either Filterable
or NotFilterable
and if filterable the FilterType
is decided based on the given type.
Some usages that work
// Errors correctly
export const a: FilterableTrait<string> = {
isFilterable: true,
filterType: FilterType.Number
};
// Works correctly
export const b: FilterableTrait<string> = {
isFilterable: true,
filterType: FilterType.String
};
export const c: FilterableTrait<string> = {
isFilterable: false,
filterType: FilterType.String // Errors correctly
};
// Errors correctly
export const d: FilterableTrait<string> | FilterableTrait<number> = {
isFilterable: true,
filterType: FilterType.Boolean
};
// Works correctly
export const e: FilterableTrait<string> | FilterableTrait<number> = {
isFilterable: true,
filterType: FilterType.Number
};
The problem starts when i do this
export const f: FilterableTrait<string> | FilterableTrait<number> = {
isFilterable: false,
filterType: FilterType.Number // No error? Auto complete doesn't add this as an option but it compiles
};
A few notes here:
- If i change
Filterable
to befilterType: FilterTypeFromLiteralType<string>
instead offilterType: FilterTypeFromLiteralType<T>
it works... it doesnt work just when i use the generic type. - I know i can use
export const g: FilterableTrait<string | number> = {
isFilterable: false,
filterType: FilterType.Number
};
and it will work but my actual use case requires me to use something like
export const h:
| ({ type: '1' } & FilterableTrait<string>)
| ({ type: '2' } & FilterableTrait<number>) = {
type: '2',
isFilterable: true,
filterType: FilterType.Number
};
Which means i can't use the above
Expected behavior:
Compilation error
Actual behavior:
Compiles successfully
Playground Link: http://www.typescriptlang.org/play/?ts=3.7-Beta&ssl=31&ssc=3&pln=17&pc=1#