Попытка использовать условные типы Typescript, чтобы сузить возможные комбинации значений, которые можно ожидать - PullRequest
2 голосов
/ 26 апреля 2019

Я пытаюсь сообщить TypeScript, что тип значения в первом параметре функции определяет тип, который может иметь второй. По сути, я хочу сузить возможные комбинации типов.

В конкретном примере я хочу, чтобы 2-й параметр был null, если параметр 1sr является примитивным типом, и я хочу, чтобы он был Set (из объектов), если 1-й является объектом.

История вопроса для тех, кто хочет знать, почему я хочу это сделать: Это оптимизация для обнаружения круга рекурсивной функции stringify. Клонирование набора seenObjects для разных ветвей, чтобы разрешить дубликаты между ветвями, которые фактически не образуют круг, необходимо, только когда объект является объектом.

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

Однако внутри функции условный тип, похоже, не имеет никакого эффекта. TS жалуется, что 2-й параметр, возможно, равен null, несмотря на проверку 1-го параметра, и что проверка вместе с условным типом - если он там использовался - ограничивает тип 2-го параметра до Set, без null.

код

TS PlayGround Link (необходимо включить строгие проверки Null)

function isObject (thing: unknown): thing is Record<string, any> {
    return typeof thing === 'object' && thing !== null;
}

function stringify<T extends unknown>(
    obj: T,
    seenObjects: T extends Record<string, any> ? Set<Record<string, any>> : null
): string {
    if (isObject(obj)) {
        seenObjects.add(obj);  // ERROR (bad)
    }

    return 'The End';
}

// No errors (good)
stringify(42, null);
stringify([], new Set());

// ERRORS (good)
stringify([], null);
stringify(42, new Set());

Вопрос

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

Однако мне любопытно, если кто-нибудь знает способ достижения желаемого результата без добавления кода. В моем реальном коде значение 2-го параметра жестко запрограммировано в зависимости от 1-го, поэтому мне кажется целесообразным решить это с помощью статического анализа типов, а не с помощью дополнительного кода, который совершенно необязательно запускается во время выполнения.


PS: Интересно, что поскольку я просто переключаю эту кодовую базу с Flow на TypeScript, в Flow я мог "взломать" Flow, предоставив ему закомментированный код, например, проверки уточнения типа в /*: …. */, в котором Flow будет интерпретировать как «живой» код. Поэтому я мог бы успокоить Flow, предоставив ему код, который он хочет видеть для проверки типа, фактически не помещая его в среду выполнения, потому что он находится в комментарии.


Обновление: ошибка TS?

Когда я изменяю условие if с проверки obj на if (seenObjects !== null), ошибка в seenObjects («может быть нулевым») все еще остается! Несмотря на то, что этот код стоит за явной проверкой null?

1 Ответ

1 голос
/ 26 апреля 2019

Анализ потока управления TypeScript не понимает, когда разные переменные имеют коррелированные типы. Если вы сузите тип одной переменной, это не повлияет на тип другой переменной. Кроме того, для TypeScript сложно выполнить анализ типов для неразрешенных условных типов, поэтому даже если вы изменили свой код для анализа типа seenObjects intead из obj, вы, вероятно, все равно получите некоторые ошибки типов.

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

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

function stringify<T extends unknown>(
    obj: T,
    seenObjects: T extends Record<string, any> ? Set<Record<string, any>> : null
): string {
    if (isObject(obj)) {
        (seenObjects as Set<Record<string,any>>).add(obj); // type assertion 
    }    
    return 'The End';
}

Хорошо, надеюсь, это поможет; удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...