function createGenericCoordinates<Type extends number | string>(
x: Type,
y: Type
) {
return { x, y };
}
const genericCoordinates = createGenericCoordinates(1, 2);
// Error (TS2322)
// Type 3 is not assignable to type 1 | 2
genericCoordinates.x = 3;
How can I change this function so that the return type of both x and y is either number or string instead of the values I passed as x and y as argument functions (1|2 in this example)? If I remove „extends number | string“ it works, but the the allowed values for Type are not limited to number or string. Tested with TS 5.1.6
TypeScript uses various heuristic rules to determine whether a string or numeric literal like
"a"or2should be given a literal type like"a"or2, or a corresponding widened type likestringornumber. These rules have been found to work well in a wide range of situations in real world code, but they don't always happen to agree with everyone's expectations.Many of the rules in question were implemented in and described in microsoft/TypeScript#10676. In particular, "during type argument inference for a call expression the type inferred for a type parameter
Tis widened to its widened literal type if [...]Thas no constraint or its constraint does not include primitive or literal types". So given the generic functionthe type parameter
Thas a constraint which includes the primitive typesstringandnumber, so when the type argument forTis inferred, it will not be widened.This suggests that one approach to change this is to remove the constraint from
Tand write the rest of your call signature so it enforces the intended constraint. Maybe like this:Now
Tis unconstrained soTwill be widened when inferred. When you callcreateGenericCoordinates(x, y), the compiler will inferTfrom the type ofx, but then check thatxis assignable to the intersectionT & (string | number), and the same check will happen fory. The only way that is possible is ifT extends string | number, so if eitherxoryare not compatible withstring | number, you'll get an error:Since
boolean & (string | number)reduces tonever, the compiler rejectstruebecause it's notnever. Since{} & (string | number)reduces tostring | number, he compiler rejects{}because it's notstring | number.If you call it with two strings or two numbers, there's no error, and the lack of widening gives you the results you wanted:
Playground link to code