Typescript: Sugestão: não permitir o uso antes da definição

Criado em 15 jul. 2014  ·  29Comentários  ·  Fonte: microsoft/TypeScript

O compilador deve emitir um erro quando o código usa valores antes que eles pudessem ser inicializados.

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

Comentários muito úteis

Só para mencionar que acabamos de ser mordidos por isso hoje e demorou algum tempo para descobrir o que estava acontecendo.

TypeScript v1.8.10, compilação baseada em Webpack, ambas as classes base e derivada definidas no mesmo arquivo, mas (aparentemente) na ordem errada, sem erros de compilação nem avisos, e mesmo se os mapas de origem estiverem funcionando, a pilha de chamadas de erro estava apontando para um local altamente inútil (o fim de outra classe importando a derivada).

Não passando por toda a discussão, mas como primeiros socorros, parece que um aviso do compilador ajudaria. Apenas nossos 2 centavos

Todos 29 comentários

Embora lançar um erro do compilador seja uma boa solução, talvez o compilador possa gerar as classes na ordem certa. Isso seria um recurso matador. Por exemplo, o compilador mantém o controle do relacionamento de dependência e produz as classes de acordo com isso, lançando o erro do compilador apenas quando não é possível resolver a ordem de dependência.

O compilador controla o relacionamento de dependência e produz as classes de acordo com isso, lançando o erro do compilador apenas quando não é possível resolver a ordem de dependência.

Devemos fazer esta uma nova sugestão? É por isso que atualmente uso módulos AMD em vez de módulos internos TypeScript; o compilador RequireJS determina a ordem de serialização do módulo apropriado usando as dependências que eu especifico na base de código (usando require() ).

Vinculando a # 274. Precisamos delinear quais seriam as regras e o escopo disso

O caso extends parece um bom candidato para verificação lexical; só precisamos garantir que, lexicamente, a classe base venha antes da classe derivada. Existem outros casos que devemos considerar?

Um problema é que a reordenação das definições de classe pode reordenar a ordem do inicializador estático silenciosamente. Se a classe base vier depois de uma classe derivada, eu voto para manter o código do inicializador estático no site de definição de classe ou apenas sinalizar um erro.

Acho que o caso de arquivo múltiplo é mais interessante e útil do ponto de vista de grande projeto / manutenção (que é o objetivo ostensivo do texto datilografado, afinal).

Portanto, acho que precisamos considerar a ordem de saída no modo de saída de arquivo único. (também seria bom poder obter esta ordem para construir arquivos html que incluem vários arquivos).

Aqui estão algumas declarações que acho que garantiriam o 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

Também podemos estender isso para funções e variáveis ​​definidas antes do uso, não apenas para classes.

PS Eu tenho um protótipo.

Não acho que haja qualquer intenção de tentar reordenar o emit para você, apenas para fornecer erros onde pudermos para coisas que certamente falharão em tempo de execução.

Dan, concordo com você sobre a reordenação em um único arquivo, mas quando vários arquivos são combinados usando --out, o compilador tem controle sobre a ordem de emissão e eu prefiro que a ordem que ele escolher funcione.

As funções

para reordenar; a filosofia que seguimos é permitir que o código de saída seja o mais próximo possível do código de entrada. em essência, deixamos o código do usuário passar, apenas retiramos os tipos. nesse sentido, um erro estaria mais alinhado com o que fizemos até agora.

Quanto à implementação, eu adicionei uma verificação de ordem lexical recentemente com Let e ​​Const, e pode ser extraída como uma verificação geral e usada para esses diferentes casos. Você pode encontrá-lo aqui:
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L329

Precisamos identificar claramente os casos em que estamos verificando, e um PR seria definitivamente bem-vindo :)

Sim, concordo que não queremos reordenar dentro de um único arquivo datilografado, mas no caso do arquivo --out, a ordem não é especificada pelo usuário, então, novamente, eu preferiria que o compilador se esforçasse ao máximo para escolha um pedido que funcione.

A função içamento é um bom exemplo de onde não precisamos nos preocupar no caso de arquivo único, mas onde compilar vários arquivos e escolher uma sequência para incluí-los em um arquivo .html pode não ser trivial para um ser humano. Variáveis ​​sendo indefinidas em uso é um ótimo exemplo de onde um comportamento inesperado pode ser introduzido pelo compilador devido a uma mudança nas linhas /// <reference> .

mas no caso do arquivo --out, a ordem não é especificada pelo usuário

Este não é realmente o caso. Temos regras muito simples aqui - use a ordem implícita nas tags reference e a ordem dos arquivos na linha de comando. Em ambos os casos, o usuário está nos fornecendo um pedido. Fazer com que o compilador ignore a ordem que o usuário forneceu é uma rota perigosa para cair. E se o compilador decidir um pedido diferente do que você prefere? Como você substituiria isso? E se um pedido quebrar 2 classes e outro pedido quebrar 2 variáveis?

Então, não devemos alterar a ordem, mas devemos pelo menos (ter a opção de) avisar o usuário que a ordem que o compilador usa provavelmente está errada?

Sim. não devemos pedir, mas sim errar.

Qual é o idioma correto no TypeScript para classes mutuamente recursivas? A declare class antes da definição real?

Se suas classes simplesmente se referem umas às outras no sistema de tipos ou em métodos de instância, não há problema. O único padrão "mutuamente recursivo" que é um problema é este:

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

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

Você pode reescrever isso como um clódulo:

class Alpha {
}

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

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

Ok, quais regras gostaríamos de implementar como parte deste problema além de 'a classe base deve ser definida lexicamente antes da classe derivada'?

Proibir referências de encaminhamento a membros do módulo interno, por exemplo

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

Proibir encaminhamento de referências a membros enum

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

IMO, o escopo desta questão é bastante limitado porque as pessoas normalmente não definem todo o seu código em um único arquivo: a classe base tem mais probabilidade de existir em um arquivo separado da classe derivada.

Eu sugiro o seguinte de @sparecycles também deve fazer parte da resolução para este problema:

Então, não devemos alterar a ordem, mas devemos pelo menos (ter a opção de) avisar o usuário que a ordem que o compilador usa provavelmente está errada?

A "ordem que o compilador usa" deve incluir a ordem especificada em tsconfig .

Quando a classe base e as classes derivadas estão no mesmo arquivo, o problema não é tão ruim: o programa travará na inicialização e o programador mudará a ordem naquele arquivo e o problema será _ corrigido_.

O caso de vários arquivos é onde o compilador deve avisar ao concatenar vários arquivos em uma ordem duvidosa, porque essa ordem pode mudar por motivos sutis.

Considere o caso em que há uma classe base e várias classes derivadas, tudo em arquivos separados. A classe base usa algumas das classes derivadas em sua implementação e, portanto, faz referência a elas, mas ainda precisa ser colocada primeiro na saída. Da mesma forma, todas as classes derivadas precisam fazer referência à classe base.

Bem, não há problema com arquivos referenciados mutuamente, se A.ts faz referência mutuamente a B.ts e X.ts inclui A.ts, então a ordem de saída será [B, A, X], e se ele fizer referência a B.ts o pedido será [A, B, X]. (Mas apenas um desses pedidos pode funcionar em tempo de execução.) Isso torna as coisas frágeis, pois a compilação será igualmente bem-sucedida se B ou A forem referenciados.

Minha solução para meu problema de sistema de classes derivadas de base: adicione um index.ts para minha hierarquia de classes e inclua, neste arquivo, todas as classes derivadas, seguidas pela classe base. Isso garantiu que a saída colocaria a classe base em primeiro lugar. (totalmente contra-intuitivo!). Descobri que, se fizesse referência direta aos arquivos que queria, isso acabaria gerando a classe base após a derivada.

O aviso do compilador será muito bom, mas também seria ótimo ser capaz de sinalizar uma das referências em um cenário de referência mútua como ordem de emissão e a outra é apenas para obter declarações. Referências mútuas de ordem de emissão seriam um erro.

(Atualmente, tenho implementado isso para gerar automaticamente a lista de .js incluídos em nosso projeto Visual Studio / Typescript, pois estamos usando a saída de vários arquivos (mais fácil de depurar). Mas o código está em C # como uma tarefa embutida. é o interesse, vou perguntar se posso compartilhá-lo. São basicamente duas execuções do algoritmo CC de Tarjan.)

Avisar quando a ordem de emissão está errada e estabilizar a ordem de emissão com uma diretiva explícita seria um grande passo para tornar o texto datilografado uma linguagem viável para grandes projetos ... sim?

Eu me deparo com esse problema com bastante frequência com minha base de código bastante pequena (cerca de 80 arquivos .ts). Idealmente, eu gostaria de não ter nenhuma tag <reference> no topo de nenhum dos meus arquivos, e o compilador pode resolver tudo para mim.

Meu aplicativo tem apenas 1 arquivo que instancia classes e executa o aplicativo (minha raiz de composição), alguns arquivos que adicionam extensões (por exemplo, adicionando Array.prototype.distinct ) e o resto são apenas definições de classe / interface.

Nesse caso, a maior parte do código é um jogo justo para reordenamento e não deve exigir definições manuais de <reference> para acertar. Vejo as definições de classe como sendo um jogo justo para qualquer reordenação do compilador e devem ser colocadas no topo da saída combinada, enquanto o restante das instruções podem preservar a ordem como era na entrada.

Seria possível um sinalizador do compilador --looseSorting ? Parece um recurso bastante procurado.

Na emissão da declaração de classe no ES6, fazemos atribuição de propriedade estática após a declaração de classe. Isso faz com que a referência à propriedade estática da classe no nome da propriedade computada se torne uso antes da definição.

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";

Só queremos alertar sobre esse erro em dois casos: nomes de propriedades computadas referem-se à propriedade estática de sua classe, ou referem-se a outra classe, que está definida abaixo dela, propriedade.

O exemplo trivial com o qual estávamos jogando hoje, apenas para incluir em qualquer teste:

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

    let i = 20;
    g();
}

Seria bom obter as permutações de usos / definições de g torno de i .

Não se esqueça de pensar nas funções definidas no escopo do bloco. Esse é um comportamento indefinido de acordo com o padrão JavaScript, e eu sei que pelo menos o Firefox e o Chrome discordam em sua implementação.

por exemplo:

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

Só para mencionar que acabamos de ser mordidos por isso hoje e demorou algum tempo para descobrir o que estava acontecendo.

TypeScript v1.8.10, compilação baseada em Webpack, ambas as classes base e derivada definidas no mesmo arquivo, mas (aparentemente) na ordem errada, sem erros de compilação nem avisos, e mesmo se os mapas de origem estiverem funcionando, a pilha de chamadas de erro estava apontando para um local altamente inútil (o fim de outra classe importando a derivada).

Não passando por toda a discussão, mas como primeiros socorros, parece que um aviso do compilador ajudaria. Apenas nossos 2 centavos

Acho ridículo que o TS não suporte esse recurso fora da caixa. A confusão que isso causa é semelhante ao uso de JS padrão. Além disso, métodos virtuais, alguém?

@MrGuardian o repro descrito no OP foi corrigido. Talvez você possa esclarecer em um novo problema ou problema existente que descreve melhor o problema que você está tendo.

(# 12673) aqui estão outros dois casos em que IMO devem ser erros:

`` ``
classe Teste
{
_b = this._a; // indefinido, sem erro / aviso
_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, você pode registrar isso em uma edição separada, por favor? Obrigado!

12673

Esta página foi útil?
0 / 5 - 0 avaliações