Búsqueda de sitios web

typescript - La recursión rompe la comprobación de tipos en funciones sobrecargadas


Dadas las siguientes funciones muy similares:

function transform1 <T extends string>(value: T): T;
function transform1 <T extends string>(value: T[]): T[]; // error, can't return 0
function transform1 <T extends string>(value: T | T[]) {
  if (Array.isArray(value)) {
    return 0;
  } else {
    return value[0];
  }
}

function transform2 <T extends string>(value: T): T;
function transform2 <T extends string>(value: T[]): T[];
function transform2 <T extends string>(value: T | T[]) {
  if (Array.isArray(value)) {
    return 0; // ok somehow?
  } else {
    return transform2([value])[0];
  }
}

¿Por qué es que para el segundo, no se me advierte sobre devolver 0? La única diferencia es que en el segundo estoy llamando a transform2 de nuevo (recursión). Ignora el hecho de que esta llamada fallará en tiempo de ejecución ya que se devolverá 0 y 0[0] es undefined.

¿Por qué la recursión evita que TypeScript la verifique? Me veo obligado a anotar explícitamente el tipo de retorno de la función aquí:

function transform2 <T extends string>(value: T | T[]): T | T[] {

Parque infantil

Respuesta #1

La verificación de tipos para declaraciones de funciones sobrecargadas es un poco extraña y no está bien documentada. El conjunto de firmas de llamadas se compara con la firma de implementación, y si el compilador decide que no están lo suficientemente "relacionadas" entre sí, entonces emitirá un error (aunque no un error muy útil, vea ms/TS#48186).

En tu código de ejemplo, el compilador tiene que inferir el tipo de retorno de la implementación a partir del cuerpo de la implementación. Para transform1 infiere string | 0 (lo cual puedes verificar copiando la implementación a una nueva función y viendo el tipo de retorno inferido), ya que value[0] está obteniendo un carácter de un string, creo. Para transform2 infiere T | 0 (de nuevo, verificable copiando), ya que value se reduce a T, y transform2([value]) devuelve T[], y al indexarlo se obtiene T.

Eso significa que, desde el punto de vista del verificador de tipos, estas son tus funciones:

function transform1<T extends string>(value: T): T;
function transform1<T extends string>(value: T[]): T[] // error!
function transform1<T extends string>(value: T | T[]): string | 0 {
  throw new Error()
}

function transform2<T extends string>(value: T): T;
function transform2<T extends string>(value: T[]): T[];
function transform2<T extends string>(value: T | T[]): T | 0 {
  throw new Error()
}

(Y así que la recursión es un poco una falsa pista aquí.)

El primero da error porque string | 0 no se considera correctamente "relacionado" con T | T[], mientras que el segundo es aceptable porque T | 0 sí se considera correctamente "relacionado" con T | T[]. Realmente no sé por qué; para el primero parece obvio que T[] no puede ser un string ni 0 ya que T extends string. Para el segundo parece igualmente imposible que T[] no pueda ser un string ni 0 por la misma razón. Todo eso significa que el compilador realmente no está usando esa lógica para permitir o no las firmas de llamada. Supongo que la presencia del genérico hace que el compilador sea más indulgente y no se preocupe por comprobar si T y T[] son compatibles o no.

Al buscar una respuesta autorizada, redescubrí un problema algo relacionado en microsoft/TypeScript#44661, donde, al preguntarle sobre las reglas para comparar las firmas de llamada sobrecargadas con las implementaciones, @RyanCavanaugh dijo:

Las reglas son un desastre de compatibilidad con versiones anteriores que nadie quería escribir.

Básicamente, la idea es detectar situaciones totalmente incorrectas y no mucho más, teniendo en cuenta la limitación de que estamos analizando el cuerpo de la función de forma opaca.

Así que eso es lo más cercano que puedo llegar a una respuesta: string | 0 es "totalmente incorrecto" para T[], mientras que T | 0 es, aparentemente, no "totalmente incorrecto", al menos hasta donde la verificación superficial del compilador sobre la "incorrectitud" puede detectar.

Enlace de Playground al código

Artículos relacionados