Cada carta pertenece a una regla, y cada regla tiene cartas a ambos lados: los pares que pasan y los que no. Los cuadrados son tuyos: una carta que acertaste la última vez, una que fallaste, una que aún no has visto.
Un tipo literal es un único valor de su primitivo, así que lo extiende — nunca al revés, y nunca entre primitivos distintos: "42" es un string, no un number. Un literal numérico es su valor, se escriba como se escriba: 0x10 es 16.
"a" extends string
truestring extends "a"
TS2322
Una unión a la derecha es una elección: lo que extiende a uno de sus miembros extiende la unión entera. PropertyKey es string | number | symbol.
"a" extends "a" | "b"
true"c" extends "a" | "b"
TS2322
Una unión a la izquierda tiene que encajar entera: cada miembro debe extender el lado derecho. boolean no es más que true | false.
"a" | "b" extends string
true"a" | "b" extends "a"
TS2322
Con strictNullChecks, null y undefined son tipos propios. Solo encajan donde se los nombra — o en unknown, any y, en el caso de undefined, también en void.
Todo tipo extiende unknown. unknown solo extiende lo que admite cualquier valor: unknown, any — o {} | null | undefined, que es el mismo conjunto escrito por partes.
string extends unknown
trueunknown extends string
TS2322
never es el tipo sin valores, así que lo extiende todo — y a él no lo extiende nada salvo el propio never.
never extends string
truestring extends never
TS2322
07any apaga la comprobación
Donde aparece any, esa parte de la comparación pasa, en cualquier dirección. Pero no cambia la estructura que lo rodea: una propiedad que falta o una longitud de tupla equivocada siguen fallando.
08{} no es un objeto vacío
{} significa «cualquier cosa salvo null o undefined», así que los primitivos lo extienden; object los deja fuera. Aun así {} extiende object mientras string extiende {}: la asignabilidad no es transitiva.
{ a: string } extends {}
truenull extends {}
TS2322
Los nombres con mayúscula son interfaces de los objetos envoltorio. Un primitivo extiende su envoltorio, y cualquier tipo de objeto hecho con los miembros del envoltorio tal como este los tipa — el length de un string es un number, incluso para "abc". Un envoltorio nunca extiende al primitivo, y Object se comporta como {}.
string extends String
trueString extends string
TS2322
Las propiedades se comparan una a una, en la misma dirección que el todo: un tipo de propiedad más estrecho encaja, uno más ancho no.
12Opcional no es | undefined
Una propiedad opcional puede faltar del todo; a: T | undefined tiene que estar. Así que la obligatoria encaja en la opcional, y la opcional no encaja en la obligatoria.
13La comprobación de tipos débiles
Un tipo cuyas propiedades son todas opcionales es «débil». Todo lo que tenga propiedades pero no comparta ninguna se rechaza con TS2559, aunque estructuralmente encajaría.
14Las propiedades readonly no cuentan
readonly en una propiedad limita lo que puedes hacer a través de ese tipo, y la asignabilidad lo ignora: { readonly a: string } y { a: string } se extienden mutuamente.
15Los arrays son covariantes
Un array de elementos más estrechos extiende a uno de elementos más anchos. Al escribir es inseguro — mete un número en un string[] visto como unknown[] — y TypeScript lo permite de todos modos.
16Los arrays readonly siguen siéndolo
Un array o una tupla readonly no tiene métodos que muten, así que no puede ocupar un hueco mutable (TS4104). Un array mutable encaja sin queja en un hueco readonly.
17Las tuplas tienen longitud
Una tupla es un array cuya longitud forma parte de su tipo — [1, 2] tiene length: 2. Las tuplas extienden arrays de sus elementos; un array nunca extiende una tupla, porque podría tener cualquier longitud.
[string, number] extends (string | number)[]
true(string | number)[] extends [string, number]
TS2322
18Los parámetros van al revés
Con strictFunctionTypes, una función extiende a otra solo si acepta todo lo que la otra puede recibir: los tipos de los parámetros se comparan al revés, y a un parámetro opcional se le puede pasar undefined. Aceptar más es la dirección segura.
19Menos parámetros no es problema
JavaScript deja que una función ignore sus argumentos, así que la que recibe menos parámetros extiende a la que recibe más. Lo que falla es exigir un argumento de más; un parámetro rest cuenta como cualquier número.
20Los retornos van hacia delante
Los tipos de retorno se comparan en la misma dirección que las funciones: una función que devuelve algo más estrecho extiende a una que devuelve algo más ancho.
21void significa «ignorado»
Un tipo de función que devuelve void solo promete que nadie leerá el resultado, así que lo extiende una función que devuelva cualquier cosa. Pero void no es ni undefined ni null: solo lo aceptan void, unknown y any.
22Los métodos son bivariantes
La sintaxis de método se salta strictFunctionTypes: los parámetros de un método se comparan en ambas direcciones, y lo que decide es la sintaxis del destino. Una propiedad con tipo de función recibe la comprobación estricta. Por eso Set<"a"> extiende Set<string>.
23Function, genéricos y constructores
Toda función extiende Function, pero Function no promete ninguna firma concreta. Una función genérica extiende cada instancia que su restricción permite, no al revés — y un constructor abstracto no puede ocupar un hueco que exige new.
Un tipo con varias firmas de llamada extiende una firma única si cualquiera de sus sobrecargas encaja en ella. Al revés, una sola firma tiene que encajar en cada sobrecarga del destino, una por una.
Una firma de índice exige que cada propiedad encaje en ella. Un literal de tipo objeto la recibe de forma implícita; una interfaz no, y object tampoco — así que ninguno extiende Record<string, unknown>, y la misma forma como alias de tipo sí. La excepción es una firma de tipo any: acepta cualquier objeto.
26Tipos de plantilla literal
Un tipo de plantilla literal es un patrón sobre strings. ${string} encaja con cualquier texto, incluido el vacío; ${number}, con todo string no vacío que JavaScript lee como número finito — "1e3" y "0x1F" sí, "Infinity" no —, y ${bigint}, solo con los enteros. Lowercase<string> y sus parientes encajan con texto que ya está en esa caja.
Una intersección tiene las propiedades de todos sus miembros, así que extiende a cada uno. La que ningún valor puede habitar — string & number, dos literales kind distintos — se reduce a never, y never lo extiende todo.
Un objeto extiende una unión de objetos cuando encaja en uno de sus miembros. Desde TypeScript 3.5, un objeto cuyo discriminante es a su vez una unión se divide en un objeto por valor, y se comprueba cada pieza — por eso { kind: "a" | "b" } extiende { kind: "a" } | { kind: "b" }.
keyof es la unión de las claves conocidas de un tipo: las numéricas siguen siendo números, las de un array incluyen el nombre de cada método, y las de una firma de índice string son string | number. keyof {} y keyof unknown son never; keyof never es toda clave posible.
T[K] es el tipo de la propiedad K de T, y una unión de claves da una unión de tipos. En un array, T[number] es el tipo del elemento; en una tupla, la unión de sus elementos, y T["length"] su longitud literal. Una propiedad opcional trae consigo su undefined.
31Los tipos utilitarios son tipos corrientes
Partial, Required, Pick, Omit y Record se resuelven en tipos de objeto corrientes, y se comprueban exactamente igual — propiedad a propiedad.
32Los tipos calculados se resuelven antes
ReturnType, Parameters, Awaited, Exclude, Extract y NonNullable son tipos condicionales. El compilador resuelve cada uno al tipo corriente que produce antes de comparar nada — así que ReturnType<() => void> es void, y NonNullable<unknown> es {}.
33Las clases son estructurales — hasta private
Dos clases con la misma forma pública se extienden mutuamente, y un tipo de objeto normal puede extender una clase; los miembros estáticos viven en el constructor, así que no cuentan. Un miembro private, protected o #private vuelve nominal a la clase: solo su propia declaración y sus subclases tienen ese miembro.
Un enum numérico sigue aceptando cualquier number, y todo literal que sea uno de sus valores — pero no un literal fuera de rango. Cada miembro extiende su propio valor, así que S.A extiende "a"; pero un enum de strings solo acepta sus propios miembros, y "a" no es S.A.
enum E { A, B }E.A extends number
trueenum E { A, B }E extends E.A
TS2322
Un unique symbol es un literal de symbol: extiende symbol y PropertyKey, symbol no lo extiende a él, y dos de ellos nunca se extienden entre sí. Los símbolos conocidos, como Symbol.iterator, también son únicos.
Un tipo genérico se compara a través de su argumento, en la dirección en que el tipo lo usa: las propiedades y los retornos de forma covariante — también las mutables — y los parámetros de tipos de función de forma contravariante. in y out fijan la dirección explícitamente, por encima de lo que mediría el compilador; in out la fija como invariante.