Optional chaining и nullish coalescing

Поддержка синтаксиса в современном JavaScript

Optional chaining (?.) и nullish coalescing (??) относятся к синтаксическим конструкциям, которые упрощают работу с потенциально отсутствующими значениями и строгой проверкой null/undefined.

Optional chaining снижает необходимость ручных проверок вложенных объектов:

const city = user?.address?.city;

Nullish coalescing позволяет задать значение по умолчанию только для null и undefined, не затрагивая ложные значения (0, ““, false):

const port = config.port ?? 3000;

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


Разбор синтаксиса на уровне AST

SWC строит AST (Abstract Syntax Tree), в котором optional chaining и nullish coalescing представлены отдельными узлами:

  • OptionalChainingExpression
  • NullishCoalescingExpression

Пример исходного кода:

const value = obj?.a?.b ?? defaultValue;

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

  • цепочку optional chaining: obj?.a?.b
  • операцию nullish coalescing: результат ?? defaultValue

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


Трансформация Optional Chaining

Базовая стратегия разворачивания

Optional chaining преобразуется в условные проверки с кешированием промежуточных значений.

Исходный код:

const x = obj?.a?.b;

Типичная трансформация:

const _obj = obj;
const x = _obj == null ? undefined : _obj.a == null ? undefined : _obj.a.b;

Логика трансформации строится на двух принципах:

  • проверка на null и undefined через == null
  • предотвращение повторного вычисления выражений

Сохранение побочных эффектов

При сложных выражениях SWC обязан предотвращать повторное выполнение:

getObj()?.a

Превращается в:

const _ref = getObj();
const _tmp = _ref == null ? undefined : _ref.a;

Ключевой момент — функция getObj() вызывается ровно один раз.


Вызовы функций и optional call

obj.method?.()

Трансформируется в:

const _obj = obj.method;
_obj == null ? undefined : _obj.call(obj);

Здесь сохраняется контекст вызова (this), что критично для корректного поведения методов.


Доступ к вложенным выражениям

a?.b.c?.d

Разворачивается каскадно:

const _a = a;
const _tmp1 = _a == null ? undefined : _a.b;
const _tmp2 = _tmp1 == null ? undefined : _tmp1.c;
const result = _tmp2 == null ? undefined : _tmp2.d;

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


Трансформация Nullish Coalescing

Базовое разворачивание

const x = a ?? b;

Преобразуется в:

const x = a != null ? a : b;

Используется именно != null, чтобы охватить и null, и undefined.


Взаимодействие с optional chaining

Комбинированные выражения требуют строгого соблюдения приоритета:

const x = obj?.value ?? 10;

Результат:

const _obj = obj;
const _val = _obj == null ? undefined : _obj.value;
const x = _val != null ? _val : 10;

Отличие от логического OR

Важно различие:

const a = value || 10;
const b = value ?? 10;

SWC не объединяет эти конструкции, так как семантика различается:

  • || учитывает falsy значения
  • ?? учитывает только null/undefined

Особенности приоритетов и группировки

При разборе AST SWC строго соблюдает приоритет операторов:

  1. Optional chaining (?.)
  2. Member access (.)
  3. Nullish coalescing (??)

Пример:

a?.b ?? c?.d

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


Сложные выражения и побочные эффекты

Двойные вызовы функций

getA()?.getB()?.value

SWC гарантирует:

  • getA() вызывается один раз
  • getB() вызывается не более одного раза
  • порядок вычисления сохраняется слева направо

Инкременты и изменяемые выражения

arr[i++]?.value

Трансформация фиксирует индекс:

const _i = i++;
const _arr = arr[_i];
const _tmp = _arr == null ? undefined : _arr.value;

delete с optional chaining

delete obj?.prop

Превращается в:

const _obj = obj;
_obj == null ? true : delete _obj.prop;

Здесь сохраняется семантика delete как выражения, возвращающего true при отсутствии объекта.


Поведение в TypeScript-коде

В связке с TypeScript SWC сначала удаляет типы, затем применяет трансформацию:

const x: number = obj?.value;

После стирания типов:

const x = obj?.value;

Дальнейшая трансформация идентична JavaScript-версии.


Конфигурация SWC

В конфигурационном файле .swcrc поддержка включается через preset-env:

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    },
    "transform": {
      "optimizer": {
        "globals": {
          "vars": {}
        }
      }
    }
  },
  "env": {
    "targets": {
      "ie": "11"
    }
  }
}

При указании старых таргетов SWC автоматически активирует трансформации optional chaining и nullish coalescing.


Отличия поведения в разных таргетах

  • Современные окружения (ES2020+) — трансформация может быть отключена
  • Legacy-окружения (IE11) — полное разворачивание выражений
  • Node.js старых версий — обязательная транспиляция

SWC анализирует target matrix и минимизирует количество трансформаций.


Оптимизации генерации кода

Устранение лишних переменных

SWC старается переиспользовать временные идентификаторы:

const _ref = a?.b?.c;

вместо:

const _a = a;
const _b = _a == null ? undefined : _a.b;
const _c = _b == null ? undefined : _b.c;

в некоторых режимах оптимизации может быть свернуто в более компактную форму.


Инлайн-выражения

Если выражение простое, SWC может избежать промежуточных переменных:

const x = (a == null ? undefined : a.b);

Source maps и отладка

При трансформации SWC сохраняет сопоставление:

  • исходное выражение obj?.a?.b
  • развернутую форму с тернарными операторами

Это позволяет отладчикам восстанавливать оригинальный код при step-through отладке.


Сравнение с другими транспиляторами

В сравнении с Babel:

  • SWC использует Rust-реализацию AST, что ускоряет обработку
  • генерация кода более агрессивно оптимизирована
  • структура временных переменных часто короче
  • семантика идентична ECMAScript спецификации

Граничные случаи семантики

Пустые значения и строгая проверка

0 ?? 10

Результат всегда 0, трансформация:

0 != null ? 0 : 10;

Глубокие цепочки с массивами

arr?.[0]?.value ?? "default"

Разворачивается как последовательность безопасных обращений к индексам и свойствам с последующим nullish fallback.


Смешанные выражения

a?.b ?? c?.d ?? e

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