Трансформация генераторов и итераторов

SWC обрабатывает генераторы и итераторы на этапе транспиляции как часть понижения современных возможностей ECMAScript до более ранних версий JavaScript. Основная цель трансформации заключается в сохранении семантики yield, протокола итераторов и асинхронной итерации при отсутствии нативной поддержки в целевой среде выполнения.

Итераторы в ECMAScript основаны на соглашении о наличии метода next(), возвращающего объект вида:

{ value: any, done: boolean }

Объекты, реализующие Symbol.iterator, предоставляют итератор, который используется конструкциями for…of, оператором расширения и деструктуризацией.

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

function* gen() {
  yield 1;
  yield 2;
}

На уровне языка это выглядит как функция с внутренним состоянием, но в исходном JavaScript до трансформации отсутствует явная модель состояния.

Принцип понижения генераторов в SWC

SWC преобразует генераторы в функции, возвращающие объект-итератор, где логика выполнения представлена конечным автоматом (state machine). Каждое yield становится точкой разбиения выполнения с сохранением контекста.

Ключевая идея трансформации заключается в следующем:

  • тело генератора разбивается на блоки (states)
  • каждый yield становится переходом между состояниями
  • локальные переменные сохраняются в замыкании или объекте состояния
  • управление осуществляется через next() с переключателем состояния

Пример упрощённого результата трансформации:

function gen() {
  let state = 0;

  return {
    next() {
      while (true) {
        switch (state) {
          case 0:
            state = 1;
            return { value: 1, done: false };
          case 1:
            state = 2;
            return { value: 2, done: false };
          case 2:
            return { value: undefined, done: true };
        }
      }
    }
  };
}

Реальная реализация SWC значительно сложнее, поскольку учитывает исключения, вложенные блоки, try/catch/finally, а также yield*.

Обработка yield и сохранение контекста

Каждый yield требует фиксации текущего состояния выполнения:

  • текущая позиция в функции
  • локальные переменные
  • промежуточные результаты выражений
  • состояние управления потоком

SWC вводит внутреннюю структуру состояния, которая часто представляется как объект-контекст. При каждом вызове next() выполнение продолжается с последнего сохранённого состояния.

Особое значение имеет тот факт, что yield является выражением:

const x = yield 10;

Значение x определяется аргументом, переданным в next(value). Это требует от трансформированного кода поддержки двунаправленного обмена данными между итератором и внешним вызовом.

Трансформация yield* и делегирование итераторов

Конструкция:

function* a() {
  yield* b();
}

разворачивается в цикл делегирования, где внешний генератор проксирует значения внутреннего итератора.

Логика трансформации включает:

  • получение итератора b()
  • последовательный вызов next() у вложенного итератора
  • проброс значений наружу
  • корректное завершение при done: true

Упрощённая модель:

let inner = b();

while (true) {
  let r = inner.next();
  if (r.done) break;
  yield r.value;
}

SWC при этом учитывает необходимость сохранения состояния внешнего генератора на время работы внутреннего.

Исключения и try/catch/finally

Одной из самых сложных частей трансформации является корректная поддержка исключений.

Генератор может быть приостановлен внутри try, а затем возобновлён с передачей ошибки через throw().

SWC строит расширенную state machine, в которой:

  • каждый блок try имеет собственную область состояний
  • finally гарантированно исполняется при завершении или исключении
  • throw() маршрутизирует выполнение в соответствующий обработчик

Пример поведения:

function* g() {
  try {
    yield 1;
  } finally {
    yield 2;
  }
}

После трансформации требуется гарантировать выполнение finally даже при внешнем прерывании итератора.

Итераторы и for…of

Конструкция:

for (const x of gen()) {}

опирается на Symbol.iterator. SWC не изменяет сам синтаксис цикла, но гарантирует, что преобразованный генератор возвращает корректный итератор.

В случаях отсутствия поддержки Symbol.iterator в целевой среде SWC может подключать полифилы через @swc/helpers.

Асинхронные генераторы и for await…of

Асинхронные генераторы:

async function* ag() {
  yield await fetchData();
}

трансформируются в структуру, основанную на Promise и очереди выполнения.

Ключевое отличие:

  • next() возвращает Promise<{value, done}>
  • каждое yield оборачивается в асинхронную цепочку
  • управление состоянием становится асинхронным автоматом

Также поддерживается протокол Symbol.asyncIterator, необходимый для for await…of.

Промежуточные представления и роль state machine

Внутренне SWC использует абстракцию конечного автомата, где:

  • узлы графа соответствуют точкам выполнения
  • переходы зависят от yield, return, throw
  • стек вызовов эмулируется вручную

Это позволяет унифицировать обработку:

  • генераторов
  • async/await
  • async generators

Несмотря на различие синтаксиса, все они сводятся к управляемой машине состояний с сохранением контекста выполнения.

Влияние замыканий и область видимости

Лексическое окружение функции сохраняется через замыкания. Однако переменные, изменяющиеся между yield, должны быть вынесены в состояние, доступное между вызовами next().

Это приводит к следующей трансформационной стратегии:

  • var/let/const переводятся в поля контекста
  • временные значения сохраняются перед yield
  • выражения разбиваются на последовательные шаги

Особую сложность создают:

  • деструктуризация
  • оператор spread
  • короткие логические выражения

Конфигурационные аспекты SWC

Поведение трансформации генераторов определяется настройками:

  • целевая версия ECMAScript (target)
  • включение трансформаций через jsc.transform
  • использование внешних хелперов (externalHelpers)
  • режим модулей

Пример логики настройки:

  • при низком target генераторы всегда понижаются
  • при современном target трансформация может быть отключена
  • async generators требуют отдельной поддержки runtime

Связь с runtime-хелперами

При трансформации SWC может подключать вспомогательные функции, обеспечивающие корректную работу итераторов. Эти хелперы обеспечивают:

  • создание iterator helpers
  • поддержку Symbol.iterator
  • совместимость с ES5-окружениями

Роль runtime критична для сохранения поведения, идентичного нативному исполнению, особенно при вложенных генераторах и асинхронных цепочках.

Особые случаи поведения

Некоторые конструкции требуют дополнительной нормализации:

  • бесконечные генераторы (while(true) yield)
  • рекурсивные генераторы
  • генераторы с побочными эффектами в выражениях yield
  • комбинирование yield* с асинхронностью

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

Семантическая эквивалентность и ограничения трансформации

Несмотря на высокую точность понижения, существуют ограничения:

  • невозможность идеально воспроизвести стек вызовов отладчика
  • различия в производительности по сравнению с нативными генераторами
  • увеличение размера кода из-за state machine
  • сложность анализа оптимизаторами движка

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