Skip to content

Too-permissive assignability with complex discriminated unions #34751

Description

@Harpush

I was trying to add type safety to some model i have and it seems to sometimes work in a weird way.

Code

exportenumFilterType{String='string',Number='number',Boolean='boolean'}exportinterfaceFilterTypeToLiteralTypeMap{[FilterType.String]: string;[FilterType.Number]: number;[FilterType.Boolean]: boolean;}exporttypeAllowedFilterTypeLiteralTypes=FilterTypeToLiteralTypeMap[FilterType];exporttypeValues<T>=T[keyofT];exporttypeFilterTypeFromLiteralType<TextendsAllowedFilterTypeLiteralTypes>=Exclude<Values<{[keyinFilterType]: FilterTypeToLiteralTypeMap[key]extendsT
? key
: never;}>,never>;exportinterfaceFilterable<TextendsAllowedFilterTypeLiteralTypes>{isFilterable: true;filterType: FilterTypeFromLiteralType<T>;}exportinterfaceNotFilterable{isFilterable: false;}exporttypeFilterableTrait<TextendsAllowedFilterTypeLiteralTypes>=|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 correctlyexportconsta: FilterableTrait<string>={isFilterable: true,filterType: FilterType.Number};// Works correctlyexportconstb: FilterableTrait<string>={isFilterable: true,filterType: FilterType.String};exportconstc: FilterableTrait<string>={isFilterable: false,filterType: FilterType.String// Errors correctly};// Errors correctlyexportconstd: FilterableTrait<string>|FilterableTrait<number>={isFilterable: true,filterType: FilterType.Boolean};// Works correctlyexportconste: FilterableTrait<string>|FilterableTrait<number>={isFilterable: true,filterType: FilterType.Number};

The problem starts when i do this

exportconstf: 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:

  1. If i change Filterable to be filterType: FilterTypeFromLiteralType<string> instead of filterType: FilterTypeFromLiteralType<T> it works... it doesnt work just when i use the generic type.
  2. I know i can use
exportconstg: FilterableTrait<string|number>={isFilterable: false,filterType: FilterType.Number};

and it will work but my actual use case requires me to use something like

exportconsth:
|({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#

Metadata

Metadata

Labels

Design LimitationConstraints of the existing architecture prevent this from being fixed

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions