TypeScript 5.4 bêta introduit le type utilitaire NoInfer

Par:
fredericmazue

ven, 02/02/2024 - 08:00

Microsoft a annoncé la disponibilité de son langage TypeScript en version 5.4 bêta. Il s'agit d'une version riche, qui introduit notamment un nouveau type utilitaire : NoInfer. Voyons de quoi il s'agit.

Lors de l'appel de fonctions génériques, TypeScript est capable de déduire le type des arguments à partir de tout ce que vous transmettez. Ce mécanisme s'appelle l'inférence de type. Il évite au programmeur de spécifier expliciement un type de données. Par exemple :

function doSomething<T>(arg: T) {
    // ...
}

// On peut déclarer expliciement que T doit être de type 'string'.
doSomething<string>("hello!");

// Ou simplement laisser le  type of 'T' être inferré.
doSomething("hello!");

Un défi, cependant, est qu’il n’est pas toujours clair de déterminer quel est le « meilleur » type à déduire. Cela peut conduire TypeScript à rejeter des appels valides, à accepter des appels douteux ou simplement à signaler des messages d'erreur pires lorsqu'il détecte un bug.

Par exemple, imaginons une fonction createStreetLight qui prend une liste de noms de couleurs, ainsi qu'une couleur par défaut facultative.

function createStreetLight<C extends string>(colors: C[], defaultColor?: C) {
    // ...
}

createStreetLight(["red", "yellow", "green"], "red");

Que se passe-t-il lorsque nous transmettons un élément defaultColor qui ne figurait pas dans le tableau colors d'origine ?

// Oups! Indésirable, mais légal!
createStreetLight(["red", "yellow", "green"], "blue");

Dans cet appel, l'inférence de type a décidé qu'un type "blue" était tout aussi valide que "red" ou "yellow" ou "green". Ainsi, au lieu de rejeter l'appel, TypeScript déduit le type de C comme étant  "red" | "yellow" | "green" | "blue". 

Une façon actuelle de gérer ce problème consiste à ajouter un paramètre de type distinct délimité par le paramètre de type existant.

function createStreetLight<C extends string, D extends C>(colors: C[], defaultColor?: D) {
}

createStreetLight(["red", "yellow", "green"], "blue");

// erreur!
// L'argument de type '"blue"' n'est pas assignable au paramètre de type '"red" | "yellow" | "green" | undefined'.

Cela fonctionne, mais c'est un maladroit car D ne sera probablement utilisé nulle part ailleurs.

C'est pourquoi TypeScript 5.4 introduit un nouveau  type d'utilitaire: NoInfer<T>. Entourer un type par NoInfer<...> donne un signal à TypeScript pour ne pas creuser en comparant les types internes pour trouver des candidats pour l'inférence de type.

En utilisant NoInfer, nous pouvons réécrire createStreetLight comme ceci :

function createStreetLight<C extends string>(colors: C[], defaultColor?: NoInfer<C>) {
    // ...
}

createStreetLight(["red", "yellow", "green"], "blue");

// erreur!
// L'argument de type '"blue"' n'est pas assignable au paramètre de type '"red" | "yellow" | "green" | undefined'.

Exclure le type de defaultColor de l'exploration pour l'inférence signifie que "blue" ne finit jamais comme candidat à l'inférence, et le vérificateur de type peut le rejeter.