Трейт Fold и трейт Visit

В системе компиляции и трансформации JavaScript/TypeScript SWC работа с AST (Abstract Syntax Tree) построена вокруг двух фундаментальных механизмов обхода и обработки узлов: Visit и Fold. Эти два трейта формируют основу анализа и преобразования синтаксического дерева, определяя различие между чтением структуры и её изменением.

AST в SWC представляет собой иерархию строго типизированных узлов: выражения, инструкции, объявления, модули и т.д. Каждый узел содержит вложенные структуры, формируя дерево, которое необходимо либо исследовать, либо трансформировать.

Для работы с этим деревом используются два подхода:

  • Visit — обход без изменения структуры
  • Fold — обход с возможностью преобразования узлов

Оба подхода реализованы через трейты Rust и активно используются в экосистеме swc_ecma_visit.


Трейт Visit: семантика обхода без изменений

Visit предназначен для анализа AST без его модификации. Он реализует паттерн обходчика (visitor pattern), при котором каждый узел посещается с возможностью выполнения побочных действий.

Ключевая характеристика:

AST остаётся неизменным, все операции — наблюдающие

Основная модель работы

Обход строится вокруг методов вида:

  • visit_* — для каждого типа узла
  • visit_children — рекурсивный обход потомков

Типичная структура посетителя:

struct Analyzer;

impl Visit for Analyzer {
    fn visit_ident(&mut self, n: &Ident) {
        // анализ идентификатора
        println!("{}", n.sym);
    }
}

Фазы обхода

Visit обычно включает две логические фазы:

  • вход в узел (pre-order)
  • обход детей
  • выход из узла (post-order, опционально)

В SWC часто используется комбинация Visit и VisitMut, но классический Visit ориентирован на чтение.

Особенности поведения

  • Отсутствие владения узлами: используются ссылки (&T)
  • Невозможность модификации AST
  • Минимальные накладные расходы
  • Гарантированная целостность дерева

Типовые сценарии использования

  • статический анализ кода
  • сбор метаданных
  • поиск идентификаторов
  • вычисление статистики кода
  • lint-проверки без фиксов

Глубина обхода и управление рекурсией

Visit позволяет контролировать глубину обхода через явные вызовы методов посещения дочерних узлов.

Пример:

impl Visit for Analyzer {
    fn visit_function(&mut self, f: &Function) {
        // обработка функции
        self.visit_block(&f.body);
    }
}

Контроль обхода важен для:

  • пропуска поддеревьев
  • оптимизации анализа
  • ограничения области поиска

Трейт Fold: трансформация AST

В отличие от Visit, Fold предназначен для преобразования дерева. Он реализует функциональную модель: входной узел → преобразованный узел.

Ключевая характеристика:

Каждый узел может быть заменён другим узлом

Основная модель работы

Fold использует владение (T вместо &T), что позволяет создавать новые структуры AST.

struct Transformer;

impl Fold for Transformer {
    fn fold_ident(&mut self, n: Ident) -> Ident {
        Ident {
            sym: n.sym.into(),
            ..n
        }
    }
}

Принцип рекурсивного преобразования

Fold работает снизу вверх:

  1. сначала преобразуются дочерние узлы
  2. затем родительский узел
  3. результат заменяет исходный узел

Это обеспечивает композиционность трансформаций.


Семантика неизменяемости и владения

Fold строго связан с моделью владения Rust:

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

Это исключает необходимость в мутабельных ссылках и снижает риск гонок данных.


Отличия Visit и Fold

По назначению

  • Visit — анализ
  • Fold — трансформация

По модели данных

  • Visit — &T (ссылки)
  • Fold — T (владение)

По результату

  • Visit — отсутствие изменений AST
  • Fold — новое AST или частично изменённое дерево

По производительности

  • Visit быстрее при чистом анализе
  • Fold дороже из-за аллокаций и пересборки узлов

Взаимодействие Visit и Fold в пайплайне

В реальных сценариях компиляции оба механизма используются последовательно:

  1. Visit для анализа и подготовки информации
  2. Fold для трансформации кода
  3. повторные Visit для проверки результата

Такая архитектура позволяет разделять ответственность между этапами компиляции.


Композиция трансформаций Fold

Fold хорошо компонуется через цепочки:

  • каждый Fold может вызывать следующий Fold
  • формируется pipeline трансформаций

Пример концептуальной композиции:

Fold1 -> Fold2 -> Fold3

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


Управление структурой узлов в Fold

Fold позволяет:

  • заменять выражения
  • удалять узлы (возвращая пустые конструкции)
  • разворачивать структуры (inline expansion)
  • переписывать синтаксис

Пример трансформации:

  • замена a + b на add(a, b)
  • инлайнинг функций
  • упрощение выражений

Посещение и трансформация вложенных структур

Fold автоматически рекурсивно применяет преобразования ко всем дочерним узлам, если не переопределено поведение.

Типичный шаблон:

impl Fold for Transformer {
    fn fold_block_stmt(&mut self, b: BlockStmt) -> BlockStmt {
        BlockStmt {
            stmts: b.stmts.into_iter().map(|s| self.fold_stmt(s)).collect(),
            ..b
        }
    }
}

Контроль порядка преобразований

Порядок выполнения Fold критичен:

  • изменение выражений до упрощения может повлиять на результат
  • ранняя нормализация AST повышает стабильность последующих проходов

В сложных компиляционных пайплайнах порядок Fold фиксируется архитектурно.


Практические различия в задачах

Visit применяется при:

  • построении символических таблиц
  • анализе зависимостей модулей
  • проверке правил кода
  • подсчёте метрик сложности

Fold применяется при:

  • транспиляции TypeScript → JavaScript
  • JSX трансформации
  • minification
  • macro expansion
  • оптимизации выражений

Внутренние механизмы swc_ecma_visit

Модуль swc_ecma_visit реализует:

  • макросы генерации методов visit_ и fold_
  • автоматическую рекурсию по AST
  • оптимизированные обходы без лишних аллокаций

Особое значение имеет автоматическая генерация обходчиков для всех типов узлов, что исключает необходимость ручного написания traversal logic.


Ошибки и ограничения модели Fold/Visit

Visit

  • невозможность изменения AST ограничивает применение
  • сложные анализы требуют внешних структур данных

Fold

  • высокая стоимость трансформаций
  • риск нарушения структуры AST при некорректной реализации
  • необходимость строгого соблюдения владения

Паттерны комбинирования

Распространённые архитектурные схемы:

  • Visit → сбор информации → Fold → повторный Visit
  • Fold с встроенным Visit для условной трансформации
  • многослойный Fold pipeline

Такие схемы позволяют строить полноценные компиляторы и трансформеры поверх SWC без изменения ядра системы.