TypeScript employs a variety of heuristic rules to determine whether a string or numeric literal like "a"
or 2
should be assigned a specific literal type such as "a"
or 2
, or if it should receive an equivalent widened type like string
or number
. These rules have been proven effective in numerous real-world coding scenarios, although they may not always align perfectly with everyone's expectations.
These rules were predominantly featured and explained in the microsoft/TypeScript#10676 resource. Specifically, "during type argument inference for a call expression, the inferred type for a type parameter T
is widened to its widened literal type when [...] T
has no constraint or its constraint does not encompass primitive or literal types". Thus, in the context of a generic function:
function createGenericCoordinates<T extends number | string>(
x: T, y: T
) { return { x, y }; }
the type parameter T
carries a constraint that includes the primitive types string
and number
; thus, the type argument for T
will not undergo widening.
To modify this behavior, one potential approach would involve eliminating the constraint from T
and structuring the remainder of your call signature to enforce the desired constraint. For example:
function createGenericCoordinates<T>(
x: T & (string | number), y: T & (string | number)) {
return { x, y };
}
By doing so, T
becomes unconstrained leading to widening upon inference of T
. When invoking createGenericCoordinates(x, y)
, the compiler infers T
from the type of x
, ensuring that both x
and y
are assignable to the intersection T & (string | number)
. Any inconsistency will result in an error:
createGenericCoordinates(true, false); // error!
// --------------------> ~~~~
// function createGenericCoordinates<boolean>(x: never, y: never);
createGenericCoordinates({}, {}); // error!
// --------------------> ~~
// function createGenericCoordinates<{}>(x: string | number, y: string | number);
The compiler rejects true
due to
boolean & (string | number)</code reducing to <code>never
, and
{}
being rejected because of
{} & (string | number)</code which reduces to <code>string | number
.
When calling the function with two strings or two numbers, there won't be any errors, and the lack of widening provides the intended outcomes:
createGenericCoordinates(1, 2);
// function createGenericCoordinates<number>(x: number, y: number);
createGenericCoordinates("a", "b");
// function createGenericCoordinates<string>(x: string, y: string);
Playground link to code