Búsqueda de sitios web

¿Por qué TypeScript no se queja cuando ignoro las definiciones de tipos genéricos?


Imagina que tienes el siguiente código:

class Superclass<T>{
    combine(object: Superclass<T>) {
        console.log("combined!");
        return object;
    }
}

class Generic1 {
    x = 12;
    // some class...
}
class Generic2 {
    y = "Hello!";
    // some class...
}

new Superclass<Generic1>().combine(new Superclass<Generic2>());

Enlace del parque

Como seguramente notarás, debería haber un error aquí. Superclass<Generic1> solo puede combinarse con Superclass<Generic1>, pero en ningún caso con Superclass<Generic2>. Visual Studio Code también muestra esto:

¡Pero no hay ningún error después de compilar! ¿Por qué? ¿Estoy entendiendo mal los genéricos?

Respuesta #1

TypeScript lo deja pasar porque el T en Superclass<T> no afecta su forma, por lo tanto Superclass<A> y Superclass<B> tienen la misma forma y, por lo tanto, TypeScript las considera "compatibles".

Recuerda que TypeScript tiene un sistema de tipos estructural donde los tipos se consideran equivalentes por sus miembros, incluso si sus definiciones reales y originales están completamente separadas (piensa en la evolución convergente). En cuanto a por qué es sorprendente: recuerda que TypeScript no tiene un sistema de tipos nominativo, donde los tipos con miembros, estructura y disposición idénticos, hasta representaciones binarias multiplataforma, se consideran estrictamente diferentes, siempre que tengan nombres de tipo diferentes (así es como funcionan los sistemas de tipos de Java, C# y muchos otros lenguajes).

Entonces, mientras Superclass<T> no esté usando T y no afecte su forma, tsc considera T superfluo y lo ignora:

Así que esto...

class Superclass<T> {
    combine(object: Superclass<T>): Superclass<T> { ... }
}

...es demostrablemente equivalente a:

class Superclass {
    combine(object: Superclass): Superclass { ... }
}

Sin embargo, tan pronto como Superclass<T> depende de T, por ejemplo, al agregar un campo con tipo T, entonces genera un error:

class Superclass<T>{

    public value: T;

    constructor(value: T) {
        this.value = value;
    }

    combine(object: Superclass<T>) {
        console.log("combined: " + object.value.toString());
        return object;
    }
}

class Generic1 {
    x = Math.random();
}
class Generic2 {
    y = Math.random();
}

var x = new Superclass<Generic1>( new Generic1() ).combine(new Superclass<Generic2>( new Generic2() ) );

Error :

Argument of type 'Superclass<Generic2>' is not assignable to parameter of type 'Superclass<Generic1>'.
  Property 'x' is missing in type 'Generic2' but required in type 'Generic1'.

En cuanto a por qué este es el caso, esto (creo) se explica en la documentación de TypeScript en la sección sobre Compatibilidad de Tipos:

La regla básica para el sistema de tipos estructurales de TypeScript es que x es compatible con y si y tiene al menos los mismos miembros que x

En este caso, cuando Superclass<T> se cierra como Superclass<Generic1> o como Superclass<Generic2>, ambos tipos tienen exactamente los mismos miembros, y TypeScript los considera compatibles entre sí.

Artículos relacionados