Typescript: Sugerencia: no permitir el uso antes de la definición

Creado en 15 jul. 2014  ·  29Comentarios  ·  Fuente: microsoft/TypeScript

El compilador debería emitir un error cuando el código usa valores antes de que puedan inicializarse.

// Error, 'Derived' declaration must be after 'Base'
class Derived extends Base { }
class Base { }
Bug

Comentario más útil

Solo para mencionar que esto nos ha mordido hoy, y nos tomó un tiempo descubrir qué estaba pasando.

TypeScript v1.8.10, compilación basada en Webpack, tanto la clase base como la derivada definidas en el mismo archivo, pero (aparentemente) en el orden incorrecto, sin errores de compilación ni advertencias, e incluso si los mapas de origen funcionan, la pila de llamadas de error apuntaba a una ubicación muy inútil (el final de otra clase que importa la derivada).

No pasar por toda la discusión, pero como primeros auxilios parece que una advertencia del compilador ayudaría. Solo nuestros 2 ¢

Todos 29 comentarios

Si bien arrojar un error del compilador es una buena solución, quizás el compilador podría generar las clases en el orden correcto. Esa sería una característica genial. Por ejemplo, el compilador realiza un seguimiento de la relación de dependencia y genera las clases de acuerdo con eso, arrojando el error del compilador solo cuando no puede resolver el orden de dependencia.

El compilador realiza un seguimiento de la relación de dependencia y genera las clases de acuerdo con eso, arrojando el error del compilador solo cuando no puede resolver el orden de dependencia.

¿Deberíamos hacer de esto una nueva sugerencia? Es por eso que actualmente utilizo módulos AMD en lugar de módulos internos de TypeScript; el compilador RequireJS determina el orden de serialización del módulo apropiado usando las dependencias que especifico en el código base (usando require() ).

Vinculando al # 274. Necesitamos delinear cuáles serían las reglas y el alcance de esto

El caso extendido parece un buen candidato para la verificación léxica; solo necesitamos asegurarnos de que léxicamente la clase base viene antes que la derivada. ¿Hay otros casos que debamos considerar?

Un problema es que reordenar las definiciones de clases podría reordenar el orden del inicializador estático de forma silenciosa. Si la clase base viene después de una clase derivada, voto para mantener el código de inicializador estático en el sitio de definición de clase o simplemente marcar un error.

Creo que el caso de archivos múltiples es más interesante y útil desde el punto de vista de un gran proyecto / mantenimiento (que es el objetivo ostensible del mecanografiado, después de todo).

Entonces, creo que debemos considerar el orden de salida en el modo de salida de un solo archivo. (También sería bueno poder obtener este orden para crear archivos html que incluyan varios archivos).

Aquí hay algunas declaraciones que creo que garantizarían el pedido:

class X extends Y {} // ensure Y is defined in prior file
module { new X(); } // ensure X is defined in prior file
class S { static z = new Z(); } // ensure Z is defined in prior file

También podríamos extender esto a funciones y variables que se definen antes de su uso, no solo a clases.

PD: tengo un prototipo.

No creo que haya ninguna intención de intentar reordenar la emisión por usted, solo para dar errores donde podamos para cosas que seguramente fallarán en tiempo de ejecución.

Dan, estoy de acuerdo contigo sobre reordenar dentro de un solo archivo, pero cuando se combinan varios archivos usando --out, el compilador tiene control sobre el orden de emisión y preferiría que el orden que elija funcione.

Las funciones de elevan a la parte superior del alcance de todos modos en tiempo de ejecución. entonces las funciones no son interesantes. Las variables también se elevan, el problema es que no se inicializarán en ese momento. ahora, el uso antes de la inicialización es un problema diferente, y creo que se rastrea en # 274.

para reordenar; la filosofía que hemos seguido es dejar que el código de salida sea lo más cercano posible al código de entrada. en esencia, dejamos pasar el código de usuario, simplemente eliminamos los tipos. en este sentido, un error estaría más en línea con lo que hemos hecho hasta ahora.

En cuanto a la implementación, recientemente agregué una verificación de orden léxico con Let y Const, y se puede extraer como una verificación general y usar para estos diferentes casos. Lo puedes encontrar aquí:
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L329

Necesitamos identificar claramente los casos en los que estamos verificando, y un RP sería definitivamente bienvenido :)

Sí, estoy de acuerdo en que no queremos reordenar dentro de un solo archivo mecanografiado, pero en el caso de --out file, el orden no lo especifica el usuario, así que, de nuevo, preferiría que el compilador haga un mayor esfuerzo para elija un orden que funcione.

La función de elevación es un buen ejemplo de dónde no debemos preocuparnos en el caso de un solo archivo, pero donde la compilación de varios archivos y la elección de una secuencia para incluirlos en un archivo .html puede no ser trivial para un humano. Las variables que no están definidas durante el uso es un gran ejemplo de dónde el compilador puede introducir un comportamiento inesperado debido a un cambio en las líneas /// <reference> .

pero en el caso de --out file, el orden no lo especifica el usuario

Este no es realmente el caso. Tenemos reglas muy simples aquí: use el orden implícito en las etiquetas reference y el orden de los archivos en la línea de comandos. En ambos casos, el usuario nos está proporcionando un pedido. Hacer que el compilador ignore el orden que proporcionó el usuario es una ruta peligrosa a seguir. ¿Qué pasa si el compilador decide un orden diferente al que usted prefiere? ¿Cómo anularías eso? ¿Qué pasa si una orden rompe 2 clases y otra orden rompe 2 variables?

Entonces no deberíamos cambiar el orden, pero ¿deberíamos al menos (tener la opción de) advertir al usuario que el orden que usa el compilador probablemente sea incorrecto?

Sip. no debemos ordenar, sino error.

¿Cuál es el idioma correcto en TypeScript para clases recursivas mutuas? ¿Un declare class antes de la definición real?

Si sus clases simplemente se refieren entre sí en el sistema de tipos o en los métodos de instancia, no hay problema. El único patrón "recursivo mutuo" que es un problema es este:

class Alpha {
    static myFriendBeta = new Beta();   
}

class Beta {
    static myFriendAlpha = new Alpha(); 
}

Puedes reescribir esto como un clodule:

class Alpha {
}

class Beta {
    static myFriendAlpha = new Alpha();
}

module Alpha {
    export var myFriendBeta = new Beta();
}

Ok, ¿qué reglas nos gustaría implementar como parte de este problema además de 'la clase base debería definirse léxicamente antes que la clase derivada'?

No permitir referencias posteriores a miembros internos del módulo, p. Ej.

var x = M.fn(); // Should error
module M {
    export function fn() {}
}

No permitir referencias posteriores a miembros de enumeración

var x = E.A; // Should error
enum E { A }

En mi opinión, el alcance de este problema es bastante limitado tal como está porque las personas normalmente no definen todo su código en un solo archivo: es más probable que la clase base exista en un archivo separado de la clase derivada.

Sugiero que lo siguiente de @sparecycles también debería ser parte de la resolución de este problema:

Entonces no deberíamos cambiar el orden, pero ¿deberíamos al menos (tener la opción de) advertir al usuario que el orden que usa el compilador probablemente sea incorrecto?

El "orden que usa el compilador" debe incluir el orden especificado en tsconfig .

Cuando la clase base y las clases derivadas están en el mismo archivo, el problema no es tan grave: el programa se bloqueará al iniciarse y el programador cambiará el orden en ese archivo y el problema se solucionará.

El caso de varios archivos es donde el compilador debe advertir cuando concatena varios archivos en un orden dudoso, porque ese orden puede cambiar por motivos sutiles.

Considere el caso donde hay una clase base y múltiples clases derivadas, todo en archivos separados. La clase base usa algunas de las clases derivadas en su implementación, por lo que hace referencia a ellas, pero aún debe colocarse en primer lugar en la salida. De manera similar, todas las clases derivadas deben hacer referencia a la clase base.

Bueno, no hay ningún problema con los archivos referenciados mutuamente, si A.ts hace referencia mutua a B.ts y X.ts incluye A.ts, entonces el orden de salida será [B, A, X], y si hace referencia a B.ts el orden será [A, B, X]. (Pero solo una de estas órdenes podría funcionar en tiempo de ejecución). Esto hace que las cosas sean frágiles, ya que la compilación tendrá el mismo éxito si se hace referencia a B o A.

Mi solución para mi problema del sistema de clases derivadas de la base: agregue un index.ts para mi jerarquía de clases e incluya, en este archivo, todas las clases derivadas, seguidas de la clase base. Esto garantizaba que la salida pondría a la clase base en primer lugar. (¡completamente contrario a la intuición!). Descubrí que si hacía referencia directamente a los archivos que quería, terminaría generando la clase base después de la derivada.

La advertencia del compilador será muy buena, pero también sería genial poder marcar una de las referencias en un escenario de referencia mutua como orden de emisión, y la otra es solo para extraer declaraciones. Las referencias mutuas de orden de emisión serían un error.

(Actualmente tengo esto implementado para generar automáticamente la lista de .js incluye en nuestro proyecto Visual Studio / Typescript ya que estamos usando salida de múltiples archivos (más fácil de depurar). Pero el código está en C # como una tarea en línea. Si hay es de interés, preguntaré si puedo compartirlo. Son básicamente dos ejecuciones del algoritmo CC de Tarjan).

Tanto advertir cuando el orden de emisión es incorrecto como estabilizar el orden de emisión con una directiva explícita contribuiría en gran medida a hacer del mecanografiado un lenguaje viable para grandes proyectos ... ¿sí?

Me encuentro con este problema con bastante frecuencia con mi base de código bastante pequeña (alrededor de 80 archivos .ts). Idealmente, me gustaría no tener etiquetas <reference> en la parte superior de ninguno de mis archivos, y el compilador puede resolverlo todo por mí.

Mi aplicación tiene solo 1 archivo que crea instancias de clases y ejecuta la aplicación (mi raíz de composición), algunos archivos que agregan extensiones (por ejemplo, agregar Array.prototype.distinct ) y el resto son solo definiciones de clase / interfaz.

En este caso, la mayor parte del código es aceptable para reordenar y no debería requerir definiciones <reference> manualmente para hacerlo bien. Considero que las definiciones de clases son un juego justo para cualquier reordenamiento del compilador, y deberían derivarse a la parte superior de la salida combinada, mientras que el resto de las declaraciones pueden preservar el orden como estaba cuando se ingresaron.

¿Sería posible un indicador de compilador --looseSorting ? Parece una característica bastante buscada.

Al emitir declaración de clase en ES6, hacemos una asignación de propiedad estática después de la declaración de clase. Esto hace que la referencia a la propiedad estática de la clase en el nombre de propiedad calculada se convierta en uso antes de la definición.

JS emitido:

class C {
    [C.p] () {}  // Use before definition
    [C.p+ C.e]() {}  // Use before definition
    [D.f] () {}  // Use before definition
}
C.p = 10;
C.e = 20;

class D {
}
D.f = "hi";

Solo queremos advertir sobre este error en dos casos: los nombres de propiedad calculada se refieren a su propiedad estática de clase, o se refieren a otra clase, que se define debajo, propiedad.

El ejemplo trivial con el que estábamos jugando hoy, solo para incluirlo en cualquier prueba:

function f() {
    function g() {
        i = 10;
    }

    let i = 20;
    g();
}

Sería bueno obtener las permutaciones de usos / definiciones de g alrededor de i .

No olvide pensar en las funciones definidas dentro del alcance del bloque. Ese es un comportamiento indefinido según el estándar de JavaScript, y sé que al menos Firefox y Chrome no están de acuerdo en su implementación.

p.ej:

function f() {
    if (true) {
        g(); // iirc, g executes in Chrome, and is undefined in Firefox
        function g() {
        }
        g(); // works in both browsers
    }
}

Solo para mencionar que esto nos ha mordido hoy, y nos tomó un tiempo descubrir qué estaba pasando.

TypeScript v1.8.10, compilación basada en Webpack, tanto la clase base como la derivada definidas en el mismo archivo, pero (aparentemente) en el orden incorrecto, sin errores de compilación ni advertencias, e incluso si los mapas de origen funcionan, la pila de llamadas de error apuntaba a una ubicación muy inútil (el final de otra clase que importa la derivada).

No pasar por toda la discusión, pero como primeros auxilios parece que una advertencia del compilador ayudaría. Solo nuestros 2 ¢

Me parece ridículo que TS no admita esta función de fábrica. La confusión que causa es similar a usar JS estándar. Además, métodos virtuales, ¿alguien?

@MrGuardian, la reproducción descrita en el OP ha sido corregida. ¿Quizás pueda aclarar un problema nuevo o existente que describa mejor el problema que tiene?

(# 12673) aquí hay otros dos casos en los que IMO deberían ser errores:

`` ``
prueba de clase
{
_b = esto._a; // indefinido, sin error / advertencia
_a = 3;

static _B = Test._A; // undefined, no error/warning
static _A = 3;

method()
{
    let a = b; // Block-scoped variable 'b' used before its declaration
    let b = 3;
}

}
`` ``

@Spongman, ¿puedes registrar eso en un número separado, por favor? ¡Gracias!

12673

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Roam-Cooper picture Roam-Cooper  ·  3Comentarios

DanielRosenwasser picture DanielRosenwasser  ·  3Comentarios

MartynasZilinskas picture MartynasZilinskas  ·  3Comentarios

siddjain picture siddjain  ·  3Comentarios

bgrieder picture bgrieder  ·  3Comentarios