Typescript: Vorschlag: Verwendung vor der Definition nicht zulassen

Erstellt am 15. Juli 2014  ·  29Kommentare  ·  Quelle: microsoft/TypeScript

Der Compiler sollte einen Fehler ausgeben, wenn der Code Werte verwendet, bevor diese möglicherweise initialisiert werden können.

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

Hilfreichster Kommentar

Nur um zu erwähnen, dass wir heute gerade davon gebissen wurden und wir einige Zeit gebraucht haben, um herauszufinden, was los war.

TypeScript v1.8.10, Webpack-basierter Build, sowohl Basis- als auch abgeleitete Klasse in derselben Datei definiert, aber (anscheinend) in der falschen Reihenfolge, keine Kompilierungsfehler oder Warnungen, und selbst wenn Quellzuordnungen funktionieren, zeigte der Fehleraufrufstapel auf Ein sehr unbenutzbarer Ort (das Ende einer anderen Klasse, die die abgeleitete importiert).

Nicht die ganze Diskussion durchgehen, aber als erste Hilfe scheint es, als würde eine Compiler-Warnung helfen. Nur unsere 2 ¢

Alle 29 Kommentare

Während das Auslösen eines Compilerfehlers eine gute Lösung ist, könnte der Compiler die Klassen möglicherweise in der richtigen Reihenfolge ausgeben. Das wäre ein Killer-Feature. ZB Der Compiler verfolgt die Abhängigkeitsbeziehung und gibt die Klassen entsprechend aus, wobei der Compilerfehler nur dann ausgelöst wird, wenn die Abhängigkeitsreihenfolge nicht aufgelöst werden kann.

Der Compiler verfolgt die Abhängigkeitsbeziehung und gibt die Klassen entsprechend aus. Der Compilerfehler wird nur ausgelöst, wenn die Abhängigkeitsreihenfolge nicht aufgelöst werden kann.

Sollten wir dies zu einem neuen Vorschlag machen? Aus diesem Grund verwende ich derzeit AMD-Module anstelle von TypeScript-internen Modulen. Der RequireJS-Compiler ermittelt die entsprechende Reihenfolge der Modulserialisierung anhand der Abhängigkeiten, die ich in der gesamten Codebasis angegeben habe (mithilfe von require() ).

Verlinkung zu # 274. Wir müssen umreißen, welche Regeln und welchen Umfang dies haben würde

Der erweiterte Fall scheint ein guter Kandidat für die lexikalische Überprüfung zu sein. Wir müssen nur sicherstellen, dass die Basisklasse lexikalisch vor der abgeleiteten Klasse steht. Gibt es andere Fälle, die wir berücksichtigen sollten?

Ein Problem besteht darin, dass durch das Neuordnen von Klassendefinitionen die Reihenfolge des statischen Initialisierers möglicherweise stillschweigend neu angeordnet wird. Wenn die Basisklasse nach einer abgeleiteten Klasse kommt, stimme ich zu, entweder den statischen Initialisierungscode an der Klassendefinitionsstelle beizubehalten oder nur einen Fehler zu kennzeichnen.

Ich denke, dass der Fall mit mehreren Dateien unter dem Gesichtspunkt eines großen Projekts / einer größeren Wartung interessanter und nützlicher ist (was schließlich das angebliche Ziel von Typoskript ist).

Ich denke, wir müssen die Ausgabereihenfolge im Einzeldatei-Ausgabemodus berücksichtigen. (Es wäre auch schön, diese Reihenfolge für das Erstellen von HTML-Dateien zu erhalten, die mehrere Dateien enthalten).

Hier sind einige Aussagen, von denen ich denke, dass sie die Bestellung sicherstellen würden:

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

Wir könnten dies auch auf Funktionen und Variablen ausweiten, die vor der Verwendung definiert werden, nicht nur auf Klassen.

PS Ich habe einen Prototyp.

Ich glaube nicht, dass die Absicht besteht, die Emission für Sie neu zu ordnen, sondern nur Fehler zu geben, wo wir können, für Dinge, die zur Laufzeit sicher fehlschlagen werden.

Dan, ich stimme Ihnen in Bezug auf die Neuordnung innerhalb einer einzelnen Datei zu, aber wenn mehrere Dateien mit --out kombiniert werden, hat der Compiler die Kontrolle über die Ausgabereihenfolge und ich würde es vorziehen, dass die von ihm gewählte Reihenfolge funktioniert.

@sparecycles- Funktionen werden zur Laufzeit sowieso an die Spitze des Bereichs gehoben. Funktionen sind also nicht interessant. Variablen werden ebenfalls hochgezogen. Das Problem ist, dass sie zu diesem Zeitpunkt nicht initialisiert werden. Jetzt vor der Initialisierung verwenden ist ein anderes Problem, und ich denke, es wird unter # 274 verfolgt.

zur Nachbestellung; Die Philosophie, der wir gefolgt sind, besteht darin, den Ausgabecode so nah wie möglich am Eingabecode zu lassen. Im Wesentlichen lassen wir den Benutzercode durch, wir entfernen nur Typen. In diesem Sinne würde ein Fehler eher mit dem übereinstimmen, was wir bisher getan haben.

In Bezug auf die Implementierung habe ich kürzlich eine lexikalische Ordnungsüberprüfung mit Let und Const hinzugefügt, die als allgemeine Überprüfung extrahiert und für diese verschiedenen Fälle verwendet werden kann. Sie finden es hier:
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L329

Wir müssen die Fälle, in denen wir prüfen, klar identifizieren, und eine PR wäre auf jeden Fall willkommen :)

Ja, ich bin damit einverstanden, dass wir nicht innerhalb einer einzelnen Typoskriptdatei neu anordnen möchten, aber im Fall --out file wird die Reihenfolge nicht vom Benutzer angegeben, daher würde ich es wieder vorziehen, wenn der Compiler sich nach besten Kräften darum bemüht Wählen Sie eine Bestellung, die funktioniert.

Das Heben von Funktionen ist ein gutes Beispiel dafür, wo wir uns im Fall einer einzelnen Datei nicht darum kümmern müssen, aber wo das Kompilieren in mehrere Dateien und das Auswählen einer Sequenz, um sie in eine HTML-Datei aufzunehmen, für einen Menschen nicht trivial sein kann. Variablen, die bei der Verwendung nicht definiert sind, sind ein gutes Beispiel dafür, wo der Compiler aufgrund einer Änderung der Zeilen /// <reference> unerwartetes Verhalten einführen kann.

Im Fall der Datei --out wird die Reihenfolge jedoch nicht vom Benutzer angegeben

Das ist nicht wirklich der Fall. Wir haben hier sehr einfache Regeln - verwenden Sie die Reihenfolge, die durch die reference -Tags und die Reihenfolge der Dateien in der Befehlszeile impliziert wird. In beiden Fällen erteilt uns der Benutzer eine Bestellung. Wenn der Compiler die vom Benutzer angegebene Reihenfolge ignoriert, ist dies ein gefährlicher Weg. Was ist, wenn der Compiler eine andere Reihenfolge als die von Ihnen bevorzugte entscheidet? Wie würden Sie das überschreiben? Was ist, wenn eine Bestellung 2 Klassen und eine andere Bestellung 2 Variablen bricht?

Dann sollten wir die Reihenfolge nicht ändern, aber sollten wir den Benutzer zumindest (mit der Option) warnen, dass die vom Compiler verwendete Reihenfolge wahrscheinlich falsch ist?

Jep. wir sollten nicht bestellen, sondern Fehler.

Was ist die richtige Redewendung in TypeScript für gegenseitig rekursive Klassen? Ein declare class vor der eigentlichen Definition?

Wenn Ihre Klassen im Typsystem oder in Instanzmethoden einfach aufeinander verweisen, ist das kein Problem. Das einzige "gegenseitig rekursive" Muster, das ein Problem darstellt, ist das folgende:

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

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

Sie können dies als Clodule umschreiben:

class Alpha {
}

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

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

Ok, welche Regeln möchten wir als Teil dieses Problems implementieren, außer "Basisklasse sollte vor der abgeleiteten Klasse lexikalisch definiert werden"?

Verweise auf interne Modulmitglieder, z

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

Verweise auf Aufzählungsmitglieder nicht weiterleiten

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

IMO ist der Umfang dieses Problems derzeit eher begrenzt, da Benutzer normalerweise nicht ihren gesamten Code in einer einzigen Datei definieren: Es ist wahrscheinlicher, dass die Basisklasse in einer separaten Datei zur abgeleiteten Klasse vorhanden ist.

Ich schlage vor, dass Folgendes von @sparecycles ebenfalls Teil der Lösung für dieses Problem sein sollte:

Dann sollten wir die Reihenfolge nicht ändern, aber sollten wir den Benutzer zumindest (mit der Option) warnen, dass die vom Compiler verwendete Reihenfolge wahrscheinlich falsch ist?

Die "Reihenfolge, die der Compiler verwendet" sollte die in tsconfig angegebene Reihenfolge enthalten.

Wenn sich die Basisklasse und die abgeleiteten Klassen in derselben Datei befinden, ist das Problem nicht so schlimm: Das Programm stürzt beim Start ab und der Programmierer ändert die Reihenfolge in dieser einen Datei und das Problem wird behoben.

Im Fall mehrerer Dateien sollte der Compiler warnen, wenn mehrere Dateien in einer zweifelhaften Reihenfolge verkettet werden, da sich diese Reihenfolge aus subtilen Gründen ändern kann.

Stellen Sie sich den Fall vor, in dem eine Basisklasse und mehrere abgeleitete Klassen in separaten Dateien vorhanden sind. Die Basisklasse verwendet einige der abgeleiteten Klassen in ihrer Implementierung und verweist daher auf sie. Sie muss jedoch weiterhin an erster Stelle in der Ausgabe stehen. Ebenso müssen alle abgeleiteten Klassen auf die Basisklasse verweisen.

Nun, es gibt kein Problem mit Dateien, auf die gegenseitig verwiesen wird. Wenn A.ts auf B.ts verweist und X.ts A.ts enthält, lautet die Ausgabereihenfolge [B, A, X], und wenn auf B.ts verwiesen wird Die Reihenfolge ist [A, B, X]. (Aber möglicherweise funktioniert nur eine dieser Anweisungen zur Laufzeit.) Dies macht die Dinge fragil, da die Kompilierung genauso gut erfolgreich ist, wenn entweder auf B oder A verwiesen wird.

Meine Lösung für mein von der Basis abgeleitetes Klassensystemproblem: Fügen Sie eine index.ts für meine Klassenhierarchie hinzu und fügen Sie in diese Datei alle abgeleiteten Klassen ein, gefolgt von der Basisklasse. Dies garantierte, dass die Ausgabe die Basisklasse an die erste Stelle setzen würde. (völlig kontraintuitiv!). Ich stellte fest, dass, wenn ich direkt auf die gewünschten Dateien verweise, die Basisklasse nach der abgeleiteten generiert wird.

Die Compiler-Warnung ist sehr nett, aber es wäre auch großartig, wenn Sie eine der Referenzen in einem Szenario mit gegenseitiger Referenz als Emit-Reihenfolge kennzeichnen könnten und die andere nur zum Einziehen von Deklarationen dient. Referenzen zur gegenseitigen Abgabe von Ordnungen wären ein Fehler.

(Ich habe dies derzeit implementiert, um die Liste der .js-Includes in unserem Visual Studio / Typescript-Projekt automatisch zu generieren, da wir die Ausgabe mehrerer Dateien verwenden (einfacher zu debuggen). Der Code befindet sich jedoch in C # als Inline-Task. Wenn vorhanden Ich werde fragen, ob ich es teilen kann. Es sind im Grunde zwei Durchläufe von Tarjans CC-Algorithmus.)

Sowohl die Warnung, wenn die Emit-Reihenfolge falsch ist, als auch die Stabilisierung der Emit-Reihenfolge durch eine explizite Anweisung würden einen großen Beitrag dazu leisten, Typoskript zu einer brauchbaren Sprache für große Projekte zu machen ... ja?

Ich stoße ziemlich häufig auf dieses Problem mit meiner eher kleinen Codebasis (ca. 80 .ts-Dateien). Im Idealfall möchte ich keine <reference> -Tags oben in einer meiner Dateien haben, und der Compiler kann alles für mich klären.

Meine App hat nur eine Datei, die Klassen instanziiert und die Anwendung ausführt (mein Kompositionsstamm), einige Dateien, die Erweiterungen hinzufügen (z. B. Array.prototype.distinct hinzufügen), und der Rest sind nur Klassen- / Schnittstellendefinitionen.

In diesem Fall ist der größte Teil des Codes ein faires Spiel für die Neuordnung und sollte keine manuellen <reference> Definitionen erfordern, um richtig zu werden. Ich sehe Klassendefinitionen als faires Spiel für jede Compiler-Neuordnung und sollte bis zum Anfang der kombinierten Ausgabe verschoben werden, während der Rest der Anweisungen die Reihenfolge beibehalten kann, wie sie bei der Eingabe war.

Wäre ein Compiler-Flag --looseSorting möglich? Es scheint ein ziemlich begehrtes Feature zu sein.

Bei der Emit-Klassendeklaration in ES6 führen wir nach der Klassendeklaration eine statische Eigenschaftszuweisung durch. Dadurch wird der Verweis auf die statische Eigenschaft der Klasse im Namen der berechneten Eigenschaft vor der Definition verwendet.

Emittierte JS:

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

Wir möchten nur in zwei Fällen vor diesem Fehler warnen: Die Namen der berechneten Eigenschaften beziehen sich auf die statische Eigenschaft der Klasse oder auf eine andere Klasse, die unter dieser Eigenschaft definiert ist.

Das triviale Beispiel, mit dem wir heute gespielt haben, um es in alle Tests einzubeziehen:

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

    let i = 20;
    g();
}

Es wäre gut, die Permutationen von Verwendungen / Definitionen von g um i .

Vergessen Sie nicht, über Funktionen nachzudenken, die im Blockbereich definiert sind. Das ist undefiniertes Verhalten gemäß dem JavaScript-Standard, und ich weiß, dass zumindest Firefox und Chrome in ihrer Implementierung nicht übereinstimmen.

z.B:

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

Nur um zu erwähnen, dass wir heute gerade davon gebissen wurden und wir einige Zeit gebraucht haben, um herauszufinden, was los war.

TypeScript v1.8.10, Webpack-basierter Build, sowohl Basis- als auch abgeleitete Klasse in derselben Datei definiert, aber (anscheinend) in der falschen Reihenfolge, keine Kompilierungsfehler oder Warnungen, und selbst wenn Quellzuordnungen funktionieren, zeigte der Fehleraufrufstapel auf Ein sehr unbenutzbarer Ort (das Ende einer anderen Klasse, die die abgeleitete importiert).

Nicht die ganze Diskussion durchgehen, aber als erste Hilfe scheint es, als würde eine Compiler-Warnung helfen. Nur unsere 2 ¢

Ich finde es lächerlich, dass TS diese Funktion nicht sofort unterstützt. Die dadurch verursachte Verwirrung ähnelt der Verwendung von Standard-JS. Auch virtuelle Methoden, jemand?

@ MrGuardian Der im OP beschriebene Repro wurde behoben. Vielleicht können Sie in einem neuen oder bestehenden Problem klarstellen, welches Problem Sie besser beschreiben?

(# 12673) Hier sind zwei weitere Fälle, in denen IMO Fehler sein sollten:

`` ``
Klassentest
{
_b = this._a; // undefiniert, kein Fehler / Warnung
_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 können Sie das bitte in einer separaten Ausgabe protokollieren? Vielen Dank!

12673

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

jbondc picture jbondc  ·  3Kommentare

blendsdk picture blendsdk  ·  3Kommentare

kyasbal-1994 picture kyasbal-1994  ·  3Kommentare

Antony-Jones picture Antony-Jones  ·  3Kommentare

remojansen picture remojansen  ·  3Kommentare