Closed
Description
Bug Report
π Search Terms
union literal generic method
π Version & Regression Information
All versions including night build 5.0.0-dev.20221118
β― Playground Link
Playground link with relevant code
π» Code
class Field<DataType> {
static readonly literalField: Field<'A'|'B'> = new Field<'A'|'B'>();
static readonly numberField: Field<1|2> = new Field<1|2>();
static readonly mixedField: Field<'A'|'B'|1> = new Field<'A'|'B'|1>();
}
class WrongFieldFilter {
filter<DataType>(field: Field<DataType>, value: DataType): void {
console.log(value);
}
}
class GoodFieldFilter<DataType> {
constructor(private field: Field<DataType>) {}
filter(value: DataType): void {
console.log(value);
}
}
// WrongFieldFilter -> this class has wrong behavior
const fieldFilter = new WrongFieldFilter();
fieldFilter.filter(Field.literalField, 'A'); // OK: 'A' is in 'A'|'B' type
fieldFilter.filter(Field.literalField, 'Z'); // !wrong behavior, should raise error: Argument of type 'Z' is not assignable to parameter of type '"A" | "B"'.(2345)
fieldFilter.filter(Field.literalField, 'Z' as const); // !wrong behavior, should raise error: Argument of type 'Z' is not assignable to parameter of type '"A" | "B"'.(2345)
fieldFilter.filter(Field.literalField, 22); // OK, reaises error: Argument of type '22' is not assignable to parameter of type '"A" | "B"'.(2345)
fieldFilter.filter(Field.numberField, 1); // OK: 1 is in 1|2 type
fieldFilter.filter(Field.numberField, 3); // !wrong behavior, should raise error: Argument of type '3' is not assignable to parameter of type '1 | 2'.(2345)
fieldFilter.filter(Field.numberField, 3 as const); // !wrong behavior, should raise error: Argument of type '3' is not assignable to parameter of type '1 | 2'.(2345)
fieldFilter.filter(Field.numberField, 'Z'); // OK, reaises error: Argument of type '"Z"' is not assignable to parameter of type '1 | 2'.(2345)
// if we use mixed union of number and string literals the behavior is correct!
fieldFilter.filter(Field.mixedField, 1); // OK: 1 is in 1|2 type
fieldFilter.filter(Field.mixedField, 3); // OK, reaises error: Argument of type '3' is not assignable to parameter of type '"A" | "B" | 1'.(2345)
fieldFilter.filter(Field.mixedField, 'A'); // OK: 'A' is in 'A'|'B' type
fieldFilter.filter(Field.mixedField, 'Z'); // OK, reaises error: Argument of type '"Z"' is not assignable to parameter of type '"A" | "B" | 1'.(2345)
// GoodFieldFilter -> this slightly different class is an example of good behavior
const fieldFilterLiteral = new GoodFieldFilter(Field.literalField); // we are providing a field in the constructor instead of the method call
fieldFilterLiteral.filter('A'); // OK: 'A' is in 'A'|'B' type
fieldFilterLiteral.filter('Z'); // OK, reaises error: Argument of type '"Z"' is not assignable to parameter of type '"A" | "B"'.(2345)
fieldFilterLiteral.filter(22); // OK, reaises error: Argument of type '22' is not assignable to parameter of type '"A" | "B"'.(2345)
const fieldFilterNumber = new GoodFieldFilter(Field.numberField); // we are providing a field in the constructor instead of the method call
fieldFilterNumber.filter(1); // OK: 1 is in 1|2 type
fieldFilterNumber.filter(3); // OK, reaises error: Argument of type '3' is not assignable to parameter of type '1 | 2'.(2345)
fieldFilterNumber.filter('Z'); // OK, reaises error: Argument of type '"Z"' is not assignable to parameter of type '1 | 2'.(2345)
π Actual behavior
When we provide as a generic method parameter a union of literals of the same type (eg. strings) TS doesn't validate parameter values against this union literals, but wider against the literal's type.
π Expected behavior
When provided a generic method parameter is a union of literals of the same type TS should validate this parameter against union literals and raise 2345 error when the passed parameter doesn't belong to this union.