Почему оператор объединения с нулевым значением не работает в машинописном тексте как средство защиты типов?

В Typescript 3.7 был введен нулевой оператор объединения. Казалось бы, идеальный охранник для таких случаев, как

const fs = (s: string) => s
const fn = (n: number) => n

let a: string | null | undefined
let b: number | null | undefined

const x = (a ?? null) && fs(a)
const y = (b ?? null) && fn(b)

Но если вы поместите этот код на игровую площадку машинописного текста, он предупредит вас обо всех параметрах a и b, переданных функциям fs / fn, например:

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

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

const fs = (str: string) => str
const create = (s: string) => s === 's' ? 'string' : s === 'n' ? null : undefined
const a: string | null | undefined = create('s')
const b: string | null | undefined = 's'
let x
if (a !== null && a !== undefined) {
    x = a
} else {
    x = fs(a)
}
const x1 = a !== null && a !== undefined && fs(a)
const x2 = a !== null && a !== void 0 && fs(a)
const x3 = (a ?? null) && fs(a)
const x4 = (b ?? null) && fs(b)
const x5 = a !== null && a !== undefined ? a : fs(a)
const something = a !== null && a !== undefined
const x6 = something ? a : fs(a)
const x7 = something && fs(a)
const x8 = (a !== null && a !== undefined) && fs(a)

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

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

1 ответ

Решение

Я потратил много времени, пытаясь написать механическое объяснение того, почему определенные выражения вроде expr1 || expr2 && expr3в одних ситуациях действовать как охранники, а в других - нет. В итоге он превратился в несколько страниц и по-прежнему не учитывал все случаи в ваших примерах. Если вам интересно, вы можете посмотреть код, реализованный для операторов выражений в https://github.com/Microsoft/TypeScript/pull/7140.


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

Компилятор, возможно, мог бы проводить этот анализ все время для каждого возможного выражения типа объединения, с которым он сталкивался. Мы могли бы назвать это "автоматическим распределенным анализом потока управления", и его преимущество почти всегда заключается в том, что вы всегда получаете желаемое поведение защиты типа. Недостатком является то, что компилятору потребуется больше памяти и времени, чем вы готовы потратить, и, возможно, больше, чем человечество может потратить из-за комбинаторного взрыва, который происходит, когда каждое дополнительное выражение типа объединения оказывает мультипликативный эффект на требуемые Ресурсы. Алгоритмы экспоненциального времени не подходят для хороших компиляторов.

Время от времени мне хотелось иметь возможность намекнуть компилятору, что определенное значение типа union в определенной области должно быть проанализировано таким образом, и я даже подавал запрос на такой "добровольный анализ потока распределенного управления", (см. https://github.com/microsoft/TypeScript/issues/25051), но даже это потребует значительных усилий для реализации и будет отклоняться от целей проектирования TS по включению шаблонов проектирования JS, не требуя от разработчика слишком много думать об анализе потока управления.

Итак, в конце концов, разработчики языка TypeScript реализуют эвристику, которая выполняет такой анализ в ограниченных масштабах, что позволяет использовать традиционные и идиоматические методы. шаблоны кодирования JavaScript. Если код вроде(a ?? null) && fs(a) не считается идиоматическим и достаточно традиционным для разработчиков языка (это частично субъективно и частично зависит от изучения корпуса реального кода), и если его реализация приведет к значительному снижению производительности компилятора, то я бы не ожидал, что язык для его поддержки.

Некоторые примеры:

  • https://github.com/microsoft/TypeScript/issues/12184: поддержка "сохранения" результата защиты типа в константу (например, вашsomethingпример) для дальнейшего использования. Это открытое предложение, помеченное как "повторное посещение" со зловещим заявлением архитектора языка, что было бы трудно сделать это на высоком уровне. Это может показаться идиоматическим, но может быть сложно реализовать эффективно.

  • https://github.com/microsoft/TypeScript/issues/37258: поддержка защиты последовательного типа, когда сужение выполняется для нескольких коррелированных переменных одновременно. Он закрыт как слишком сложный, потому что, чтобы избежать алгоритма экспоненциального времени, вам нужно было бы жестко запрограммировать его для небольшого количества проверок, что в целом было бы не очень полезно. Предложение разработчиков языка: используйте больше идиоматических проверок.


Так что это самое близкое к авторитетному или официальному ответу на этот вопрос. Надеюсь, это поможет; удачи!

Другие вопросы по тегам