Consistencia de tipos de unión en Typescript
Tener una variable de Typescript de tipo unión A
type A = {
b: true
x: number
} | {
b: false
x: string
}
declare const v: A
Puedo asignar correctamente la propiedad x al tipo correcto, verificando el tipo de valor de la propiedad b con un bloque discriminante if para proteger la consistencia de tipo A
if (v.b) { // v.x is number
// ok for compiler
v.x = 3
// compiler error as v.x should be number
v.x = ''
} else { // v.x is string
// compiler error as v.x should be string
v.x = 3
// ok for compiler
v.x = ''
}
fuera del bloque discriminante v.x correctamente parece ser number | string
sin embargo, el compilador no se queja al asignar x a number | string a pesar de que eso rompería la consistencia de tipo A
v.x = 3 // ok for compiler
v.x = '' // ok for compiler
¿Hay alguna manera de obligar al compilador a rechazar esto?
míralo en typescriptlang.org/play
Respuesta #1
De acuerdo, creo que he encontrado el problema oficial de GitHub sobre esto: microsoft/TypeScript#14150, una sugerencia de que "no deberían permitirse asignaciones inseguras incompatibles de tipo". Todavía es un problema abierto (al 13-09-2019) marcado como "a la espera de más comentarios", así que si crees que tienes un caso de uso convincente que no se menciona ya allí, podrías querer comentar allí. Sin embargo, no contaría con que esto se implemente pronto, ya que los problemas relacionados, como hacer cumplir la estricta read-only mediante una bandera y habilitar anotaciones de variancia, están cerrados o no se les ha dado seguimiento.
El problema aquí involucra la falta de solidez del sistema de tipos. Un sistema de tipos sólido solo te permitiría hacer cosas seguras. Pero aquí te permite hacer una asignación de propiedad a un objeto que podría violar el tipo declarado del objeto. Esta permisividad insegura significa que el sistema de tipos es no sólido. Eso, por sí mismo, no se considera un error. No es uno de los objetivos de diseño de TypeScript "aplicar un sistema de tipos sólido o 'demostrablemente correcto'". Hay un compromiso entre corrección y productividad, y es bastante posible que arreglar este problema pueda ser más problemático de lo que vale la pena. Consulta microsoft/TypeScript#9825 para más discusión sobre la solidez de TypeScript y/o su ausencia.
La falta de solidez particular aquí: el compilador asume que es seguro escribir el mismo tipo en una propiedad de la cual se puede leer. Esto no es cierto en general, como se muestra en tu ejemplo, y en este ejemplo relacionado del problema enlazado:
interface A { kind: "A"; foo(): void; }
interface B { kind: "B"; bar(): void; }
function setKindToB(x: A | B): void {
x.kind = "B"; // clearly unsafe
}
¿Qué se puede hacer? No estoy seguro. TypeScript 3.5 introdujo un cambio en las escrituras de acceso indexadas (como foo[bar]=baz) de modo que si la clave es de tipo unión (por ejemplo, bar es Math.random()<0.5 ? "a" : "b"), entonces debes escribir la intersección de los tipos de propiedades, no la unión (por lo que el tipo de baz debe ser typeof foo.a & typeof foo.b y ya no aceptará typeof foo.a | typeof foo.b). Esta es una mejora de la solidez que prohíbe algunas cosas inválidas que antes estaban permitidas. Y también prohíbe muchas cosas válidas que antes estaban permitidas. Y mucha gente sigue molesta por ello y los nuevos problemas siguen presentándose con bastante frecuencia. Imagino que el mismo problema ocurriría aquí si lo solucionaran... Obtendrías el error que esperabas y muchas bases de código fallarían. Por ahora diría que probablemente deberías evitar hacer estas tareas, lo cual entiendo que no es mucho consuelo.
De todos modos, espero que esa información te sea de utilidad. ¡Buena suerte!
Respuesta #2
Ejemplo de código para alias de tipos genéricos en tu caso: échale un vistazo en typescriptlang.org/play
