In the realm of TypeScript, the constraints imposed by generics act as an upper limit on a type rather than a lower one. For instance, with T extends string[]
, it mandates that T
must be a subtype or assignable to string[]
. What is truly desired is a lower boundary where string[]
can be assigned to T
instead. This indicates a need for a type that can accept a string array, not exclusively provide one. Although there exists a feature request at microsoft/TypeScript#14520 advocating for this change, it has not been incorporated into the language as of yet.
Essentially, what's required here is a slightly altered constraint. The objective is to define T
as an array type through T extends readonly unknown[]
, allowing the element type of that array to potentially be some form of a string. Rather than constraining the element type to solely a string from above or below, the aim is to establish an overlap between the type and string
. Therefore, if the elements in the array encompass strings, string unions like string | boolean
, specific strings like
"a" | "b" | "c"
, or even combinations like
"a" | number
, these should all be acceptable. Rejection would only occur if there is no intersection at all, such as with a type like
number | boolean
.
To express this constraint effectively, one can do so as follows:
function processArray<T extends readonly unknown[] &
(string & T[number] extends never ? never : unknown)
>(arr: T): void {
console.log(arr);
}
This self-referential constraint, also known as F-bounded quantification, requires that T
is assignable to readonly unknown[]
while simultaneously satisfying the condition specified by the intersection type representing an overlap - string & T[number]
. If this overlap is non-existent, leading to the impossible never
type, the conditional assessment yields the outcome of rejecting everything. Conversely, when an overlap exists, resulting in the accept-everything unknown
type, the evaluation is successful.
In summary, if the element type within T
can potentially include a string
type, the constraint evaluates to T extends readonly unknown[]
, hence accepting the array. However, failure is inevitable if the element type cannot incorporate a string, causing the constraint to evaluate to T extends never
.
An experiment can help demonstrate the functionality:
const sArr = ["a", "b", "c"];
const nArr = [1, 2, 3];
const bArr = [true, false];
const arr1 ...
processArray(arr3); // error
processArray(["a", "b"] as const); // okay
processArray(["a", 1] as const); // okay
processArray([1, 2] as const); // error
The test outcomes verify that arrays incapable of accommodating any type of string will indeed be rejected.
Access the Playground link to view the code snippet