Anders Hejlsberg le concepteur de TypeScript a annonc� il y a deux semaines que son �quipe travaillait � l'impl�mentation des unions de types.

L'union de type est un concept assez simple � comprendre mais �trangement peu r�pandu dans les langages typ�s. Voici la traduction de la sp�cification #805 :

L'op�rateur | pour les types

Ceci est un r�sum� des sp�cifications imagin�es par Anders Hejlsberg.

Cas d'utilisation

Beaucoup de biblioth�ques JavaScript acceptent des valeurs de plus d'un seul type. Par exemple, la propri�t� AJAX jsonp avec jQuery peut �tre soit false (i.e. de type boolean) ou une cha�ne de caract�res (type string). Les fichiers de d�finition TypeScript (.d.ts) doivent repr�senter cette propri�t� avec le type any, perdant ainsi la s�curit� du typage.

De m�me, la configuration du service HTTP d'AngularJS (https://docs.angularjs.org/api/ng/service/$http#usage) poss�de des propri�t�s de type soit boolean ou Cache ou number ou encore Promise.

Solutions de contournement actuelles

Cette lacune peut souvent �tre contourn�e avec des surcharges de fonction, mais il n'y a pas d'�quivalent pour les propri�t�s des objets, les contraintes sur les types, ou d'autres r�les concernant les types.

Introduction

Syntaxe

Le nouvel op�rateur |, lorsqu'il est utilis� pour s�parer deux types, produit une union de types repr�sentant une valeur qui est de l'un des types en entr�e.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
interface Settings {
    foo: number|string;
}
function setSetting(s: Settings) { /* ... */ }
setSettings({ foo: 42 }); // OK
setSettings({ foo: '100' }); // OK
setSettings({ foo: false }); // Error, false is not assignable to number|string
Plusieurs types peuvent �tre combin�s de cette fa�on :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
function process(n: string|HTMLElement|JQuery) { /* ... */ }
process('foo'); // OK
process($('div')); // OK
process(42); // Error
N'importe quel type est un op�rande valide pour l'op�rateur |. Quelques exemples et comment ils seraient analys�s :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
var x: number|string[]; // x is a number or a string[]
var y: number[]|string[]; // y is a number[] or a string[]
var z: Array<number|string>; // z is an array of number|string
var t: F|typeof G|H; // t is an F, typeof G, or H
var u: () => string|number; // u is a function that returns a string|number
var v: { (): string; }|number; // v is a function that returns a string, or a number
Notez que les parenth�ses ne sont pas n�cessaires pour lever l'ambigu�t�, de sorte qu'elles ne sont pas accept�es.

Interpr�tation

La signification de A|B est un type qui est soit un A ou un B . En particulier, c'est diff�rent d'un type qui combinerait tous les membres de A et de B. Nous examinerons ceci dans des exemples plus loin.

S�mantique

Notions de base

Quelques r�gles simples:
  • Identit� : A|A est �quivalent � A
  • Commutativit� : A|B est �quivalent � B|A
  • Associativit� : (A|B)|C est �quivalent � A|(B|C)
  • Effacement du sous-type : A|B est �quivalent � A si B est un sous-type de A


Propri�t�s (attributs)

Le type A|B poss�de une propri�t� P de type X|Y si A poss�de une propri�t� P de type X et B poss�de une propri�t� P de type Y. Ces propri�t�s doivent �tre soit � la fois publiques, ou doivent provenir du m�me site de d�claration (tel que sp�cifi� dans les r�gles pour private / protected). Si l'une des propri�t� est facultative, la propri�t� qui en r�sulte est �galement facultative.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Car {
    weight: number;
    gears: number;
    type: string;
}
interface Bicycle {
    weight: number;
    gears: boolean;
    size: string;
}
var transport: Car|Bicycle = /* ... */;
var w: number = transport.weight; // OK
var g = transport.gears; // OK, g is of type number|boolean
 
console.log(transport.type); // Error, transport does not have 'type' property
console.log((<Car>transport).type); // OK
Appel et construction de signatures

Le type A|B a une signature d'appel F si A a une signature d'appel F et B a une signature d'appel F.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
var t: string|boolean = /* ... */;
console.log(t.toString()); // OK (both string and boolean have a toString method)
La m�me r�gle est appliqu�e pour construire signatures.

Signatures d'indice

Le type A|B a une signature d'indice [x: number]: T ou [x: string]: T si les deux A et B ont une signature d'indice de ce type.

R�ductibilit� (assignability) et sous-typage

Nous d�crivons ici la r�ductibilit� ; le sous-typage est la m�me chose, sauf que � est r�ductible � � est remplac� par � est un sous-type de �.

Le type S est r�ductible au type T1|T2 si S est r�ductible � T1 ou si S est r�ductible � T2.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
var x: string|number;
x = 'hello'; // OK, can assign a string to string|number
x = 42; // OK
x = { }; // Error, { } is not assignable to string or assignable to number
Le type S1|S2 est r�ductible au type T si les deux S1 et S2 sont r�ductibles � T.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
var x: string|number = /* ... */;
var y: string = x; // Error, number is not assignable to string
var z: number = x; // Error, string is not assignable to number
En combinant les r�gles, le type S1|S2 est r�ductible au type T1|T2 si S1 est r�ductible � T1 ou T2 et S2 est r�ductible � T1 ou T2. Plus g�n�ralement, tous les types sur la partie droite de la r�duction doivent �tre r�duits � au moins un type sur la partie gauche.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
var x: string|number;
var y: string|number|boolean;
x = y; // Error, boolean is not assignable to string or number
y = x; // OK (both string and number are assignable to string|number)
Meilleur type commun

L'algorithme actuel du meilleur type commun (c.f. sp�cifications section 3.10) est seulement capable de produire un type d�j� existant parmi les candidats, ou le type {}. Par exemple, le tableau [1, 2, "hello"] est de type {}[] . Avec la possibilit� de repr�senter les unions de types, nous pouvons changer l'algorithme du meilleur type commun pour produire une union de types lorsqu'on est en pr�sence d'un ensemble de candidats sans supertype.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
class Animal { run(); }
class Dog extends Animal { woof(); }
class Cat extends Animal { meow(); }
class Rhino extends Animal { charge(); }
var x = [new Dog(), new Cat()];
// Current behavior: x is of type {}[]
// Proposed: x is of type Array<Dog|Cat>
Notez que dans ce cas, le type Dog|Cat est structurellement �quivalent � Animal en termes de ses membres, mais il ce serait une erreur d'essayer d'attribuer un Rhinox[0] , car Rhino n'est pas r�ductible � Cat ou Dog.

Le meilleur type commun est utilis� pour plusieurs inf�rences r�alis�es par le langage. Dans les cas
  • x || y,
  • z ? x : y,
  • z ? x : y, et
  • [x, y],

le type r�sultant sera X | Y (o� X est le type de x et Y est le type de y). Pour les instructions return dans une fonction et l'inf�rence du type g�n�rique, nous allons exiger de l'existence d'un supertype entre les candidats.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
11
12
// Error, no best common type among 'string' and 'number'
function fn() {
    if(Math.random() > 0.5) {
        return 'hello';
    } else { 
        return 42;
    }
}
// OK with type annotation
function fn(): string|number {
    /* ... same as above ... */
}
Prochaines �tapes possibles

Combinaison des membres de types

D'autres sc�narios n�cessitent un type construit � partir de A et B ayant tous ses membres pr�sents dans l'un ou l'autre des deux types, mais pas dans les deux. Au lieu d'ajouter une nouvelle syntaxe de type, nous pouvons repr�senter facilement en supprimant la restriction qui fait que les clauses extends peuvent ne pas r�f�rencer les param�tres de type de leur d�claration.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
interface HasFoo<T> extends T {
    foo: string;
}
interface Point {
    x: number;
    y: number;
}
var p: HasFoo<Point> = /* ... */;
console.log(p.foo); // OK
console.log(p.x.toString(); // OK
Signification locale des unions de types

Pour les unions de types o� un op�rande est une primitive, nous avons pu d�tecter certains sch�mas syntaxiques et ajuster le type d'un identifiant dans les blocs conditionnels.

Exemple :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
var p: string|Point = /* ... */;
if(typeof p === 'string') {
    console.log(p); // OK, 'p' has type string in this block
} else {
    console.log(p.x.toString()); // OK, 'p' has type Point in this block
}
Cela pourrait �galement s'�tendre � des v�rifications d'appartenance :
Code : S�lectionner tout - Visualiser dans une fen�tre � part
1
2
3
4
5
6
7
8
9
10
interface Animal { run(); }
interface Dog extends Animal { woof(); }
interface Cat extends Animal { meow(); }
var x: Cat|Dog = /* ... */;
if(x.woof) {
   // x is 'Dog' here
}
if(typeof x.meow !== 'undefined') {
   // x is 'Cat' here
}