Typescript: Предложение: запретить использование до определения

Созданный на 15 июл. 2014  ·  29Комментарии  ·  Источник: microsoft/TypeScript

Компилятор должен выдавать ошибку, если код использует значения до того, как они могут быть инициализированы.

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

Самый полезный комментарий

Просто упомяну, что нас это просто укусило сегодня, и нам потребовалось некоторое время, чтобы понять, что происходит.

TypeScript v1.8.10, сборка на основе Webpack, базовый и производный класс определены в одном файле, но (по-видимому) в неправильном порядке, без ошибок компиляции и предупреждений, и даже если исходные карты работают, стек вызовов ошибок указывал на крайне бесполезное место (конец другого класса, импортирующего производный).

Не вдаваясь в подробное обсуждение, но в качестве первой помощи кажется, что предупреждение компилятора могло бы помочь. Только наши 2 ¢

Все 29 Комментарий

Хотя выдача ошибки компилятора - хорошее решение, возможно, компилятор сможет вывести классы в правильном порядке. Это было бы убийственной особенностью. Например, компилятор отслеживает отношения зависимости и выводит классы в соответствии с этим, выдавая ошибку компилятора только в том случае, если не может разрешить порядок зависимости.

Компилятор отслеживает отношения зависимостей и выводит классы в соответствии с ними, выдавая ошибку компилятора только в том случае, если не может разрешить порядок зависимостей.

Должны ли мы сделать это новое предложение? Вот почему в настоящее время я использую модули AMD, а не внутренние модули TypeScript; компилятор RequireJS определяет соответствующий порядок сериализации модулей, используя зависимости, которые я указываю в кодовой базе (используя require() ).

Ссылка на # 274. Нам нужно обрисовать в общих чертах, какими будут правила и объем этого.

Расширенный падеж кажется хорошим кандидатом для лексической проверки; нам просто нужно убедиться, что лексически базовый класс предшествует производному. Есть ли другие случаи, которые нам следует рассмотреть?

Одна из проблем заключается в том, что изменение порядка определений классов может незаметно изменить порядок статического инициализатора. Если базовый класс идет после производного класса, я голосую за то, чтобы сохранить статический код инициализатора на сайте определения класса или просто отметить ошибку.

Я думаю, что случай с несколькими файлами более интересен и полезен с точки зрения большого проекта / обслуживания (в конце концов, это якобы цель машинописного текста).

Итак, я думаю, нам нужно учитывать порядок вывода в режиме вывода одного файла. (также было бы неплохо получить этот порядок для создания файлов HTML, включающих несколько файлов).

Вот некоторые утверждения, которые, я думаю, обеспечат порядок:

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

Мы также могли бы распространить это на функции и переменные, определенные перед использованием, а не только на классы.

PS У меня есть прототип.

Я не думаю, что есть какое-либо намерение пытаться переупорядочить emit для вас, только чтобы выдавать ошибки там, где мы можем, для вещей, которые наверняка потерпят неудачу во время выполнения.

Дэн, я согласен с вами по поводу переупорядочения в одном файле, но когда несколько файлов объединяются с помощью --out, компилятор контролирует порядок вывода, и я бы предпочел, чтобы порядок, который он выбирает, работал.

Функции @sparecycles в любом случае поднимаются в верхнюю часть области видимости во время выполнения. так что функции не интересны. переменные также поднимаются, проблема в том, что они не будут инициализированы в этот момент. теперь использование перед инициализацией - это другая проблема, и я думаю, что она отслеживается в # 274.

для повторного заказа; философия, которой мы следовали, заключается в том, чтобы выходной код был как можно ближе к входному коду. по сути, мы пропускаем код пользователя, мы просто удаляем типы. в этом смысле ошибка будет больше соответствовать тому, что мы сделали до сих пор.

Что касается реализации, я недавно добавил проверку лексического порядка с помощью Let и Const, и ее можно извлечь в качестве общей проверки и использовать для этих разных случаев. Вы можете найти это здесь:
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L329

Нам нужно четко обозначить случаи, когда мы проверяем, и пиар обязательно будет приветствоваться :)

Да, я согласен с тем, что мы не хотим переупорядочивать в одном файле машинописного текста, но в случае файла --out порядок не указывается пользователем, поэтому, опять же, я бы предпочел, чтобы компилятор сделал все возможное, чтобы выберите порядок, который работает.

Поднятие функции - хороший пример того, когда нам не нужно заботиться о случае с одним файлом, но когда компиляция в несколько файлов и выбор последовательности для включения их в файл .html могут быть нетривиальными для человека. Неопределенность переменных при использовании - отличный пример того, как компилятор может вызвать непредвиденное поведение из-за изменения в строках /// <reference> .

но в случае файла --out порядок не указывается пользователем

На самом деле это не так. Здесь у нас очень простые правила - используйте порядок, подразумеваемый тегами reference и порядок файлов в командной строке. В обоих случаях пользователь отправляет нам заказ. Игнорирование компилятором порядка, предоставленного пользователем, является опасным путем для отказа. Что, если компилятор выберет порядок, отличный от того, который вы предпочитаете? Как бы вы это преодолели? Что, если один ордер разбивает 2 класса, а другой - 2 переменные?

Тогда мы не должны изменять порядок, но должны ли мы по крайней мере (иметь возможность) предупреждать пользователя о том, что порядок, который использует компилятор, вероятно, неправильный?

Ага. мы не должны заказывать, а вместо этого делать ошибку.

Какая идиома в TypeScript подходит для взаимно рекурсивных классов? А declare class перед фактическим определением?

Если ваши классы просто ссылаются друг на друга в системе типов или в методах экземпляра, это не проблема. Единственный «взаимно рекурсивный» шаблон, вызывающий проблемы, - это:

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

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

Вы можете переписать это как clodule:

class Alpha {
}

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

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

Хорошо, какие правила мы хотели бы реализовать в рамках этой проблемы, кроме «базовый класс должен быть лексически определен перед производным классом»?

Запретить прямые ссылки на внутренние элементы модуля, например

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

Запретить прямые ссылки на члены перечисления

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

IMO объем этой проблемы довольно ограничен, поскольку люди обычно не определяют весь свой код в одном файле: базовый класс, скорее всего, будет существовать в отдельном файле для производного класса.

Я предлагаю следующее от @sparecycles также должно быть частью решения этой проблемы:

Тогда мы не должны изменять порядок, но должны ли мы по крайней мере (иметь возможность) предупреждать пользователя о том, что порядок, который использует компилятор, вероятно, неправильный?

«Порядок, который использует компилятор» должен включать порядок, указанный в tsconfig .

Когда базовый класс и производные классы находятся в одном файле, проблема не так серьезна: программа выйдет из строя при запуске, и программист изменит порядок в этом одном файле, и проблема будет _исправлена_.

В случае нескольких файлов компилятор должен предупреждать при объединении нескольких файлов в сомнительном порядке, потому что этот порядок может измениться по незаметным причинам.

Рассмотрим случай, когда есть базовый класс и несколько производных классов, все в отдельных файлах. Базовый класс использует некоторые производные классы в своей реализации, поэтому он ссылается на них, но его все равно нужно поместить первым в выводе. Точно так же все производные классы должны ссылаться на базовый класс.

Что ж, проблем с файлами, на которые есть взаимные ссылки, нет, если A.ts взаимно ссылается на B.ts, а X.ts включает A.ts, то порядок вывода будет [B, A, X], а если он ссылается на B.ts порядок будет [A, B, X]. (Но только один из этих приказов может работать во время выполнения.) Это делает вещи хрупкими, поскольку компиляция будет успешной, если будет ссылка на B или A.

Мое решение моей проблемы с системой базовых производных классов: добавить index.ts для моей иерархии классов и включить в этот файл все производные классы, за которыми следует базовый класс. Это гарантировало, что при выводе на первое место будет помещен базовый класс. (совершенно нелогично!). Я обнаружил, что если бы я напрямую сослался на нужные мне файлы, он бы в конечном итоге сгенерировал базовый класс после производного.

Предупреждение компилятора будет очень приятным, но также было бы замечательно иметь возможность пометить одну из ссылок в сценарии взаимных ссылок как упорядочение по отправке, а другую - просто для извлечения объявлений. Взаимные ссылки на порядок отправки будут ошибкой.

(В настоящее время у меня это реализовано для автоматического создания списка .js, включенных в наш проект Visual Studio / Typescript, поскольку мы используем многофайловый вывод (легче отлаживать). Но код находится на C # как встроенная задача. Если есть Интересно, я спрошу, могу ли я им поделиться. По сути, это два прогона алгоритма CC Тарьяна.)

Как предупреждение, когда emit-order неверен, так и стабилизация emit-order с помощью явной директивы будут иметь большое значение для того, чтобы сделать машинописный язык жизнеспособным языком для больших проектов ... да?

Я довольно часто сталкиваюсь с этой проблемой из-за моей небольшой базы кода (около 80 файлов .ts). В идеале я бы хотел, чтобы в верхней части любого из моих файлов не было никаких тегов <reference> , и компилятор может все это обработать за меня.

В моем приложении есть только 1 файл, который создает экземпляры классов и выполняет приложение (мой корень композиции), несколько файлов, которые добавляют расширения (например, добавление Array.prototype.distinct ), а остальные являются просто определениями классов / интерфейсов.

В этом случае большая часть кода является честной игрой для переупорядочивания и не требует ручных определений <reference> чтобы получить правильные ответы. Я считаю, что определения классов являются справедливой игрой для любого переупорядочения компилятора, и их следует перемещать в верхнюю часть объединенного вывода, в то время как остальные операторы могут сохранять порядок, как это было при вводе.

Возможен ли флаг компилятора --looseSorting ? Кажется, это довольно востребованная функция.

При создании объявления класса в ES6 мы назначаем статическое свойство после объявления класса. Это делает ссылку на статическое свойство класса в имени вычисляемого свойства на использование до определения.

Выпущенный 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";

Мы хотим предупредить об этой ошибке только в двух случаях: имена вычисляемых свойств относятся к статическому свойству своего класса или относятся к другому классу, определенному ниже, свойству.

Тривиальный пример, с которым мы сегодня игрались, просто для включения в любые тесты:

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

    let i = 20;
    g();
}

Было бы хорошо получить перестановки использований / определений g около i .

Не забывайте думать о функциях, определенных в области видимости блока. Это неопределенное поведение в соответствии со стандартом JavaScript, и я знаю, что, по крайней мере, Firefox и Chrome не согласны с их реализацией.

например:

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

Сейчас отслеживается https://github.com/Microsoft/TypeScript/issues/2854 .

Просто упомяну, что нас это просто укусило сегодня, и нам потребовалось некоторое время, чтобы понять, что происходит.

TypeScript v1.8.10, сборка на основе Webpack, базовый и производный класс определены в одном файле, но (по-видимому) в неправильном порядке, без ошибок компиляции и предупреждений, и даже если исходные карты работают, стек вызовов ошибок указывал на крайне бесполезное место (конец другого класса, импортирующего производный).

Не вдаваясь в подробное обсуждение, но в качестве первой помощи кажется, что предупреждение компилятора могло бы помочь. Только наши 2 ¢

Мне кажется смешным, что TS не поддерживает эту функцию из коробки. Путаница, которую это вызывает, аналогична использованию стандартного JS. Кроме того, виртуальные методы есть у кого?

@MrGuardian репро, описанное в OP, было исправлено. Возможно, вы сможете уточнить в новом или существующем выпуске, который лучше описывает вашу проблему?

(# 12673) вот еще два случая, когда IMO должны быть ошибками:

`` ''
класс Test
{
_b = this._a; // не определено, без ошибок / предупреждений
_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, не могли бы вы

12673

Была ли эта страница полезной?
0 / 5 - 0 рейтинги